chiark / gitweb /
Fix builtins.
[userv.git] / client.c
1 /*
2  * userv - client.c
3  * client code
4  *
5  * Copyright (C)1996-1997 Ian Jackson
6  *
7  * This is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with userv; if not, write to the Free Software
19  * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21
22 #include <fcntl.h>
23 #include <stdarg.h>
24 #include <errno.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <assert.h>
29 #include <unistd.h>
30 #include <ctype.h>
31 #include <pwd.h>
32 #include <grp.h>
33 #include <signal.h>
34 #include <limits.h>
35 #include <sys/time.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <sys/socket.h>
39 #include <sys/un.h>
40 #include <sys/resource.h>
41 #include <sys/wait.h>
42
43 #include "config.h"
44 #include "common.h"
45 #include "version.h"
46
47 struct optioninfo;
48
49 typedef void optionfunction(const struct optioninfo*, const char *value, char *key);
50
51 struct optioninfo {
52   int abbrev;
53   const char *full;
54   int values; /* 0: no value; 1: single value; 2: key and value */
55   optionfunction *fn;
56 };
57
58 enum fdmodifiervalues {
59   fdm_read=       00001,
60   fdm_write=      00002,
61   fdm_create=     00004,
62   fdm_exclusive=  00010,
63   fdm_truncate=   00020,
64   fdm_append=     00040,
65   fdm_sync=       00100,
66   fdm_fd=         00200,
67   fdm_wait=       01000,
68   fdm_nowait=     02000,
69   fdm_close=      04000,
70 };
71
72 struct fdmodifierinfo {
73   const char *string;
74   int implies;
75   int conflicts;
76   int oflags;
77 };
78
79 const struct fdmodifierinfo fdmodifierinfos[]= {
80   { "read",      fdm_read,
81                  fdm_write,
82                  O_RDONLY                                                         },
83   { "write",     fdm_write,
84                  fdm_read,
85                  O_WRONLY                                                         },
86   { "overwrite", fdm_write|fdm_create|fdm_truncate,
87                  fdm_read|fdm_fd|fdm_exclusive,
88                  O_WRONLY|O_CREAT|O_TRUNC                                         },
89   { "create",    fdm_write|fdm_create,
90                  fdm_read|fdm_fd,
91                  O_WRONLY|O_CREAT                                                 },
92   { "creat",     fdm_write|fdm_create,
93                  fdm_read|fdm_fd,
94                  O_WRONLY|O_CREAT                                                 },
95   { "exclusive", fdm_write|fdm_create|fdm_exclusive,
96                  fdm_read|fdm_fd|fdm_truncate,
97                  O_WRONLY|O_CREAT|O_EXCL                                          },
98   { "excl",      fdm_write|fdm_create|fdm_exclusive,
99                  fdm_read|fdm_fd|fdm_truncate,
100                  O_WRONLY|O_CREAT|O_EXCL                                          },
101   { "truncate",  fdm_write|fdm_truncate,
102                  fdm_read|fdm_fd|fdm_exclusive,
103                  O_WRONLY|O_CREAT|O_EXCL                                          },
104   { "trunc",     fdm_write|fdm_truncate,
105                  fdm_read|fdm_fd|fdm_exclusive,
106                  O_WRONLY|O_CREAT|O_EXCL                                          },
107   { "append",    fdm_write|fdm_append,
108                  fdm_read|fdm_fd,
109                  O_WRONLY|O_CREAT|O_APPEND                                        },
110   { "sync",      fdm_write|fdm_sync,
111                  fdm_read|fdm_fd,
112                  O_WRONLY|O_CREAT|O_SYNC                                          },
113   { "wait",      fdm_wait,
114                  fdm_nowait|fdm_close,
115                  0                                                                },
116   { "nowait",    fdm_nowait,
117                  fdm_wait|fdm_close,
118                  0                                                                },
119   { "close",     fdm_close,
120                  fdm_wait|fdm_nowait,
121                  0                                                                },
122   { "fd",        fdm_fd,
123                  fdm_create|fdm_exclusive|fdm_truncate|fdm_append|fdm_sync,
124                  0                                                                },
125   {  0                                                                            }
126 };
127
128 struct fdsetupstate {
129   const char *filename;
130   int copyfd;
131   int mods, oflags, pipefd, killed;
132   pid_t catpid;
133 };
134
135 enum signalsexitspecials { se_number=-100, se_numbernocore, se_highbit, se_stdout };
136 enum overridetypes { ot_none, ot_string, ot_file, ot_builtin };
137
138 struct constkeyvaluepair { const char *key, *value; };
139
140 static const char *serviceuser;
141 static uid_t serviceuid, myuid;
142 static struct fdsetupstate *fdsetup;
143 static int fdsetupsize;
144 static struct constkeyvaluepair *defvararray;
145 static int defvaravail, defvarused;
146 static unsigned long timeout;
147 static int signalsexit=254;
148 static int sigpipeok, hidecwd;
149 static int overridetype= ot_none;
150 static const char *overridevalue, *spoofuser=0;
151
152 static FILE *srfile, *swfile;
153
154 static void blocksignals(int how) {
155   sigset_t set;
156   static const char blockerrmsg[]= "userv: failed to [un]block signals: ";
157   const char *str;
158
159   sigemptyset(&set);
160   sigaddset(&set,SIGCHLD);
161   sigaddset(&set,SIGALRM);
162   if (sigprocmask(how,&set,0)) {
163     str= strerror(errno);
164     write(2,blockerrmsg,sizeof(blockerrmsg)-1);
165     write(2,str,strlen(str));
166     write(2,"\n",1);
167     exit(-1);
168   }
169 }
170
171 static void NONRETURNPRINTFFORMAT(1,2) miscerror(const char *fmt, ...) {
172   va_list al;
173
174   blocksignals(SIG_BLOCK);
175   va_start(al,fmt);
176   fprintf(stderr,"userv: failure: ");
177   vfprintf(stderr,fmt,al);
178   fprintf(stderr,"\n");
179   exit(-1);
180 }
181
182 static void NONRETURNPRINTFFORMAT(1,2) syscallerror(const char *fmt, ...) {
183   va_list al;
184   int e;
185
186   e= errno;
187   blocksignals(SIG_BLOCK);
188   va_start(al,fmt);
189   fprintf(stderr,"userv: system call failure: ");
190   vfprintf(stderr,fmt,al);
191   fprintf(stderr,": %s\n",strerror(e));
192   exit(-1);
193 }
194
195 static void NONRETURNING protoreaderror(FILE *file, const char *where) {
196   int e;
197
198   e= errno;
199   blocksignals(SIG_BLOCK);
200   if (ferror(file)) {
201     fprintf(stderr,"userv: failure: read error %s: %s\n",where,strerror(e));
202   } else {
203     assert(feof(file));
204     fprintf(stderr,"userv: internal failure: EOF from server %s\n",where);
205   }
206   exit(-1);
207 }
208
209 static void NONRETURNPRINTFFORMAT(1,2) protoerror(const char *fmt, ...) {
210   va_list al;
211
212   blocksignals(SIG_BLOCK);
213   va_start(al,fmt);
214   fprintf(stderr,"userv: internal failure: protocol error: ");
215   vfprintf(stderr,fmt,al);
216   fprintf(stderr,"\n");
217   exit(-1);
218 }
219
220 #ifdef DEBUG
221 static void priv_suspend(void) { }
222 static void priv_resume(void) { }
223 static void priv_permanentlyrevokesuspended(void) { }
224 #else
225 static void priv_suspend(void) {
226   if (setreuid(0,myuid) != 0) syscallerror("suspend root setreuid(0,myuid)");
227 }
228 static void priv_resume(void) {
229   if (setreuid(myuid,0) != 0) syscallerror("resume root setreuid(myuid,0)");
230 }
231 static void priv_permanentlyrevokesuspended(void) {
232   if (setreuid(myuid,myuid) != 0) syscallerror("revoke root setreuid(myuid,myuid)");
233   if (setreuid(myuid,myuid) != 0) syscallerror("rerevoke root setreuid(myuid,myuid)");
234   if (myuid) {
235     if (!setreuid(myuid,0)) miscerror("revoked root but setreuid(0,0) succeeded !");
236     if (errno != EPERM) syscallerror("revoked and setreuid(myuid,0) unexpected error");
237   }
238 }
239 #endif
240
241 static void *xmalloc(size_t s) {
242   void *p;
243   p= malloc(s?s:1);
244   if (!p) syscallerror("malloc (%lu bytes)",(unsigned long)s);
245   return p;
246 }
247
248 static void *xrealloc(void *p, size_t s) {
249   p= realloc(p,s);
250   if (!p) syscallerror("realloc (%lu bytes)",(unsigned long)s);
251   return p;
252 }
253
254 static void xfread(void *p, size_t sz, FILE *file) {
255   size_t nr;
256   nr= fread(p,1,sz,file);
257   if (nr != sz) protoreaderror(file,"in data");
258 }
259
260 static void xfwrite(const void *p, size_t sz, FILE *file) {
261   size_t nr;
262   nr= fwrite(p,1,sz,file); if (nr == sz) return;
263   syscallerror("writing to server");
264 }
265
266 static void xfwritestring(const char *s, FILE *file) {
267   int l;
268   l= strlen(s);
269   assert(l<=MAX_GENERAL_STRING);
270   xfwrite(&l,sizeof(l),file);
271   xfwrite(s,sizeof(*s)*l,file);
272 }
273
274 static void xfflush(FILE *file) {
275   if (fflush(file)) syscallerror("flush server socket");
276 }
277
278 static void usage(void) {
279   if (fprintf(stderr,
280     "usage: userv <options> [--] <service-user> <service-name> [<argument> ...]\n"
281     "usage: userv <options> -B|--builtin [--] <builtin-service> [<info-argument> ...]\n"
282     "options: -f|--file <fd>[<fdmodifiers>]=<filename>\n"
283     "         -D|--defvar <name>=<value>\n"
284     "         -t|--timeout <seconds>\n"
285     "         -S|--signals <status>|number|number-nocore|highbit|stdout\n"
286     "         -w|--fdwait <fd>=wait|nowait|close\n"
287     "         -P|--sigpipe  -H|--hidecwd  -h|--help  --copyright\n"
288     "         --override <configuration-data> } available only\n"
289     "         --override-file <filename>      }  to root\n"
290     "         --spoof-user <username>         }  or same user\n"
291     "fdmodifiers:            read    write  overwrite    trunc[ate]\n"
292     "(separate with commas)  append  sync   excl[usive]  creat[e]  fd\n\n"
293     "userv and uservd version " VERSION "; copyright (C)1996-1997 Ian Jackson.\n"
294     "there is NO WARRANTY; type `userv --copyright' for details.\n")
295       == EOF) syscallerror("write usage to stderr");
296 }
297
298 static void NONRETURNPRINTFFORMAT(1,2) usageerror(const char *fmt, ...) {
299   va_list al;
300   va_start(al,fmt);
301   fprintf(stderr,"userv: ");
302   vfprintf(stderr,fmt,al);
303   fprintf(stderr,"\n\n");
304   usage();
305   exit(-1);
306 }
307
308 static void addfdmodifier(struct fdsetupstate *fdsus, int fd, const char *key) {
309   const struct fdmodifierinfo *fdmip;
310   
311   if (!*key) return;
312   for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,key); fdmip++);
313   if (!fdmip->string) usageerror("unknown fdmodifer `%s' for fd %d",key,fd);
314   if (fdmip->conflicts & fdsetup[fd].mods)
315     usageerror("fdmodifier `%s' conflicts with another for fd %d",key,fd);
316   fdsetup[fd].mods |= fdmip->implies;
317   fdsetup[fd].oflags |= fdmip->oflags;
318 }
319
320 static int fdstdnumber(const char *string) {
321   if (!strcmp(string,"stdin")) return 0;
322   else if (!strcmp(string,"stdout")) return 1;
323   else if (!strcmp(string,"stderr")) return 2;
324   else return -1;
325 }
326
327 static void of_file(const struct optioninfo *oip, const char *value, char *key) {
328   unsigned long fd, copyfd;
329   struct stat stab;
330   int oldarraysize, r;
331   char *delim;
332
333   fd= strtoul(key,&delim,10);
334   if (delim == key) {
335     delim= strchr(key,',');
336     if (delim) *delim++= 0;
337     fd= fdstdnumber(key);
338     if (fd<0) usageerror("first part of argument to -f or --file must be numeric "
339                          "file descriptor or `stdin', `stdout' or `stderr' - `%s' "
340                          "is not recognized",key);
341   }
342   if (fd > MAX_ALLOW_FD)
343     usageerror("file descriptor specified (%lu) is larger than maximum allowed (%d)",
344                fd,MAX_ALLOW_FD);
345   if (fd >= fdsetupsize) {
346     oldarraysize= fdsetupsize;
347     fdsetupsize+=2; fdsetupsize<<=1;
348     fdsetup= xrealloc(fdsetup,sizeof(struct fdsetupstate)*fdsetupsize);
349     while (oldarraysize < fdsetupsize) {
350       fdsetup[oldarraysize].filename= 0;
351       fdsetup[oldarraysize].copyfd= -1;
352       fdsetup[oldarraysize].mods= 0;
353       fdsetup[oldarraysize].catpid= -1;
354       fdsetup[oldarraysize].killed= 0;
355       fdsetup[oldarraysize++].filename= 0;
356       oldarraysize++;
357     }
358   }
359   fdsetup[fd].filename= value;
360   fdsetup[fd].oflags= 0;
361   fdsetup[fd].mods= 0;
362   fdsetup[fd].copyfd= -1;
363   while (delim && *delim) {
364     key= delim;
365     delim= strchr(key,',');
366     if (delim) *delim++= 0;
367     addfdmodifier(&fdsetup[fd],fd,key);
368   }
369   if (!(fdsetup[fd].mods & (fdm_read|fdm_write))) {
370     if (fd != 1 && fd != 2) {
371       addfdmodifier(&fdsetup[fd],fd,"read");
372     } else if (fdsetup[fd].mods & fdm_fd) {
373       addfdmodifier(&fdsetup[fd],fd,"write");
374     } else {
375       addfdmodifier(&fdsetup[fd],fd,"overwrite");
376     }
377   }
378   if (fdsetup[fd].mods & fdm_fd) {
379     copyfd= fdstdnumber(value);
380     if (copyfd<0) {
381       copyfd= strtoul(value,&delim,0);
382       if (*delim)
383         usageerror("value part of argument to --file with fd modifier must be "
384                    "numeric or fd name- `%s' is not recognised",value);
385       else if (copyfd > MAX_ALLOW_FD)
386         usageerror("file descriptor %lu named as target of file descriptor redirection"
387                    " (for file descriptor %lu) is larger than maximum allowed (%d)",
388                    copyfd,fd,MAX_ALLOW_FD);
389     }
390     do { r= fstat(copyfd,&stab); } while (r && errno==EINTR);
391     if (r) {
392       if (oip) syscallerror("check filedescriptor %lu (named as target of file "
393                             "descriptor redirection for %lu)",copyfd,fd);
394       else syscallerror("check basic filedescriptor %lu at program start",copyfd);
395     }
396     fdsetup[fd].copyfd= copyfd;
397   }
398 }
399
400 static void of_fdwait(const struct optioninfo *oip, const char *value, char *key) {
401   const struct fdmodifierinfo *fdmip;
402   unsigned long ul;
403   int fd;
404   char *delim;
405   
406   fd= fdstdnumber(key);
407   if (fd<0) {
408     ul= strtoul(key,&delim,0);
409     if (*delim) usageerror("first part of argument to --fdwait must be "
410                            "numeric or fd name - `%s' is not recognised",key);
411     if (ul>INT_MAX) usageerror("first part of argument to --fdwait is far too large");
412     fd= ul;
413   }
414   if (fd >= fdsetupsize || !fdsetup[fd].filename)
415     usageerror("file descriptor %d specified in --fdwait option is not open",fd);
416   for (fdmip= fdmodifierinfos; fdmip->string && strcmp(fdmip->string,value); fdmip++);
417   if (!fdmip->string || !(fdmip->implies & (fdm_wait|fdm_nowait|fdm_close)))
418     usageerror("value for --fdwait must be `wait', `nowait' or `close', not `%s'",value);
419   fdsetup[fd].mods &= ~(fdm_wait|fdm_nowait|fdm_close);
420   fdsetup[fd].mods |= fdmip->implies;
421 }
422
423 static void of_defvar(const struct optioninfo *oip, const char *value, char *key) {
424   int i;
425
426   if (!key[0])
427     usageerror("empty string not allowed as variable name");
428   if (strlen(key)>MAX_GENERAL_STRING)
429     usageerror("variable name `%s' is far too long",key);
430   if (strlen(value)>MAX_GENERAL_STRING)
431     usageerror("variable `%s' has value `%s' which is far too long",key,value);
432   for (i=0; i<defvarused && strcmp(defvararray[i].key,key); i++);
433   if (defvarused >= MAX_ARGSDEFVAR) usageerror("far too many --defvar or -D options");
434   if (i>=defvaravail) {
435     defvaravail+=10; defvaravail<<=1;
436     defvararray= xrealloc(defvararray,sizeof(struct constkeyvaluepair)*defvaravail);
437   }
438   if (i==defvarused) defvarused++;
439   defvararray[i].key= key;
440   defvararray[i].value= value;
441 }
442
443 static void of_timeout(const struct optioninfo *oip, const char *value, char *key) {
444   char *endp;
445   timeout= strtoul(value,&endp,0);
446   if (*endp) usageerror("timeout value `%s' must be a plain decimal string",value);
447   if (timeout>INT_MAX) usageerror("timeout value %lu too large",timeout);
448 }
449
450 static void of_signals(const struct optioninfo *oip, const char *value, char *key) {
451   unsigned long numvalue;
452   char *endp;
453   numvalue= strtoul(value,&endp,0);
454   if (*endp) {
455     if (!strcmp(value,"number")) signalsexit= se_number;
456     else if (!strcmp(value,"number-nocore")) signalsexit= se_numbernocore;
457     else if (!strcmp(value,"highbit")) signalsexit= se_highbit;
458     else if (!strcmp(value,"stdout")) signalsexit= se_stdout;
459     else usageerror("value `%s' for --signals not understood",value);
460   } else {
461     if (numvalue<0 || numvalue>255)
462       usageerror("value %lu for --signals not 0...255",numvalue);
463     signalsexit= numvalue;
464   }
465 }
466
467 static void of_sigpipe(const struct optioninfo *oip, const char *value, char *key) {
468   sigpipeok=1;
469 }
470
471 static void of_hidecwd(const struct optioninfo *oip, const char *value, char *key) {
472   hidecwd=1;
473 }
474
475 static void of_help(const struct optioninfo *oip, const char *value, char *key) {
476   usage();
477   exit(0);
478 }
479
480 static void of_copyright(const struct optioninfo *oip, const char *value, char *key) {
481   if (fprintf(stdout,
482 " userv - user service daemon and client; copyright (C)1996-1997 Ian Jackson\n\n"
483 " This is free software; you can redistribute it and/or modify it under the\n"
484 " terms of the GNU General Public License as published by the Free Software\n"
485 " Foundation; either version 2 of the License, or (at your option) any\n"
486 " later version.\n\n"
487 " This program is distributed in the hope that it will be useful, but\n"
488 " WITHOUT ANY WARRANTY; without even the implied warranty of\n"
489 " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General\n"
490 " Public License for more details.\n\n"
491 " You should have received a copy of the GNU General Public License along\n"
492 " with userv; if not, write to Ian Jackson <ian@chiark.greenend.org.uk> or\n"
493 " to the Free Software Foundation, 59 Temple Place - Suite 330, Boston,\n"
494 " MA 02111-1307, USA.\n"
495               ) == EOF) syscallerror("write usage to stderr");
496   exit(0);
497 }
498
499 static void of_builtin(const struct optioninfo *oip, const char *value, char *key) {
500   overridetype= ot_builtin;
501 }
502
503 static void of_override(const struct optioninfo *oip, const char *value, char *key) {
504   overridetype= ot_string;
505   overridevalue= value;
506 }
507
508 static void of_overridefile(const struct optioninfo *oip,
509                             const char *value, char *key) {
510   overridetype= ot_file;
511   overridevalue= value;
512 }
513
514 static void of_spoofuser(const struct optioninfo *oip,
515                          const char *value, char *key) {
516   spoofuser= value;
517 }
518
519 const struct optioninfo optioninfos[]= {
520   { 'f', "file",          2, of_file         },
521   { 'w', "fdwait",        2, of_fdwait       },
522   { 'D', "defvar",        2, of_defvar       },
523   { 't', "timeout",       1, of_timeout      },
524   { 'S', "signals",       1, of_signals      },
525   { 'P', "sigpipe",       0, of_sigpipe      },
526   { 'H', "hidecwd",       0, of_hidecwd      },
527   { 'B', "builtin",       0, of_builtin      },
528   { 'h', "help",          0, of_help         },
529   {  0,  "copyright",     0, of_copyright    },
530   {  0,  "override",      1, of_override     },
531   {  0,  "override-file", 1, of_overridefile },
532   {  0,  "spoof-user",    1, of_spoofuser    },
533   {  0,   0                                  }
534 };
535
536 static void callvalueoption(const struct optioninfo *oip, char *arg) {
537   char *equals;
538   if (oip->values == 2) {
539     equals= strchr(arg,'=');
540     if (!equals)
541       if (oip->abbrev)
542         usageerror("option --%s (-%c) passed argument `%s' with no `='",
543                    oip->full,oip->abbrev,arg);
544       else
545         usageerror("option --%s passed argument `%s' with no `='",
546                    oip->full,arg);
547     *equals++= 0;
548     (oip->fn)(oip,equals,arg);
549   } else {
550     (oip->fn)(oip,arg,0);
551   }
552 }
553
554 static void checkmagic(unsigned long was, unsigned long should, const char *when) {
555   if (was != should)
556     protoerror("magic number %s was %08lx, expected %08lx",when,was,should);
557 }
558
559 static void getprogress(struct progress_msg *progress_r, FILE *file) {
560   int i, c;
561   unsigned long ul;
562
563   for (;;) {
564     xfread(progress_r,sizeof(struct progress_msg),file);
565     switch (progress_r->type) {
566     case pt_failed:
567       blocksignals(SIG_BLOCK);
568       fputs("userv: uservd reports that service failed\n",stderr);
569       exit(-1);
570     case pt_errmsg:
571       blocksignals(SIG_BLOCK);
572       fputs("uservd: ",stderr);
573       if (progress_r->data.errmsg.messagelen>4096)
574         protoerror("stderr message length %d is far too long",
575                    progress_r->data.errmsg.messagelen);
576       for (i=0; i<progress_r->data.errmsg.messagelen; i++) {
577         c= getc(file); if (c==EOF) protoreaderror(file,"in error message");
578         if (isprint(c)) putc(c,stderr);
579         else fprintf(stderr,"\\x%02x",(unsigned char)c);
580       }
581       putc('\n',stderr);
582       if (ferror(stderr)) syscallerror("printing error message");
583       xfread(&ul,sizeof(ul),file);
584       checkmagic(ul,PROGRESS_ERRMSG_END_MAGIC,"after error message");
585       blocksignals(SIG_UNBLOCK);
586       break;
587     default:
588       return;
589     }
590   }
591 }
592
593 static void xfwritefds(int modifier, int expected, FILE *file) {
594   int i, fdcount;
595
596   for (i=0, fdcount=0; i<fdsetupsize; i++) {
597     if (!(fdsetup[i].filename && (fdsetup[i].mods & modifier)))
598       continue;
599     xfwrite(&i,sizeof(int),file); fdcount++;
600   }
601   assert(fdcount == expected);
602 }
603
604 static void disconnect(void) /* DOES return, unlike in daemon */ {
605   struct event_msg event_mbuf;
606
607   if (!swfile) {
608     fputs("userv: failed, after service program terminated\n",stderr);
609     _exit(255);
610   }
611   memset(&event_mbuf,0,sizeof(event_mbuf));
612   event_mbuf.magic= EVENT_MAGIC;
613   event_mbuf.type= et_disconnect;
614   xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
615   xfflush(swfile);
616 }
617
618 static void sighandler_alrm(int ignored) /* DOES return, unlike in daemon */ {
619   int es;
620   es= errno;
621   fputs("userv: timeout\n",stderr);
622   disconnect();
623   errno= es;
624 }
625
626 static void sighandler_chld(int ignored) /* DOES return, unlike in daemon */ {
627   struct event_msg event_mbuf;
628   pid_t child;
629   int status, fd, r, es;
630
631   es= errno;
632   for (;;) {
633     child= wait3(&status,WNOHANG,0);
634     if (child == 0 || (child == -1 && errno == ECHILD)) break;
635     if (child == -1) syscallerror("wait for child process (in sigchld handler)");
636     for (fd=0; fd<fdsetupsize && fdsetup[fd].catpid != child; fd++);
637     if (fd>=fdsetupsize) continue; /* perhaps the invoker gave us children */
638     if ((WIFEXITED(status) && WEXITSTATUS(status)==0) ||
639         (WIFSIGNALED(status) && WTERMSIG(status)==SIGPIPE) ||
640         (fdsetup[fd].killed && WIFSIGNALED(status) && WTERMSIG(status)==SIGKILL)) {
641       if (swfile && fdsetup[fd].mods & fdm_read) {
642         memset(&event_mbuf,0,sizeof(event_mbuf));
643         event_mbuf.magic= EVENT_MAGIC;
644         event_mbuf.type= et_closereadfd;
645         r= fwrite(&event_mbuf,1,sizeof(event_mbuf),swfile);
646         if (r != sizeof(event_mbuf) || fflush(swfile))
647           if (errno != EPIPE) syscallerror("inform service of closed read fd");
648       }
649     } else {
650       if (WIFEXITED(status))
651         fprintf(stderr,"userv: cat for fd %d exited with error exit status %d\n",
652                 fd,WEXITSTATUS(status));
653       else if (WIFSIGNALED(status))
654         if (WCOREDUMP(status))
655           fprintf(stderr,"userv: cat for fd %d dumped core due to signal %s (%d)\n",
656                   fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
657         else
658           fprintf(stderr,"userv: cat for fd %d terminated by signal %s (%d)\n",
659                   fd,strsignal(WTERMSIG(status)),WTERMSIG(status));
660       else
661         fprintf(stderr,"userv: cat for fd %d gave unknown wait status %d\n",
662                 fd,status);
663       disconnect();
664     }
665     fdsetup[fd].catpid= -1;
666   }
667   errno= es;
668 }
669
670 static void catdup(const char *which, int from, int to) {
671   if (dup2(from,to)<0) {
672     blocksignals(SIG_BLOCK);
673     fprintf(stderr,"userv: %s: cannot dup for %s: %s\n",which,
674             to?"stdout":"stdin", strerror(errno));
675     exit(-1);
676   }
677 }
678
679 int main(int argc, char *const *argv) {
680   static char fd0key[]= "stdin,fd,read";
681   static char fd1key[]= "stdout,fd,write";
682   static char fd2key[]= "stderr,fd,write";
683   static char stderrbuf[BUFSIZ], stdoutbuf[1024];
684   
685   char *const *argpp;
686   char *argp;
687   const struct optioninfo *oip;
688   struct sockaddr_un ssockname;
689   int sfd, ngids, i, tempfd, l, c, reading, fd, r, status, ngidssize;
690   sigset_t sset;
691   unsigned long ul;
692   size_t cwdbufsize;
693   char *cwdbuf, **mem;
694   struct opening_msg opening_mbuf;
695   struct request_msg request_mbuf;
696   struct progress_msg progress_mbuf;
697   struct event_msg event_mbuf;
698   struct passwd *pw;
699   struct group *gr;
700   gid_t mygid, spoofgid, *gidarray;
701   uid_t spoofuid;
702   pid_t mypid;
703   const char *logname;
704   FILE *ovfile;
705   char *ovbuf;
706   int ovavail, ovused;
707   char pipepathbuf[PIPEPATHMAXLEN], catnamebuf[sizeof(int)*3+30];
708   struct sigaction sig;
709
710 #ifdef NDEBUG
711 # error Do not disable assertions in this security-critical code !
712 #endif
713
714   mypid= getpid(); if (mypid == (pid_t)-1) syscallerror("getpid");
715   myuid= getuid(); if (myuid == (uid_t)-1) syscallerror("getuid");
716   mygid= getgid(); if (mygid == (gid_t)-1) syscallerror("getgid");
717   ngids= getgroups(0,0); if (ngids == (gid_t)-1) syscallerror("getgroups(0,0)");
718   gidarray= xmalloc(sizeof(gid_t)*ngids);
719   if (getgroups(ngids,gidarray) != ngids) syscallerror("getgroups(ngids,)");
720   priv_suspend();
721
722   assert(argv[0]);
723   of_file(0,"stdin",fd0key);
724   of_file(0,"stdout",fd1key);
725   of_file(0,"stderr",fd2key);
726
727   for (argpp= argv+1;
728        (argp= *argpp) && *argp == '-' && argp[1];
729        argpp++) {
730     if (!*++argp) usageerror("unknown option/argument `%s'",*argpp);
731     if (*argp == '-') { /* Two hyphens */
732       if (!*++argp) { argpp++; break; /* End of options. */ }
733       for (oip= optioninfos; oip->full && strcmp(oip->full,argp); oip++);
734       if (!oip->full) usageerror("unknown long option `%s'",*argpp);
735       if (oip->values) {
736         if (!argpp[1]) usageerror("long option `%s' needs a value",*argpp);
737         callvalueoption(oip,*++argpp);
738       } else {
739         (oip->fn)(oip,0,0);
740       }
741     } else {
742       for (; *argp; argp++) {
743         for (oip= optioninfos; oip->full && oip->abbrev != *argp; oip++);
744         if (!oip->full) usageerror("unknown short option `-%c' in argument `%s'",
745                                     *argp, *argpp);
746         if (oip->values) {
747           if (argp[1]) {
748             argp++;
749           } else {
750             if (!argpp[1]) usageerror("short option `-%c' in argument `%s' needs"
751                                       " a value",*argp,*argpp);
752             argp= *++argpp;
753           }
754           callvalueoption(oip,argp);
755           break; /* No more options in this argument, go on to the next one. */
756         } else {
757           (oip->fn)(oip,0,0);
758         }
759       }
760     }
761   }
762   if (overridetype == ot_builtin) {
763     serviceuser= "-";
764   } else {
765     if (!*argpp) usageerror("no service user given after options");
766     serviceuser= *argpp++;
767   }
768   if (!*argpp) usageerror(overridetype == ot_builtin ?
769                           "no service name given after options and service user" :
770                           "no builtin service given after options");
771   
772   for (fd=0; fd<fdsetupsize; fd++) {
773     if (!fdsetup[fd].filename) continue;
774     if (fdsetup[fd].mods & (fdm_wait|fdm_nowait|fdm_close)) continue;
775     assert(fdsetup[fd].mods & (fdm_read|fdm_write));
776     fdsetup[fd].mods |= (fdsetup[fd].mods & fdm_read) ? fdm_close : fdm_wait;
777   }
778
779   if (setvbuf(stderr,stderrbuf,_IOLBF,sizeof(stderrbuf)))
780     syscallerror("set buffering on stderr");
781   if (setvbuf(stdout,stdoutbuf,_IOFBF,sizeof(stdoutbuf)))
782     syscallerror("set buffering on stdout");
783
784   argc-= (argpp-argv);
785   argv= argpp;
786   if (argc > MAX_ARGSDEFVAR) usageerror("far too many arguments");
787   if (ngids > MAX_GIDS) miscerror("caller is in far too many gids");
788   
789   spoofuid= myuid;
790   spoofgid= mygid;
791   logname= getenv("LOGNAME");
792   if (!logname) logname= getenv("USER");
793   if (logname) {
794     pw= getpwnam(logname);
795     if (!pw || pw->pw_uid != myuid) logname= 0;
796   }
797   if (!logname) {
798     pw= getpwuid(myuid); if (!pw) miscerror("cannot determine your login name");
799     logname= pw->pw_name;
800   }
801
802   if (!strcmp(serviceuser,"-")) serviceuser= logname;
803   pw= getpwnam(serviceuser);
804   if (!pw) miscerror("requested service user `%s' is not a user",serviceuser);
805   serviceuid= pw->pw_uid;
806
807   if ((overridetype != ot_none || spoofuser) && myuid != 0 && myuid != serviceuid)
808     miscerror("--override and --spoof options only available to root or to"
809               " the user who will be providing the service");
810
811   if (spoofuser) {
812     logname= spoofuser;
813     pw= getpwnam(logname);
814     if (!pw) miscerror("spoofed login name `%s' is not valid",logname);
815     spoofuid= pw->pw_uid;
816     spoofgid= pw->pw_gid;
817     ngidssize= ngids; ngids= 0;
818     if (ngidssize<5) {
819       ngidssize= 5;
820       gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize);
821     }
822     gidarray[ngids++]= spoofgid;
823     while ((gr= getgrent())) { /* ouch! getgrent has no error behaviour! */
824       for (mem= gr->gr_mem; *mem && strcmp(*mem,logname); mem++);
825       if (!*mem) continue;
826       if (ngids>=ngidssize) {
827         ngidssize= (ngids+5)<<1;
828         gidarray= xrealloc(gidarray,sizeof(gid_t)*ngidssize);
829       }
830       gidarray[ngids++]= gr->gr_gid;
831     }
832   }
833
834   cwdbufsize= 0; cwdbuf= 0;
835   if (!hidecwd) {
836     for (;;) {
837       assert(cwdbufsize < INT_MAX/3);
838       cwdbufsize <<= 1; cwdbufsize+= 100;
839       cwdbuf= xrealloc(cwdbuf,cwdbufsize);
840       cwdbuf[cwdbufsize-1]= 0;
841       if (getcwd(cwdbuf,cwdbufsize-1)) { cwdbufsize= strlen(cwdbuf); break; }
842       if (errno != ERANGE) { cwdbufsize= 0; break; }
843     }
844   }
845
846   switch (overridetype) {
847   case ot_none:
848     ovused= -1;
849     ovbuf= 0;
850     break;
851   case ot_builtin:
852     l= strlen(argv[0]);
853     if (l >= MAX_OVERRIDE_LEN-20)
854       miscerror("builtin service string is too long (%d, max is %d)",
855                 l,MAX_OVERRIDE_LEN-21);
856     l+= 20;
857     ovbuf= xmalloc(l);
858     snprintf(ovbuf,l,"execute-builtin %s\n",argv[0]);
859     ovused= strlen(ovbuf);
860     break;
861   case ot_string:
862     l= strlen(overridevalue);
863     if (l >= MAX_OVERRIDE_LEN)
864       miscerror("override string is too long (%d, max is %d)",l,MAX_OVERRIDE_LEN-1);
865     ovbuf= xmalloc(l+2);
866     snprintf(ovbuf,l+2,"%s\n",overridevalue);
867     ovused= l+1;
868     break;
869   case ot_file:
870     ovfile= fopen(overridevalue,"r");
871     if (!ovfile) syscallerror("open overriding configuration file `%s'",overridevalue);
872     ovbuf= 0; ovavail= ovused= 0;
873     while ((c= getc(ovfile)) != EOF) {
874       if (!c) miscerror("overriding config file `%s' contains null(s)",overridevalue);
875       if (ovused >= MAX_OVERRIDE_LEN)
876         miscerror("override file is too long (max is %d)",MAX_OVERRIDE_LEN);
877       if (ovused >= ovavail) {
878         ovavail+=80; ovavail<<=2; ovbuf= xrealloc(ovbuf,ovavail);
879       }
880       ovbuf[ovused++]= c;
881     }
882     if (ferror(ovfile) || fclose(ovfile))
883       syscallerror("read overriding configuration file `%s'",overridevalue);
884     ovbuf= xrealloc(ovbuf,ovused+1);
885     ovbuf[ovused]= 0;
886     break;
887   default:
888     abort();
889   }
890
891   sig.sa_handler= SIG_IGN;
892   sigemptyset(&sig.sa_mask);
893   sig.sa_flags= 0;
894   if (sigaction(SIGPIPE,&sig,0)) syscallerror("ignore sigpipe");
895
896   sfd= socket(AF_UNIX,SOCK_STREAM,0);
897   if (!sfd) syscallerror("create client socket");
898
899   assert(sizeof(ssockname.sun_path) > sizeof(RENDEZVOUSPATH));
900   ssockname.sun_family= AF_UNIX;
901   strcpy(ssockname.sun_path,RENDEZVOUSPATH);
902   priv_resume();
903   while (connect(sfd,(struct sockaddr*)&ssockname,sizeof(ssockname))) {
904     if (errno == ECONNREFUSED || errno == ENOENT)
905       syscallerror("uservd daemon is not running - service not available");
906     syscallerror("unable to connect to uservd daemon");
907   }
908   priv_suspend();
909
910   srfile= fdopen(sfd,"r");
911   if (!srfile) syscallerror("turn socket fd into FILE* for read");
912   if (setvbuf(srfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket reads");
913
914   swfile= fdopen(sfd,"w");
915   if (!swfile) syscallerror("turn socket fd into FILE* for write");
916   if (setvbuf(swfile,0,_IOFBF,BUFSIZ)) syscallerror("set buffering on socket writes");
917
918   xfread(&opening_mbuf,sizeof(opening_mbuf),srfile);
919   checkmagic(opening_mbuf.magic,OPENING_MAGIC,"in opening message");
920   if (memcmp(protocolchecksumversion,opening_mbuf.protocolchecksumversion,PCSUMSIZE))
921     protoerror("protocol version checksum mismatch - server not same as client");
922
923   for (fd=0; fd<fdsetupsize; fd++) {
924     if (!fdsetup[fd].filename) continue;
925     sprintf(pipepathbuf, PIPEPATHFORMAT,
926             (unsigned long)mypid, (unsigned long)opening_mbuf.serverpid, fd);
927     priv_resume();
928     if (unlink(pipepathbuf) && errno != ENOENT)
929       syscallerror("remove any old pipe `%s'",pipepathbuf);
930     if (mkfifo(pipepathbuf,0600)) /* permissions are irrelevant */
931       syscallerror("create pipe `%s'",pipepathbuf);
932     tempfd= open(pipepathbuf,O_RDWR);
933     if (tempfd == -1) syscallerror("prelim open pipe `%s' for read+write",pipepathbuf);
934     assert(fdsetup[fd].mods & (fdm_read|fdm_write));
935     fdsetup[fd].pipefd=
936       open(pipepathbuf, (fdsetup[fd].mods & fdm_read) ? O_WRONLY : O_RDONLY);
937     if (fdsetup[fd].pipefd == -1) syscallerror("real open pipe `%s'",pipepathbuf);
938     if (close(tempfd)) syscallerror("close prelim fd onto pipe `%s'",pipepathbuf);
939     priv_suspend();
940   }
941
942   memset(&request_mbuf,0,sizeof(request_mbuf));
943   request_mbuf.magic= REQUEST_MAGIC;
944   request_mbuf.clientpid= getpid();
945   request_mbuf.serviceuserlen= strlen(serviceuser);
946   request_mbuf.servicelen= strlen(argv[0]);
947   request_mbuf.lognamelen= strlen(logname);
948   request_mbuf.cwdlen= cwdbufsize;
949   request_mbuf.callinguid= spoofuid;
950   request_mbuf.ngids= ngids+1;
951   request_mbuf.nreadfds= 0;
952   request_mbuf.nwritefds= 0;
953   for (fd=0; fd<fdsetupsize; fd++) {
954     if (!fdsetup[fd].filename) continue;
955     assert(fdsetup[fd].mods & (fdm_write|fdm_read));
956     if (fdsetup[fd].mods & fdm_write) request_mbuf.nwritefds++;
957     else request_mbuf.nreadfds++;
958   }
959   request_mbuf.nargs= argc-1;
960   request_mbuf.nvars= defvarused;
961   request_mbuf.overridelen= ovused;
962   xfwrite(&request_mbuf,sizeof(request_mbuf),swfile);
963   xfwrite(serviceuser,sizeof(*serviceuser)*request_mbuf.serviceuserlen,swfile);
964   xfwrite(argv[0],sizeof(*argv[0])*request_mbuf.servicelen,swfile);
965   xfwrite(logname,sizeof(*logname)*request_mbuf.lognamelen,swfile);
966   xfwrite(cwdbuf,sizeof(*cwdbuf)*request_mbuf.cwdlen,swfile);
967   if (ovused>=0) xfwrite(ovbuf,sizeof(*ovbuf)*ovused,swfile);
968   xfwrite(&spoofgid,sizeof(gid_t),swfile);
969   xfwrite(gidarray,sizeof(gid_t)*ngids,swfile);
970   xfwritefds(fdm_read,request_mbuf.nreadfds,swfile);
971   xfwritefds(fdm_write,request_mbuf.nwritefds,swfile);
972   for (i=1; i<argc; i++)
973     xfwritestring(argv[i],swfile);
974   for (i=0; i<defvarused; i++) {
975     xfwritestring(defvararray[i].key,swfile);
976     xfwritestring(defvararray[i].value,swfile);
977   }
978   ul= REQUEST_END_MAGIC; xfwrite(&ul,sizeof(ul),swfile);
979   xfflush(swfile);
980
981   priv_permanentlyrevokesuspended(); /* Must not do this before we give our real id */
982
983   getprogress(&progress_mbuf,srfile);
984   if (progress_mbuf.type != pt_ok)
985     protoerror("progress message during configuration phase"
986                " unexpected type %d",progress_mbuf.type);
987
988   sig.sa_handler= sighandler_chld;
989   sigemptyset(&sig.sa_mask);
990   sigaddset(&sig.sa_mask,SIGCHLD);
991   sigaddset(&sig.sa_mask,SIGALRM);
992   sig.sa_flags= 0;
993   if (sigaction(SIGCHLD,&sig,0)) syscallerror("set up sigchld handler");
994
995   sig.sa_handler= sighandler_alrm;
996   if (sigaction(SIGALRM,&sig,0)) syscallerror("set up sigalrm handler");
997
998   for (fd=0; fd<fdsetupsize; fd++) {
999     if (!fdsetup[fd].filename) continue;
1000     if (!(fdsetup[fd].mods & fdm_fd)) {
1001       fdsetup[fd].copyfd=
1002         open(fdsetup[fd].filename,fdsetup[fd].oflags,0777);
1003       if (fdsetup[fd].copyfd<0)
1004         syscallerror("open file `%s' for fd %d",fdsetup[fd].filename,fd);
1005     }
1006     fdsetup[fd].catpid= fork();
1007     if (fdsetup[fd].catpid==-1) syscallerror("fork for cat for fd %d",fd);
1008     if (!fdsetup[fd].catpid) {
1009       snprintf(catnamebuf,sizeof(catnamebuf),"cat fd%d",fd);
1010       catnamebuf[sizeof(catnamebuf)-1]= 0;
1011       sig.sa_handler= SIG_DFL;
1012       sigemptyset(&sig.sa_mask);
1013       sig.sa_flags= 0;
1014       if (sigaction(SIGPIPE,&sig,0)) {
1015         fprintf(stderr,"userv: %s: reset sigpipe handler for cat: %s",
1016                 catnamebuf,strerror(errno));
1017         exit(-1);
1018       }
1019       reading= fdsetup[fd].mods & fdm_read;
1020       catdup(catnamebuf, fdsetup[fd].copyfd, reading ? 0 : 1);
1021       catdup(catnamebuf, fdsetup[fd].pipefd, reading ? 1 : 0);
1022       execl("/bin/cat",catnamebuf,(char*)0);
1023       fprintf(stderr,"userv: %s: cannot exec `cat': %s\n",catnamebuf,strerror(errno));
1024       exit(-1);
1025     }
1026     if (fdsetup[fd].copyfd>2)
1027       if (close(fdsetup[fd].copyfd)) syscallerror("close real fd for %d",fd);
1028     if (close(fdsetup[fd].pipefd)) syscallerror("close pipe fd for %d",fd);
1029   }
1030
1031   if (timeout)
1032     if (alarm(timeout)<0) syscallerror("set up timeout alarm");
1033
1034   blocksignals(SIG_BLOCK);
1035   memset(&event_mbuf,0,sizeof(event_mbuf));
1036   event_mbuf.magic= EVENT_MAGIC;
1037   event_mbuf.type= et_confirm;
1038   xfwrite(&event_mbuf,sizeof(event_mbuf),swfile);
1039   xfflush(swfile);
1040   blocksignals(SIG_UNBLOCK);
1041
1042   getprogress(&progress_mbuf,srfile);
1043   if (progress_mbuf.type != pt_terminated)
1044     protoerror("progress message during execution phase"
1045                " unexpected type %d",progress_mbuf.type);
1046
1047   swfile= 0;
1048
1049   blocksignals(SIG_BLOCK);
1050   for (fd=0; fd<fdsetupsize; fd++) {
1051     if (!(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_close))) continue;
1052     if (kill(fdsetup[fd].catpid,SIGKILL)) syscallerror("kill cat for %d",fd);
1053     fdsetup[fd].killed= 1;
1054   }
1055   blocksignals(SIG_UNBLOCK);
1056
1057   for (;;) {
1058     blocksignals(SIG_BLOCK);
1059     for (fd=0;
1060          fd<fdsetupsize && !(fdsetup[fd].catpid!=-1 && (fdsetup[fd].mods & fdm_wait));
1061          fd++);
1062     if (fd>=fdsetupsize) break;
1063     sigemptyset(&sset);
1064     r= sigsuspend(&sset);
1065     if (r && errno != EINTR) syscallerror("sigsuspend failed in unexpected way");
1066     blocksignals(SIG_UNBLOCK);
1067   }
1068
1069   blocksignals(SIG_BLOCK);
1070
1071   status= progress_mbuf.data.terminated.status;
1072   if (sigpipeok && signalsexit != se_stdout && WIFSIGNALED(status) &&
1073       WTERMSIG(status)==SIGPIPE && !WCOREDUMP(status)) status= 0;
1074   
1075   switch (signalsexit) {
1076   case se_number:
1077   case se_numbernocore:
1078     if (WIFEXITED(status))
1079       _exit(WEXITSTATUS(status));
1080     else if (WIFSIGNALED(status))
1081       _exit(WTERMSIG(status) + (signalsexit==se_number && WCOREDUMP(status) ? 128 : 0));
1082     break;
1083   case se_highbit:
1084     if (WIFEXITED(status))
1085       _exit(WEXITSTATUS(status)<=127 ? WEXITSTATUS(status) : 127);
1086     else if (WIFSIGNALED(status) && WTERMSIG(status)<=126)
1087       _exit(WTERMSIG(status)+128);
1088     break;
1089   case se_stdout:
1090     printf("\n%d %d ",(status>>8)&0x0ff,status&0x0ff);
1091     if (WIFEXITED(status))
1092       printf("exited with code %d",WEXITSTATUS(status));
1093     else if (WIFSIGNALED(status))
1094       printf("killed by %s (signal %d)%s",
1095              strsignal(WTERMSIG(status)),WTERMSIG(status),
1096              WCOREDUMP(status) ? ", core dumped " : "");
1097     else
1098       printf("unknown wait status");
1099     putchar('\n');
1100     if (ferror(stdout) || fflush(stdout)) syscallerror("write exit status to stdout");
1101     _exit(0);
1102   default:
1103     if (WIFEXITED(status))
1104       _exit(WEXITSTATUS(status));
1105     else if (WIFSIGNALED(status))
1106       _exit(signalsexit);
1107     break;
1108   }
1109       
1110   fprintf(stderr,"unknown wait status %d\n",status);
1111   _exit(-1);
1112 }