]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/aux/listen.c
aux/listen: do not redirect stderr (fd 2) of the listener to the network connection
[plan9front.git] / sys / src / cmd / aux / listen.c
1 #include <u.h>
2 #include <libc.h>
3 #include <auth.h>
4
5 #define NAMELEN 64      /* reasonable upper limit for name elements */
6
7 typedef struct Service  Service;
8 struct Service
9 {
10         char    serv[NAMELEN];          /* name of the service */
11         char    remote[3*NAMELEN];      /* address of remote system */
12         char    prog[5*NAMELEN+1];      /* program to execute */
13 };
14
15 typedef struct Announce Announce;
16 struct Announce
17 {
18         Announce        *next;
19         char    *a;
20         int     announced;
21         int     whined;
22 };
23
24 int     readstr(char*, char*, char*, int);
25 void    dolisten(char*, char*, int, char*, char*, long*);
26 void    newcall(int, char*, char*, Service*);
27 int     findserv(char*, char*, Service*, char*);
28 int     getserv(char*, char*, Service*);
29 void    error(char*);
30 void    scandir(char*, char*, char*);
31 void    becomenone(void);
32 void    listendir(char*, char*, int);
33
34 char    listenlog[] = "listen";
35
36 long    procs;
37 long    maxprocs;
38 int     quiet;
39 int     immutable;
40 char    *cpu;
41 char    *proto;
42 Announce *announcements;
43 #define SEC 1000
44
45 char *namespace;
46
47 void
48 usage(void)
49 {
50         error("usage: aux/listen [-q] [-n namespace] [-d servdir] [-t trustdir] [-p maxprocs]"
51                 " [proto]");
52 }
53
54 /*
55  * based on libthread's threadsetname, but drags in less library code.
56  * actually just sets the arguments displayed.
57  */
58 static void
59 procsetname(char *fmt, ...)
60 {
61         int fd;
62         char *cmdname;
63         char buf[128];
64         va_list arg;
65
66         va_start(arg, fmt);
67         cmdname = vsmprint(fmt, arg);
68         va_end(arg);
69         if (cmdname == nil)
70                 return;
71         snprint(buf, sizeof buf, "#p/%d/args", getpid());
72         if((fd = open(buf, OWRITE)) >= 0){
73                 write(fd, cmdname, strlen(cmdname)+1);
74                 close(fd);
75         }
76         free(cmdname);
77 }
78
79 void
80 main(int argc, char *argv[])
81 {
82         Service *s;
83         char *protodir;
84         char *trustdir;
85         char *servdir;
86
87         servdir = 0;
88         trustdir = 0;
89         proto = "tcp";
90         quiet = 0;
91         immutable = 0;
92         argv0 = argv[0];
93         maxprocs = 0;
94         cpu = getenv("cputype");
95         if(cpu == 0)
96                 error("can't get cputype");
97
98         ARGBEGIN{
99         case 'd':
100                 servdir = EARGF(usage());
101                 break;
102         case 'q':
103                 quiet = 1;
104                 break;
105         case 't':
106                 trustdir = EARGF(usage());
107                 break;
108         case 'n':
109                 namespace = EARGF(usage());
110                 break;
111         case 'p':
112                 maxprocs = atoi(EARGF(usage()));
113                 break;
114         case 'i':
115                 /*
116                  * fixed configuration, no periodic
117                  * rescan of the service directory.
118                  */
119                 immutable = 1;
120                 break;
121         default:
122                 usage();
123         }ARGEND;
124
125         if(!servdir && !trustdir)
126                 servdir = "/bin/service";
127
128         if(servdir && strlen(servdir) + NAMELEN >= sizeof(s->prog))
129                 error("service directory too long");
130         if(trustdir && strlen(trustdir) + NAMELEN >= sizeof(s->prog))
131                 error("trusted service directory too long");
132
133         switch(argc){
134         case 1:
135                 proto = argv[0];
136                 break;
137         case 0:
138                 break;
139         default:
140                 usage();
141         }
142
143         syslog(0, listenlog, "started on %s", proto);
144
145         protodir = proto;
146         proto = strrchr(proto, '/');
147         if(proto == 0)
148                 proto = protodir;
149         else
150                 proto++;
151         listendir(protodir, servdir, 0);
152         listendir(protodir, trustdir, 1);
153
154         /* command returns */
155         exits(0);
156 }
157
158 static void
159 dingdong(void*, char *msg)
160 {
161         if(strstr(msg, "alarm") != nil)
162                 noted(NCONT);
163         noted(NDFLT);
164 }
165
166 void
167 listendir(char *protodir, char *srvdir, int trusted)
168 {
169         int ctl, pid, start;
170         char dir[40], err[128], ds[128];
171         long childs;
172         Announce *a;
173         Waitmsg *wm;
174         int whined;
175
176         if (srvdir == 0)
177                 return;
178
179         /*
180          * insulate ourselves from later
181          * changing of console environment variables
182          * erase privileged crypt state
183          */
184         switch(rfork(RFNOTEG|RFPROC|RFFDG|RFNOWAIT|RFENVG|RFNAMEG)) {
185         case -1:
186                 error("fork");
187         case 0:
188                 break;
189         default:
190                 return;
191         }
192
193         procsetname("%s %s %s", protodir, srvdir, namespace);
194         if (!trusted)
195                 becomenone();
196
197         notify(dingdong);
198
199         pid = getpid();
200         scandir(proto, protodir, srvdir);
201         for(;;){
202                 /*
203                  * loop through announcements and process trusted services in
204                  * invoker's ns and untrusted in none's.
205                  */
206                 for(a = announcements; a; a = a->next){
207                         if(a->announced > 0)
208                                 continue;
209
210                         sleep((pid*10)%200);
211
212                         /* copy to stack */
213                         strncpy(ds, a->a, sizeof(ds));
214                         whined = a->whined;
215
216                         /* a process per service */
217                         switch(pid = rfork(RFFDG|RFPROC|RFMEM)){
218                         case -1:
219                                 syslog(1, listenlog, "couldn't fork for %s", ds);
220                                 break;
221                         case 0:
222                                 childs = 0;
223                                 for(;;){
224                                         ctl = announce(ds, dir);
225                                         if(ctl < 0) {
226                                                 errstr(err, sizeof err);
227                                                 if (!whined)
228                                                         syslog(1, listenlog,
229                                                            "giving up on %s: %r",
230                                                         ds);
231                                                 if(strstr(err, "address in use")
232                                                     != nil)
233                                                         exits("addr-in-use");
234                                                 else
235                                                         exits("ctl");
236                                         }
237                                         dolisten(proto, dir, ctl, srvdir, ds, &childs);
238                                         close(ctl);
239                                 }
240                         default:
241                                 a->announced = pid;
242                                 break;
243                         }
244                 }
245
246                 /*
247                  * if not running a fixed configuration,
248                  * pick up any children that gave up and
249                  * sleep for at least 60 seconds.
250                  * If a service process dies in a fixed
251                  * configuration what should be done -
252                  * nothing? restart? restart after a delay?
253                  * - currently just wait for something to
254                  * die and delay at least 60 seconds
255                  * between restarts.
256                  */
257                 start = time(0);
258                 if(!immutable)
259                         alarm(60*1000);
260                 while((wm = wait()) != nil) {
261                         for(a = announcements; a; a = a->next)
262                                 if(a->announced == wm->pid) {
263                                         a->announced = 0;
264                                         if (strstr(wm->msg, "addr-in-use") !=
265                                             nil)
266                                                 /* don't fill log file */
267                                                 a->whined = 1;
268                                 }
269                         free(wm);
270                         if(immutable)
271                                 break;
272                 }
273                 if(!immutable){
274                         alarm(0);
275                         scandir(proto, protodir, srvdir);
276                 }
277                 start = 60 - (time(0)-start);
278                 if(start > 0)
279                         sleep(start*1000);
280         }
281         /* not reached */
282 }
283
284 /*
285  *  make a list of all services to announce for
286  */
287 void
288 addannounce(char *str)
289 {
290         Announce *a, **l;
291
292         /* look for duplicate */
293         l = &announcements;
294         for(a = announcements; a; a = a->next){
295                 if(strcmp(str, a->a) == 0)
296                         return;
297                 l = &a->next;
298         }
299
300         /* accept it */
301         a = mallocz(sizeof(*a) + strlen(str) + 1, 1);
302         if(a == 0)
303                 return;
304         a->a = ((char*)a)+sizeof(*a);
305         strcpy(a->a, str);
306         a->announced = 0;
307         *l = a;
308 }
309
310 /*
311  *  delete a service for announcement list
312  */
313 void
314 delannounce(char *str)
315 {
316         Announce *a, **l;
317
318         /* look for service */
319         l = &announcements;
320         for(a = announcements; a; a = a->next){
321                 if(strcmp(str, a->a) == 0)
322                         break;
323                 l = &a->next;
324         }
325         if (a == nil)
326                 return;
327         *l = a->next;                   /* drop from the list */
328         if (a->announced > 0)
329                 postnote(PNPROC, a->announced, "die");
330         a->announced = 0;
331         free(a);
332 }
333
334 static int
335 ignore(char *srvdir, char *name)
336 {
337         int rv;
338         char *file = smprint("%s/%s", srvdir, name);
339         Dir *d = dirstat(file);
340
341         rv = !d || d->length <= 0;      /* ignore unless it's non-empty */
342         free(d);
343         free(file);
344         return rv;
345 }
346
347 void
348 scandir(char *proto, char *protodir, char *dname)
349 {
350         int fd, i, n, nlen;
351         char *nm;
352         char ds[128];
353         Dir *db;
354
355         fd = open(dname, OREAD);
356         if(fd < 0)
357                 return;
358
359         nlen = strlen(proto);
360         while((n=dirread(fd, &db)) > 0){
361                 for(i=0; i<n; i++){
362                         nm = db[i].name;
363                         if(!(db[i].qid.type&QTDIR) &&
364                             strncmp(nm, proto, nlen) == 0) {
365                                 snprint(ds, sizeof ds, "%s!*!%s", protodir,
366                                         nm + nlen);
367                                 if (ignore(dname, nm))
368                                         delannounce(ds);
369                                 else
370                                         addannounce(ds);
371                         }
372                 }
373                 free(db);
374         }
375
376         close(fd);
377 }
378
379 void
380 becomenone(void)
381 {
382         int fd;
383
384         fd = open("#c/user", OWRITE);
385         if(fd < 0 || write(fd, "none", strlen("none")) < 0)
386                 error("can't become none");
387         close(fd);
388         if(newns("none", namespace) < 0)
389                 error("can't build namespace");
390 }
391
392 void
393 dolisten(char *proto, char *dir, int ctl, char *srvdir, char *dialstr, long *pchilds)
394 {
395         Service s;
396         char ndir[40], wbuf[64];
397         int nctl, data, wfd, nowait;
398
399         procsetname("%s %s", dir, dialstr);
400
401         wfd = -1;
402         nowait = RFNOWAIT;
403         if(pchilds && maxprocs > 0){
404                 snprint(wbuf, sizeof(wbuf), "/proc/%d/wait", getpid());
405                 if((wfd = open(wbuf, OREAD)) >= 0)
406                         nowait = 0;
407         }
408
409         for(;;){
410                 if(!nowait){
411                         static int hit = 0;
412                         Dir *d;
413
414                         /*
415                          *  check for exited subprocesses
416                          */
417                         if(procs >= maxprocs || (*pchilds % 8) == 0)
418                                 while(*pchilds > 0){
419                                         d = dirfstat(wfd);
420                                         if(d == nil || d->length == 0){
421                                                 free(d);
422                                                 break;
423                                         }
424                                         free(d);
425                                         if(read(wfd, wbuf, sizeof(wbuf)) > 0){
426                                                 adec(&procs);
427                                                 pchilds[0]--;
428                                         }
429                                 }
430
431                         if(procs >= maxprocs){
432                                 if(!quiet && !hit)
433                                         syslog(1, listenlog, "%s: process limit of %ld reached",
434                                                 proto, maxprocs);
435                                 if(hit < 8)
436                                         hit++;
437                                 sleep(10<<hit);
438                                 continue;
439                         } 
440                         if(hit > 0)
441                                 hit--;
442                 }
443
444                 /*
445                  *  wait for a call (or an error)
446                  */
447                 nctl = listen(dir, ndir);
448                 if(nctl < 0){
449                         if(!quiet)
450                                 syslog(1, listenlog, "listen: %r");
451                         if(wfd >= 0)
452                                 close(wfd);
453                         return;
454                 }
455
456                 /*
457                  *  start a subprocess for the connection
458                  */
459                 switch(rfork(RFFDG|RFPROC|RFMEM|RFENVG|RFNAMEG|RFNOTEG|nowait)){
460                 case -1:
461                         reject(nctl, ndir, "host overloaded");
462                         close(nctl);
463                         continue;
464                 case 0:
465                         /*
466                          *  see if we know the service requested
467                          */
468                         memset(&s, 0, sizeof s);
469                         if(!findserv(proto, ndir, &s, srvdir)){
470                                 if(!quiet)
471                                         syslog(1, listenlog, "%s: unknown service '%s' from '%s': %r",
472                                                 proto, s.serv, s.remote);
473                                 reject(nctl, ndir, "connection refused");
474                                 exits(0);
475                         }
476                         data = accept(nctl, ndir);
477                         if(data < 0){
478                                 syslog(1, listenlog, "can't open %s/data: %r", ndir);
479                                 exits(0);
480                         }
481                         fprint(nctl, "keepalive");
482                         close(ctl);
483                         close(nctl);
484                         if(wfd >= 0)
485                                 close(wfd);
486                         newcall(data, proto, ndir, &s);
487                         exits(0);
488                 default:
489                         close(nctl);
490                         if(nowait)
491                                 break;
492                         ainc(&procs);
493                         pchilds[0]++;
494                         break;
495                 }
496         }
497 }
498
499 /*
500  * look in the service directory for the service.
501  * if the shell script or program is zero-length, ignore it,
502  * thus providing a way to disable a service with a bind.
503  */
504 int
505 findserv(char *proto, char *dir, Service *s, char *srvdir)
506 {
507         int rv;
508         Dir *d;
509
510         if(!getserv(proto, dir, s))
511                 return 0;
512         snprint(s->prog, sizeof s->prog, "%s/%s", srvdir, s->serv);
513         d = dirstat(s->prog);
514         rv = d && d->length > 0;        /* ignore unless it's non-empty */
515         free(d);
516         return rv;
517 }
518
519 /*
520  *  get the service name out of the local address
521  */
522 int
523 getserv(char *proto, char *dir, Service *s)
524 {
525         char addr[128], *serv, *p;
526         long n;
527
528         readstr(dir, "remote", s->remote, sizeof(s->remote)-1);
529         if(p = utfrune(s->remote, L'\n'))
530                 *p = '\0';
531
532         n = readstr(dir, "local", addr, sizeof(addr)-1);
533         if(n <= 0)
534                 return 0;
535         if(p = utfrune(addr, L'\n'))
536                 *p = '\0';
537         serv = utfrune(addr, L'!');
538         if(!serv)
539                 serv = "login";
540         else
541                 serv++;
542
543         /*
544          * disallow service names like
545          * ../../usr/user/bin/rc/su
546          */
547         if(strlen(serv) +strlen(proto) >= NAMELEN || utfrune(serv, L'/') || *serv == '.')
548                 return 0;
549         snprint(s->serv, sizeof s->serv, "%s%s", proto, serv);
550
551         return 1;
552 }
553
554 char *
555 remoteaddr(char *dir)
556 {
557         char buf[128], *p;
558         int n, fd;
559
560         snprint(buf, sizeof buf, "%s/remote", dir);
561         fd = open(buf, OREAD);
562         if(fd < 0)
563                 return strdup("");
564         n = read(fd, buf, sizeof(buf));
565         close(fd);
566         if(n > 0){
567                 buf[n] = 0;
568                 p = strchr(buf, '!');
569                 if(p)
570                         *p = 0;
571                 return strdup(buf);
572         }
573         return strdup("");
574 }
575
576 void
577 newcall(int fd, char *proto, char *dir, Service *s)
578 {
579         char data[4*NAMELEN];
580         char *p;
581
582         if(!quiet){
583                 if(dir != nil){
584                         p = remoteaddr(dir);
585                         syslog(0, listenlog, "%s call for %s on chan %s (%s)", proto, s->serv, dir, p);
586                         free(p);
587                 } else
588                         syslog(0, listenlog, "%s call for %s on chan %s", proto, s->serv, dir);
589         }
590
591         snprint(data, sizeof data, "%s/data", dir);
592         bind(data, "/dev/cons", MREPL);
593         dup(fd, 0);
594         dup(fd, 1);
595         /* dup(fd, 2); keep stderr */
596         close(fd);
597
598         /*
599          * close all the fds
600          */
601         for(fd=3; fd<20; fd++)
602                 close(fd);
603         execl(s->prog, s->prog, s->serv, proto, dir, nil);
604         error(s->prog);
605 }
606
607 void
608 error(char *s)
609 {
610         syslog(1, listenlog, "%s: %s: %r", proto, s);
611         exits(0);
612 }
613
614 /*
615  *  read a string from a device
616  */
617 int
618 readstr(char *dir, char *info, char *s, int len)
619 {
620         int n, fd;
621         char buf[3*NAMELEN+4];
622
623         snprint(buf, sizeof buf, "%s/%s", dir, info);
624         fd = open(buf, OREAD);
625         if(fd<0)
626                 return 0;
627
628         n = read(fd, s, len-1);
629         if(n<=0){
630                 close(fd);
631                 return -1;
632         }
633         s[n] = 0;
634         close(fd);
635
636         return n+1;
637 }