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