be more social about killing attached processes (vhangup takes care of it)
[unix-history] / usr / src / libexec / telnetd / telnetd.c
CommitLineData
8c5eec2f
DF
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
8char copyright[] =
9"@(#) Copyright (c) 1983 Regents of the University of California.\n\
10 All rights reserved.\n";
11#endif not lint
12
ac6e6727 13#ifndef lint
d9b06a2d 14static char sccsid[] = "@(#)telnetd.c 5.4 (Berkeley) %G%";
8c5eec2f 15#endif not lint
ac6e6727 16
66b878f6
BJ
17/*
18 * Stripped-down telnet server.
19 */
de3b21e8
SL
20#include <sys/types.h>
21#include <sys/socket.h>
ce4fd43b 22#include <sys/wait.h>
1a33b848 23#include <sys/file.h>
c29f876c 24#include <sys/stat.h>
de3b21e8
SL
25
26#include <netinet/in.h>
27
2b597a6b
SL
28#include <arpa/telnet.h>
29
66b878f6
BJ
30#include <stdio.h>
31#include <signal.h>
32#include <errno.h>
33#include <sgtty.h>
9f005877 34#include <netdb.h>
3f99c0f7 35#include <syslog.h>
de3b21e8 36
4898c5bf 37#define BELL '\07'
122f5efc 38#define BANNER "\r\n\r\n4.3 BSD UNIX (%s)\r\n\r\r\n\r%s"
66b878f6
BJ
39
40char hisopts[256];
41char myopts[256];
42
43char doopt[] = { IAC, DO, '%', 'c', 0 };
44char dont[] = { IAC, DONT, '%', 'c', 0 };
45char will[] = { IAC, WILL, '%', 'c', 0 };
46char wont[] = { IAC, WONT, '%', 'c', 0 };
47
48/*
49 * I/O data buffers, pointers, and counters.
50 */
51char ptyibuf[BUFSIZ], *ptyip = ptyibuf;
52char ptyobuf[BUFSIZ], *pfrontp = ptyobuf, *pbackp = ptyobuf;
53char netibuf[BUFSIZ], *netip = netibuf;
9ef3087d 54char netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
66b878f6
BJ
55int pcc, ncc;
56
57int pty, net;
58int inter;
fe5c5547 59extern char **environ;
66b878f6 60extern int errno;
c29f876c 61char *line;
66b878f6 62
66b878f6
BJ
63main(argc, argv)
64 char *argv[];
65{
bb933cc2 66 struct sockaddr_in from;
bcb894cb 67 int on = 1, fromlen;
bb933cc2 68
076ae92c 69 openlog("telnetd", LOG_PID | LOG_ODELAY, LOG_DAEMON);
bb933cc2
MK
70 fromlen = sizeof (from);
71 if (getpeername(0, &from, &fromlen) < 0) {
72 fprintf(stderr, "%s: ", argv[0]);
73 perror("getpeername");
74 _exit(1);
de3b21e8 75 }
bcb894cb 76 if (setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0) {
3f99c0f7 77 syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
de3b21e8 78 }
bb933cc2 79 doit(0, &from);
f553aca8
SL
80}
81
fe5c5547 82char *envinit[] = { "TERM=network", 0 };
66b878f6
BJ
83int cleanup();
84
85/*
86 * Get a pty, scan input lines.
87 */
37c640e2
SL
88doit(f, who)
89 int f;
90 struct sockaddr_in *who;
66b878f6 91{
c29f876c 92 char *host, *inet_ntoa();
1a33b848 93 int i, p, t;
66b878f6 94 struct sgttyb b;
37c640e2 95 struct hostent *hp;
c29f876c 96 char c;
1a33b848 97
c29f876c
MK
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;
1a33b848 106 for (i = 0; i < 16; i++) {
c29f876c
MK
107 line[strlen("/dev/ptyp")] = "0123456789abcdef"[i];
108 p = open(line, 2);
1a33b848
SL
109 if (p > 0)
110 goto gotpty;
111 }
66b878f6 112 }
8f2758db
SL
113 fatal(f, "All network ports in use");
114 /*NOTREACHED*/
66b878f6
BJ
115gotpty:
116 dup2(f, 0);
c29f876c 117 line[strlen("/dev/")] = 't';
1a33b848 118 t = open("/dev/tty", O_RDWR);
66b878f6
BJ
119 if (t >= 0) {
120 ioctl(t, TIOCNOTTY, 0);
121 close(t);
122 }
c29f876c 123 t = open(line, O_RDWR);
8f2758db 124 if (t < 0)
c29f876c 125 fatalperror(f, line, errno);
66b878f6 126 ioctl(t, TIOCGETP, &b);
9ef3087d 127 b.sg_flags = CRMOD|XTABS|ANYP;
66b878f6 128 ioctl(t, TIOCSETP, &b);
9ef3087d 129 ioctl(p, TIOCGETP, &b);
da96b661 130 b.sg_flags &= ~ECHO;
9ef3087d 131 ioctl(p, TIOCSETP, &b);
37c640e2
SL
132 hp = gethostbyaddr(&who->sin_addr, sizeof (struct in_addr),
133 who->sin_family);
134 if (hp)
135 host = hp->h_name;
136 else
05fa5465 137 host = inet_ntoa(who->sin_addr);
8f2758db
SL
138 if ((i = fork()) < 0)
139 fatalperror(f, "fork", errno);
66b878f6
BJ
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);
fe5c5547 148 environ = envinit;
0c285f22 149 execl("/bin/login", "login", "-h", host, 0);
8f2758db
SL
150 fatalperror(f, "/bin/login", errno);
151 /*NOTREACHED*/
152}
153
154fatal(f, msg)
155 int f;
156 char *msg;
157{
158 char buf[BUFSIZ];
159
1a33b848 160 (void) sprintf(buf, "telnetd: %s.\r\n", msg);
8f2758db 161 (void) write(f, buf, strlen(buf));
66b878f6
BJ
162 exit(1);
163}
164
8f2758db
SL
165fatalperror(f, msg, errno)
166 int f;
167 char *msg;
168 int errno;
169{
170 char buf[BUFSIZ];
171 extern char *sys_errlist[];
172
1a33b848 173 (void) sprintf(buf, "%s: %s\r\n", msg, sys_errlist[errno]);
8f2758db
SL
174 fatal(f, buf);
175}
176
66b878f6
BJ
177/*
178 * Main loop. Select from pty and network, and
179 * hand data to telnet receiver finite state machine.
180 */
181telnet(f, p)
182{
183 int on = 1;
0c285f22 184 char hostname[32];
66b878f6
BJ
185
186 net = f, pty = p;
187 ioctl(f, FIONBIO, &on);
188 ioctl(p, FIONBIO, &on);
189 signal(SIGTSTP, SIG_IGN);
8a53982e 190 signal(SIGCHLD, cleanup);
66b878f6 191
da96b661
SL
192 /*
193 * Request to do remote echo.
194 */
195 dooption(TELOPT_ECHO);
196 myopts[TELOPT_ECHO] = 1;
0c285f22
SL
197 /*
198 * Show banner that getty never gave.
199 */
200 gethostname(hostname, sizeof (hostname));
201 sprintf(nfrontp, BANNER, hostname, "");
202 nfrontp += strlen(nfrontp);
66b878f6
BJ
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 */
1a33b848 211 if (nfrontp - nbackp || pcc > 0)
66b878f6
BJ
212 obits |= (1 << f);
213 else
214 ibits |= (1 << p);
1a33b848 215 if (pfrontp - pbackp || ncc > 0)
66b878f6
BJ
216 obits |= (1 << p);
217 else
218 ibits |= (1 << f);
219 if (ncc < 0 && pcc < 0)
220 break;
de3b21e8 221 select(16, &ibits, &obits, 0, 0);
66b878f6
BJ
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
286telrcv()
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:
1a33b848
SL
333 strcpy(nfrontp, "\r\n[Yes]\r\n");
334 nfrontp += 9;
66b878f6
BJ
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);
da96b661 407 nfrontp += sizeof (wont) - 2;
66b878f6
BJ
408 }
409 state = TS_DATA;
410 continue;
411
412 default:
de3b21e8 413 printf("telnetd: panic state=%d\n", state);
66b878f6
BJ
414 exit(1);
415 }
416 }
417}
418
419willoption(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 }
13646f15 448 sprintf(nfrontp, fmt, option);
da96b661 449 nfrontp += sizeof (dont) - 2;
66b878f6
BJ
450}
451
452wontoption(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);
da96b661 477 nfrontp += sizeof (doopt) - 2;
66b878f6
BJ
478}
479
480dooption(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);
da96b661 509 nfrontp += sizeof (doopt) - 2;
66b878f6
BJ
510}
511
512mode(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 */
529interrupt()
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
544ptyflush()
545{
546 int n;
547
548 if ((n = pfrontp - pbackp) > 0)
549 n = write(pty, pbackp, n);
9f005877
SL
550 if (n < 0)
551 return;
66b878f6
BJ
552 pbackp += n;
553 if (pbackp == pfrontp)
554 pbackp = pfrontp = ptyobuf;
555}
556
557netflush()
558{
559 int n;
560
561 if ((n = nfrontp - nbackp) > 0)
562 n = write(net, nbackp, n);
9f005877
SL
563 if (n < 0) {
564 if (errno == EWOULDBLOCK)
565 return;
566 /* should blow this guy away... */
567 return;
568 }
66b878f6
BJ
569 nbackp += n;
570 if (nbackp == nfrontp)
571 nbackp = nfrontp = netobuf;
572}
573
574cleanup()
575{
66b878f6
BJ
576
577 rmut();
a7f6263e 578 vhangup(); /* XXX */
ff24c640 579 shutdown(net, 2);
66b878f6
BJ
580 exit(1);
581}
582
583#include <utmp.h>
584
585struct utmp wtmp;
586char wtmpf[] = "/usr/adm/wtmp";
122f5efc
JB
587char utmpf[] = "/etc/utmp";
588#define SCPYN(a, b) strncpy(a, b, sizeof(a))
589#define SCMPN(a, b) strncmp(a, b, sizeof(a))
66b878f6
BJ
590
591rmut()
592{
593 register f;
594 int found = 0;
122f5efc
JB
595 struct utmp *u, *utmp;
596 int nutmp;
597 struct stat statbf;
66b878f6 598
122f5efc 599 f = open(utmpf, O_RDWR);
66b878f6 600 if (f >= 0) {
122f5efc
JB
601 fstat(f, &statbf);
602 utmp = (struct utmp *)malloc(statbf.st_size);
603 if (!utmp)
604 syslog(LOG_ERR, "utmp malloc failed");
605 if (statbf.st_size && utmp) {
606 nutmp = read(f, utmp, statbf.st_size);
607 nutmp /= sizeof(struct utmp);
608
609 for (u = utmp ; u < &utmp[nutmp] ; u++) {
610 if (SCMPN(u->ut_line, line+5) ||
611 u->ut_name[0]==0)
612 continue;
613 lseek(f, ((long)u)-((long)utmp), L_SET);
614 SCPYN(u->ut_name, "");
615 SCPYN(u->ut_host, "");
616 time(&u->ut_time);
617 write(f, (char *)u, sizeof(wtmp));
618 found++;
619 }
66b878f6
BJ
620 }
621 close(f);
622 }
623 if (found) {
1a33b848 624 f = open(wtmpf, O_WRONLY|O_APPEND);
66b878f6
BJ
625 if (f >= 0) {
626 SCPYN(wtmp.ut_line, line+5);
627 SCPYN(wtmp.ut_name, "");
37c640e2 628 SCPYN(wtmp.ut_host, "");
66b878f6 629 time(&wtmp.ut_time);
122f5efc 630 write(f, (char *)&wtmp, sizeof(wtmp));
66b878f6
BJ
631 close(f);
632 }
633 }
634 chmod(line, 0666);
635 chown(line, 0, 0);
636 line[strlen("/dev/")] = 'p';
637 chmod(line, 0666);
638 chown(line, 0, 0);
639}