xref: /plan9/sys/src/cmd/aux/listen.c (revision 4a24e4d74773f68ffb4b14928bdbd758772e554b)
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
usage(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
procsetname(char * fmt,...)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
main(int argc,char * argv[])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
dingdong(void *,char * msg)153 dingdong(void*, char *msg)
154 {
155 	if(strstr(msg, "alarm") != nil)
156 		noted(NCONT);
157 	noted(NDFLT);
158 }
159 
160 void
listendir(char * protodir,char * srvdir,int trusted)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
addannounce(char * str)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
delannounce(char * str)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
ignore(char * srvdir,char * name)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
scandir(char * proto,char * protodir,char * dname)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
becomenone(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
dolisten(char * proto,char * dir,int ctl,char * srvdir,char * dialstr)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
findserv(char * proto,char * dir,Service * s,char * srvdir)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
getserv(char * proto,char * dir,Service * s)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 *
remoteaddr(char * dir)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
newcall(int fd,char * proto,char * dir,Service * s)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)",
522 				proto, s->serv, dir, p);
523 			free(p);
524 		} else
525 			syslog(0, listenlog, "%s call for %s on chan %s",
526 				proto, s->serv, dir);
527 	}
528 
529 	snprint(data, sizeof data, "%s/data", dir);
530 	bind(data, "/dev/cons", MREPL);
531 	dup(fd, 0);
532 	dup(fd, 1);
533 	dup(fd, 2);
534 	close(fd);
535 
536 	/*
537 	 * close all the fds
538 	 */
539 	for(fd=3; fd<20; fd++)
540 		close(fd);
541 	execl(s->prog, s->prog, s->serv, proto, dir, nil);
542 	error(s->prog);
543 }
544 
545 void
error(char * s)546 error(char *s)
547 {
548 	syslog(1, listenlog, "%s: %s: %r", proto, s);
549 	exits(0);
550 }
551 
552 /*
553  *  read a string from a device
554  */
555 int
readstr(char * dir,char * info,char * s,int len)556 readstr(char *dir, char *info, char *s, int len)
557 {
558 	int n, fd;
559 	char buf[3*NAMELEN+4];
560 
561 	snprint(buf, sizeof buf, "%s/%s", dir, info);
562 	fd = open(buf, OREAD);
563 	if(fd<0)
564 		return 0;
565 
566 	n = read(fd, s, len-1);
567 	if(n<=0){
568 		close(fd);
569 		return -1;
570 	}
571 	s[n] = 0;
572 	close(fd);
573 
574 	return n+1;
575 }
576