xref: /csrg-svn/libexec/telnetd/telnetd.c (revision 12216)
1 #ifndef lint
2 static char sccsid[] = "@(#)telnetd.c	4.19 83/05/03";
3 #endif
4 
5 /*
6  * Stripped-down telnet server.
7  */
8 #include <sys/types.h>
9 #include <sys/socket.h>
10 
11 #include <netinet/in.h>
12 
13 #include <arpa/telnet.h>
14 
15 #include <stdio.h>
16 #include <signal.h>
17 #include <errno.h>
18 #include <sgtty.h>
19 #include <wait.h>
20 #include <netdb.h>
21 
22 #define	BELL		'\07'
23 
24 char	hisopts[256];
25 char	myopts[256];
26 
27 char	doopt[] = { IAC, DO, '%', 'c', 0 };
28 char	dont[] = { IAC, DONT, '%', 'c', 0 };
29 char	will[] = { IAC, WILL, '%', 'c', 0 };
30 char	wont[] = { IAC, WONT, '%', 'c', 0 };
31 
32 /*
33  * I/O data buffers, pointers, and counters.
34  */
35 char	ptyibuf[BUFSIZ], *ptyip = ptyibuf;
36 char	ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf;
37 char	netibuf[BUFSIZ], *netip = netibuf;
38 char	netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
39 int	pcc, ncc;
40 
41 int	pty, net;
42 int	inter;
43 int	reapchild();
44 extern	int errno;
45 char	line[] = "/dev/ptyp0";
46 
47 struct	sockaddr_in sin = { AF_INET };
48 
49 main(argc, argv)
50 	char *argv[];
51 {
52 	int s, pid, options;
53 	struct servent *sp;
54 
55 	sp = getservbyname("telnet", "tcp");
56 	if (sp == 0) {
57 		fprintf(stderr, "telnetd: tcp/telnet: unknown service\n");
58 		exit(1);
59 	}
60 	sin.sin_port = sp->s_port;
61 	argc--, argv++;
62 	if (argc > 0 && !strcmp(*argv, "-d")) {
63 		options |= SO_DEBUG;
64 		argc--, argv++;
65 	}
66 	if (argc > 0) {
67 		sin.sin_port = atoi(*argv);
68 		if (sin.sin_port <= 0) {
69 			fprintf(stderr, "telnetd: %s: bad port #\n", *argv);
70 			exit(1);
71 		}
72 		sin.sin_port = htons((u_short)sin.sin_port);
73 	}
74 #ifndef DEBUG
75 	if (fork())
76 		exit(0);
77 	for (s = 0; s < 10; s++)
78 		(void) close(s);
79 	(void) open("/", 0);
80 	(void) dup2(0, 1);
81 	(void) dup2(0, 2);
82 	{ int tt = open("/dev/tty", 2);
83 	  if (tt > 0) {
84 		ioctl(tt, TIOCNOTTY, 0);
85 		close(tt);
86 	  }
87 	}
88 #endif
89 again:
90 	s = socket(AF_INET, SOCK_STREAM, 0, 0);
91 	if (s < 0) {
92 		perror("telnetd: socket");;
93 		sleep(5);
94 		goto again;
95 	}
96 	if (options & SO_DEBUG)
97 		if (setsockopt(s, SOL_SOCKET, SO_DEBUG, 0, 0) < 0)
98 			perror("telnetd: setsockopt (SO_DEBUG)");
99 	if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, 0, 0) < 0)
100 		perror("telnetd: setsockopt (SO_KEEPALIVE)");
101 	while (bind(s, (caddr_t)&sin, sizeof (sin), 0) < 0) {
102 		perror("telnetd: bind");
103 		sleep(5);
104 	}
105 	sigset(SIGCHLD, reapchild);
106 	listen(s, 10);
107 	for (;;) {
108 		int s2;
109 
110 		s2 = accept(s, (caddr_t)0, 0, 0);
111 		if (s2 < 0) {
112 			if (errno == EINTR)
113 				continue;
114 			perror("telnetd: accept");
115 			sleep(1);
116 			continue;
117 		}
118 		if ((pid = fork()) < 0)
119 			printf("Out of processes\n");
120 		else if (pid == 0) {
121 			signal(SIGCHLD, SIG_IGN);
122 			doit(s2);
123 		}
124 		close(s2);
125 	}
126 	/*NOTREACHED*/
127 }
128 
129 reapchild()
130 {
131 	union wait status;
132 
133 	while (wait3(&status, WNOHANG, 0) > 0)
134 		;
135 }
136 
137 int	cleanup();
138 
139 /*
140  * Get a pty, scan input lines.
141  */
142 doit(f)
143 {
144 	char *cp = line;
145 	int i, p, cc, t;
146 	struct sgttyb b;
147 
148 	for (i = 0; i < 16; i++) {
149 		cp[strlen("/dev/ptyp")] = "0123456789abcdef"[i];
150 		p = open(cp, 2);
151 		if (p > 0)
152 			goto gotpty;
153 	}
154 	fatal(f, "All network ports in use");
155 	/*NOTREACHED*/
156 gotpty:
157 	dup2(f, 0);
158 	cp[strlen("/dev/")] = 't';
159 	t = open("/dev/tty", 2);
160 	if (t >= 0) {
161 		ioctl(t, TIOCNOTTY, 0);
162 		close(t);
163 	}
164 	t = open(cp, 2);
165 	if (t < 0)
166 		fatalperror(f, cp, errno);
167 	ioctl(t, TIOCGETP, &b);
168 	b.sg_flags = CRMOD|XTABS|ANYP;
169 	ioctl(t, TIOCSETP, &b);
170 	ioctl(p, TIOCGETP, &b);
171 	b.sg_flags &= ~ECHO;
172 	ioctl(p, TIOCSETP, &b);
173 	if ((i = fork()) < 0)
174 		fatalperror(f, "fork", errno);
175 	if (i)
176 		telnet(f, p);
177 	close(f);
178 	close(p);
179 	dup2(t, 0);
180 	dup2(t, 1);
181 	dup2(t, 2);
182 	close(t);
183 	execl("/bin/login", "telnet-login", 0);
184 	fatalperror(f, "/bin/login", errno);
185 	/*NOTREACHED*/
186 }
187 
188 fatal(f, msg)
189 	int f;
190 	char *msg;
191 {
192 	char buf[BUFSIZ];
193 
194 	(void) sprintf(buf, "telnetd: %s.\n", msg);
195 	(void) write(f, buf, strlen(buf));
196 	exit(1);
197 }
198 
199 fatalperror(f, msg, errno)
200 	int f;
201 	char *msg;
202 	int errno;
203 {
204 	char buf[BUFSIZ];
205 	extern char *sys_errlist[];
206 
207 	(void) sprintf(buf, "%s: %s", msg, sys_errlist[errno]);
208 	fatal(f, buf);
209 }
210 
211 /*
212  * Main loop.  Select from pty and network, and
213  * hand data to telnet receiver finite state machine.
214  */
215 telnet(f, p)
216 {
217 	int on = 1;
218 
219 	net = f, pty = p;
220 	ioctl(f, FIONBIO, &on);
221 	ioctl(p, FIONBIO, &on);
222 	signal(SIGTSTP, SIG_IGN);
223 	sigset(SIGCHLD, cleanup);
224 
225 	/*
226 	 * Request to do remote echo.
227 	 */
228 	dooption(TELOPT_ECHO);
229 	myopts[TELOPT_ECHO] = 1;
230 	for (;;) {
231 		int ibits = 0, obits = 0;
232 		register int c;
233 
234 		/*
235 		 * Never look for input if there's still
236 		 * stuff in the corresponding output buffer
237 		 */
238 		if (nfrontp - nbackp)
239 			obits |= (1 << f);
240 		else
241 			ibits |= (1 << p);
242 		if (pfrontp - pbackp)
243 			obits |= (1 << p);
244 		else
245 			ibits |= (1 << f);
246 		if (ncc < 0 && pcc < 0)
247 			break;
248 		select(16, &ibits, &obits, 0, 0);
249 		if (ibits == 0 && obits == 0) {
250 			sleep(5);
251 			continue;
252 		}
253 
254 		/*
255 		 * Something to read from the network...
256 		 */
257 		if (ibits & (1 << f)) {
258 			ncc = read(f, netibuf, BUFSIZ);
259 			if (ncc < 0 && errno == EWOULDBLOCK)
260 				ncc = 0;
261 			else {
262 				if (ncc <= 0)
263 					break;
264 				netip = netibuf;
265 			}
266 		}
267 
268 		/*
269 		 * Something to read from the pty...
270 		 */
271 		if (ibits & (1 << p)) {
272 			pcc = read(p, ptyibuf, BUFSIZ);
273 			if (pcc < 0 && errno == EWOULDBLOCK)
274 				pcc = 0;
275 			else {
276 				if (pcc <= 0)
277 					break;
278 				ptyip = ptyibuf;
279 			}
280 		}
281 
282 		while (pcc > 0) {
283 			if ((&netobuf[BUFSIZ] - nfrontp) < 2)
284 				break;
285 			c = *ptyip++ & 0377, pcc--;
286 			if (c == IAC)
287 				*nfrontp++ = c;
288 			*nfrontp++ = c;
289 		}
290 		if ((obits & (1 << f)) && (nfrontp - nbackp) > 0)
291 			netflush();
292 		if (ncc > 0)
293 			telrcv();
294 		if ((obits & (1 << p)) && (pfrontp - pbackp) > 0)
295 			ptyflush();
296 	}
297 	cleanup();
298 }
299 
300 /*
301  * State for recv fsm
302  */
303 #define	TS_DATA		0	/* base state */
304 #define	TS_IAC		1	/* look for double IAC's */
305 #define	TS_CR		2	/* CR-LF ->'s CR */
306 #define	TS_BEGINNEG	3	/* throw away begin's... */
307 #define	TS_ENDNEG	4	/* ...end's (suboption negotiation) */
308 #define	TS_WILL		5	/* will option negotiation */
309 #define	TS_WONT		6	/* wont " */
310 #define	TS_DO		7	/* do " */
311 #define	TS_DONT		8	/* dont " */
312 
313 telrcv()
314 {
315 	register int c;
316 	static int state = TS_DATA;
317 	struct sgttyb b;
318 
319 	while (ncc > 0) {
320 		if ((&ptyobuf[BUFSIZ] - pfrontp) < 2)
321 			return;
322 		c = *netip++ & 0377, ncc--;
323 		switch (state) {
324 
325 		case TS_DATA:
326 			if (c == IAC) {
327 				state = TS_IAC;
328 				break;
329 			}
330 			if (inter > 0)
331 				break;
332 			*pfrontp++ = c;
333 			if (!myopts[TELOPT_BINARY] && c == '\r')
334 				state = TS_CR;
335 			break;
336 
337 		case TS_CR:
338 			if (c && c != '\n')
339 				*pfrontp++ = c;
340 			state = TS_DATA;
341 			break;
342 
343 		case TS_IAC:
344 			switch (c) {
345 
346 			/*
347 			 * Send the process on the pty side an
348 			 * interrupt.  Do this with a NULL or
349 			 * interrupt char; depending on the tty mode.
350 			 */
351 			case BREAK:
352 			case IP:
353 				interrupt();
354 				break;
355 
356 			/*
357 			 * Are You There?
358 			 */
359 			case AYT:
360 				*pfrontp++ = BELL;
361 				break;
362 
363 			/*
364 			 * Erase Character and
365 			 * Erase Line
366 			 */
367 			case EC:
368 			case EL:
369 				ptyflush();	/* half-hearted */
370 				ioctl(pty, TIOCGETP, &b);
371 				*pfrontp++ = (c == EC) ?
372 					b.sg_erase : b.sg_kill;
373 				break;
374 
375 			/*
376 			 * Check for urgent data...
377 			 */
378 			case DM:
379 				break;
380 
381 			/*
382 			 * Begin option subnegotiation...
383 			 */
384 			case SB:
385 				state = TS_BEGINNEG;
386 				continue;
387 
388 			case WILL:
389 			case WONT:
390 			case DO:
391 			case DONT:
392 				state = TS_WILL + (c - WILL);
393 				continue;
394 
395 			case IAC:
396 				*pfrontp++ = c;
397 				break;
398 			}
399 			state = TS_DATA;
400 			break;
401 
402 		case TS_BEGINNEG:
403 			if (c == IAC)
404 				state = TS_ENDNEG;
405 			break;
406 
407 		case TS_ENDNEG:
408 			state = c == SE ? TS_DATA : TS_BEGINNEG;
409 			break;
410 
411 		case TS_WILL:
412 			if (!hisopts[c])
413 				willoption(c);
414 			state = TS_DATA;
415 			continue;
416 
417 		case TS_WONT:
418 			if (hisopts[c])
419 				wontoption(c);
420 			state = TS_DATA;
421 			continue;
422 
423 		case TS_DO:
424 			if (!myopts[c])
425 				dooption(c);
426 			state = TS_DATA;
427 			continue;
428 
429 		case TS_DONT:
430 			if (myopts[c]) {
431 				myopts[c] = 0;
432 				sprintf(nfrontp, wont, c);
433 				nfrontp += sizeof (wont) - 2;
434 			}
435 			state = TS_DATA;
436 			continue;
437 
438 		default:
439 			printf("telnetd: panic state=%d\n", state);
440 			exit(1);
441 		}
442 	}
443 }
444 
445 willoption(option)
446 	int option;
447 {
448 	char *fmt;
449 
450 	switch (option) {
451 
452 	case TELOPT_BINARY:
453 		mode(RAW, 0);
454 		goto common;
455 
456 	case TELOPT_ECHO:
457 		mode(0, ECHO|CRMOD);
458 		/*FALL THRU*/
459 
460 	case TELOPT_SGA:
461 	common:
462 		hisopts[option] = 1;
463 		fmt = doopt;
464 		break;
465 
466 	case TELOPT_TM:
467 		fmt = dont;
468 		break;
469 
470 	default:
471 		fmt = dont;
472 		break;
473 	}
474 	sprintf(nfrontp, fmt, option);
475 	nfrontp += sizeof (dont) - 2;
476 }
477 
478 wontoption(option)
479 	int option;
480 {
481 	char *fmt;
482 
483 	switch (option) {
484 
485 	case TELOPT_ECHO:
486 		mode(ECHO|CRMOD, 0);
487 		goto common;
488 
489 	case TELOPT_BINARY:
490 		mode(0, RAW);
491 		/*FALL THRU*/
492 
493 	case TELOPT_SGA:
494 	common:
495 		hisopts[option] = 0;
496 		fmt = dont;
497 		break;
498 
499 	default:
500 		fmt = dont;
501 	}
502 	sprintf(nfrontp, fmt, option);
503 	nfrontp += sizeof (doopt) - 2;
504 }
505 
506 dooption(option)
507 	int option;
508 {
509 	char *fmt;
510 
511 	switch (option) {
512 
513 	case TELOPT_TM:
514 		fmt = wont;
515 		break;
516 
517 	case TELOPT_ECHO:
518 		mode(ECHO|CRMOD, 0);
519 		goto common;
520 
521 	case TELOPT_BINARY:
522 		mode(RAW, 0);
523 		/*FALL THRU*/
524 
525 	case TELOPT_SGA:
526 	common:
527 		fmt = will;
528 		break;
529 
530 	default:
531 		fmt = wont;
532 		break;
533 	}
534 	sprintf(nfrontp, fmt, option);
535 	nfrontp += sizeof (doopt) - 2;
536 }
537 
538 mode(on, off)
539 	int on, off;
540 {
541 	struct sgttyb b;
542 
543 	ptyflush();
544 	ioctl(pty, TIOCGETP, &b);
545 	b.sg_flags |= on;
546 	b.sg_flags &= ~off;
547 	ioctl(pty, TIOCSETP, &b);
548 }
549 
550 /*
551  * Send interrupt to process on other side of pty.
552  * If it is in raw mode, just write NULL;
553  * otherwise, write intr char.
554  */
555 interrupt()
556 {
557 	struct sgttyb b;
558 	struct tchars tchars;
559 
560 	ptyflush();	/* half-hearted */
561 	ioctl(pty, TIOCGETP, &b);
562 	if (b.sg_flags & RAW) {
563 		*pfrontp++ = '\0';
564 		return;
565 	}
566 	*pfrontp++ = ioctl(pty, TIOCGETC, &tchars) < 0 ?
567 		'\177' : tchars.t_intrc;
568 }
569 
570 ptyflush()
571 {
572 	int n;
573 
574 	if ((n = pfrontp - pbackp) > 0)
575 		n = write(pty, pbackp, n);
576 	if (n < 0)
577 		return;
578 	pbackp += n;
579 	if (pbackp == pfrontp)
580 		pbackp = pfrontp = ptyobuf;
581 }
582 
583 netflush()
584 {
585 	int n;
586 
587 	if ((n = nfrontp - nbackp) > 0)
588 		n = write(net, nbackp, n);
589 	if (n < 0) {
590 		if (errno == EWOULDBLOCK)
591 			return;
592 		/* should blow this guy away... */
593 		return;
594 	}
595 	nbackp += n;
596 	if (nbackp == nfrontp)
597 		nbackp = nfrontp = netobuf;
598 }
599 
600 cleanup()
601 {
602 
603 	rmut();
604 	vhangup();	/* XXX */
605 	shutdown(net, 2);
606 	kill(0, SIGKILL);
607 	exit(1);
608 }
609 
610 #include <utmp.h>
611 
612 struct	utmp wtmp;
613 char	wtmpf[]	= "/usr/adm/wtmp";
614 char	utmp[] = "/etc/utmp";
615 #define SCPYN(a, b)	strncpy(a, b, sizeof (a))
616 #define SCMPN(a, b)	strncmp(a, b, sizeof (a))
617 
618 rmut()
619 {
620 	register f;
621 	int found = 0;
622 
623 	f = open(utmp, 2);
624 	if (f >= 0) {
625 		while(read(f, (char *)&wtmp, sizeof (wtmp)) == sizeof (wtmp)) {
626 			if (SCMPN(wtmp.ut_line, line+5) || wtmp.ut_name[0]==0)
627 				continue;
628 			lseek(f, -(long)sizeof (wtmp), 1);
629 			SCPYN(wtmp.ut_name, "");
630 			time(&wtmp.ut_time);
631 			write(f, (char *)&wtmp, sizeof (wtmp));
632 			found++;
633 		}
634 		close(f);
635 	}
636 	if (found) {
637 		f = open(wtmpf, 1);
638 		if (f >= 0) {
639 			SCPYN(wtmp.ut_line, line+5);
640 			SCPYN(wtmp.ut_name, "");
641 			time(&wtmp.ut_time);
642 			lseek(f, (long)0, 2);
643 			write(f, (char *)&wtmp, sizeof (wtmp));
644 			close(f);
645 		}
646 	}
647 	chmod(line, 0666);
648 	chown(line, 0, 0);
649 	line[strlen("/dev/")] = 'p';
650 	chmod(line, 0666);
651 	chown(line, 0, 0);
652 }
653