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