xref: /original-bsd/usr.bin/telnet/telnet.c (revision 8208c1e2)
1 static char sccsid[] = "@(#)telnet.c	4.5 (Berkeley) 03/23/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 	if (showoptions)
329 		printoption("<--", doopt, TELOPT_SGA);
330 	sprintf(nfrontp, doopt, TELOPT_SGA);
331 	nfrontp += sizeof(doopt) - 2;
332 	if (showoptions)
333 		printoption("<--", will, TELOPT_SGA);
334 	sprintf(nfrontp, will, TELOPT_SGA);
335 	nfrontp += sizeof(doopt) - 2;
336 	ioctl(s, FIONBIO, &on);
337 	for (;;) {
338 		int ibits = 0, obits = 0;
339 
340 		if (nfrontp - nbackp)
341 			obits |= (1 << s);
342 		else
343 			ibits |= (1 << tin);
344 		if (tfrontp - tbackp)
345 			obits |= (1 << tout);
346 		else
347 			ibits |= (1 << s);
348 		if (scc < 0 && tcc < 0)
349 			break;
350 		select(32, &ibits, &obits, INFINITY);
351 		if (ibits == 0 && obits == 0) {
352 			sleep(5);
353 			continue;
354 		}
355 
356 		/*
357 		 * Something to read from the network...
358 		 */
359 		if (ibits & (1 << s)) {
360 			scc = read(s, sibuf, sizeof(sibuf));
361 			if (scc < 0 && errno == EWOULDBLOCK)
362 				scc = 0;
363 			else {
364 				if (scc <= 0)
365 					break;
366 				sbp = sibuf;
367 			}
368 		}
369 
370 		/*
371 		 * Something to read from the tty...
372 		 */
373 		if (ibits & (1 << tin)) {
374 			tcc = read(tin, tibuf, sizeof(tibuf));
375 			if (tcc < 0 && errno == EWOULDBLOCK)
376 				tcc = 0;
377 			else {
378 				if (tcc <= 0)
379 					break;
380 				tbp = tibuf;
381 			}
382 		}
383 
384 		while (tcc > 0) {
385 			register int c;
386 
387 			if ((&netobuf[BUFSIZ] - nfrontp) < 2)
388 				break;
389 			c = *tbp++ & 0377, tcc--;
390 			if (strip(c) == escape) {
391 				command(0);
392 				tcc = 0;
393 				break;
394 			}
395 			*nfrontp++ = c;
396 		}
397 		if ((obits & (1 << s)) && (nfrontp - nbackp) > 0)
398 			netflush(s);
399 		if (scc > 0)
400 			telrcv();
401 		if ((obits & (1 << tout)) && (tfrontp - tbackp) > 0)
402 			ttyflush(tout);
403 	}
404 	mode(0);
405 }
406 
407 command(top)
408 	int top;
409 {
410 	register struct cmd *c;
411 	int oldmode, wasopen;
412 
413 	oldmode = mode(0);
414 	if (!top)
415 		putchar('\n');
416 	else
417 		sigset(SIGINT, SIG_DFL);
418 	for (;;) {
419 		printf("%s> ", prompt);
420 		if (gets(line) == 0)
421 			break;
422 		if (line[0] == 0)
423 			break;
424 		makeargv();
425 		c = getcmd(margv[0]);
426 		if (c == (struct cmd *)-1) {
427 			printf("?Ambiguous command\n");
428 			continue;
429 		}
430 		if (c == 0) {
431 			printf("?Invalid command\n");
432 			continue;
433 		}
434 		(*c->handler)(margc, margv);
435 		if (c->handler != help)
436 			break;
437 	}
438 	if (!top) {
439 		if (!connected)
440 			longjmp(toplevel, 1);
441 		mode(oldmode);
442 	}
443 }
444 
445 /*
446  * Telnet receiver states for fsm
447  */
448 #define	TS_DATA		0
449 #define	TS_IAC		1
450 #define	TS_WILL		2
451 #define	TS_WONT		3
452 #define	TS_DO		4
453 #define	TS_DONT		5
454 
455 telrcv()
456 {
457 	register int c;
458 	static int state = TS_DATA;
459 
460 	while (scc > 0) {
461 		c = *sbp++ & 0377, scc--;
462 		switch (state) {
463 
464 		case TS_DATA:
465 			if (c == IAC)
466 				state = TS_IAC;
467 			else
468 				*tfrontp++ = c;
469 			continue;
470 
471 		case TS_IAC:
472 			switch (c) {
473 
474 			case WILL:
475 				state = TS_WILL;
476 				continue;
477 
478 			case WONT:
479 				state = TS_WONT;
480 				continue;
481 
482 			case DO:
483 				state = TS_DO;
484 				continue;
485 
486 			case DONT:
487 				state = TS_DONT;
488 				continue;
489 
490 			case DM:
491 				ioctl(fileno(stdout), TIOCFLUSH, 0);
492 				break;
493 
494 			case NOP:
495 			case GA:
496 				break;
497 
498 			default:
499 				break;
500 			}
501 			state = TS_DATA;
502 			continue;
503 
504 		case TS_WILL:
505 			if (showoptions)
506 				printoption("-->", will, c, !hisopts[c]);
507 			if (!hisopts[c])
508 				willoption(c);
509 			state = TS_DATA;
510 			continue;
511 
512 		case TS_WONT:
513 			if (showoptions)
514 				printoption("-->", wont, c, hisopts[c]);
515 			if (hisopts[c])
516 				wontoption(c);
517 			state = TS_DATA;
518 			continue;
519 
520 		case TS_DO:
521 			if (showoptions)
522 				printoption("-->", doopt, c, !myopts[c]);
523 			if (!myopts[c])
524 				dooption(c);
525 			state = TS_DATA;
526 			continue;
527 
528 		case TS_DONT:
529 			if (showoptions)
530 				printoption("-->", dont, c, myopts[c]);
531 			if (myopts[c]) {
532 				myopts[c] = 0;
533 				sprintf(nfrontp, wont, c);
534 				nfrontp += sizeof(wont) - 2;
535 				if (showoptions)
536 					printoption("<--", wont, c);
537 			}
538 			state = TS_DATA;
539 			continue;
540 		}
541 	}
542 }
543 
544 willoption(option)
545 	int option;
546 {
547 	char *fmt;
548 
549 	switch (option) {
550 
551 	case TELOPT_ECHO:
552 		mode(1);
553 
554 	case TELOPT_SGA:
555 		hisopts[option] = 1;
556 		fmt = doopt;
557 		break;
558 
559 	case TELOPT_TM:
560 		fmt = dont;
561 		break;
562 
563 	default:
564 		fmt = dont;
565 		break;
566 	}
567 	sprintf(nfrontp, fmt, option);
568 	nfrontp += sizeof(dont) - 2;
569 	if (showoptions)
570 		printoption("<--", fmt, option);
571 }
572 
573 wontoption(option)
574 	int option;
575 {
576 	char *fmt;
577 
578 	switch (option) {
579 
580 	case TELOPT_ECHO:
581 		mode(2);
582 
583 	case TELOPT_SGA:
584 		hisopts[option] = 0;
585 		fmt = dont;
586 		break;
587 
588 	default:
589 		fmt = dont;
590 	}
591 	sprintf(nfrontp, fmt, option);
592 	nfrontp += sizeof(doopt) - 2;
593 	if (showoptions)
594 		printoption("<--", fmt, option);
595 }
596 
597 dooption(option)
598 	int option;
599 {
600 	char *fmt;
601 
602 	switch (option) {
603 
604 	case TELOPT_TM:
605 		fmt = wont;
606 		break;
607 
608 	case TELOPT_SGA:
609 		fmt = will;
610 		break;
611 
612 	default:
613 		fmt = wont;
614 		break;
615 	}
616 	sprintf(nfrontp, fmt, option);
617 	nfrontp += sizeof(doopt) - 2;
618 	if (showoptions)
619 		printoption("<--", fmt, option);
620 }
621 
622 /*
623  * Set the escape character.
624  */
625 setescape(argc, argv)
626 	int argc;
627 	char *argv[];
628 {
629 	register char *arg;
630 	char buf[50];
631 
632 	if (argc > 2)
633 		arg = argv[1];
634 	else {
635 		printf("new escape character: ");
636 		gets(buf);
637 		arg = buf;
638 	}
639 	if (arg[0] != '\0')
640 		escape = arg[0];
641 	printf("Escape character is '%s'.\n", control(escape));
642 }
643 
644 /*VARARGS*/
645 setoptions()
646 {
647 	showoptions = !showoptions;
648 	printf("%s show option processing.\n", showoptions ? "Will" : "Wont");
649 }
650 
651 /*
652  * Construct a control character sequence
653  * for a special character.
654  */
655 char *
656 control(c)
657 	register int c;
658 {
659 	static char buf[3];
660 
661 	if (c == 0177)
662 		return ("^?");
663 	if (c >= 040) {
664 		buf[0] = c;
665 		buf[1] = 0;
666 	} else {
667 		buf[0] = '^';
668 		buf[1] = '@'+c;
669 		buf[2] = 0;
670 	}
671 	return (buf);
672 }
673 
674 struct cmd *
675 getcmd(name)
676 	register char *name;
677 {
678 	register char *p, *q;
679 	register struct cmd *c, *found;
680 	register int nmatches, longest;
681 
682 	longest = 0;
683 	nmatches = 0;
684 	found = 0;
685 	for (c = cmdtab; p = c->name; c++) {
686 		for (q = name; *q == *p++; q++)
687 			if (*q == 0)		/* exact match? */
688 				return (c);
689 		if (!*q) {			/* the name was a prefix */
690 			if (q - name > longest) {
691 				longest = q - name;
692 				nmatches = 1;
693 				found = c;
694 			} else if (q - name == longest)
695 				nmatches++;
696 		}
697 	}
698 	if (nmatches > 1)
699 		return ((struct cmd *)-1);
700 	return (found);
701 }
702 
703 deadpeer()
704 {
705 	sigset(SIGPIPE, deadpeer);
706 	mode(0);
707 	longjmp(peerdied, -1);
708 }
709 
710 intr()
711 {
712 	sigset(SIGINT, intr);
713 	mode(0);
714 	longjmp(toplevel, -1);
715 }
716 
717 ttyflush(fd)
718 {
719 	int n;
720 
721 	if ((n = tfrontp - tbackp) > 0)
722 		n = write(fd, tbackp, n);
723 	if (n < 0 && errno == EWOULDBLOCK)
724 		n = 0;
725 	tbackp += n;
726 	if (tbackp == tfrontp)
727 		tbackp = tfrontp = ttyobuf;
728 }
729 
730 netflush(fd)
731 {
732 	int n;
733 
734 	if ((n = nfrontp - nbackp) > 0)
735 		n = write(fd, nbackp, n);
736 	if (n < 0 && errno == EWOULDBLOCK)
737 		n = 0;
738 	nbackp += n;
739 	if (nbackp == nfrontp)
740 		nbackp = nfrontp = netobuf;
741 }
742 
743 /*VARARGS*/
744 printoption(direction, fmt, option, what)
745 	char *direction, *fmt;
746 	int option, what;
747 {
748 	printf("%s ", direction);
749 	if (fmt == doopt)
750 		fmt = "do";
751 	else if (fmt == dont)
752 		fmt = "dont";
753 	else if (fmt == will)
754 		fmt = "will";
755 	else if (fmt == wont)
756 		fmt = "wont";
757 	else
758 		fmt = "???";
759 	if (option < TELOPT_SUPDUP)
760 		printf("%s %s", fmt, telopts[option]);
761 	else
762 		printf("%s %d", fmt, option);
763 	if (*direction == '<') {
764 		printf("\r\n");
765 		return;
766 	}
767 	printf(" (%s)\r\n", what ? "reply" : "don't reply");
768 }
769