xref: /freebsd/usr.sbin/inetd/builtins.c (revision daf1cffce2e07931f27c6c6998652e90df6ba87e)
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, size;
117 	char text[LINESIZ+2];
118 
119 	if (endring == 0) {
120 		initring();
121 		rs = ring;
122 	}
123 
124 	size = sizeof(ss);
125 	if (recvfrom(s, text, sizeof(text), 0,
126 		     (struct sockaddr *)&ss, &size) < 0)
127 		return;
128 
129 	if (check_loop((struct sockaddr *)&ss, sep))
130 		return;
131 
132 	if ((len = endring - rs) >= LINESIZ)
133 		memmove(text, rs, LINESIZ);
134 	else {
135 		memmove(text, rs, len);
136 		memmove(text + len, ring, LINESIZ - len);
137 	}
138 	if (++rs == endring)
139 		rs = ring;
140 	text[LINESIZ] = '\r';
141 	text[LINESIZ + 1] = '\n';
142 	(void) sendto(s, text, sizeof(text), 0,
143 		      (struct sockaddr *)&ss, sizeof(ss));
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 	int 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, sizeof(ss));
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) write(s, buffer, strlen(buffer));
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, size;
273 	struct sockaddr_storage ss;
274 
275 	size = sizeof(ss);
276 	if ((i = recvfrom(s, buffer, sizeof(buffer), 0,
277 			  (struct sockaddr *)&ss, &size)) < 0)
278 		return;
279 
280 	if (check_loop((struct sockaddr *)&ss, sep))
281 		return;
282 
283 	(void) sendto(s, buffer, i, 0, (struct sockaddr *)&ss,
284 		      sizeof(ss));
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 	write(s, p, strlen(p));
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 	struct sockaddr_in6 sin6[2];
339 	struct sockaddr_storage ss[2];
340 	struct ucred uc;
341 	struct timeval tv = {
342 		10,
343 		0
344 	};
345 	struct passwd *pw = NULL;
346 	fd_set fdset;
347 	char buf[BUFSIZE], *cp = NULL, *p, **av, *osname = NULL, garbage[7];
348 	char *fallback = NULL;
349 	int len, c, fflag = 0, nflag = 0, rflag = 0, argc = 0, usedfallback = 0;
350 	int gflag = 0, Rflag = 0, getcredfail = 0;
351 	u_short lport, fport;
352 
353 	inetd_setproctitle(sep->se_service, s);
354 	/*
355 	 * Reset getopt() since we are a fork() but not an exec() from
356 	 * a parent which used getopt() already.
357 	 */
358 	optind = 1;
359 	optreset = 1;
360 	/*
361 	 * Take the internal argument vector and count it out to make an
362 	 * argument count for getopt. This can be used for any internal
363 	 * service to read arguments and use getopt() easily.
364 	 */
365 	for (av = sep->se_argv; *av; av++)
366 		argc++;
367 	if (argc) {
368 		int sec, usec;
369 		size_t i;
370 		u_int32_t random;
371 
372 		while ((c = getopt(argc, sep->se_argv, "d:fgno:rt:")) != -1)
373 			switch (c) {
374 			case 'd':
375 				fallback = optarg;
376 				break;
377 			case 'f':
378 				fflag = 1;
379 				break;
380 			case 'g':
381 				gflag = 1;
382 				random = 0;	/* Shush, compiler. */
383 				/*
384 				 * The number of bits in "random" divided
385 				 * by the number of bits needed per iteration
386 				 * gives a more optimal way to reload the
387 				 * random number only when necessary.
388 				 *
389 				 * I'm using base-36, so I need at least 6
390 				 * bits; round it up to 8 bits to make it
391 				 * easier.
392 				 */
393 				for (i = 0; i < sizeof(garbage) - 1; i++) {
394 					const char *const base36 =
395 					    "0123456789"
396 					    "abcdefghijklmnopqrstuvwxyz";
397 					if (i % (sizeof(random) * 8 / 8) == 0)
398 						random = arc4random();
399 					garbage[i] =
400 					    base36[(random & 0xff) % 36];
401 					random >>= 8;
402 				}
403 				garbage[i] = '\0';
404 				break;
405 			case 'n':
406 				nflag = 1;
407 				break;
408 			case 'o':
409 				osname = optarg;
410 				break;
411 			case 'R':
412 				Rflag = 2;
413 				break;
414 			case 'r':
415 				rflag = 1;
416 				break;
417 			case 't':
418 				switch (sscanf(optarg, "%d.%d", &sec, &usec)) {
419 				case 2:
420 					tv.tv_usec = usec;
421 				case 1:
422 					tv.tv_sec = sec;
423 					break;
424 				default:
425 					if (debug)
426 						warnx("bad -t argument");
427 					break;
428 				}
429 				break;
430 			default:
431 				break;
432 			}
433 	}
434 	if (osname == NULL) {
435 		if (uname(&un) == -1)
436 			iderror(0, 0, s, errno);
437 		osname = un.sysname;
438 	}
439 	len = sizeof(ss[0]);
440 	if (getsockname(s, (struct sockaddr *)&ss[0], &len) == -1)
441 		iderror(0, 0, s, errno);
442 	len = sizeof(ss[1]);
443 	if (getpeername(s, (struct sockaddr *)&ss[1], &len) == -1)
444 		iderror(0, 0, s, errno);
445 	/*
446 	 * We're going to prepare for and execute reception of a
447 	 * packet of data from the user. The data is in the format
448 	 * "local_port , foreign_port\r\n" (with local being the
449 	 * server's port and foreign being the client's.)
450 	 */
451 	FD_ZERO(&fdset);
452 	FD_SET(s, &fdset);
453 	if (select(s + 1, &fdset, NULL, NULL, &tv) == -1)
454 		iderror(0, 0, s, errno);
455 	if (ioctl(s, FIONREAD, &len) == -1)
456 		iderror(0, 0, s, errno);
457 	if (len >= sizeof(buf))
458 		len = sizeof(buf) - 1;
459 	len = read(s, buf, len);
460 	if (len == -1)
461 		iderror(0, 0, s, errno);
462 	buf[len] = '\0';
463 	if (sscanf(buf, "%hu , %hu", &lport, &fport) != 2)
464 		iderror(0, 0, s, 0);
465 	if (gflag) {
466 		cp = garbage;
467 		goto printit;
468 	}
469 
470 	if (!rflag)	/* Send HIDDEN-USER immediately if not "real" */
471 		iderror(lport, fport, s, -1);
472 	/*
473 	 * We take the input and construct an array of two sockaddr_ins
474 	 * which contain the local address information and foreign
475 	 * address information, respectively, used to look up the
476 	 * credentials for the socket (which are returned by the
477 	 * sysctl "net.inet.tcp.getcred" when we call it.) The
478 	 * arrays have been filled in above via get{peer,sock}name(),
479 	 * so right here we are only setting the ports.
480 	 */
481 	if (ss[0].ss_family != ss[1].ss_family)
482 		iderror(lport, fport, s, errno);
483 	len = sizeof(uc);
484 	switch (ss[0].ss_family) {
485 	case AF_INET:
486 		sin[0] = *(struct sockaddr_in *)&ss[0];
487 		sin[0].sin_port = htons(lport);
488 		sin[1] = *(struct sockaddr_in *)&ss[1];
489 		sin[1].sin_port = htons(fport);
490 		if (sysctlbyname("net.inet.tcp.getcred", &uc, &len, sin,
491 				 sizeof(sin)) == -1)
492 			getcredfail = 1;
493 		break;
494 #ifdef INET6
495 	case AF_INET6:
496 		sin6[0] = *(struct sockaddr_in6 *)&ss[0];
497 		sin6[0].sin6_port = htons(lport);
498 		sin6[1] = *(struct sockaddr_in6 *)&ss[1];
499 		sin6[1].sin6_port = htons(fport);
500 		if (sysctlbyname("net.inet6.tcp6.getcred", &uc, &len, sin6,
501 				 sizeof(sin6)) == -1)
502 			getcredfail = 1;
503 		break;
504 #endif
505 	default: /* should not reach here */
506 		getcredfail = 1;
507 		break;
508 	}
509 	if (getcredfail != 0) {
510 		if (fallback == NULL)		/* Use a default, if asked to */
511 			iderror(lport, fport, s, errno);
512 		usedfallback = 1;
513 	} else {
514 		/* Look up the pw to get the username */
515 		pw = getpwuid(uc.cr_uid);
516 	}
517 	if (pw == NULL && !usedfallback)		/* No such user... */
518 		iderror(lport, fport, s, errno);
519 	/*
520 	 * If enabled, we check for a file named ".noident" in the user's
521 	 * home directory. If found, we return HIDDEN-USER.
522 	 */
523 	if (nflag && !usedfallback) {
524 		if (asprintf(&p, "%s/.noident", pw->pw_dir) == -1)
525 			iderror(lport, fport, s, errno);
526 		if (lstat(p, &sb) == 0) {
527 			free(p);
528 			iderror(lport, fport, s, -1);
529 		}
530 		free(p);
531 	}
532 	/*
533 	 * Here, if enabled, we read a user's ".fakeid" file in their
534 	 * home directory. It consists of a line containing the name
535 	 * they want.
536 	 */
537 	if (fflag && !usedfallback) {
538 		FILE *fakeid = NULL;
539 
540 		if (asprintf(&p, "%s/.fakeid", pw->pw_dir) == -1)
541 			iderror(lport, fport, s, errno);
542 		/*
543 		 * Here we set ourself to effectively be the user, so we don't
544 		 * open any files we have no permission to open, especially
545 		 * symbolic links to sensitive root-owned files or devices.
546 		 */
547 		seteuid(pw->pw_uid);
548 		setegid(pw->pw_gid);
549 		/*
550 		 * If we were to lstat() here, it would do no good, since it
551 		 * would introduce a race condition and could be defeated.
552 		 * Therefore, we open the file we have permissions to open
553 		 * and if it's not a regular file, we close it and end up
554 		 * returning the user's real username.
555 		 */
556 		fakeid = fopen(p, "r");
557 		free(p);
558 		if (fakeid != NULL &&
559 		    fstat(fileno(fakeid), &sb) != -1 && S_ISREG(sb.st_mode)) {
560 			buf[sizeof(buf) - 1] = '\0';
561 			if (fgets(buf, sizeof(buf), fakeid) == NULL) {
562 				cp = pw->pw_name;
563 				fclose(fakeid);
564 				goto printit;
565 			}
566 			fclose(fakeid);
567 			/*
568 			 * Usually, the file will have the desired identity
569 			 * in the form "identity\n", so we use strtok() to
570 			 * end the string (which fgets() doesn't do.)
571 			 */
572 			strtok(buf, "\r\n");
573 			/* User names of >16 characters are invalid */
574 			if (strlen(buf) > 16)
575 				buf[16] = '\0';
576 			cp = buf;
577 			/* Allow for beginning white space... */
578 			while (isspace(*cp))
579 				cp++;
580 			/* ...and ending white space. */
581 			strtok(cp, " \t");
582 			/*
583 			 * If the name is a zero-length string or matches
584 			 * the name of another user, it's invalid, so
585 			 * we will return their real identity instead.
586 			 */
587 
588 			if (!*cp || getpwnam(cp))
589 				cp = getpwuid(uc.cr_uid)->pw_name;
590 		} else
591 			cp = pw->pw_name;
592 	} else if (!usedfallback)
593 		cp = pw->pw_name;
594 	else
595 		cp = fallback;
596 printit:
597 	/* Finally, we make and send the reply. */
598 	if (asprintf(&p, "%d , %d : USERID : %s : %s\r\n", lport, fport, osname,
599 	    cp) == -1) {
600 		syslog(LOG_ERR, "asprintf: %m");
601 		exit(EX_OSERR);
602 	}
603 	write(s, p, strlen(p));
604 	free(p);
605 
606 	exit(0);
607 }
608 
609 /*
610  * RFC738 Time Server.
611  * Return a machine readable date and time, in the form of the
612  * number of seconds since midnight, Jan 1, 1900.  Since gettimeofday
613  * returns the number of seconds since midnight, Jan 1, 1970,
614  * we must add 2208988800 seconds to this figure to make up for
615  * some seventy years Bell Labs was asleep.
616  */
617 
618 unsigned long
619 machtime()
620 {
621 	struct timeval tv;
622 
623 	if (gettimeofday(&tv, (struct timezone *)NULL) < 0) {
624 		if (debug)
625 			warnx("unable to get time of day");
626 		return (0L);
627 	}
628 #define	OFFSET ((u_long)25567 * 24*60*60)
629 	return (htonl((long)(tv.tv_sec + OFFSET)));
630 #undef OFFSET
631 }
632 
633 /* ARGSUSED */
634 void
635 machtime_dg(s, sep)
636 	int s;
637 	struct servtab *sep;
638 {
639 	unsigned long result;
640 	struct sockaddr_storage ss;
641 	int size;
642 
643 	size = sizeof(ss);
644 	if (recvfrom(s, (char *)&result, sizeof(result), 0,
645 		     (struct sockaddr *)&ss, &size) < 0)
646 		return;
647 
648 	if (check_loop((struct sockaddr *)&ss, sep))
649 		return;
650 
651 	result = machtime();
652 	(void) sendto(s, (char *) &result, sizeof(result), 0,
653 		      (struct sockaddr *)&ss, sizeof(ss));
654 }
655 
656 /* ARGSUSED */
657 void
658 machtime_stream(s, sep)
659 	int s;
660 	struct servtab *sep;
661 {
662 	unsigned long result;
663 
664 	result = machtime();
665 	(void) write(s, (char *) &result, sizeof(result));
666 }
667 
668 /*
669  * RFC1078 TCP Port Service Multiplexer (TCPMUX). Service connections to
670  * services based on the service name sent.
671  *
672  *  Based on TCPMUX.C by Mark K. Lottor November 1988
673  *  sri-nic::ps:<mkl>tcpmux.c
674  */
675 
676 #define MAX_SERV_LEN	(256+2)		/* 2 bytes for \r\n */
677 #define strwrite(fd, buf)	(void) write(fd, buf, sizeof(buf)-1)
678 
679 static int		/* # of characters upto \r,\n or \0 */
680 getline(fd, buf, len)
681 	int fd;
682 	char *buf;
683 	int len;
684 {
685 	int count = 0, n;
686 	struct sigaction sa;
687 
688 	sa.sa_flags = 0;
689 	sigemptyset(&sa.sa_mask);
690 	sa.sa_handler = SIG_DFL;
691 	sigaction(SIGALRM, &sa, (struct sigaction *)0);
692 	do {
693 		alarm(10);
694 		n = read(fd, buf, len-count);
695 		alarm(0);
696 		if (n == 0)
697 			return (count);
698 		if (n < 0)
699 			return (-1);
700 		while (--n >= 0) {
701 			if (*buf == '\r' || *buf == '\n' || *buf == '\0')
702 				return (count);
703 			count++;
704 			buf++;
705 		}
706 	} while (count < len);
707 	return (count);
708 }
709 
710 struct servtab *
711 tcpmux(s)
712 	int s;
713 {
714 	struct servtab *sep;
715 	char service[MAX_SERV_LEN+1];
716 	int len;
717 
718 	/* Get requested service name */
719 	if ((len = getline(s, service, MAX_SERV_LEN)) < 0) {
720 		strwrite(s, "-Error reading service name\r\n");
721 		return (NULL);
722 	}
723 	service[len] = '\0';
724 
725 	if (debug)
726 		warnx("tcpmux: someone wants %s", service);
727 
728 	/*
729 	 * Help is a required command, and lists available services,
730 	 * one per line.
731 	 */
732 	if (!strcasecmp(service, "help")) {
733 		for (sep = servtab; sep; sep = sep->se_next) {
734 			if (!ISMUX(sep))
735 				continue;
736 			(void)write(s,sep->se_service,strlen(sep->se_service));
737 			strwrite(s, "\r\n");
738 		}
739 		return (NULL);
740 	}
741 
742 	/* Try matching a service in inetd.conf with the request */
743 	for (sep = servtab; sep; sep = sep->se_next) {
744 		if (!ISMUX(sep))
745 			continue;
746 		if (!strcasecmp(service, sep->se_service)) {
747 			if (ISMUXPLUS(sep)) {
748 				strwrite(s, "+Go\r\n");
749 			}
750 			return (sep);
751 		}
752 	}
753 	strwrite(s, "-Service not available\r\n");
754 	return (NULL);
755 }
756