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