chiark / gitweb /
33ba2df28a362962a09a886bbc37da43d41bea95
[userv-utils.git] / ipif / service.c
1 /*
2  * userv service (or standalone program)
3  * for per-user IP subranges.
4  *
5  * This is invoked as root, directly from userv.
6  * Its arguments are supposed to be, in order:
7  *  <config>
8  *      Specifies address ranges and gids which own them.
9  *  --  Indicates that the remaining arguments are user-supplied
10  *      and therefore untrusted.
11  *  <local-addr>,<peer-addr>,<mtu>,<proto>
12  *      As for slattach.  Supported protocols are slip, cslip, and
13  *      adaptive.  Alternatively, set to `debug' to print debugging
14  *      info.  <local-addr> is address of the interface on chiark;
15  *      <peer-addr> is the address of the point-to-point peer.
16  *  <prefix>/<mask>,<prefix>/<mask>,...
17  *      List of additional routes to add for this interface.
18  *      May be the empty argument.
19  *
20  * <config> is either
21  *    <gid>,<prefix>/<len>[,<junk>]
22  *      indicating that that gid may allocate addresses in
23  *      the relevant subspace (<junk> is ignored)
24  * or #...
25  *      which is a comment
26  * or /<config-file-name> or ./<config-file-name> or ../<config-file-name>
27  *      which refers to a file which contains lines which
28  *      are each <config>
29  * or *
30  *      which means that anything is permitted
31  * 
32  * Should be run from userv with no-disconnect-hup.
33  */
34
35 #include <stdio.h>
36 #include <string.h>
37 #include <stdlib.h>
38 #include <assert.h>
39 #include <errno.h>
40 #include <stdarg.h>
41 #include <ctype.h>
42 #include <limits.h>
43 #include <signal.h>
44 #include <unistd.h>
45
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #include <sys/stat.h>
49
50 #define NARGS 4
51 #define MAXEXROUTES 5
52 #define ATXTLEN 12
53
54 static const unsigned long gidmaxval= (unsigned long)((gid_t)-2);
55 static const char *const protos_ok[]= { "slip", "cslip", "adaptive", 0 };
56 static const int signals[]= { SIGHUP, SIGINT, SIGTERM, 0 };
57
58 static const char *configstr, *proto;
59 static unsigned long localaddr, peeraddr, mtu;
60 static int localallow, peerallow, allallow;
61 static int nexroutes;
62 static struct exroute {
63   unsigned long prefix, mask;
64   int allow;
65   char prefixtxt[ATXTLEN], masktxt[ATXTLEN];
66 } exroutes[MAXEXROUTES];
67
68 static char localtxt[ATXTLEN];
69 static char peertxt[ATXTLEN];
70
71 static struct pplace {
72   struct pplace *parent;
73   const char *filename;
74   int lineno;
75 } *cpplace;
76
77
78 static int slpipe[2], ptmaster;
79 static const char *ifname;
80 static const char *ptyname;
81
82 #define NPIDS 4
83
84 static union {
85   struct { pid_t sl, cout, cin, task; } byname;
86   pid_t bynumber[NPIDS];
87 } pids;
88 sigset_t emptyset, fullset;
89
90
91 static void terminate(int estatus) {
92   int i, status;
93   pid_t pid;
94   
95   for (i=0; i<NPIDS; i++)
96     if (pids.bynumber[i]) kill(pids.bynumber[i], SIGTERM);
97   
98   for (;;) {
99     pid= waitpid(-1,&status,0);
100     if (pid == (pid_t)-1) break;
101   }
102   exit(estatus);
103 }
104
105
106 static void fatal(const char *fmt, ...)
107      __attribute__((format(printf,1,2)));
108 static void fatal(const char *fmt, ...) {
109   va_list al;
110   va_start(al,fmt);
111
112   fputs("userv-ipif service: fatal error: ",stderr);
113   vfprintf(stderr, fmt, al);
114   putc('\n',stderr);
115   terminate(8);
116 }
117   
118 static void sysfatal(const char *fmt, ...)
119      __attribute__((format(printf,1,2)));
120 static void sysfatal(const char *fmt, ...) {
121   va_list al;
122   int e;
123
124   e= errno;
125   va_start(al,fmt);
126
127   fputs("userv-ipif service: fatal system error",stderr);
128   vfprintf(stderr, fmt, al);
129   fprintf(stderr,"%s\n", strerror(e));
130   terminate(12);
131 }
132
133
134 static void badusage(const char *fmt, ...)
135      __attribute__((format(printf,1,2)));
136 static void badusage(const char *fmt, ...) {
137   va_list al;
138   struct pplace *cpp;
139   
140   if (cpplace) {
141     fprintf(stderr,
142             "userv-ipif service: %s:%d: ",
143             cpplace->filename, cpplace->lineno);
144   } else {
145     fputs("userv-ipif service: invalid usage: ",stderr);
146   }
147   va_start(al,fmt);
148   vfprintf(stderr, fmt, al);
149   putc('\n',stderr);
150
151   if (cpplace) {
152     for (cpp=cpplace->parent; cpp; cpp=cpp->parent) {
153       fprintf(stderr,
154               "userv-ipif service: %s:%d: ... in file included from here\n",
155               cpp->filename, cpp->lineno);
156     }
157   }
158   terminate(16);
159 }
160
161 static char *ip2txt(unsigned long addr, char *buf) {
162   sprintf(buf, "%lu.%lu.%lu.%lu",
163           (addr>>24) & 0x0ff,
164           (addr>>16) & 0x0ff,
165           (addr>>8) & 0x0ff,
166           (addr) & 0x0ff);
167   return buf;
168 }
169
170 static unsigned long eat_number(const char **argp, const char *what,
171                                 unsigned long min, unsigned long max,
172                                 const char *endchars, int *endchar_r) {
173   /* If !endchars then the endchar must be a nul, otherwise it may be
174    * a nul (resulting in *argp set to 0) or something else (*argp set
175    * to point to after delim, *endchar_r set to delim).
176    * *endchar_r may be 0.
177    */
178   unsigned long rv;
179   char *ep;
180   int endchar;
181
182   if (!*argp) { badusage("missing number %s\n",what); }
183   rv= strtoul(*argp,&ep,0);
184   if ((endchar= *ep)) {
185     if (!endchars) badusage("junk after number %s\n",what);
186     if (!strchr(endchars,endchar))
187       badusage("invalid character or delimiter `%c' in or after number, %s:"
188                " expected %s (or none?)\n", endchar,what,endchars);
189     *argp= ep+1;
190   } else {
191     *argp= 0;
192   }
193   if (endchar_r) *endchar_r= endchar;
194   if (rv < min || rv > max) badusage("number %s value %lu out of range %lu..%lu",
195                                      what, rv, min, max);
196   return rv;
197 }
198
199 static void addrnet_mustdiffer(const char *w1, unsigned long p1, unsigned long m1,
200                                const char *w2, unsigned long p2, unsigned long m2) {
201   unsigned long mask;
202
203   mask= m1&m2;
204   if ((p1 & mask) != (p2 & mask)) return;
205   badusage("%s %08lx/%08lx overlaps/clashes with %s %08lx/%08lx",
206            w1,p1,m1, w2,p2,m2);
207 }
208   
209 static unsigned long eat_addr(const char **argp, const char *what,
210                               const char *endchars, int *endchar_r) {
211   char whatbuf[100];
212   unsigned long rv;
213   int i;
214
215   for (rv=0, i=0;
216        i<4;
217        i++) {
218     rv <<= 8;
219     sprintf(whatbuf,"%s byte #%d",what,i);
220     rv |= eat_number(argp,whatbuf, 0,255, i<3 ? "." : endchars, endchar_r);
221   }
222
223   return rv;
224 }
225
226 static void eat_prefixmask(const char **argp, const char *what,
227                            const char *endchars, int *endchar_r,
228                            unsigned long *prefix_r, unsigned long *mask_r, int *len_r) {
229   /* mask_r and len_r may be 0 */
230   char whatbuf[100];
231   int len;
232   unsigned long prefix, mask;
233
234   prefix= eat_addr(argp,what, "/",0);
235   sprintf(whatbuf,"%s length",what);
236   len= eat_number(argp,whatbuf, 0,32, endchars,endchar_r);
237
238   mask= (~0UL << (32-len));
239   if (prefix & ~mask) badusage("%s prefix %08lx not fully contained in mask %08lx\n",
240                                what,prefix,mask);
241   *prefix_r= prefix;
242   if (mask_r) *mask_r= mask;
243   if (len_r) *len_r= len;
244 }
245
246 static int addrnet_isin(unsigned long prefix, unsigned long mask,
247                         unsigned long mprefix, unsigned long mmask) {
248   return  !(~mask & mmask)  &&  (prefix & mmask) == mprefix;
249 }
250   
251
252 static void permit(unsigned long pprefix, unsigned long pmask) {
253   int i, any;
254   
255   assert(!(pprefix & ~pmask));
256
257   if (!proto) fputs("permits",stdout);
258   if (addrnet_isin(localaddr,~0UL, pprefix,pmask)) {
259     if (!proto) fputs(" local-addr",stdout);
260     any= localallow= 1;
261   }
262   if (addrnet_isin(peeraddr,~0UL, pprefix,pmask)) {
263     if (!proto) fputs(" peer-addr",stdout);
264     any= peerallow= 1;
265   }
266   for (i=0; i<nexroutes; i++) {
267     if (addrnet_isin(exroutes[i].prefix,exroutes[i].mask, pprefix,pmask)) {
268       if (!proto) printf(" route#%d",i);
269       any= exroutes[i].allow= 1;
270     }
271   }
272   if (!proto) {
273     if (!any) fputs(" nothing!",stderr);
274     putchar('\n');
275   }
276 }
277
278 static void pconfig(const char *configstr, int truncated);
279
280 static void pfile(const char *filename) {
281   FILE *file;
282   char buf[PATH_MAX];
283   int l, truncated, c;
284   struct pplace npp, *cpp;
285
286   for (cpp=cpplace; cpp; cpp=cpp->parent) {
287     if (!strcmp(cpp->filename,filename))
288       badusage("recursive configuration file `%s'",filename);
289   }
290
291   file= fopen(filename,"r");
292   if (!file)
293     badusage("cannot open configuration file `%s': %s", filename, strerror(errno));
294
295   if (!proto) printf("config file `%s':\n",filename);
296
297   npp.parent= cpplace;
298   npp.filename= filename;
299   npp.lineno= 0;
300   cpplace= &npp;
301
302   while (fgets(buf, sizeof(buf), file)) {
303     npp.lineno++;
304     l= strlen(buf);
305     if (!l) continue;
306
307     truncated= (buf[l-1] != '\n');
308     while (l>0 && isspace((unsigned char) buf[l-1])) l--;
309     if (!l) continue;
310     buf[l]= 0;
311
312     if (truncated) {
313       while ((c= getc(file)) != EOF && c != '\n');
314       if (c == EOF) break;
315     }
316
317     pconfig(buf,truncated);
318   }
319   if (ferror(file))
320     badusage("failed while reading configuration file: %s", strerror(errno));
321
322   cpplace= npp.parent;
323 }
324
325 static void pconfig(const char *configstr, int truncated) {
326   unsigned long fgid, tgid, pprefix, pmask;
327   int plen;
328   char ptxt[ATXTLEN];
329   const char *gidlist;
330   
331   switch (configstr[0]) {
332   case '*':
333     if (strcmp(configstr,"*")) badusage("`*' directive must be only thing on line");
334     permit(0UL,0UL);
335     return;
336     
337   case '#':
338     return;
339     
340   case '/': case '.':
341     if (truncated) badusage("filename too long (`%.100s...')",configstr);
342     pfile(configstr);
343     return;
344     
345   default:
346     if (!isdigit((unsigned char)configstr[0]))
347       badusage("unknown configuration directive");
348     
349     fgid= eat_number(&configstr,"gid", 0,gidmaxval, ",",0);
350     eat_prefixmask(&configstr,"permitted-prefix", ",",0, &pprefix,&pmask,&plen);
351     if (!configstr && truncated) badusage("gid,prefix/len,... spec too long");
352
353     if (!proto) printf(" %5lu,%s/%d: ", fgid, ip2txt(pprefix,ptxt), plen);
354
355     gidlist= getenv("USERV_GID");
356     if (!gidlist) fatal("USERV_GID not set");
357     for (;;) {
358       if (!gidlist) {
359         if (!proto) printf("no matching gid\n");
360         return;
361       }
362       tgid= eat_number(&gidlist,"userv-gid", 0,gidmaxval, " ",0);
363       if (tgid == fgid) break;
364     }
365     permit(pprefix,pmask);
366     return;
367   }
368 }
369
370 static void checkallow(int allow, const char *what,
371                        const char *prefixtxt, const char *masktxt) {
372   if (allow) return;
373   fprintf(stderr,"userv-ipif service: access denied for %s, %s/%s\n",
374           what, prefixtxt, masktxt);
375   allallow= 0;
376 }
377
378 static void parseargs(int argc, const char *const *argv) {
379   unsigned long routeaddr, routemask;
380   const char *carg;
381   const char *const *cprotop;
382   int i;
383   char erwhatbuf[100], erwhatbuf2[100];
384   
385   if (argc < NARGS+1) { badusage("too few arguments"); }
386   if (argc > NARGS+1) { badusage("too many arguments"); }
387
388   configstr= *++argv;
389   
390   carg= *++argv;
391   if (strcmp(carg,"--")) badusage("separator argument `--' not found, got `%s'",carg);
392
393   carg= *++argv;
394   localaddr= eat_addr(&carg,"local-addr", ",",0);
395   peeraddr= eat_addr(&carg,"peer-addr", ",",0);
396   mtu= eat_number(&carg,"mtu", 576,65536, ",",0);
397   localallow= peerallow= 0;
398   
399   if (!strcmp(carg,"debug")) {
400     proto= 0;
401   } else {
402     for (cprotop= protos_ok;
403          (proto= *cprotop) && strcmp(proto,carg);
404          cprotop++);
405     if (!proto) fatal("invalid protocol");
406   }
407   
408   addrnet_mustdiffer("local-addr",localaddr,~0UL, "peer-addr",peeraddr,~0UL);
409   
410   carg= *++argv;
411   for (nexroutes=0;
412        carg;
413        nexroutes++) {
414     if (nexroutes == MAXEXROUTES)
415       fatal("too many extra routes (only %d allowed)",MAXEXROUTES);
416     sprintf(erwhatbuf,"route#%d",nexroutes);
417     
418     eat_prefixmask(&carg,erwhatbuf, ",",0, &routeaddr,&routemask,0);
419     if (routemask == ~0UL) {
420       addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "local-addr",localaddr,~0UL);
421       addrnet_mustdiffer(erwhatbuf,routeaddr,routemask, "peer-addr",peeraddr,~0UL);
422     }
423     for (i=0; i<nexroutes; i++) {
424       sprintf(erwhatbuf2,"route#%d",i);
425       addrnet_mustdiffer(erwhatbuf,routeaddr,routemask,
426                          erwhatbuf2,exroutes[i].prefix,exroutes[i].mask);
427     }
428     exroutes[nexroutes].prefix= routeaddr;
429     exroutes[nexroutes].mask= routemask;
430     exroutes[nexroutes].allow= 0;
431     ip2txt(routeaddr,exroutes[nexroutes].prefixtxt);
432     ip2txt(routemask,exroutes[nexroutes].masktxt);
433   }
434   ip2txt(localaddr,localtxt);
435   ip2txt(peeraddr,peertxt);
436 }
437
438 static void checkpermit(void) {
439   int i;
440   char erwhatbuf[100];
441   
442   allallow= 1;
443   checkallow(localallow,"local-addr", localtxt,"32");
444   checkallow(peerallow,"peer-addr", peertxt,"32");
445   for (i=0; i<nexroutes; i++) {
446     sprintf(erwhatbuf, "route#%d", i);
447     checkallow(exroutes[i].allow, erwhatbuf, exroutes[i].prefixtxt, exroutes[i].masktxt);
448   }
449   if (!allallow) fatal("access denied");
450 }
451
452 static void dumpdebug(void) __attribute__((noreturn));
453 static void dumpdebug(void) {
454   int i;
455   char erwhatbuf[100];
456   
457   printf("protocol: debug\n"
458          "local:    %08lx == %s\n"
459          "peer:     %08lx == %s\n"
460          "mtu:      %ld\n"
461          "routes:   %d\n",
462          localaddr, localtxt,
463          peeraddr, peertxt,
464          mtu,
465          nexroutes);
466   for (i=0; i<nexroutes; i++) {
467     sprintf(erwhatbuf, "route#%d:", i);
468     printf("%-9s %08lx/%08lx == %s/%s\n",
469            erwhatbuf,
470            exroutes[i].prefix, exroutes[i].mask,
471            exroutes[i].prefixtxt, exroutes[i].masktxt);
472   }
473   if (ferror(stdout) || fclose(stdout)) sysfatal("flush stdout");
474   exit(0);
475 }
476
477 static void setsignals(void (*handler)(int), struct sigaction *sa, int chldflags) {
478   const int *signalp;
479   int r, sig;
480   
481   sa->sa_handler= handler;
482   sa->sa_flags= 0; 
483   for (signalp=signals; (sig=*signalp); signalp++) {
484     r= sigaction(sig, sa, 0);  if (r) sysfatal("uncatch signal");
485   }
486   sa->sa_flags= chldflags;
487   r= sigaction(SIGCHLD, sa, 0);  if (r) sysfatal("uncatch children");
488 }
489
490 static void setsigmask(const sigset_t *ss) {
491   int r;
492   
493   r= sigprocmask(SIG_SETMASK, ss, 0);
494   if (r) sysfatal("[un]block signals");
495 }  
496
497 static void infork(void) {
498   struct sigaction sa;
499
500   memset(&pids,0,sizeof(pids));
501   sigemptyset(&sa.sa_mask);
502   setsignals(SIG_DFL,&sa,0);
503   setsigmask(&emptyset);
504 }
505
506 static pid_t makesubproc(void (*entry)(void)) {
507   pid_t pid;
508
509   pid= fork();  if (pid == (pid_t)-1) sysfatal("fork for subprocess");
510   if (pid) return pid;
511
512   infork();
513   entry();
514   abort();
515 }
516
517 static int task(void) {
518   pid_t pid;
519
520   pid= fork();
521   if (pids.byname.task == (pid_t)-1) sysfatal("fork for task");
522   if (!pids.byname.task) { infork(); return 1; }
523
524   pids.byname.task= pid;
525   while (pids.byname.task) sigsuspend(&emptyset);
526   return 0;
527 }
528
529 static void sl_entry(void) {
530   if (dup2(slpipe[1],1) != 1) sysfatal("dup2 stdout in slattach child");
531   execlp("slattach", "slattach", "-v", "-L", "-p",proto, ptyname, (char*)0);
532   sysfatal("cannot exec slattach");
533 }
534
535 static void cin_entry(void) {
536   if (dup2(ptmaster,1) != 1) sysfatal("dup2 stdout in cat input child");
537   execlp("cat", "cat", (char*)0);
538   sysfatal("cannot exec cat input");
539 }
540
541 static void cout_entry(void) {
542   if (dup2(ptmaster,0) != 1) sysfatal("dup2 stdin in cat output child");
543   execlp("cat", "cat", (char*)0);
544   sysfatal("cannot exec cat output");
545 }
546
547 static void sighandler(int signum) {
548   pid_t pid;
549   int estatus, status;
550   const char *taskfail;
551
552   estatus= 4;
553   
554   if (signum == SIGCHLD) {
555     for (;;) {
556       pid= waitpid(-1,&status,WNOHANG);
557       if (!pid || pid == (pid_t)-1) return;
558
559       if (pid == pids.byname.task) {
560         pids.byname.task= 0;
561         if (!status) return;
562         taskfail= "task";
563       } else if (pid == pids.byname.cin) {
564         pids.byname.cin= 0;
565         if (status) {
566           taskfail= "input cat";
567         } else {
568           taskfail= 0;
569           estatus= 0;
570         }
571       } else if (pid == pids.byname.cout) {
572         pids.byname.cout= 0;
573         taskfail= "output cat";
574       } else if (pid == pids.byname.sl) {
575         pids.byname.sl= 0;
576         taskfail= "slattach";
577       } else {
578         continue;
579       }
580     }
581     if (taskfail) {
582       fprintf(stderr,
583               "userv-ipif service: %s unexpected terminated with status code %d\n",
584               taskfail, status);
585     }
586   } else {
587     fprintf(stderr,
588             "userv-ipif service: received signal %d, terminating\n",
589             signum);
590   }
591
592   terminate(estatus);
593 }
594
595 static void startup(void) {
596   int r;
597   struct sigaction sa;
598   
599   sigfillset(&fullset);
600   sigemptyset(&emptyset);
601
602   ptmaster= getpt();  if (ptmaster==-1) sysfatal("allocate pty master");
603   r= grantpt(ptmaster);  if (r) sysfatal("grab/grant pty slave");
604   ptyname= ptsname(ptmaster);  if (!ptyname) sysfatal("get pty slave name");
605   r= chmod(ptyname,0600);  if (r) sysfatal("chmod pty slave");
606
607   sigfillset(&sa.sa_mask);
608   setsignals(sighandler,&sa,SA_NOCLDSTOP);
609   setsigmask(&fullset);
610 }
611
612 static void startslattach(void) {
613   FILE *piper;
614   char ifnbuf[200];
615   int r, l, k;
616
617   r= pipe(slpipe);  if (r) sysfatal("create pipe");
618   piper= fdopen(slpipe[0],"r");  if (!piper) sysfatal("fdopen pipe");
619
620   pids.byname.sl= makesubproc(sl_entry);
621
622   close(slpipe[1]);
623   setsigmask(&emptyset);
624   if (!fgets(ifnbuf,sizeof(ifnbuf),piper)) {
625     if (ferror(piper)) sysfatal("cannot read ifname from slattach");
626     else fatal("cannot read ifname from slattach");
627   }
628   setsigmask(&fullset);
629   l= strlen(ifnbuf);
630   if (l<0 || ifnbuf[l-1] != '\n') fatal("slattach gave strange output `%s'",ifnbuf);
631   ifnbuf[l-1]= 0;
632   for (k=l; k>0 && ifnbuf[k-1]!=' '; k--);
633   ifname= ifnbuf+k;
634 }
635
636 static void netconfigure(void) {
637   char mtutxt[100];
638   int i;
639
640   if (task()) {
641     sprintf(mtutxt,"%lu",mtu);
642   
643     execlp("ifconfig", "ifconfig", ifname, localtxt,
644            "netmask","255.255.255.255", "-broadcast", "pointopoint",peertxt,
645            "mtu",mtutxt, "up", (char*)0);
646     sysfatal("cannot exec ifconfig");
647   }
648
649   if (task()) {
650     execlp("route","route", "-host",localtxt, "netmask","255.255.255.255",
651            "dev",ifname, (char*)0);
652     sysfatal("cannot exec route (for local)");
653   }
654
655   if (task()) {
656     execlp("route","route", "-host",peertxt, "netmask","255.255.255.255",
657            "dev",ifname, (char*)0);
658     sysfatal("cannot exec route (for peer)");
659   }
660
661   for (i=0; i<nexroutes; i++) {
662     if (task()) {
663       execlp("route","route", "-net",exroutes[i].prefixtxt,
664              "netmask",exroutes[i].masktxt,
665              "gw",peertxt, "dev",ifname, (char*)0);
666       sysfatal("cannot exec route (for route)");
667     }
668   }
669 }
670
671 static void copydata(void) __attribute__((noreturn));
672 static void copydata(void) {  
673   pids.byname.cin= makesubproc(cin_entry);
674   pids.byname.cout= makesubproc(cout_entry);
675
676   for (;;) sigsuspend(&emptyset);
677 }
678
679 int main(int argc, const char *const *argv) {
680   parseargs(argc,argv);
681   pconfig(configstr,0);
682   checkpermit();
683   if (!proto) dumpdebug();
684
685   startup();
686   startslattach();
687   netconfigure();
688   copydata();
689 }