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