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