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