/* 2004.07.08 gs code()gluelogic.com * - redirected all error messages to stderr * - modified main() to read args from stdin if "-" passed on CLI instead of arg * (e.g. command is read on stdin after newline-terminated password is read) * - modified main() so that max command size is ARGS_MAX, not a piddly 8 KB */ /****************************************************************************** * * * LISTSERV V2 - send command to LISTSERV on remote node via TCPGUI interface * * * * Copyright L-Soft international 1996-97 - All rights reserved * * * * Syntax: * * * * lcmdx hostname[:port] address password command * * * * Connects to 'hostname' on port 'port' (default=2306) using the LISTSERV * * TCPGUI protocol, then executes the LISTSERV command 'command' from the * * origin 'address'. 'password' is the personal LISTSERV password associated * * with the command origin ('address') - see the description of the PW ADD * * command for more information on LISTSERV passwords. The reply from * * LISTSERV is echoed to standard output (the command is executed * * synchronously). * * * ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #ifdef INTUX #include #endif #include #include #ifndef ARG_MAX #define ARG_MAX 65536 #endif #define DEFAULT_PORT 2306 #ifdef ultrix /* Use read() rather than recv() to bypass a bug in Ultrix */ #define recv(a, b, c, d) read(a, b, c) #endif static int receive(int ss, char *buf, int len) { char *w, *e; int l; for (w = buf, e = buf + len; w < e;) { l = recv(ss, w, e - w, 0); if (l <= 0) return(l); w += l; } return(len); } int LSV_send_command(char *hostname, unsigned short port, char *origin, char *pw, char *command, FILE *writeto) { char buf[256], *reply = 0, *cmd = command, *w, *r, *e; unsigned char *wb; int rc, ss, len, orglen, n; unsigned int ibuf[2]; struct sockaddr_in sa_connect; struct hostent *H; /* Initialize */ if (strstr(command, "PW=") == NULL) { /* (If command does not contain PW=, add it) */ cmd = malloc(strlen(command) + strlen(pw) + 5); sprintf(cmd, "%s PW=%s", command, pw); } orglen = strlen(origin); /* Create a socket */ if ((ss = socket(AF_INET, SOCK_STREAM, 0)) < 0) goto Socket_Error; /* Prepare sa_connect structure */ memset(&sa_connect, 0, sizeof(sa_connect)); sa_connect.sin_family = AF_INET; sa_connect.sin_port = htons(port); if ((H = gethostbyname(hostname)) && H->h_addr_list[0]) memcpy(&sa_connect.sin_addr, H->h_addr_list[0], 4); else goto Socket_Error; /* Connect to the TCPGUI port */ if (connect(ss, (struct sockaddr *)&sa_connect, sizeof(sa_connect)) < 0) goto Socket_Error; /* Send the protocol level request and the command header */ wb = (unsigned char *)buf; len = strlen(cmd); n = len + orglen + 1; /* Byte length */ *wb++ = '1'; /* Protocol level: 1 */ *wb++ = 'B'; /* Mode: binary */ *wb++ = '\r'; *wb++ = '\n'; *wb++ = n / 256; /* Request length byte 1 */ *wb++ = n & 255; /* Request length byte 2 */ *wb++ = orglen; /* Origin length: 1 */ for (r = origin; *r;) *wb++ = (unsigned char)*r++; if (send(ss, buf, (char *)wb - buf, 0) < 0) goto Socket_Error; /* Await confirmation */ for (w = buf;;) { n = recv(ss, w, buf + sizeof(buf) - w, 0); if (n <= 0) goto Socket_Error; w += n; for (r = buf; r < w && *r != '\n'; r++); if (r != w) break; } /* Anything other than 250 is an error */ if (buf[0] != '2' || buf[1] != '5' || buf[2] != '0') goto Protocol_Error; /* Finish sending the command text */ if (send(ss, cmd, len, 0) < 0) goto Socket_Error; /* Read the return code and reply length */ if (receive(ss, (char *)ibuf, 8) <= 0) goto Socket_Error; /* Exit if the return code is not 0 */ if (ntohl(ibuf[0])) goto Protocol_Error; /* Read the reply */ len = ntohl(ibuf[1]); reply = malloc(len + 1); if (receive(ss, (char *)reply, len) <= 0) goto Socket_Error; /* Cut it into individual lines, and output it */ for (r = reply, e = reply + len; r < e;) { for (w = r; w < e && *w != '\r'; w++); *w++ = '\0'; fprintf(writeto, "%s\n", r); r = w; if (r < e && *r == '\n') r++; } /* Close the socket and return */ rc = 0; goto Done; Protocol_Error: rc = 1000; goto Done; Socket_Error: rc = errno; goto Done; Done: if (cmd != command) free(cmd); if (reply) free(reply); if (ss >= 0) close(ss); return(rc); } #ifndef NO_MAIN char * read_arg_from_stdin(char *buf, size_t n, char *label) { char *w; if (fgets(buf, n, stdin) != NULL) { if ((w = strchr(buf, '\n')) != NULL) *w = '\0'; if (strlen(buf) > n-2) fprintf(stderr, ">>> Warning: %s may have been truncated\n", label); return buf; } else { fprintf(stderr, ">>> %s is missing or invalid.\n", label); exit(EINVAL); } } int main(int argc, char **argv) { char cmd[ARG_MAX], hostname_buf[128], email_buf[64], password_buf[64]; char *hostname, *email, *password, *w; int rc; unsigned short port = DEFAULT_PORT; /* NB: very little error checking is done on arguments */ /* RFE: modify LSV_send_command() to write the command sequence to LISTSERV * in multiple parts, reading it in chunks directly from stdin rather than * reading it completely here and possibly copying it again when creating * data to send to LISTSERV. */ /* Parse positional parameters * (Backwards compatible with old (insecure) lcmdx) * Recommended: '-'s should be passed as args to indicate reads from stdin * (otherwise passwords, LISTSERV auth cookies, etc are visible to * anyone who can list system processes and view the command lines) */ if (argc < 5) { fprintf(stderr, "\nSyntax: lcmdx hostname[:port] email-address password command" "\nAny argument specified as '-' will be read from stdin" "\n (separate multiple args on stdin with newlines)\n\n"); return(EINVAL); } hostname = (argv[1][0] == '-' && argv[1][1] == '\0') ? read_arg_from_stdin(hostname_buf, sizeof(hostname_buf), "hostname") : argv[1]; if ((w = strchr(argv[1], ':')) != NULL) { *w = '\0'; port = atoi(w+1); } email = (argv[2][0] == '-' && argv[2][1] == '\0') ? read_arg_from_stdin(email_buf, sizeof(email_buf), "email address") : argv[2]; password = (argv[3][0] == '-' && argv[3][1] == '\0') ? read_arg_from_stdin(password_buf, sizeof(password_buf), "password") : argv[1]; for (w = password; (*w = toupper(*w)); w++) /* uppercase the password */ ; if (argv[4][0] == '-' && argv[4][1] == '\0') { /* While we read into a fixed-size buffer here, such a limitation could * easily be removed to support arbitrarily large commands. If your * commands are larger than ARG_MAX on your system, you'll have to fix * this code to do that (probably by modifying LSV_send_command() to * read the command directly from stdin) */ size_t i = 0, n = 0; while (n < sizeof(cmd)-1 && (i=fread(cmd+n,1,sizeof(cmd)-n-1,stdin)) >0) n += i; if (i != 0) { if (i > 0) errno = ENOSPC; perror(">>> Error reading requested command from stdin"); return errno; } cmd[n] = '\0'; } else { int i, n; for (i = -1, n = 4; n < argc; n++) { w = argv[n]; for (w = argv[n], ++i; i < sizeof(cmd) && (cmd[i] = *w++); i++) ; if (i < sizeof(cmd)) cmd[i] = ' '; else { fprintf(stderr, ">>> Requested command is too long.\n"); return ENOSPC; } } cmd[i] = '\0'; } /* Execute the command */ rc = LSV_send_command(hostname, port, email, password, cmd, stdout); if (rc == 1000) fprintf(stderr, ">>> Protocol error while communicating with LISTSERV.\n"); else if (rc != 0) fprintf(stderr, ">>> Error - unable to initiate communication with " "LISTSERV (errno=%d).\n", rc); return(rc); } #endif