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