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