chiark / gitweb /
@@ -1,3 +1,9 @@
[userv.git] / client.c
index 57b3f219c42e3d9153ed2a90d8ac461b84d7dd36..60869b09af0d3478b3fb74ca87fd53d1040fece8 100644 (file)
--- a/client.c
+++ b/client.c
@@ -168,7 +168,7 @@ static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) {
   exit(-1);
 }
 
-static void NONRETURNPRINTFFORMAT(1,2) syscallerror(const char *fmt, ...) {
+static void NONRETURNPRINTFFORMAT(1,2) fsyscallerror(const char *fmt, ...) {
   va_list al;
   int e;
 
@@ -181,6 +181,10 @@ static void NONRETURNPRINTFFORMAT(1,2) syscallerror(const char *fmt, ...) {
   exit(-1);
 }
 
+void syscallerror(const char *what) {
+  fsyscallerror("%s",what);
+}
+
 static void NONRETURNING protoreaderror(FILE *file, const char *where) {
   int e;
 
@@ -280,7 +284,7 @@ static void getprogress(struct progress_msg *progress_r, FILE *file) {
       for (i=0; i<progress_r->data.errmsg.messagelen; i++) {
        c= working_getc(file);
        if (c==EOF) protoreaderror(file,"in error message");
-       if (isprint(c)) putc(c,stderr);
+       if (ISCHAR(isprint,c)) putc(c,stderr);
        else fprintf(stderr,"\\x%02x",(unsigned char)c);
       }
       putc('\n',stderr);
@@ -300,18 +304,7 @@ static void getprogress(struct progress_msg *progress_r, FILE *file) {
  * the signal asynchronicity starts.  They can do anything they like.
  */
 
-static void *xmalloc(size_t s) {
-  void *p;
-  p= malloc(s?s:1);
-  if (!p) syscallerror("malloc (%lu bytes)",(unsigned long)s);
-  return p;
-}
-
-static void *xrealloc(void *p, size_t s) {
-  p= realloc(p,s);
-  if (!p) syscallerror("realloc (%lu bytes)",(unsigned long)s);
-  return p;
-}
+/* This includes xmalloc and xrealloc from both.c */
 
 static void xfwritestring(const char *s, FILE *file) {
   int l;
@@ -348,7 +341,7 @@ static void disconnect(void) /* DOES return, unlike in daemon */ {
     event_mbuf.type= et_disconnect;
     r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
     if ((r != sizeof(event_mbuf) || fflush(swfile)) && errno != EPIPE)
-      syscallerror("write to server when disconnecting\n");
+      syscallerror("write to server when disconnecting");
   }
   systemerror= 1;
 }
@@ -422,7 +415,7 @@ struct optioninfo {
   optionfunction *fn;
 };
 
-static void usage(void) {
+static void usage(FILE *stream) {
   if (fputs(
     "usage: userv <options> [--] <service-user> <service-name> [<argument> ...]\n"
     "usage: userv <options> -B|--builtin [--] <builtin-service> [<info-argument> ...]\n"
@@ -431,16 +424,18 @@ static void usage(void) {
     "         -t|--timeout <seconds>\n"
     "         -S|--signals <status>|number|number-nocore|highbit|stdout\n"
     "         -w|--fdwait <fd>=wait|nowait|close\n"
-    "         -P|--sigpipe  -H|--hidecwd  -h|--help  --copyright\n"
+    "         -P|--sigpipe  -H|--hidecwd  -h|--help|--version  --copyright\n"
     "         --override <configuration-data> } available only\n"
     "         --override-file <filename>      }  to root\n"
     "         --spoof-user <username>         }  or same user\n"
     "fdmodifiers:            read    write  overwrite    trunc[ate]\n"
-    "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n\n"
+    "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n"
+    "userv -B 'X' ... is same as userv --override 'execute-builtin X' - 'X' ...\n"
+    "         for help, type `userv -B help'; remember to quote multi-word X\n"
     "userv and uservd version " VERSION VEREXT "; copyright (C)1996-1999 Ian Jackson.\n"
     "there is NO WARRANTY; type `userv --copyright' for details.\n",
-            stderr) < 0)
-    syscallerror("write usage to stderr");
+            stream) < 0)
+    syscallerror("write usage message");
 }
 
 static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) {
@@ -449,7 +444,7 @@ static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) {
   fputs("userv: ",stderr);
   vfprintf(stderr,fmt,al);
   fputs("\n\n",stderr);
-  usage();
+  usage(stderr);
   exit(-1);
 }
 
@@ -502,43 +497,99 @@ static const struct fdmodifierinfo fdmodifierinfos[]= {
   {  0                                                                            }
 };
 
-static void addfdmodifier(int fd, const char *key) {
+static void addfdmodifier(int fd, const char *key, size_t key_len) {
   const struct fdmodifierinfo *fdmip;
   
   if (!*key) return;
-  for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,key); fdmip++);
-  if (!fdmip->string) usageerror("unknown fdmodifer `%s' for fd %d",key,fd);
+  for (fdmip= fdmodifierinfos;
+       fdmip->string &&
+        !(strlen(fdmip->string) == key_len &&
+          !memcmp(fdmip->string,key,key_len));
+       fdmip++);
+  if (!fdmip->string) usageerror("unknown fdmodifer `%.*s' for fd %d",
+                                (int)key_len,key,fd);
   if (fdmip->conflicts & fdsetup[fd].mods)
-    usageerror("fdmodifier `%s' conflicts with another for fd %d",key,fd);
+    usageerror("fdmodifier `%.*s' conflicts with another for fd %d",
+              (int)key_len,key,fd);
   fdsetup[fd].mods |= fdmip->implies;
   fdsetup[fd].oflags |= fdmip->oflags;
 }
 
-static int fdstdnumber(const char *string) {
-  if (!strcmp(string,"stdin")) return 0;
-  else if (!strcmp(string,"stdout")) return 1;
-  else if (!strcmp(string,"stderr")) return 2;
-  else return -1;
+static void addfdmodifier_fixed(int fd, const char *key) {
+  addfdmodifier(fd, key, strlen(key));
 }
 
+static int fdstdnumber(const char *string, const char **delim_r) {
+#define FN(v,s) do{                            \
+    if (!memcmp(string,s,sizeof(s)-1)) {       \
+      *delim_r= string+sizeof(s)-1;            \
+      return v;                                        \
+    }                                          \
+  }while(0)
+
+  FN(0,"stdin");
+  FN(1,"stdout");
+  FN(2,"stderr");
+
+  return -1;
+}
+
+static int strtofd(const char *string,
+                  const char **mods_r /* 0: no modifiers, must go to end */,
+                  const char *what) {
+  int fd;
+  unsigned long ul;
+  char *delim_v;
+  const char *mods;
+  
+  fd= fdstdnumber(string,&mods);
+  if (fd>=0) {
+    if (*mods && *mods != ',')
+      usageerror("%s, when it is `stdin', `stdout' or `stderr',"
+                " must be delimited with a comma from any following"
+                " modifiers - `%s' is not permitted",
+                what, mods);
+    goto parsed;
+  }
+
+  errno= 0;
+  ul= strtoul(string,&delim_v,10);
+  if (errno || delim_v == string || ul > INT_MAX)
+    usageerror("%s must be must be numeric file descriptor"
+              " or `stdin', `stdout' or `stderr'"
+              " - `%s' is not recognized",
+              what, string);
+  mods= delim_v;
+  fd= ul;
+
+ parsed:
+  if (*mods==',')
+    mods++;
+  
+  if (mods_r)
+    *mods_r= mods;
+  else if (*mods)
+    usageerror("%s must be must be only file descriptor"
+              " - trailing portion or modifiers `%s' not permitted",
+              what, mods);
+
+  if (fd > MAX_ALLOW_FD)
+    usageerror("%s file descriptor specified (%d)"
+              " is larger than maximum allowed (%d)",
+              what, fd, MAX_ALLOW_FD);
+  
+  return fd;
+}
+  
 static void of_file(const struct optioninfo *oip, const char *value, char *key) {
   unsigned long fd, copyfd;
   struct stat stab;
   int oldarraysize, r;
-  char *delim;
-
-  fd= strtoul(key,&delim,10);
-  if (delim == key) {
-    delim= strchr(key,',');
-    if (delim) *delim++= 0;
-    fd= fdstdnumber(key);
-    if (fd<0) usageerror("first part of argument to -f or --file must be numeric "
-                        "file descriptor or `stdin', `stdout' or `stderr' - `%s' "
-                        "is not recognized",key);
-  }
-  if (fd > MAX_ALLOW_FD)
-    usageerror("file descriptor specified (%lu) is larger than maximum allowed (%d)",
-              fd,MAX_ALLOW_FD);
+  size_t mod_len;
+  const char *mods, *delim;
+
+  fd= strtofd(key,&mods,"first part of argument to -f or --file");
+
   if (fd >= fdsetupsize) {
     oldarraysize= fdsetupsize;
     fdsetupsize+=2; fdsetupsize<<=1;
@@ -559,38 +610,29 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key)
   fdsetup[fd].oflags= 0;
   fdsetup[fd].mods= 0;
   fdsetup[fd].copyfd= -1;
-  while (delim && *delim) {
-    key= delim;
-    delim= strchr(key,',');
-    if (delim) *delim++= 0;
-    addfdmodifier(fd,key);
+  while (mods && *mods) {
+    delim= strchr(mods,',');
+    mod_len= delim ? delim-mods : strlen(mods);
+    addfdmodifier(fd,mods,mod_len);
+    mods= delim ? delim+1 : 0;
   }
   if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
     if (fd == 0) {
-      addfdmodifier(fd,"read");
+      addfdmodifier_fixed(fd,"read");
     } else if (fdsetup[fd].mods & fdm_fd) {
-      addfdmodifier(fd,"write");
+      addfdmodifier_fixed(fd,"write");
     } else {
-      addfdmodifier(fd,"overwrite");
+      addfdmodifier_fixed(fd,"overwrite");
     }
   }
   if (fdsetup[fd].mods & fdm_fd) {
-    copyfd= fdstdnumber(value);
-    if (copyfd<0) {
-      copyfd= strtoul(value,&delim,0);
-      if (*delim)
-       usageerror("value part of argument to --file with fd modifier must be "
-                  "numeric or fd name - `%s' is not recognised",value);
-      else if (copyfd > MAX_ALLOW_FD)
-       usageerror("file descriptor %lu named as target of file descriptor redirection"
-                  " (for file descriptor %lu) is larger than maximum allowed (%d)",
-                  copyfd,fd,MAX_ALLOW_FD);
-    }
+    copyfd= strtofd(value,0,
+                   "value part of argument to --file with fd modifier");
     r= fstat(copyfd,&stab);
     if (r) {
-      if (oip) syscallerror("check filedescriptor %lu (named as target of file "
-                           "descriptor redirection for %lu)",copyfd,fd);
-      else syscallerror("check basic filedescriptor %lu at program start",copyfd);
+      if (oip) fsyscallerror("check filedescriptor %lu (named as target of file "
+                            "descriptor redirection for %lu)",copyfd,fd);
+      else fsyscallerror("check basic filedescriptor %lu at program start",copyfd);
     }
     fdsetup[fd].copyfd= copyfd;
   }
@@ -598,18 +640,9 @@ static void of_file(const struct optioninfo *oip, const char *value, char *key)
 
 static void of_fdwait(const struct optioninfo *oip, const char *value, char *key) {
   const struct fdmodifierinfo *fdmip;
-  unsigned long ul;
   int fd;
-  char *delim;
   
-  fd= fdstdnumber(key);
-  if (fd<0) {
-    ul= strtoul(key,&delim,0);
-    if (*delim) usageerror("first part of argument to --fdwait must be "
-                          "numeric or fd name - `%s' is not recognised",key);
-    if (ul>MAX_ALLOW_FD) usageerror("first part of argument to --fdwait is too large");
-    fd= ul;
-  }
+  fd= strtofd(key,0,"first part of argument to --fdwait");
   if (fd >= fdsetupsize || !fdsetup[fd].filename)
     usageerror("file descriptor %d specified in --fdwait option is not open",fd);
   for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,value); fdmip++);
@@ -642,9 +675,11 @@ static void of_defvar(const struct optioninfo *oip, const char *value, char *key
 static void of_timeout(const struct optioninfo *oip, const char *value, char *key) {
   char *endp;
   unsigned long ul;
-  
-  ul= strtoul(value,&endp,0);
-  if (*endp) usageerror("timeout value `%s' must be a plain decimal string",value);
+
+  errno= 0;
+  ul= strtoul(value,&endp,10);
+  if (errno || *endp)
+    usageerror("timeout value `%s' must be a plain decimal string",value);
   if (ul>INT_MAX) usageerror("timeout value %lu too large",ul);
   timeout= ul;
 }
@@ -652,9 +687,10 @@ static void of_timeout(const struct optioninfo *oip, const char *value, char *ke
 static void of_signals(const struct optioninfo *oip, const char *value, char *key) {
   unsigned long numvalue;
   char *endp;
-  
-  numvalue= strtoul(value,&endp,0);
-  if (*endp) {
+
+  errno= 0;
+  numvalue= strtoul(value,&endp,10);
+  if (errno || *endp) {
     if (!strcmp(value,"number")) signalsexit= se_number;
     else if (!strcmp(value,"number-nocore")) signalsexit= se_numbernocore;
     else if (!strcmp(value,"highbit")) signalsexit= se_highbit;
@@ -676,7 +712,14 @@ static void of_hidecwd(const struct optioninfo *oip, const char *value, char *ke
 }
 
 static void of_help(const struct optioninfo *oip, const char *value, char *key) {
-  usage();
+  usage(stdout);
+  if (fclose(stdout)) syscallerror("fclose stdout after writing usage message");
+  exit(0);
+}
+
+static void of_version(const struct optioninfo *oip, const char *value, char *key) {
+  if (puts(VERSION VEREXT) == EOF || fclose(stdout))
+    syscallerror("write version number");
   exit(0);
 }
 
@@ -692,7 +735,7 @@ static void of_copyright(const struct optioninfo *oip, const char *value, char *
 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General\n"
 " Public License for more details.\n\n"
 " You should have received a copy of the GNU General Public License along\n"
-" with userv; if not, write to Ian Jackson <ian@chiark.greenend.org.uk> or\n"
+" with userv; if not, write to Ian Jackson <ian@davenant.greenend.org.uk> or\n"
 " to the Free Software Foundation, 59 Temple Place - Suite 330, Boston,\n"
 " MA 02111-1307, USA.\n",
            stdout) < 0) syscallerror("write usage to stderr");
@@ -729,6 +772,7 @@ const struct optioninfo optioninfos[]= {
   { 'H', "hidecwd",       0, of_hidecwd      },
   { 'B', "builtin",       0, of_builtin      },
   { 'h', "help",          0, of_help         },
+  {  0,  "version",       0, of_version      },
   {  0,  "copyright",     0, of_copyright    },
   {  0,  "override",      1, of_override     },
   {  0,  "override-file", 1, of_overridefile },
@@ -863,7 +907,7 @@ static void determine_users(void) {
   }
   if (!loginname) {
     pw= getpwuid(myuid); if (!pw) miscerror("cannot determine your login name");
-    loginname= pw->pw_name;
+    loginname= xstrsave(pw->pw_name);
   }
 
   if (!strcmp(serviceuser,"-")) serviceuser= loginname;
@@ -944,7 +988,7 @@ static void process_override(const char *servicename) {
     break;
   case ot_file:
     ovfile= fopen(overridevalue,"r");
-    if (!ovfile) syscallerror("open overriding configuration file `%s'",overridevalue);
+    if (!ovfile) fsyscallerror("open overriding configuration file `%s'",overridevalue);
     ovbuf= 0; ovavail= ovused= 0;
     while ((c= getc(ovfile)) != EOF) {
       if (!c) miscerror("overriding config file `%s' contains null(s)",overridevalue);
@@ -956,7 +1000,7 @@ static void process_override(const char *servicename) {
       ovbuf[ovused++]= c;
     }
     if (ferror(ovfile) || fclose(ovfile))
-      syscallerror("read overriding configuration file `%s'",overridevalue);
+      fsyscallerror("read overriding configuration file `%s'",overridevalue);
     ovbuf= xrealloc(ovbuf,ovused+1);
     ovbuf[ovused]= 0;
     break;
@@ -993,7 +1037,7 @@ static int server_connect(void) {
     if (errno == ECONNREFUSED || errno == ENOENT)
       syscallerror("uservd daemon is not running - service not available");
     if (errno != EINTR)
-      syscallerror("unable to connect to uservd daemon: %m");
+      fsyscallerror("unable to connect to uservd daemon: %m");
   }
 
   return sfd;
@@ -1026,16 +1070,16 @@ static void server_preparepipes(void) {
     assert(!pipepathbuf[PIPEPATHMAXLEN]);
     priv_resume();
     if (unlink(pipepathbuf) && errno != ENOENT)
-      syscallerror("remove any old pipe `%s'",pipepathbuf);
+      fsyscallerror("remove any old pipe `%s'",pipepathbuf);
     if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
-      syscallerror("create pipe `%s'",pipepathbuf);
+      fsyscallerror("create pipe `%s'",pipepathbuf);
     tempfd= open(pipepathbuf,O_RDWR);
-    if (tempfd<0) syscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
+    if (tempfd<0) fsyscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
     assert(fdsetup[fd].mods & (fdm_read|fdm_write));
     fdsetup[fd].pipefd=
       open(pipepathbuf, (fdsetup[fd].mods & fdm_read) ? O_WRONLY : O_RDONLY);
-    if (fdsetup[fd].pipefd<0) syscallerror("real open pipe `%s'",pipepathbuf);
-    if (close(tempfd)) syscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
+    if (fdsetup[fd].pipefd<0) fsyscallerror("real open pipe `%s'",pipepathbuf);
+    if (close(tempfd)) fsyscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
     priv_suspend();
   }
 }
@@ -1119,6 +1163,21 @@ static void prepare_asynchsignals(void) {
   if (alarm(timeout)<0) syscallerror("set up timeout alarm");
 }
 
+
+static void close_unwanted_pipes(void) {
+  int fd;
+
+  for (fd=0; fd<fdsetupsize; fd++) {
+    if (!fdsetup[fd].filename) continue;
+    if (close(fdsetup[fd].pipefd)) fsyscallerror("close pipe fd for %d",fd);
+    if (fdsetup[fd].copyfd>2)
+      if (close(fdsetup[fd].copyfd))
+       if (errno != EBADF)
+         /* EBADF can be induced if cmd line specifies same fd twice */
+         fsyscallerror("close real fd for %d",fd);
+  }
+}
+
 static void catdup(const char *which, int from, int to) {
   if (dup2(from,to)<0) {
     blocksignals(SIG_BLOCK);
@@ -1140,13 +1199,13 @@ static void connect_pipes(void) {
       fdsetup[fd].copyfd=
        open(fdsetup[fd].filename,fdsetup[fd].oflags|O_NOCTTY,0777);
       if (fdsetup[fd].copyfd<0)
-       syscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
+       fsyscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
     }
     blocksignals(SIG_BLOCK);
     child= fork();
     fdsetup[fd].catpid= child;
     blocksignals(SIG_UNBLOCK);
-    if (child==-1) syscallerror("fork for cat for fd %d",fd);
+    if (child==-1) fsyscallerror("fork for cat for fd %d",fd);
     if (!child) {
       snprintf(catnamebuf,sizeof(catnamebuf),"cat fd%d",fd);
       catnamebuf[sizeof(catnamebuf)-1]= 0;
@@ -1161,14 +1220,13 @@ static void connect_pipes(void) {
       reading= fdsetup[fd].mods & fdm_read;
       catdup(catnamebuf, fdsetup[fd].copyfd, reading ? 0 : 1);
       catdup(catnamebuf, fdsetup[fd].pipefd, reading ? 1 : 0);
+      close_unwanted_pipes();
       execl("/bin/cat",catnamebuf,(char*)0);
       fprintf(stderr,"userv: %s: cannot exec `cat': %s\n",catnamebuf,strerror(errno));
       exit(-1);
     }
-    if (fdsetup[fd].copyfd>2)
-      if (close(fdsetup[fd].copyfd)) syscallerror("close real fd for %d",fd);
-    if (close(fdsetup[fd].pipefd)) syscallerror("close pipe fd for %d",fd);
   }
+  close_unwanted_pipes();
 }
 
 static void server_sendconfirm(void) {
@@ -1202,7 +1260,7 @@ static void dispose_remaining_pipes(void) {
   blocksignals(SIG_BLOCK);
   for (fd=0; fd<fdsetupsize; fd++) {
     if (!(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_close))) continue;
-    if (kill(fdsetup[fd].catpid,SIGKILL)) syscallerror("kill cat for %d",fd);
+    if (kill(fdsetup[fd].catpid,SIGKILL)) fsyscallerror("kill cat for %d",fd);
     fdsetup[fd].killed= 1;
   }
   blocksignals(SIG_UNBLOCK);