xref: /original-bsd/usr.bin/telnet/telnet.c (revision 3f839ad3)
1 static char sccsid[] = "@(#)telnet.c	4.13 (Berkeley) 11/17/82";
2 /*
3  * User telnet program.
4  */
5 #include <sys/types.h>
6 #include <sys/socket.h>
7 
8 #include <netinet/in.h>
9 
10 #include <stdio.h>
11 #include <ctype.h>
12 #include <errno.h>
13 #include <signal.h>
14 #include <sgtty.h>
15 #include <setjmp.h>
16 #include <netdb.h>
17 
18 #define	TELOPTS
19 #include "telnet.h"
20 
21 #define	ctrl(x)		((x) & 037)
22 #define	strip(x)	((x)&0177)
23 
24 char	ttyobuf[BUFSIZ], *tfrontp = ttyobuf, *tbackp = ttyobuf;
25 char	netobuf[BUFSIZ], *nfrontp = netobuf, *nbackp = netobuf;
26 
27 char	hisopts[256];
28 char	myopts[256];
29 
30 char	doopt[] = { IAC, DO, '%', 'c', 0 };
31 char	dont[] = { IAC, DONT, '%', 'c', 0 };
32 char	will[] = { IAC, WILL, '%', 'c', 0 };
33 char	wont[] = { IAC, WONT, '%', 'c', 0 };
34 
35 int	connected;
36 int	net;
37 int	showoptions;
38 int	options;
39 char	*prompt;
40 char	escape = ctrl(']');
41 
42 char	line[200];
43 int	margc;
44 char	*margv[20];
45 
46 jmp_buf	toplevel;
47 jmp_buf	peerdied;
48 
49 extern	int errno;
50 
51 int	tn(), quit(), suspend(), bye(), help();
52 int	setescape(), status(), toggle(), setoptions();
53 
54 #define HELPINDENT (sizeof ("connect"))
55 
56 struct cmd {
57 	char	*name;
58 	char	*help;
59 	int	(*handler)();
60 };
61 
62 char	ohelp[] = "connect to a site";
63 char	chelp[] = "close current connection";
64 char	qhelp[] = "exit telnet";
65 char	zhelp[] = "suspend telnet";
66 char	ehelp[] = "set escape character";
67 char	shelp[] = "print status information";
68 char	hhelp[] = "print help information";
69 char	phelp[] = "toggle viewing of options processing";
70 
71 struct cmd cmdtab[] = {
72 	{ "open",	ohelp,		tn },
73 	{ "close",	chelp,		bye },
74 	{ "quit",	qhelp,		quit },
75 	{ "z",		zhelp,		suspend },
76 	{ "escape",	ehelp,		setescape },
77 	{ "status",	shelp,		status },
78 	{ "options",	phelp,		setoptions },
79 	{ "?",		hhelp,		help },
80 	0
81 };
82 
83 struct sockaddr_in sin = { AF_INET };
84 
85 int	intr(), deadpeer();
86 char	*control();
87 struct	cmd *getcmd();
88 struct	servent *sp;
89 
90 struct	sgttyb ostbuf;
91 struct	tchars otchars;
92 int	odisc;
93 
94 main(argc, argv)
95 	int argc;
96 	char *argv[];
97 {
98 	sp = getservbyname("telnet", "tcp");
99 	if (sp == 0) {
100 		fprintf(stderr, "telnet: tcp/telnet: unknown service\n");
101 		exit(1);
102 	}
103 	ioctl(0, TIOCGETP, (char *)&ostbuf);
104 	ioctl(0, TIOCGETC, (char *)&otchars);
105 	ioctl(0, TIOCGETD, (char *)&odisc);
106 	setbuf(stdin, 0);
107 	setbuf(stdout, 0);
108 	prompt = argv[0];
109 	if (argc > 1 && !strcmp(argv[1], "-d"))
110 		options = SO_DEBUG, argv++, argc--;
111 	if (argc != 1) {
112 		if (setjmp(toplevel) != 0)
113 			exit(0);
114 		tn(argc, argv);
115 	}
116 	setjmp(toplevel);
117 	for (;;)
118 		command(1);
119 }
120 
121 char	*hostname;
122 char	hnamebuf[32];
123 
124 tn(argc, argv)
125 	int argc;
126 	char *argv[];
127 {
128 	register int c;
129 	register struct hostent *host;
130 
131 	if (connected) {
132 		printf("?Already connected to %s\n", hostname);
133 		return;
134 	}
135 	if (argc < 2) {
136 		strcpy(line, "Connect ");
137 		printf("(to) ");
138 		gets(&line[strlen(line)]);
139 		makeargv();
140 		argc = margc;
141 		argv = margv;
142 	}
143 	if (argc > 3) {
144 		printf("usage: %s host-name [port]\n", argv[0]);
145 		return;
146 	}
147 	host = gethostbyname(argv[1]);
148 	if (host) {
149 		sin.sin_family = host->h_addrtype;
150 		bcopy(host->h_addr, (caddr_t)&sin.sin_addr, host->h_length);
151 		hostname = host->h_name;
152 	} else {
153 		sin.sin_family = AF_INET;
154 		sin.sin_addr.s_addr = inet_addr(argv[1]);
155 		if (sin.sin_addr.s_addr == -1) {
156 			printf("%s: unknown host\n", argv[1]);
157 			return;
158 		}
159 		strcpy(hnamebuf, argv[1]);
160 		hostname = hnamebuf;
161 	}
162 	sin.sin_port = sp->s_port;
163 	if (argc == 3) {
164 		sin.sin_port = atoi(argv[2]);
165 		if (sin.sin_port < 0) {
166 			printf("%s: bad port number\n", argv[2]);
167 			return;
168 		}
169 	}
170 	sin.sin_port = htons(sin.sin_port);
171 	net = socket(AF_INET, SOCK_STREAM, 0, 0);
172 	if (net < 0) {
173 		perror("telnet: socket");
174 		return;
175 	}
176 	sigset(SIGINT, intr);
177 	sigset(SIGPIPE, deadpeer);
178 	printf("Trying...\n");
179 	if (connect(net, (caddr_t)&sin, sizeof (sin), 0) < 0) {
180 		perror("telnet: connect");
181 		sigset(SIGINT, SIG_DFL);
182 		return;
183 	}
184 	connected++;
185 	call(status, "status", 0);
186 	if (setjmp(peerdied) == 0)
187 		telnet(net);
188 	fprintf(stderr, "Connection closed by foreign host.\n");
189 	exit(1);
190 }
191 
192 /*
193  * Print status about the connection.
194  */
195 /*VARARGS*/
196 status()
197 {
198 	if (connected)
199 		printf("Connected to %s.\n", hostname);
200 	else
201 		printf("No connection.\n");
202 	printf("Escape character is '%s'.\n", control(escape));
203 }
204 
205 makeargv()
206 {
207 	register char *cp;
208 	register char **argp = margv;
209 
210 	margc = 0;
211 	for (cp = line; *cp;) {
212 		while (isspace(*cp))
213 			cp++;
214 		if (*cp == '\0')
215 			break;
216 		*argp++ = cp;
217 		margc += 1;
218 		while (*cp != '\0' && !isspace(*cp))
219 			cp++;
220 		if (*cp == '\0')
221 			break;
222 		*cp++ = '\0';
223 	}
224 	*argp++ = 0;
225 }
226 
227 /*VARARGS*/
228 suspend()
229 {
230 	register int save;
231 
232 	save = mode(0);
233 	kill(0, SIGTSTP);
234 	/* reget parameters in case they were changed */
235 	ioctl(0, TIOCGETP, (char *)&ostbuf);
236 	ioctl(0, TIOCGETC, (char *)&otchars);
237 	ioctl(0, TIOCGETD, (char *)&odisc);
238 	(void) mode(save);
239 }
240 
241 /*VARARGS*/
242 bye()
243 {
244 	int how = 2;
245 	register char *op;
246 
247 	(void) mode(0);
248 	if (connected) {
249 #ifndef notdef
250 		ioctl(net, SIOCDONE, &how);
251 #endif
252 		printf("Connection closed.\n");
253 		close(net);
254 		connected = 0;
255 		/* reset his options */
256 		for (op = hisopts; op < &hisopts[256]; op++)
257 			*op = 0;
258 	}
259 }
260 
261 /*VARARGS*/
262 quit()
263 {
264 	call(bye, "bye", 0);
265 	exit(0);
266 }
267 
268 /*
269  * Help command.
270  * Call each command handler with argc == 0 and argv[0] == name.
271  */
272 help(argc, argv)
273 	int argc;
274 	char *argv[];
275 {
276 	register struct cmd *c;
277 
278 	if (argc == 1) {
279 		printf("Commands may be abbreviated.  Commands are:\n\n");
280 		for (c = cmdtab; c->name; c++)
281 			printf("%-*s\t%s\n", HELPINDENT, c->name, c->help);
282 		return;
283 	}
284 	while (--argc > 0) {
285 		register char *arg;
286 		arg = *++argv;
287 		c = getcmd(arg);
288 		if (c == (struct cmd *)-1)
289 			printf("?Ambiguous help command %s\n", arg);
290 		else if (c == (struct cmd *)0)
291 			printf("?Invalid help command %s\n", arg);
292 		else
293 			printf("%s\n", c->help);
294 	}
295 }
296 
297 /*
298  * Call routine with argc, argv set from args (terminated by 0).
299  * VARARGS2
300  */
301 call(routine, args)
302 	int (*routine)();
303 	int args;
304 {
305 	register int *argp;
306 	register int argc;
307 
308 	for (argc = 0, argp = &args; *argp++ != 0; argc++)
309 		;
310 	(*routine)(argc, &args);
311 }
312 
313 mode(f)
314 	register int f;
315 {
316 	struct sgttyb stbuf;
317 	static int prevmode = 0;
318 	struct tchars tchars;
319 	int onoff, disc, old;
320 
321 	if (prevmode == f)
322 		return (f);
323 	old = prevmode;
324 	prevmode = f;
325 	stbuf = ostbuf;
326 	tchars = otchars;
327 	switch (f) {
328 
329 	case 0:
330 		disc = odisc;
331 		onoff = 0;
332 		break;
333 
334 	case 1:
335 	case 2:
336 		stbuf.sg_flags |= CBREAK;
337 		if (f == 1)
338 			stbuf.sg_flags &= ~ECHO;
339 		else
340 			stbuf.sg_flags |= ECHO;
341 		tchars.t_intrc = tchars.t_quitc = -1;
342 		tchars.t_stopc = tchars.t_startc = -1;
343 		disc = OTTYDISC;
344 		onoff = 1;
345 	}
346 	ioctl(fileno(stdin), TIOCSETD, &disc);
347 	ioctl(fileno(stdin), TIOCSETC, &tchars);
348 	ioctl(fileno(stdin), TIOCSETN, &stbuf);
349 	ioctl(fileno(stdin), FIONBIO, &onoff);
350 	ioctl(fileno(stdout), FIONBIO, &onoff);
351 	return (old);
352 }
353 
354 char	sibuf[BUFSIZ], *sbp;
355 char	tibuf[BUFSIZ], *tbp;
356 int	scc, tcc;
357 
358 /*
359  * Select from tty and network...
360  */
361 telnet(s)
362 	int s;
363 {
364 	register int c;
365 	int tin = fileno(stdin), tout = fileno(stdout);
366 	int on = 1;
367 
368 	(void) mode(2);
369 	ioctl(s, FIONBIO, &on);
370 	for (;;) {
371 		int ibits = 0, obits = 0;
372 
373 		if (nfrontp - nbackp)
374 			obits |= (1 << s);
375 		else
376 			ibits |= (1 << tin);
377 		if (tfrontp - tbackp)
378 			obits |= (1 << tout);
379 		else
380 			ibits |= (1 << s);
381 		if (scc < 0 && tcc < 0)
382 			break;
383 		select(16, &ibits, &obits, 0, 0);
384 		if (ibits == 0 && obits == 0) {
385 			sleep(5);
386 			continue;
387 		}
388 
389 		/*
390 		 * Something to read from the network...
391 		 */
392 		if (ibits & (1 << s)) {
393 			scc = read(s, sibuf, sizeof (sibuf));
394 			if (scc < 0 && errno == EWOULDBLOCK)
395 				scc = 0;
396 			else {
397 				if (scc <= 0)
398 					break;
399 				sbp = sibuf;
400 			}
401 		}
402 
403 		/*
404 		 * Something to read from the tty...
405 		 */
406 		if (ibits & (1 << tin)) {
407 			tcc = read(tin, tibuf, sizeof (tibuf));
408 			if (tcc < 0 && errno == EWOULDBLOCK)
409 				tcc = 0;
410 			else {
411 				if (tcc <= 0)
412 					break;
413 				tbp = tibuf;
414 			}
415 		}
416 
417 		while (tcc > 0) {
418 			register int c;
419 
420 			if ((&netobuf[BUFSIZ] - nfrontp) < 2)
421 				break;
422 			c = *tbp++ & 0377, tcc--;
423 			if (strip(c) == escape) {
424 				command(0);
425 				tcc = 0;
426 				break;
427 			}
428 			*nfrontp++ = c;
429 		}
430 		if ((obits & (1 << s)) && (nfrontp - nbackp) > 0)
431 			netflush(s);
432 		if (scc > 0)
433 			telrcv();
434 		if ((obits & (1 << tout)) && (tfrontp - tbackp) > 0)
435 			ttyflush(tout);
436 	}
437 	(void) mode(0);
438 }
439 
440 command(top)
441 	int top;
442 {
443 	register struct cmd *c;
444 	int oldmode, wasopen;
445 
446 	oldmode = mode(0);
447 	if (!top)
448 		putchar('\n');
449 	else
450 		sigset(SIGINT, SIG_DFL);
451 	for (;;) {
452 		printf("%s> ", prompt);
453 		if (gets(line) == 0)
454 			break;
455 		if (line[0] == 0)
456 			break;
457 		makeargv();
458 		c = getcmd(margv[0]);
459 		if (c == (struct cmd *)-1) {
460 			printf("?Ambiguous command\n");
461 			continue;
462 		}
463 		if (c == 0) {
464 			printf("?Invalid command\n");
465 			continue;
466 		}
467 		(*c->handler)(margc, margv);
468 		if (c->handler != help)
469 			break;
470 	}
471 	if (!top) {
472 		if (!connected)
473 			longjmp(toplevel, 1);
474 		(void) mode(oldmode);
475 	}
476 }
477 
478 /*
479  * Telnet receiver states for fsm
480  */
481 #define	TS_DATA		0
482 #define	TS_IAC		1
483 #define	TS_WILL		2
484 #define	TS_WONT		3
485 #define	TS_DO		4
486 #define	TS_DONT		5
487 
488 telrcv()
489 {
490 	register int c;
491 	static int state = TS_DATA;
492 
493 	while (scc > 0) {
494 		c = *sbp++ & 0377, scc--;
495 		switch (state) {
496 
497 		case TS_DATA:
498 			if (c == IAC)
499 				state = TS_IAC;
500 			else
501 				*tfrontp++ = c;
502 			continue;
503 
504 		case TS_IAC:
505 			switch (c) {
506 
507 			case WILL:
508 				state = TS_WILL;
509 				continue;
510 
511 			case WONT:
512 				state = TS_WONT;
513 				continue;
514 
515 			case DO:
516 				state = TS_DO;
517 				continue;
518 
519 			case DONT:
520 				state = TS_DONT;
521 				continue;
522 
523 			case DM:
524 				ioctl(fileno(stdout), TIOCFLUSH, 0);
525 				break;
526 
527 			case NOP:
528 			case GA:
529 				break;
530 
531 			default:
532 				break;
533 			}
534 			state = TS_DATA;
535 			continue;
536 
537 		case TS_WILL:
538 			printoption("RCVD", will, c, !hisopts[c]);
539 			if (!hisopts[c])
540 				willoption(c);
541 			state = TS_DATA;
542 			continue;
543 
544 		case TS_WONT:
545 			printoption("RCVD", wont, c, hisopts[c]);
546 			if (hisopts[c])
547 				wontoption(c);
548 			state = TS_DATA;
549 			continue;
550 
551 		case TS_DO:
552 			printoption("RCVD", doopt, c, !myopts[c]);
553 			if (!myopts[c])
554 				dooption(c);
555 			state = TS_DATA;
556 			continue;
557 
558 		case TS_DONT:
559 			printoption("RCVD", dont, c, myopts[c]);
560 			if (myopts[c]) {
561 				myopts[c] = 0;
562 				sprintf(nfrontp, wont, c);
563 				nfrontp += sizeof (wont) - 2;
564 				printoption("SENT", wont, c);
565 			}
566 			state = TS_DATA;
567 			continue;
568 		}
569 	}
570 }
571 
572 willoption(option)
573 	int option;
574 {
575 	char *fmt;
576 
577 	switch (option) {
578 
579 	case TELOPT_ECHO:
580 		(void) mode(1);
581 
582 	case TELOPT_SGA:
583 		hisopts[option] = 1;
584 		fmt = doopt;
585 		break;
586 
587 	case TELOPT_TM:
588 		fmt = dont;
589 		break;
590 
591 	default:
592 		fmt = dont;
593 		break;
594 	}
595 	sprintf(nfrontp, fmt, option);
596 	nfrontp += sizeof (dont) - 2;
597 	printoption("SENT", fmt, option);
598 }
599 
600 wontoption(option)
601 	int option;
602 {
603 	char *fmt;
604 
605 	switch (option) {
606 
607 	case TELOPT_ECHO:
608 		(void) mode(2);
609 
610 	case TELOPT_SGA:
611 		hisopts[option] = 0;
612 		fmt = dont;
613 		break;
614 
615 	default:
616 		fmt = dont;
617 	}
618 	sprintf(nfrontp, fmt, option);
619 	nfrontp += sizeof (doopt) - 2;
620 	printoption("SENT", fmt, option);
621 }
622 
623 dooption(option)
624 	int option;
625 {
626 	char *fmt;
627 
628 	switch (option) {
629 
630 	case TELOPT_TM:
631 		fmt = wont;
632 		break;
633 
634 	case TELOPT_SGA:
635 		fmt = will;
636 		break;
637 
638 	default:
639 		fmt = wont;
640 		break;
641 	}
642 	sprintf(nfrontp, fmt, option);
643 	nfrontp += sizeof (doopt) - 2;
644 	printoption("SENT", fmt, option);
645 }
646 
647 /*
648  * Set the escape character.
649  */
650 setescape(argc, argv)
651 	int argc;
652 	char *argv[];
653 {
654 	register char *arg;
655 	char buf[50];
656 
657 	if (argc > 2)
658 		arg = argv[1];
659 	else {
660 		printf("new escape character: ");
661 		gets(buf);
662 		arg = buf;
663 	}
664 	if (arg[0] != '\0')
665 		escape = arg[0];
666 	printf("Escape character is '%s'.\n", control(escape));
667 }
668 
669 /*VARARGS*/
670 setoptions()
671 {
672 	showoptions = !showoptions;
673 	printf("%s show option processing.\n", showoptions ? "Will" : "Wont");
674 }
675 
676 /*
677  * Construct a control character sequence
678  * for a special character.
679  */
680 char *
681 control(c)
682 	register int c;
683 {
684 	static char buf[3];
685 
686 	if (c == 0177)
687 		return ("^?");
688 	if (c >= 040) {
689 		buf[0] = c;
690 		buf[1] = 0;
691 	} else {
692 		buf[0] = '^';
693 		buf[1] = '@'+c;
694 		buf[2] = 0;
695 	}
696 	return (buf);
697 }
698 
699 struct cmd *
700 getcmd(name)
701 	register char *name;
702 {
703 	register char *p, *q;
704 	register struct cmd *c, *found;
705 	register int nmatches, longest;
706 
707 	longest = 0;
708 	nmatches = 0;
709 	found = 0;
710 	for (c = cmdtab; p = c->name; c++) {
711 		for (q = name; *q == *p++; q++)
712 			if (*q == 0)		/* exact match? */
713 				return (c);
714 		if (!*q) {			/* the name was a prefix */
715 			if (q - name > longest) {
716 				longest = q - name;
717 				nmatches = 1;
718 				found = c;
719 			} else if (q - name == longest)
720 				nmatches++;
721 		}
722 	}
723 	if (nmatches > 1)
724 		return ((struct cmd *)-1);
725 	return (found);
726 }
727 
728 deadpeer()
729 {
730 	sigset(SIGPIPE, deadpeer);
731 	(void) mode(0);
732 	longjmp(peerdied, -1);
733 }
734 
735 intr()
736 {
737 	sigset(SIGINT, intr);
738 	(void) mode(0);
739 	longjmp(toplevel, -1);
740 }
741 
742 ttyflush(fd)
743 {
744 	int n;
745 
746 	if ((n = tfrontp - tbackp) > 0)
747 		n = write(fd, tbackp, n);
748 	if (n < 0)
749 		return;
750 	tbackp += n;
751 	if (tbackp == tfrontp)
752 		tbackp = tfrontp = ttyobuf;
753 }
754 
755 netflush(fd)
756 {
757 	int n;
758 
759 	if ((n = nfrontp - nbackp) > 0)
760 		n = write(fd, nbackp, n);
761 	if (n < 0) {
762 		if (errno != ENOBUFS && errno != EWOULDBLOCK) {
763 			(void) mode(0);
764 			perror(hostname);
765 			close(fd);
766 			longjmp(peerdied, -1);
767 			/*NOTREACHED*/
768 		}
769 		n = 0;
770 	}
771 	nbackp += n;
772 	if (nbackp == nfrontp)
773 		nbackp = nfrontp = netobuf;
774 }
775 
776 /*VARARGS*/
777 printoption(direction, fmt, option, what)
778 	char *direction, *fmt;
779 	int option, what;
780 {
781 	if (!showoptions)
782 		return;
783 	printf("%s ", direction);
784 	if (fmt == doopt)
785 		fmt = "do";
786 	else if (fmt == dont)
787 		fmt = "dont";
788 	else if (fmt == will)
789 		fmt = "will";
790 	else if (fmt == wont)
791 		fmt = "wont";
792 	else
793 		fmt = "???";
794 	if (option < TELOPT_SUPDUP)
795 		printf("%s %s", fmt, telopts[option]);
796 	else
797 		printf("%s %d", fmt, option);
798 	if (*direction == '<') {
799 		printf("\r\n");
800 		return;
801 	}
802 	printf(" (%s)\r\n", what ? "reply" : "don't reply");
803 }
804