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