xref: /freebsd/usr.sbin/inetd/builtins.c (revision b601c69bdbe8755d26570261d7fd4c02ee4eff74)
1 /*-
2  * Copyright (c) 1983, 1991, 1993, 1994
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  *
28  */
29 
30 #include <sys/filio.h>
31 #include <sys/ioccom.h>
32 #include <sys/param.h>
33 #include <sys/stat.h>
34 #include <sys/socket.h>
35 #include <sys/sysctl.h>
36 #include <sys/ucred.h>
37 #include <sys/uio.h>
38 #include <sys/utsname.h>
39 
40 #include <ctype.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <limits.h>
44 #include <pwd.h>
45 #include <signal.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <sysexits.h>
49 #include <syslog.h>
50 #include <unistd.h>
51 
52 #include "inetd.h"
53 
54 extern int	 debug;
55 extern struct servtab *servtab;
56 
57 char ring[128];
58 char *endring;
59 
60 int check_loop __P((struct sockaddr *, struct servtab *sep));
61 void inetd_setproctitle __P((char *, int));
62 
63 struct biltin biltins[] = {
64 	/* Echo received data */
65 	{ "echo",	SOCK_STREAM,	1, -1,	echo_stream },
66 	{ "echo",	SOCK_DGRAM,	0, 1,	echo_dg },
67 
68 	/* Internet /dev/null */
69 	{ "discard",	SOCK_STREAM,	1, -1,	discard_stream },
70 	{ "discard",	SOCK_DGRAM,	0, 1,	discard_dg },
71 
72 	/* Return 32 bit time since 1970 */
73 	{ "time",	SOCK_STREAM,	0, -1,	machtime_stream },
74 	{ "time",	SOCK_DGRAM,	0, 1,	machtime_dg },
75 
76 	/* Return human-readable time */
77 	{ "daytime",	SOCK_STREAM,	0, -1,	daytime_stream },
78 	{ "daytime",	SOCK_DGRAM,	0, 1,	daytime_dg },
79 
80 	/* Familiar character generator */
81 	{ "chargen",	SOCK_STREAM,	1, -1,	chargen_stream },
82 	{ "chargen",	SOCK_DGRAM,	0, 1,	chargen_dg },
83 
84 	{ "tcpmux",	SOCK_STREAM,	1, -1,	(void (*)())tcpmux },
85 
86 	{ "auth",	SOCK_STREAM,	1, -1,	ident_stream },
87 
88 	{ NULL }
89 };
90 
91 /*
92  * RFC864 Character Generator Protocol. Generates character data without
93  * any regard for input.
94  */
95 
96 void
97 initring()
98 {
99 	int i;
100 
101 	endring = ring;
102 
103 	for (i = 0; i <= 128; ++i)
104 		if (isprint(i))
105 			*endring++ = i;
106 }
107 
108 /* ARGSUSED */
109 void
110 chargen_dg(s, sep)		/* Character generator */
111 	int s;
112 	struct servtab *sep;
113 {
114 	struct sockaddr_storage ss;
115 	static char *rs;
116 	int len;
117 	socklen_t size;
118 	char text[LINESIZ+2];
119 
120 	if (endring == 0) {
121 		initring();
122 		rs = ring;
123 	}
124 
125 	size = sizeof(ss);
126 	if (recvfrom(s, text, sizeof(text), 0,
127 		     (struct sockaddr *)&ss, &size) < 0)
128 		return;
129 
130 	if (check_loop((struct sockaddr *)&ss, sep))
131 		return;
132 
133 	if ((len = endring - rs) >= LINESIZ)
134 		memmove(text, rs, LINESIZ);
135 	else {
136 		memmove(text, rs, len);
137 		memmove(text + len, ring, LINESIZ - len);
138 	}
139 	if (++rs == endring)
140 		rs = ring;
141 	text[LINESIZ] = '\r';
142 	text[LINESIZ + 1] = '\n';
143 	(void) sendto(s, text, sizeof(text), 0, (struct sockaddr *)&ss, size);
144 }
145 
146 /* ARGSUSED */
147 void
148 chargen_stream(s, sep)		/* Character generator */
149 	int s;
150 	struct servtab *sep;
151 {
152 	int len;
153 	char *rs, text[LINESIZ+2];
154 
155 	inetd_setproctitle(sep->se_service, s);
156 
157 	if (!endring) {
158 		initring();
159 		rs = ring;
160 	}
161 
162 	text[LINESIZ] = '\r';
163 	text[LINESIZ + 1] = '\n';
164 	for (rs = ring;;) {
165 		if ((len = endring - rs) >= LINESIZ)
166 			memmove(text, rs, LINESIZ);
167 		else {
168 			memmove(text, rs, len);
169 			memmove(text + len, ring, LINESIZ - len);
170 		}
171 		if (++rs == endring)
172 			rs = ring;
173 		if (write(s, text, sizeof(text)) != sizeof(text))
174 			break;
175 	}
176 	exit(0);
177 }
178 
179 /*
180  * RFC867 Daytime Protocol. Sends the current date and time as an ascii
181  * character string without any regard for input.
182  */
183 
184 /* ARGSUSED */
185 void
186 daytime_dg(s, sep)		/* Return human-readable time of day */
187 	int s;
188 	struct servtab *sep;
189 {
190 	char buffer[256];
191 	time_t clock;
192 	struct sockaddr_storage ss;
193 	socklen_t size;
194 
195 	clock = time((time_t *) 0);
196 
197 	size = sizeof(ss);
198 	if (recvfrom(s, buffer, sizeof(buffer), 0,
199 		     (struct sockaddr *)&ss, &size) < 0)
200 		return;
201 
202 	if (check_loop((struct sockaddr *)&ss, sep))
203 		return;
204 
205 	(void) sprintf(buffer, "%.24s\r\n", ctime(&clock));
206 	(void) sendto(s, buffer, strlen(buffer), 0,
207 		      (struct sockaddr *)&ss, size);
208 }
209 
210 /* ARGSUSED */
211 void
212 daytime_stream(s, sep)		/* Return human-readable time of day */
213 	int s;
214 	struct servtab *sep;
215 {
216 	char buffer[256];
217 	time_t clock;
218 
219 	clock = time((time_t *) 0);
220 
221 	(void) sprintf(buffer, "%.24s\r\n", ctime(&clock));
222 	(void) send(s, buffer, strlen(buffer), MSG_EOF);
223 }
224 
225 /*
226  * RFC863 Discard Protocol. Any data received is thrown away and no response
227  * is sent.
228  */
229 
230 /* ARGSUSED */
231 void
232 discard_dg(s, sep)		/* Discard service -- ignore data */
233 	int s;
234 	struct servtab *sep;
235 {
236 	char buffer[BUFSIZE];
237 
238 	(void) read(s, buffer, sizeof(buffer));
239 }
240 
241 /* ARGSUSED */
242 void
243 discard_stream(s, sep)		/* Discard service -- ignore data */
244 	int s;
245 	struct servtab *sep;
246 {
247 	int ret;
248 	char buffer[BUFSIZE];
249 
250 	inetd_setproctitle(sep->se_service, s);
251 	while (1) {
252 		while ((ret = read(s, buffer, sizeof(buffer))) > 0)
253 			;
254 		if (ret == 0 || errno != EINTR)
255 			break;
256 	}
257 	exit(0);
258 }
259 
260 /*
261  * RFC862 Echo Protocol. Any data received is sent back to the sender as
262  * received.
263  */
264 
265 /* ARGSUSED */
266 void
267 echo_dg(s, sep)			/* Echo service -- echo data back */
268 	int s;
269 	struct servtab *sep;
270 {
271 	char buffer[BUFSIZE];
272 	int i;
273 	socklen_t size;
274 	struct sockaddr_storage ss;
275 
276 	size = sizeof(ss);
277 	if ((i = recvfrom(s, buffer, sizeof(buffer), 0,
278 			  (struct sockaddr *)&ss, &size)) < 0)
279 		return;
280 
281 	if (check_loop((struct sockaddr *)&ss, sep))
282 		return;
283 
284 	(void) sendto(s, buffer, i, 0, (struct sockaddr *)&ss, size);
285 }
286 
287 /* ARGSUSED */
288 void
289 echo_stream(s, sep)		/* Echo service -- echo data back */
290 	int s;
291 	struct servtab *sep;
292 {
293 	char buffer[BUFSIZE];
294 	int i;
295 
296 	inetd_setproctitle(sep->se_service, s);
297 	while ((i = read(s, buffer, sizeof(buffer))) > 0 &&
298 	    write(s, buffer, i) > 0)
299 		;
300 	exit(0);
301 }
302 
303 /*
304  * RFC1413 Identification Protocol. Given a TCP port number pair, return a
305  * character string which identifies the owner of that connection on the
306  * server's system. Extended to allow for ~/.fakeid support and ~/.noident
307  * support.
308  */
309 
310 /* ARGSUSED */
311 void
312 iderror(lport, fport, s, er)	/* Generic ident_stream error-sending func */
313 	int lport, fport, s, er;
314 {
315 	char *p;
316 
317 	asprintf(&p, "%d , %d : ERROR : %s\r\n", lport, fport,
318 	    er == -1 ? "HIDDEN-USER" : er ? strerror(er) : "UNKNOWN-ERROR");
319 	if (p == NULL) {
320 		syslog(LOG_ERR, "asprintf: %m");
321 		exit(EX_OSERR);
322 	}
323 	send(s, p, strlen(p), MSG_EOF);
324 	free(p);
325 
326 	exit(0);
327 }
328 
329 /* ARGSUSED */
330 void
331 ident_stream(s, sep)		/* Ident service (AKA "auth") */
332 	int s;
333 	struct servtab *sep;
334 {
335 	struct utsname un;
336 	struct stat sb;
337 	struct sockaddr_in sin[2];
338 #ifdef INET6
339 	struct sockaddr_in6 sin6[2];
340 #endif
341 	struct sockaddr_storage ss[2];
342 	struct ucred uc;
343 	struct timeval tv = {
344 		10,
345 		0
346 	}, to;
347 	struct passwd *pw = NULL;
348 	fd_set fdset;
349 	char buf[BUFSIZE], *cp = NULL, *p, **av, *osname = NULL, garbage[7], e;
350 	char *fallback = NULL;
351 	socklen_t socklen;
352 	ssize_t ssize;
353 	size_t size, bufsiz;
354 	int c, fflag = 0, nflag = 0, rflag = 0, argc = 0, usedfallback = 0;
355 	int gflag = 0, getcredfail = 0, onreadlen;
356 	u_short lport, fport;
357 
358 	inetd_setproctitle(sep->se_service, s);
359 	/*
360 	 * Reset getopt() since we are a fork() but not an exec() from
361 	 * a parent which used getopt() already.
362 	 */
363 	optind = 1;
364 	optreset = 1;
365 	/*
366 	 * Take the internal argument vector and count it out to make an
367 	 * argument count for getopt. This can be used for any internal
368 	 * service to read arguments and use getopt() easily.
369 	 */
370 	for (av = sep->se_argv; *av; av++)
371 		argc++;
372 	if (argc) {
373 		int sec, usec;
374 		size_t i;
375 		u_int32_t random;
376 
377 		while ((c = getopt(argc, sep->se_argv, "d:fgno:rt:")) != -1)
378 			switch (c) {
379 			case 'd':
380 				fallback = optarg;
381 				break;
382 			case 'f':
383 				fflag = 1;
384 				break;
385 			case 'g':
386 				gflag = 1;
387 				random = 0;	/* Shush, compiler. */
388 				/*
389 				 * The number of bits in "random" divided
390 				 * by the number of bits needed per iteration
391 				 * gives a more optimal way to reload the
392 				 * random number only when necessary.
393 				 *
394 				 * I'm using base-36, so I need at least 6
395 				 * bits; round it up to 8 bits to make it
396 				 * easier.
397 				 */
398 				for (i = 0; i < sizeof(garbage) - 1; i++) {
399 					const char *const base36 =
400 					    "0123456789"
401 					    "abcdefghijklmnopqrstuvwxyz";
402 					if (i % (sizeof(random) * 8 / 8) == 0)
403 						random = arc4random();
404 					garbage[i] =
405 					    base36[(random & 0xff) % 36];
406 					random >>= 8;
407 				}
408 				garbage[i] = '\0';
409 				break;
410 			case 'n':
411 				nflag = 1;
412 				break;
413 			case 'o':
414 				osname = optarg;
415 				break;
416 			case 'r':
417 				rflag = 1;
418 				break;
419 			case 't':
420 				switch (sscanf(optarg, "%d.%d", &sec, &usec)) {
421 				case 2:
422 					tv.tv_usec = usec;
423 				case 1:
424 					tv.tv_sec = sec;
425 					break;
426 				default:
427 					if (debug)
428 						warnx("bad -t argument");
429 					break;
430 				}
431 				break;
432 			default:
433 				break;
434 			}
435 	}
436 	if (osname == NULL) {
437 		if (uname(&un) == -1)
438 			iderror(0, 0, s, errno);
439 		osname = un.sysname;
440 	}
441 	socklen = sizeof(ss[0]);
442 	if (getsockname(s, (struct sockaddr *)&ss[0], &socklen) == -1)
443 		iderror(0, 0, s, errno);
444 	socklen = sizeof(ss[1]);
445 	if (getpeername(s, (struct sockaddr *)&ss[1], &socklen) == -1)
446 		iderror(0, 0, s, errno);
447 	/*
448 	 * We're going to prepare for and execute reception of a
449 	 * packet of data from the user. The data is in the format
450 	 * "local_port , foreign_port\r\n" (with local being the
451 	 * server's port and foreign being the client's.)
452 	 */
453 	gettimeofday(&to, NULL);
454 	to.tv_sec += tv.tv_sec;
455 	if ((to.tv_usec += tv.tv_usec) >= 1000000) {
456 		to.tv_usec -= 1000000;
457 		to.tv_sec++;
458 	}
459 
460 	size = 0;
461 	bufsiz = sizeof(buf) - 1;
462 	FD_ZERO(&fdset);
463  	while (bufsiz > 0 && (size == 0 || buf[size - 1] != '\n')) {
464 		gettimeofday(&tv, NULL);
465 		tv.tv_sec = to.tv_sec - tv.tv_sec;
466 		tv.tv_usec = to.tv_usec - tv.tv_usec;
467 		if (tv.tv_usec < 0) {
468 			tv.tv_usec += 1000000;
469 			tv.tv_sec--;
470 		}
471 		if (tv.tv_sec < 0)
472 			break;
473 		FD_SET(s, &fdset);
474 		if (select(s + 1, &fdset, NULL, NULL, &tv) == -1)
475 			iderror(0, 0, s, errno);
476 		if (ioctl(s, FIONREAD, &onreadlen) == -1)
477 			iderror(0, 0, s, errno);
478 		if (onreadlen > bufsiz)
479 			onreadlen = bufsiz;
480 		ssize = read(s, &buf[size], (size_t)onreadlen);
481 		if (ssize == -1)
482 			iderror(0, 0, s, errno);
483 		bufsiz -= ssize;
484 		size += ssize;
485  	}
486 	buf[size] = '\0';
487 	/* Read two characters, and check for a delimiting character */
488 	if (sscanf(buf, "%hu , %hu%c", &lport, &fport, &e) != 3 || isdigit(e))
489 		iderror(0, 0, s, 0);
490 	if (gflag) {
491 		cp = garbage;
492 		goto printit;
493 	}
494 
495 	/*
496 	 * If not "real" (-r), send a HIDDEN-USER error for everything.
497 	 * If -d is used to set a fallback username, this is used to
498 	 * override it, and the fallback is returned instead.
499 	 */
500 	if (!rflag) {
501 		if (fallback == NULL)
502 			iderror(lport, fport, s, -1);
503 		else {
504 			cp = fallback;
505 			goto printit;
506 		}
507 	}
508 
509 	/*
510 	 * We take the input and construct an array of two sockaddr_ins
511 	 * which contain the local address information and foreign
512 	 * address information, respectively, used to look up the
513 	 * credentials for the socket (which are returned by the
514 	 * sysctl "net.inet.tcp.getcred" when we call it.) The
515 	 * arrays have been filled in above via get{peer,sock}name(),
516 	 * so right here we are only setting the ports.
517 	 */
518 	if (ss[0].ss_family != ss[1].ss_family)
519 		iderror(lport, fport, s, errno);
520 	size = sizeof(uc);
521 	switch (ss[0].ss_family) {
522 	case AF_INET:
523 		sin[0] = *(struct sockaddr_in *)&ss[0];
524 		sin[0].sin_port = htons(lport);
525 		sin[1] = *(struct sockaddr_in *)&ss[1];
526 		sin[1].sin_port = htons(fport);
527 		if (sysctlbyname("net.inet.tcp.getcred", &uc, &size, sin,
528 				 sizeof(sin)) == -1)
529 			getcredfail = 1;
530 		break;
531 #ifdef INET6
532 	case AF_INET6:
533 		sin6[0] = *(struct sockaddr_in6 *)&ss[0];
534 		sin6[0].sin6_port = htons(lport);
535 		sin6[1] = *(struct sockaddr_in6 *)&ss[1];
536 		sin6[1].sin6_port = htons(fport);
537 		if (sysctlbyname("net.inet6.tcp6.getcred", &uc, &size, sin6,
538 				 sizeof(sin6)) == -1)
539 			getcredfail = 1;
540 		break;
541 #endif
542 	default: /* should not reach here */
543 		getcredfail = 1;
544 		break;
545 	}
546 	if (getcredfail != 0) {
547 		if (fallback == NULL)		/* Use a default, if asked to */
548 			iderror(lport, fport, s, errno);
549 		usedfallback = 1;
550 	} else {
551 		/* Look up the pw to get the username */
552 		pw = getpwuid(uc.cr_uid);
553 	}
554 	if (pw == NULL && !usedfallback)		/* No such user... */
555 		iderror(lport, fport, s, errno);
556 	/*
557 	 * If enabled, we check for a file named ".noident" in the user's
558 	 * home directory. If found, we return HIDDEN-USER.
559 	 */
560 	if (nflag && !usedfallback) {
561 		if (asprintf(&p, "%s/.noident", pw->pw_dir) == -1)
562 			iderror(lport, fport, s, errno);
563 		if (lstat(p, &sb) == 0) {
564 			free(p);
565 			iderror(lport, fport, s, -1);
566 		}
567 		free(p);
568 	}
569 	/*
570 	 * Here, if enabled, we read a user's ".fakeid" file in their
571 	 * home directory. It consists of a line containing the name
572 	 * they want.
573 	 */
574 	if (fflag && !usedfallback) {
575 		FILE *fakeid = NULL;
576 
577 		if (asprintf(&p, "%s/.fakeid", pw->pw_dir) == -1)
578 			iderror(lport, fport, s, errno);
579 		/*
580 		 * Here we set ourself to effectively be the user, so we don't
581 		 * open any files we have no permission to open, especially
582 		 * symbolic links to sensitive root-owned files or devices.
583 		 */
584 		seteuid(pw->pw_uid);
585 		setegid(pw->pw_gid);
586 		/*
587 		 * If we were to lstat() here, it would do no good, since it
588 		 * would introduce a race condition and could be defeated.
589 		 * Therefore, we open the file we have permissions to open
590 		 * and if it's not a regular file, we close it and end up
591 		 * returning the user's real username.
592 		 */
593 		fakeid = fopen(p, "r");
594 		free(p);
595 		if (fakeid != NULL &&
596 		    fstat(fileno(fakeid), &sb) != -1 && S_ISREG(sb.st_mode)) {
597 			buf[sizeof(buf) - 1] = '\0';
598 			if (fgets(buf, sizeof(buf), fakeid) == NULL) {
599 				cp = pw->pw_name;
600 				fclose(fakeid);
601 				goto printit;
602 			}
603 			fclose(fakeid);
604 			/*
605 			 * Usually, the file will have the desired identity
606 			 * in the form "identity\n", so we use strtok() to
607 			 * end the string (which fgets() doesn't do.)
608 			 */
609 			buf[strcspn(buf, "\r\n")] = '\0';
610 			cp = buf;
611 			/* Allow for beginning white space... */
612 			while (isspace(*cp))
613 				cp++;
614 			/* ...and ending white space. */
615 			cp[strcspn(cp, " \t")] = '\0';
616 			/* User names of >16 characters are invalid */
617 			if (strlen(cp) > 16)
618 				cp[16] = '\0';
619 			/*
620 			 * If the name is a zero-length string or matches
621 			 * the name of another user, it's invalid, so
622 			 * we will return their real identity instead.
623 			 */
624 
625 			if (!*cp || getpwnam(cp))
626 				cp = getpwuid(uc.cr_uid)->pw_name;
627 		} else
628 			cp = pw->pw_name;
629 	} else if (!usedfallback)
630 		cp = pw->pw_name;
631 	else
632 		cp = fallback;
633 printit:
634 	/* Finally, we make and send the reply. */
635 	if (asprintf(&p, "%d , %d : USERID : %s : %s\r\n", lport, fport, osname,
636 	    cp) == -1) {
637 		syslog(LOG_ERR, "asprintf: %m");
638 		exit(EX_OSERR);
639 	}
640 	send(s, p, strlen(p), MSG_EOF);
641 	free(p);
642 
643 	exit(0);
644 }
645 
646 /*
647  * RFC738 Time Server.
648  * Return a machine readable date and time, in the form of the
649  * number of seconds since midnight, Jan 1, 1900.  Since gettimeofday
650  * returns the number of seconds since midnight, Jan 1, 1970,
651  * we must add 2208988800 seconds to this figure to make up for
652  * some seventy years Bell Labs was asleep.
653  */
654 
655 unsigned long
656 machtime()
657 {
658 	struct timeval tv;
659 
660 	if (gettimeofday(&tv, (struct timezone *)NULL) < 0) {
661 		if (debug)
662 			warnx("unable to get time of day");
663 		return (0L);
664 	}
665 #define	OFFSET ((u_long)25567 * 24*60*60)
666 	return (htonl((long)(tv.tv_sec + OFFSET)));
667 #undef OFFSET
668 }
669 
670 /* ARGSUSED */
671 void
672 machtime_dg(s, sep)
673 	int s;
674 	struct servtab *sep;
675 {
676 	unsigned long result;
677 	struct sockaddr_storage ss;
678 	socklen_t size;
679 
680 	size = sizeof(ss);
681 	if (recvfrom(s, (char *)&result, sizeof(result), 0,
682 		     (struct sockaddr *)&ss, &size) < 0)
683 		return;
684 
685 	if (check_loop((struct sockaddr *)&ss, sep))
686 		return;
687 
688 	result = machtime();
689 	(void) sendto(s, (char *) &result, sizeof(result), 0,
690 		      (struct sockaddr *)&ss, size);
691 }
692 
693 /* ARGSUSED */
694 void
695 machtime_stream(s, sep)
696 	int s;
697 	struct servtab *sep;
698 {
699 	unsigned long result;
700 
701 	result = machtime();
702 	(void) send(s, (char *) &result, sizeof(result), MSG_EOF);
703 }
704 
705 /*
706  * RFC1078 TCP Port Service Multiplexer (TCPMUX). Service connections to
707  * services based on the service name sent.
708  *
709  *  Based on TCPMUX.C by Mark K. Lottor November 1988
710  *  sri-nic::ps:<mkl>tcpmux.c
711  */
712 
713 #define MAX_SERV_LEN	(256+2)		/* 2 bytes for \r\n */
714 #define strwrite(fd, buf)	(void) write(fd, buf, sizeof(buf)-1)
715 
716 static int		/* # of characters upto \r,\n or \0 */
717 getline(fd, buf, len)
718 	int fd;
719 	char *buf;
720 	int len;
721 {
722 	int count = 0, n;
723 	struct sigaction sa;
724 
725 	sa.sa_flags = 0;
726 	sigemptyset(&sa.sa_mask);
727 	sa.sa_handler = SIG_DFL;
728 	sigaction(SIGALRM, &sa, (struct sigaction *)0);
729 	do {
730 		alarm(10);
731 		n = read(fd, buf, len-count);
732 		alarm(0);
733 		if (n == 0)
734 			return (count);
735 		if (n < 0)
736 			return (-1);
737 		while (--n >= 0) {
738 			if (*buf == '\r' || *buf == '\n' || *buf == '\0')
739 				return (count);
740 			count++;
741 			buf++;
742 		}
743 	} while (count < len);
744 	return (count);
745 }
746 
747 struct servtab *
748 tcpmux(s)
749 	int s;
750 {
751 	struct servtab *sep;
752 	char service[MAX_SERV_LEN+1];
753 	int len;
754 
755 	/* Get requested service name */
756 	if ((len = getline(s, service, MAX_SERV_LEN)) < 0) {
757 		strwrite(s, "-Error reading service name\r\n");
758 		return (NULL);
759 	}
760 	service[len] = '\0';
761 
762 	if (debug)
763 		warnx("tcpmux: someone wants %s", service);
764 
765 	/*
766 	 * Help is a required command, and lists available services,
767 	 * one per line.
768 	 */
769 	if (!strcasecmp(service, "help")) {
770 		for (sep = servtab; sep; sep = sep->se_next) {
771 			if (!ISMUX(sep))
772 				continue;
773 			(void)write(s,sep->se_service,strlen(sep->se_service));
774 			strwrite(s, "\r\n");
775 		}
776 		return (NULL);
777 	}
778 
779 	/* Try matching a service in inetd.conf with the request */
780 	for (sep = servtab; sep; sep = sep->se_next) {
781 		if (!ISMUX(sep))
782 			continue;
783 		if (!strcasecmp(service, sep->se_service)) {
784 			if (ISMUXPLUS(sep)) {
785 				strwrite(s, "+Go\r\n");
786 			}
787 			return (sep);
788 		}
789 	}
790 	strwrite(s, "-Service not available\r\n");
791 	return (NULL);
792 }
793