/* * Copyright(c) 1997, Space Science and Engineering Center, UW-Madison * Refer to "McIDAS Software Acquisition and Distribution Policies" * in the file mcidas/data/license.txt */ /**** $Id: mcserv.c,v 1.19 1999/02/23 15:06:44 chadj Tst $ ****/ /* * M c I D A S DDE server interface * j.benson 04/92. * * Completely rewritten. * Now fires up local servers, too, and can be started by inetd. * Acts as a dummy server (writes the request to a file) when invoked * with a base name other than 'mcserv'. * No authentication services. * David Sanderson 12/93. */ /* * Optional compression added by j.benson 07/97. * Compression is controlled on the client side by the existance * of the environment variable. On the server side, it is * triggered by the client having connected to the 'compress' * port rather than the usual port. * * future porting note: * the 'compress' and 'uncompress' commands are found in /bin * on AIX, HPUX, and Solaris. They are found in /usr/ucb on IRIX. * if you are porting this to another UNIX which has 'compress' * in a different path, add yet another execl to the stack already * there. Be careful to do it for both compress and uncompress. * * coding style note: * you'll notice that return codes are not checked. stderr has * been duped into oblivion, and if you fail to pipe or fork, you * can't even send an error message back upstream, because you would be sending * it in clear text instead of compressed. So just dying is * about the best you can do. */ #include #include #include #include #include #include #include #include #include #include #include #include "m0select.h" #include "mcidas.h" #include "mcidasp.h" #include "m0adde.h" /* * compression mods added for INM 07.97 jmb */ #define COMPRESS_PORT 503 #define COMPRESS_STRING "MCCOMPRESS" #ifndef INADDR_LOOPBACK #define INADDR_LOOPBACK (unsigned long)0x7f000001 #endif /* * swrite() - write() a "string variable" (char *) */ #define swrite(fd, s) (void) write((fd), (s), strlen(s)) /* * lwrite() - write() a "string literal" (char []) */ #define lwrite(fd, lit) (void) write((fd), (lit), sizeof(lit)-1) /* * DDE protocol defines */ #define VERHDRLEN 4 /* length of DDE version header, in bytes */ #define PREHDRLEN 12 /* length of DDE pre-request header, in bytes */ #define REQHDRLEN 160 /* length of DDE request header, in bytes */ #define ANSLEN 96 /* length of DDE reply header, in bytes */ #define ANSTEXTLEN 72 /* length of message text in DDE reply header, in bytes */ #define PROTOCOL_VERSION 1 /* protocol version number */ /* this is the composition of the version header */ typedef struct { Mcuint4 version; /* protocol version number */ } Verhdr; /* this is the composition of the pre-request header */ typedef struct { Mcuint4 servaddr; /* IP address of server, net byte order */ Mcuint4 servport; /* port of server, net byte order */ char servtype[4]; /* data type on server */ } Prehdr; /* this is the composition of the request header */ typedef struct { Mcint4 word[40]; } Reqhdr; /* * DDE error numbers and strings */ #define EC_TCPIP (-99) #define ES_TCPIP "Cannot initialize OS/2 TCPIP" #define EC_CONNECT (-100) #define ES_CONNECT "Cannot contact server (connect() failed)" #define EC_SOCKET (-101) #define ES_SOCKET "Socket call failed (socket() failed)" #define EC_WRITE (-102) #define ES_WRITE "TCP write failed" /* NEW ONES */ #define EC_CMDLINE (-104) /* mcserv given bad command line arguments */ #define EC_DEVNULL (-105) /* error opening /dev/null (should not happen) ... */ #define EC_DUPERR (-106) #define ES_DUPERR "error in dup() (should not happen)" #define EC_ASROOT (-107) #define ES_ASROOT "error in /etc/inetd.conf: server running as root" #define EC_PASSWD (-108) /* cannot find passwd entry for ... */ #define EC_MALLOC (-109) #define ES_MALLOC "malloc() failed (should not happen)" #define EC_PUTENV (-110) #define ES_PUTENV "putenv() failed (should not happen)" #define EC_MCPATH (-111) #define ES_MCPATH "error in prefixing MCPATH" #define EC_PATH (-112) #define ES_PATH "error in prefixing PATH" #define EC_CHDIR (-113) /* error in changing directory to ... */ #define EC_NOSERVER (-114) /* cannot find server program ... */ #define EC_BADSERVER (-115) /* cannot run server program ... */ #define EC_PROTOVERS (-116) /* protocol version mismatch: ... */ /* * Command-line related variables. * * If opt_R is set (-R was given), then we know that we may access * remote sites we were NOT run from inetd. * * If opt_R is NOT set, then we have been started from inetd. * * This makes the installation process more bulletproof, since the * option will always be supplied by the library code (and no command * line option need be put in /etc/inetd.conf). */ static char cmdopt[] = "R"; static char cmdusg[] = " [-R]\n"; static int opt_R = 0; /* * Protocol status variables * * have_verhdr is true when we have read the version header. * have_prehdr is true when we have read the pre-request header. * have_reqhdr is true when we have read the request header. * * client_version is defined only when have_verhdr is true; it gives * the protocol version level the client speaks. * * These are necessary so that die() can tell how much it has to read * from the client before it can write the error to the client. */ static int have_verhdr = 0; static int have_prehdr = 0; static int have_reqhdr = 0; static Mcuint4 client_version; /* * Prototypes (roughly showing the call tree, too) */ static int cmdline(int argc, char **argv); static void usage(char *cmd); static char *mybasename(char *s); static int dbserv(char *basename); static int mcserv(void); static void initenv(void); static int prevar(const char *var, char *val, const char *sep); static int getsockfd(Mcuint4 netaddr, unsigned short netport); static int cpio(int ifd, int ofd, size_t nbytes); static void strdn(char *s); static void doproto(int fds[2],unsigned short port); static void die(Mcint4 code, const char *text); /* * read and write routines guarded by select() * * this means that it doesn't matter if the * file descriptors are set blocking or non-blocking * everything is going to work anyway */ static int s_write(int fd, void *buf, int count) { fd_set wset; struct timeval timeout; int rc; timeout.tv_sec=600; timeout.tv_usec=0; FD_ZERO(&wset); FD_SET(fd,&wset); switch (select(fd+1,0,&wset,0,&timeout)) { case -1: perror("select1"); exit(1); case 0: fprintf(stderr,"select timed out\n"); exit(1); default: break; } return write(fd,buf,count); } static int s_read(int fd, void *buf, int count) { fd_set rset; struct timeval timeout; timeout.tv_sec=600; timeout.tv_usec=0; FD_ZERO(&rset); FD_SET(fd,&rset); switch (select(fd+1,&rset,0,0,&timeout)) { case -1: perror("select2"); exit(1); case 0: fprintf(stderr,"select timed out\n"); exit(1); default: break; } return read(fd,buf,count); } /* * This is where it's at. */ int main(int argc, char **argv) { char *argv0 = mybasename(argv[0]); /* behave nicely if someone ran us interactively by accident */ if(isatty(STDIN) || isatty(STDOUT)) { char *msg; msg = stralloc("ERROR: ", argv0 , " is not a user-level command\n" , (char *)0); if(msg == (char *)0) { return 1; } swrite(STDERR, msg); free(msg); return 1; } /* get any command line arguments */ cmdline(argc, argv); /* and do our thing */ /* * If we have a name other than mcserv, then * behave as a stupid local server that dumps its * request block to a file and exits. * * This is for use in DEBUGGING local servers. */ if(strcmp(argv0, "mcserv") != 0) { return dbserv(argv0); } return mcserv(); } /* * cmdline() - return the index of the first non-option argument */ static int cmdline(int argc, char **argv) { extern char *optarg; /* points to the option argument */ extern int opterr; /* when 0, getopt is silent on errors */ extern int optind; /* argv[optind] is the next argument */ extern int optopt; /* current option letter */ int errflag = 0; /* usage error flag */ int errcode = 1; /* usage error exit code */ int c; while((c = getopt(argc, argv, cmdopt)) != -1) switch(c) { case 'R': opt_R = 1; break; case '?': errflag = 1; if(optopt == '?') errcode = 0; break; } if(errflag) { usage(argv[0]); exit(errcode); } return optind; } /* * print the command usage to stderr */ static void usage(char *cmd) { static char prefix[] = "usage: "; const char *p = cmd ? mybasename(cmd) : "a.out"; char *msg; msg = stralloc(prefix, p, cmdusg, (char *)0); /* * If running interactively, print an intelligible message. * Otherwise report it via the DDE protocol mechanism. */ if(isatty(STDIN) || isatty(STDOUT)) { if(msg) { swrite(STDERR, msg); } } else { die(EC_CMDLINE, msg); /* NOTREACHED */ } if(msg) { free(msg); } } /* * mybasename(s) - assumes the string does not end in '/' * * This used to be * * #define mybasename(s) (strrchr((s),'/') ? strrchr((s),'/')+1 : (s)) * * but some systems don't have strrchr() and I didn't want to mess * with switching between strrchr() and rindex(). */ static char * mybasename(char *s) { char *p; if (!s || !*s) return s; for(p = s; *s; s++) if(*s == '/') p = s+1; return p; } /* * dbserv() - act as a dummy local server and copy the request * block to an output file. * Return value is exit status of process. */ static int dbserv(char *argv0) { int nullfd; int filefd; char *file; /* * Open files for server output and input. * The output file name is the program file name * with ".req" appended. * * This has to be hardcoded since the local servers * run with no arguments. */ file = stralloc(argv0, ".req", (char *)0); if(file == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if((filefd = open(file, O_WRONLY|O_APPEND|O_CREAT|O_TRUNC|O_NOCTTY, 0666)) == -1) { /* this isn't the right error code, * but it'll do for now I suppose */ die(EC_DEVNULL, "error opening output file"); /* NOTREACHED */ } if((nullfd = open("/dev/null", O_RDONLY, 0)) == -1) { die(EC_DEVNULL, "error opening /dev/null"); /* NOTREACHED */ } /* * Now call the code which does the server protocol. * Since we've spoofed the file descriptors, the input * to this process goes into a file. */ { int dummyfds[2]; dummyfds[0] = nullfd; dummyfds[1] = filefd; doproto(dummyfds,0); } /* * Clean up file descriptors. */ close(filefd); close(nullfd); return 0; } /* * mcserv() - fire up the real server and copy to and fro. * Return value is exit status of process. */ static int mcserv(void) { /* * server process access: read from fds[0] to read from the server, * write to fds[1] to write to the server. */ int fds[2]; Mcuint4 netaddr; /* address, net byte order */ unsigned short netport; /* port, net byte order */ Verhdr ver; /* version record */ Prehdr pre; /* pre-request record */ assert(sizeof(ver) == VERHDRLEN); /* paranoia */ assert(sizeof(pre) == PREHDRLEN); /* paranoia */ /* read the transport protocol version header */ memset(&ver, 0, sizeof(ver)); if(M0adderead(STDIN, &ver, sizeof(ver)) != sizeof(ver)) { return 0; } client_version = ntohl(ver.version); have_verhdr = 1; /* * Check the protocol version */ if(client_version != PROTOCOL_VERSION) { char cliver[32]; char srvver[32]; char *errmsg; sprintf(cliver, "%ld", (long) client_version); sprintf(srvver, "%ld", (long) PROTOCOL_VERSION); errmsg = stralloc("protocol version mismatch:" "client wants ", cliver, ", server knows ", srvver, (char *)0); die(EC_PROTOVERS, errmsg); /* NOTREACHED */ } /* * Process initializations */ /* * If started by inetd, * set up environment, current directory, etc. */ if(!opt_R) { initenv(); } /* read the pre-request header */ memset(&pre, 0, sizeof(pre)); if(M0adderead(STDIN, &pre, sizeof(pre)) != sizeof(pre)) { return 0; } have_prehdr = 1; /* * extract the address and port number of the data server */ netaddr = pre.servaddr; netport = htons(ntohl(pre.servport) & 0xFFFF); /* * If we are allowed to contact remote servers, * and the service address is not the loopback address, * then try to connect to the remote server. * Set the fds[] so that we can talk to the remote server. * * Otherwise, exec() a local server. */ if(opt_R && netaddr != htonl(INADDR_LOOPBACK)) { /* * if compression is enabled, * modify the port number in the pre-header * contacting the server on that new port number * is the signal that you want return data to be compressed */ if(getenv(COMPRESS_STRING)) { pre.servport=htonl(COMPRESS_PORT); netport=htons((unsigned short)COMPRESS_PORT); } /* * now connect */ fds[0] = fds[1] = getsockfd(netaddr, netport); } else { char *vec[2]; /* server process command vector */ char cmd[128]; /* server process command name */ int pipes[2]; /* pipes for compress coprocess */ /* * open /dev/null so stderr doesn't get set to it */ { int fd; #if 0 if((fd = open("/tmp/johnb", O_WRONLY | O_CREAT | O_APPEND, 0)) == -1) #else if((fd = open("/dev/null", O_RDONLY, 0)) == -1) #endif { char *errmsg; int err; /* save errno before doing any other library calls */ err = errno; errmsg = stralloc("error opening /tmp/johnb" " (should not happen): ", strerror(err) , (char *)0); die(EC_DEVNULL, errmsg); /* NOTREACHED */ } close(STDERR); if(dup(fd) != STDERR) { die(EC_DUPERR, ES_DUPERR); /* NOTREACHED */ } close(fd); } /* * exec() local server. * * Here, we have to select which process to run as a coprocess. * Start it up, setting fds[0] and fds[1]. */ /* * construct the command name & vector. * For now we blindly assume that the four characters * in pre.servtype are okay, since that's what the fortran did. */ strncpy(cmd, pre.servtype, sizeof(pre.servtype)); cmd[sizeof(pre.servtype)] = '\0'; strdn(cmd); strcat(cmd, "serv"); vec[0] = cmd; vec[1] = (char *)0; /* * do compression? * if so we need to start another coprocess: the 'compress' * command */ if( netaddr != htonl(INADDR_LOOPBACK) &&pre.servport== htonl(COMPRESS_PORT)) { /* * on AIX and HPUX, we need yet another coprocess * copying the data from pipe to socket * because sending the socket directly to compress * appeared not to work * * when reading this section of code, the main point to remember is the * child always inherates open file descriptors of the parent * * note that read may return some arbitrary number of bytes * and that it may take more than one write to get them all out */ #if 1 /* defined(_AIX) || defined(__hpux) */ unsigned char b[5120]; int i,j,k; pipe(pipes); if(fork()==0) { /* * child; writing to the socket so close the * write pipe; don't need it. */ close(pipes[1]); while(1) { i=s_read(pipes[0],b,sizeof(b)); if(i==0) exit(0); /*normal end of file*/ if(i<0) { exit(1); /*error*/ } j=0; while(i) { k=s_write(1,&b[j],i); if(k<0) { exit(1); } i-=k; j+=k; } /* end copying out */ } /* end copying in */ } /* end child block */ /* * parent; redirect output from compress (see below) * into pipe. will be copied to socket by child code * above */ close(1); dup(pipes[1]); close(pipes[0]); close(pipes[1]); #endif /* _AIX || __hpux */ /* * another set of pipes, connected to compress */ pipe(pipes); if(fork()==0) { /* * child; you are compress * your stdin will be one end of the pipe * the other end of the pipe is closed * so you will get eof when the other process * quits. eof is not sent until all file * descriptors are closed * everywhere except SGI, the first exec will * succeed and the second will not be used * on SGI, the second will succeed. */ close(0); dup(pipes[0]); close(pipes[1]); execl("/bin/compress", "compress", "-cf",0); execl("/usr/ucb/compress", "compress", "-cf",0); exit(1); } /* * parent; you are going to be the server * your stdout is re-routed to the pipe, so it will * reach the input end of 'compress' */ close(1); dup(pipes[1]); close (pipes[0]); close(pipes[1]); } /* end of code for compress IF statement */ /* * Try to exec() the actual server module for the data type. */ execvp(vec[0], vec); /* * If we get to here, we can't run the command. * Give up. */ { char *errmsg; errmsg = stralloc("cannot run server '" , vec[0], "': ", strerror(errno) , (char *)0); die(EC_NOSERVER, errmsg); /* NOTREACHED */ } } /* * If we get to here, we are talking to a remote server, * and we need to pass along the version header and the * pre-request header. */ /* transmit version header */ if (M0addewrite(fds[1], (char *) &ver, sizeof(ver)) != sizeof(ver)) { die(EC_WRITE, ES_WRITE); /* NOTREACHED */ } /* transmit pre-request header */ if (M0addewrite(fds[1], (char *) &pre, sizeof(pre)) != sizeof(pre)) { die(EC_WRITE, ES_WRITE); /* NOTREACHED */ } /* do the rest of the protocol */ doproto(fds,netport); /* * Clean up file descriptors. */ close(fds[0]); if(fds[0] != fds[1]) { close(fds[1]); } return 0; } /* * doproto - copy data between the client and the remote server * as the protocol demands, beginning with the request header. */ static void doproto(int fds[2],unsigned short port) { size_t rlen; Reqhdr req; /* request record */ int pipes[2]; assert(sizeof(req) == REQHDRLEN); /* paranoia */ /* read the request header */ memset(&req, 0, sizeof(req)); if(M0adderead(STDIN, &req, sizeof(req)) != sizeof(req)) { return; } have_reqhdr = 1; /* transmit request header */ if (M0addewrite(fds[1], (char *) &req, sizeof(req)) != sizeof(req)) { die(EC_WRITE, ES_WRITE); /* NOTREACHED */ } /* length of rest of request */ rlen = ntohl(req.word[9]); /* copy request from stdin to server */ if(cpio(STDIN, fds[1], rlen) == -1) { exit(0); } /* * uncompress? */ if((unsigned short)port==(unsigned short)htons(COMPRESS_PORT)) { /* * uncompress * stdin will be the socket * stdout will be the client */ pipe(pipes); if(fork()==0) { char b[5120]; int i=1; close(pipes[0]); while (i) { i=s_read(fds[0],b,sizeof(b)); if(i<0) { perror("pipe read"); exit(1); } if(i==0) break; s_write(pipes[1],b,i); } exit(0); } close(0); dup(pipes[0]); close(pipes[1]); #if 0 execl("/usr/sww/bin/zcat","zcat",0); #endif execl("/bin/uncompress", "uncompress","-cf",0); execl("/usr/ucb/uncompress", "uncompress","-cf",0); execl(M0prefixRootPath ("/usr/contrib/uncompress"), "uncompress", "-cf", 0); exit(1); } /* copy data from server to stdout */ do { Mcint4 xlen; /* * Obtain the length of the block (possibly 0) * and also write it to stdout. */ if(M0adderead(fds[0], &xlen, 4) != 4) { exit(0); } if(M0addewrite(STDOUT, &xlen, 4) != 4) { exit(0); } /* rlen is the block size */ rlen = ntohl(xlen); if (rlen > 0) { /* process the block */ if(cpio(fds[0], STDOUT, rlen) == -1) { exit(0); } } else { /* process the trailer */ if(cpio(fds[0], STDOUT, ANSLEN-4) == -1) { exit(0); } } } while(rlen > 0); } /* * initenv - set the environment of the process, its umask, * and its current directory appropriately when the process * has been run from inetd. */ static void initenv(void) { struct passwd *pw; /* passwd entry for the current uid */ char *str; /* temporary pointer to new env strings */ char *dir; /* temporary pointer to name of data dir */ /* home directory of mcidas account, if any */ char *mcidas_home = (char *)0; uid_t mcidas_uid; /* name of the mcidas account */ static char mcidas_user[] = "mcidas"; /* * Do NOT allow this process to run as root. */ if(getuid() == 0 || geteuid() == 0) { die(EC_ASROOT, ES_ASROOT); /* NOTREACHED */ } /* * Set the umask. */ umask(0022); /* * Try to get the home directory of the 'mcidas' account. * Continue even if there isn't one. * * Note that every time you call getpwuid() or getpwnam() * you reset the data the return pointer points to, so * you can't mix them without saving your own copy of * at least some of the data. We look up the mcidas account * first and save its home directory, then look for the * account of the current uid. */ mcidas_uid = -1; mcidas_home = (char *)0; if((pw = getpwnam(mcidas_user)) != (struct passwd *)0) { mcidas_uid = pw->pw_uid; mcidas_home = stralloc(pw->pw_dir, (char *)0); if(mcidas_home == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } } /* * If we can't get the passwd info for this uid, then * give up. */ if((pw = getpwuid(getuid())) == (struct passwd *)0) { char uid[32]; char errmsg[128]; sprintf(uid, "%ld", (long)getuid()); strcpy(errmsg, "cannot find passwd entry for uid "); strcat(errmsg, uid); die(EC_PASSWD, errmsg); /* NOTREACHED */ } /* set HOME */ if((str = stralloc("HOME=", pw->pw_dir, (char *)0)) == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(putenv(str) != 0) { die(EC_PUTENV, ES_PUTENV); /* NOTREACHED */ } /* set LOGNAME */ if((str = stralloc("LOGNAME=", pw->pw_name, (char *)0)) == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(putenv(str) != 0) { die(EC_PUTENV, ES_PUTENV); /* NOTREACHED */ } /* set USER */ if((str = stralloc("USER=", pw->pw_name, (char *)0)) == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(putenv(str) != 0) { die(EC_PUTENV, ES_PUTENV); /* NOTREACHED */ } /* set MCPATH, if it is not already set */ /* * If MCPATH is not set, we set it to: * * $HOME/mcidas/data:$HOME/mcidas/help:~mcidas/data:~mcidas/help * * This is what the mcidas.sh script sets it to by default. */ if(getenv("MCPATH") == (char *)0) { /* prepend ~mcidas/data:~mcidas/help to any MCPATH */ if(mcidas_home != (char *)0) { str = stralloc(mcidas_home, "/", "help", (char *)0); if(str == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(prevar("MCPATH", str, ":") == -1) { die(EC_MCPATH, ES_MCPATH); /* NOTREACHED */ } free(str); str = stralloc(mcidas_home, "/", "data", (char *)0); if(str == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(prevar("MCPATH", str, ":") == -1) { die(EC_MCPATH, ES_MCPATH); /* NOTREACHED */ } free(str); } /* prepend $HOME/mcidas/data:$HOME/mcidas/help to any MCPATH */ str = stralloc(pw->pw_dir, "/", "mcidas/help", (char *)0); if(str == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(prevar("MCPATH", str, ":") == -1) { die(EC_MCPATH, ES_MCPATH); /* NOTREACHED */ } free(str); str = stralloc(pw->pw_dir, "/", "mcidas/data", (char *)0); if(str == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(prevar("MCPATH", str, ":") == -1) { die(EC_MCPATH, ES_MCPATH); /* NOTREACHED */ } free(str); } /* prepend to any PATH */ /* * We prepend $HOME/bin:$HOME/mcidas/bin:~mcidas/bin to PATH * * Naturally, we do the prepending in reverse order. * * The directories, relative to $HOME, where the executables * live. This choice of paths is for compatibility with * current McIDAS, more than anything else. (It could be * completely different...) * * There should be a mechanism for expanding the PATH * setting beyond this, possibly via the command line. * * bindirtail0 will precede bindirtail1 in PATH. */ /* prepend ~mcidas/bin to any PATH */ if(mcidas_home != (char *)0) { str = stralloc(mcidas_home, "/", "bin", (char *)0); if(str == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(prevar("PATH", str, ":") == -1) { die(EC_PATH, ES_PATH); /* NOTREACHED */ } free(str); /* if got this far, str nonnull so safe to call free */ } /* prepend $HOME/mcidas/bin to any PATH */ str = stralloc(pw->pw_dir, "/", "mcidas/bin", (char *)0); if(str == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(prevar("PATH", str, ":") == -1) { die(EC_PATH, ES_PATH); /* NOTREACHED */ } free(str); /* if got this far, str nonnull so safe to call free */ /* prepend $HOME/bin to any PATH */ str = stralloc(pw->pw_dir, "/", "bin", (char *)0); if(str == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(prevar("PATH", str, ":") == -1) { die(EC_PATH, ES_PATH); /* NOTREACHED */ } free(str); /* if got this far, str nonnull so safe to call free */ /* * construct name of data directory & chdir there * * If our user id is that of the mcidas account, we * use $HOME/data, otherwise we use $HOME/mcidas/data. * * The directory, relative to $HOME, is where the data lives. * We chdir there for compatibility with current McIDAS. * * (Now that MCPATH is in core McIDAS-X, it doesn't really matter * what our current directory is, since files are located via * MCPATH.) */ dir = stralloc(pw->pw_dir, "/", (getuid() == mcidas_uid) ? "data" : "mcidas/data", (char *)0); if(dir == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(chdir(dir) == -1) { char *errmsg; int err; /* save errno before doing any other library calls */ err = errno; errmsg = stralloc("cannot chdir to " , dir, ": ", strerror(err) , (char *)0); die(EC_CHDIR, errmsg); /* NOTREACHED */ } free(dir); /* close the passwd file */ endpwent(); /* free our copy of the home directory of mcidas, if any */ if(mcidas_home != (char *)0) { free(mcidas_home); } } /* * prepend the given value to any existing value for the given environment * variable var. * separate the new value from any old value with the given * separator string. * * makes local copies of all args as necessary, so the caller * is responsible for freeing his own memory. * * return 0 on success, -1 on failure. */ static int prevar(const char *var, char *val, const char *sep) { char *oldval; char *newval; if(!var || !val) { return -1; } if(!sep) { sep = ""; } /* get the old value, if any */ oldval = getenv(var); /* construct the new value */ if(oldval) { newval = stralloc(var, "=", val, sep, oldval, (char *)0); } else { newval = stralloc(var, "=", val, (char *)0); } /* put it in */ if(newval == (char *)0) { die(EC_MALLOC, ES_MALLOC); /* NOTREACHED */ } if(putenv(newval) != 0) { die(EC_PUTENV, ES_PUTENV); /* NOTREACHED */ } return 0; } /* call to foreign host; returns fd of socket or doesn't return at all. */ static int getsockfd(Mcuint4 netaddr, unsigned short netport) { int s; int rc; struct sockaddr_in foreign; /* foreign address */ s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { die(EC_SOCKET, ES_SOCKET); /* NOTREACHED */ } memset(&foreign, 0, sizeof(foreign)); foreign.sin_family = AF_INET; foreign.sin_port = netport; foreign.sin_addr.s_addr = netaddr; rc = connect(s, (struct sockaddr *)&foreign, sizeof(foreign)); if (rc < 0) { close(s); die(EC_CONNECT, ES_CONNECT); /* NOTREACHED */ } return s; } /* cpio() - copy the given number of bytes from ifd to ofd * * ifd - input file descriptor * ofd - output file descriptor * numbyte - number of bytes to copy from ifd to ofd * * returns -1 on error, 0 on success. */ static int cpio(int ifd, int ofd, size_t numbyte) { size_t byteleft; byteleft = numbyte; while (byteleft > 0) { ssize_t nr; size_t nread; ssize_t rc; char buffer[1024]; if (byteleft < sizeof(buffer)) { nread = byteleft; } else { nread = sizeof(buffer); } nr = M0adderead(ifd, buffer, nread); if (nr == 0 || nr == -1) { return -1; } rc = M0addewrite(ofd, buffer, nr); /* copy bytes to user */ if (rc != nr) { return -1; } byteleft -= nr; } return 0; } /* strdn() - downcase the given C string */ static void strdn(char *s) { for(; *s; s++) { if(isascii(*s) && isupper(*s)) { *s = tolower(*s); } } } /* * send an error to stdout and exit */ static void die(Mcint4 code, const char *text) { Mcint4 repl[24]; char defbuf[80]; /* * Do something reasonable if called with a null pointer or * empty string. */ if(text == (char *)0 || text[0] == '\0') { sprintf(defbuf, "error code %d (no text)", code); text = &defbuf[0]; } /* * If we have read the version header AND can deal with * the version, then do any other reads that are necessary * before we can send the error message. * * Otherwise, blindly send the error message and hope for the * best. */ if(have_verhdr && (client_version == PROTOCOL_VERSION)) { /* read the pre-request header if necessary */ if(!have_prehdr) { Prehdr pre; M0adderead(STDIN, &pre, sizeof(pre)); have_prehdr = 1; } /* read the request header if necessary */ if(!have_reqhdr) { Reqhdr req; M0adderead(STDIN, &req, sizeof(req)); have_reqhdr = 1; } } /* send the error */ assert(sizeof(repl) == ANSLEN); memset(&repl[0], 0, sizeof(repl)); /* length of data in response */ repl[0] = 0; /* condition code, in network byte order */ repl[2] = htonl(code); /* message text */ strtofs((char *)&repl[3], text, ANSTEXTLEN); (void) M0addewrite(STDOUT, repl, ANSLEN); exit(0); }