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