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