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