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