blob: f276035270ba18542daff007c60a2334048f914e [file] [log] [blame]
Eric Andersen28c70b32000-06-14 20:42:57 +00001/* vi: set sw=4 ts=4: */
Erik Andersenf7c49ef2000-02-22 17:17:45 +00002/*
Eric Andersen28c70b32000-06-14 20:42:57 +00003 * telnet implementation for busybox
Erik Andersenf7c49ef2000-02-22 17:17:45 +00004 *
Eric Andersen28c70b32000-06-14 20:42:57 +00005 * Author: Tomi Ollila <too@iki.fi>
6 * Copyright (C) 1994-2000 by Tomi Ollila
7 *
8 * Created: Thu Apr 7 13:29:41 1994 too
9 * Last modified: Fri Jun 9 14:34:24 2000 too
Erik Andersenf7c49ef2000-02-22 17:17:45 +000010 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 *
Eric Andersen28c70b32000-06-14 20:42:57 +000025 * HISTORY
26 * Revision 3.1 1994/04/17 11:31:54 too
27 * initial revision
28 * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen
29 * <andersen@lineo.com>
Erik Andersenf7c49ef2000-02-22 17:17:45 +000030 *
Erik Andersenf7c49ef2000-02-22 17:17:45 +000031 */
32
Eric Andersen28c70b32000-06-14 20:42:57 +000033
Erik Andersenf7c49ef2000-02-22 17:17:45 +000034#include "internal.h"
Erik Andersenf7c49ef2000-02-22 17:17:45 +000035#include <termios.h>
Eric Andersen28c70b32000-06-14 20:42:57 +000036#include <unistd.h>
37#include <errno.h>
38#include <stdlib.h>
39#include <stdarg.h>
40#include <string.h>
41#include <signal.h>
42#include <arpa/telnet.h>
Erik Andersenf7c49ef2000-02-22 17:17:45 +000043#include <sys/types.h>
44#include <sys/socket.h>
Eric Andersen28c70b32000-06-14 20:42:57 +000045#include <netinet/in.h>
46#include <netdb.h>
Erik Andersenf7c49ef2000-02-22 17:17:45 +000047
Eric Andersen28c70b32000-06-14 20:42:57 +000048#if 0
49#define DOTRACE 1
50#endif
51
52#if DOTRACE
53#include <arpa/inet.h> /* for inet_ntoa()... */
54#define TRACE(x, y) do { if (x) printf y; } while (0)
55#else
56#define TRACE(x, y)
57#endif
58
59#if 0
60#define USE_POLL
61#include <sys/poll.h>
62#else
63#include <sys/time.h>
64#endif
65
66#define DATABUFSIZE 128
67#define IACBUFSIZE 128
68
69#define CHM_TRY 0
70#define CHM_ON 1
71#define CHM_OFF 2
72
73#define UF_ECHO 0x01
74#define UF_SGA 0x02
75
76#define TS_0 1
77#define TS_IAC 2
78#define TS_OPT 3
79#define TS_SUB1 4
80#define TS_SUB2 5
81
82#define WriteCS(fd, str) write(fd, str, sizeof str -1)
83
84typedef unsigned char byte;
85
86/* use globals to reduce size ??? */ /* test this hypothesis later */
87struct Globalvars {
88 int netfd; /* console fd:s are 0 and 1 (and 2) */
89 /* same buffer used both for network and console read/write */
90 char * buf; /* allocating so static size is smaller */
91 short len;
92 byte telstate; /* telnet negotiation state from network input */
93 byte telwish; /* DO, DONT, WILL, WONT */
94 byte charmode;
95 byte telflags;
96 byte gotsig;
97 /* buffer to handle telnet negotiations */
98 char * iacbuf;
99 short iaclen; /* could even use byte */
100 struct termios termios_def;
101 struct termios termios_raw;
102} G;
103
104#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */
105
106#ifdef USE_GLOBALVAR_PTR
107struct Globalvars * Gptr;
108#define G (*Gptr)
109#else
110struct Globalvars G;
111#endif
112
113static inline void iacflush()
114{
115 write(G.netfd, G.iacbuf, G.iaclen);
116 G.iaclen = 0;
117}
118
119/* Function prototypes */
120static int getport(char * p);
121static struct in_addr getserver(char * p);
122static int create_socket();
123static void setup_sockaddr_in(struct sockaddr_in * addr, int port);
124static int remote_connect(struct in_addr addr, int port);
125static void rawmode();
126static void cookmode();
127static void do_linemode();
128static void will_charmode();
129static void telopt(byte c);
130static int subneg(byte c);
131#if 0
132static int local_bind(int port);
133#endif
134
135/* Some globals */
136static int one = 1;
137static const char telnet_usage[] =
138 "telnet host [port]\n"
Erik Andersen7ab9c7e2000-05-12 19:41:47 +0000139#ifndef BB_FEATURE_TRIVIAL_HELP
Eric Andersen28c70b32000-06-14 20:42:57 +0000140 "\nTelnet is used to establish interactive communication with another\n"
141 "computer over a network using the TELNET protocol.\n"
Erik Andersen7ab9c7e2000-05-12 19:41:47 +0000142#endif
143 ;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000144
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000145
Eric Andersen28c70b32000-06-14 20:42:57 +0000146static void doexit(int ev)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000147{
Eric Andersen28c70b32000-06-14 20:42:57 +0000148 cookmode();
149 exit(ev);
150}
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000151
Eric Andersen28c70b32000-06-14 20:42:57 +0000152static void conescape()
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000153{
Eric Andersen28c70b32000-06-14 20:42:57 +0000154 char b;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000155
Eric Andersen28c70b32000-06-14 20:42:57 +0000156 if (G.gotsig) /* came from line mode... go raw */
157 rawmode();
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000158
Eric Andersen28c70b32000-06-14 20:42:57 +0000159 WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n"
160 " l go to line mode\r\n"
161 " c go to character mode\r\n"
162 " z suspend telnet\r\n"
163 " e exit telnet\r\n");
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000164
Eric Andersen28c70b32000-06-14 20:42:57 +0000165 if (read(0, &b, 1) <= 0)
166 doexit(1);
167
168 switch (b)
169 {
170 case 'l':
171 if (!G.gotsig)
172 {
173 do_linemode();
174 goto rrturn;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000175 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000176 break;
177 case 'c':
178 if (G.gotsig)
179 {
180 will_charmode();
181 goto rrturn;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000182 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000183 break;
184 case 'z':
185 cookmode();
186 kill(0, SIGTSTP);
187 rawmode();
188 break;
189 case 'e':
190 doexit(0);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000191 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000192
193 WriteCS(1, "continuing...\r\n");
194
195 if (G.gotsig)
196 cookmode();
197
198 rrturn:
199 G.gotsig = 0;
200
201}
202static void handlenetoutput()
203{
204 /* here we could do smart tricks how to handle 0xFF:s in output
205 * stream like writing twice every sequence of FF:s (thus doing
206 * many write()s. But I think interactive telnet application does
207 * not need to be 100% 8-bit clean, so changing every 0xff:s to
208 * 0x7f:s */
209
210 int i;
211 byte * p = G.buf;
212
213 for (i = G.len; i > 0; i--, p++)
214 {
215 if (*p == 0x1d)
216 {
217 conescape();
218 return;
219 }
220 if (*p == 0xff)
221 *p = 0x7f;
222 }
223 write(G.netfd, G.buf, G.len);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000224}
225
Eric Andersen28c70b32000-06-14 20:42:57 +0000226
227static void handlenetinput()
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000228{
Eric Andersen28c70b32000-06-14 20:42:57 +0000229 int i;
230 int cstart = 0;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000231
Eric Andersen28c70b32000-06-14 20:42:57 +0000232 for (i = 0; i < G.len; i++)
233 {
234 byte c = G.buf[i];
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000235
Eric Andersen28c70b32000-06-14 20:42:57 +0000236 if (G.telstate == 0) /* most of the time state == 0 */
237 {
238 if (c == IAC)
239 {
240 cstart = i;
241 G.telstate = TS_IAC;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000242 }
243 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000244 else
245 switch (G.telstate)
246 {
247 case TS_0:
248 if (c == IAC)
249 G.telstate = TS_IAC;
250 else
251 G.buf[cstart++] = c;
252 break;
253
254 case TS_IAC:
255 if (c == IAC) /* IAC IAC -> 0xFF */
256 {
257 G.buf[cstart++] = c;
258 G.telstate = TS_0;
259 break;
260 }
261 /* else */
262 switch (c)
263 {
264 case SB:
265 G.telstate = TS_SUB1;
266 break;
267 case DO:
268 case DONT:
269 case WILL:
270 case WONT:
271 G.telwish = c;
272 G.telstate = TS_OPT;
273 break;
274 default:
275 G.telstate = TS_0; /* DATA MARK must be added later */
276 }
277 break;
278 case TS_OPT: /* WILL, WONT, DO, DONT */
279 telopt(c);
280 G.telstate = TS_0;
281 break;
282 case TS_SUB1: /* Subnegotiation */
283 case TS_SUB2: /* Subnegotiation */
284 if (subneg(c) == TRUE)
285 G.telstate = TS_0;
286 break;
287 }
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000288 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000289 if (G.telstate)
290 {
291 if (G.iaclen) iacflush();
292 if (G.telstate == TS_0) G.telstate = 0;
293
294 G.len = cstart;
295 }
296
297 if (G.len)
298 write(1, G.buf, G.len);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000299}
300
Eric Andersen28c70b32000-06-14 20:42:57 +0000301
302/* ******************************* */
303
304static inline void putiac(int c)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000305{
Eric Andersen28c70b32000-06-14 20:42:57 +0000306 G.iacbuf[G.iaclen++] = c;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000307}
308
Eric Andersen28c70b32000-06-14 20:42:57 +0000309
310static void putiac2(byte wwdd, byte c)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000311{
Eric Andersen28c70b32000-06-14 20:42:57 +0000312 if (G.iaclen + 3 > IACBUFSIZE)
313 iacflush();
314
315 putiac(IAC);
316 putiac(wwdd);
317 putiac(c);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000318}
319
Eric Andersen28c70b32000-06-14 20:42:57 +0000320#if 0
321static void putiac1(byte c)
322{
323 if (G.iaclen + 2 > IACBUFSIZE)
324 iacflush();
325
326 putiac(IAC);
327 putiac(c);
328}
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000329#endif
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000330
Eric Andersen28c70b32000-06-14 20:42:57 +0000331/* void putiacstring (subneg strings) */
332
333/* ******************************* */
334
335char const escapecharis[] = "\r\nEscape character is ";
336
337static void setConMode()
338{
339 if (G.telflags & UF_ECHO)
340 {
341 if (G.charmode == CHM_TRY) {
342 G.charmode = CHM_ON;
343 fprintf(stdout, "\r\nEntering character mode%s'^]'.\r\n", escapecharis);
344 rawmode();
345 }
346 }
347 else
348 {
349 if (G.charmode != CHM_OFF) {
350 G.charmode = CHM_OFF;
351 fprintf(stdout, "\r\nEntering line mode%s'^C'.\r\n", escapecharis);
352 cookmode();
353 }
354 }
355}
356
357/* ******************************* */
358
359static void will_charmode()
360{
361 G.charmode = CHM_TRY;
362 G.telflags |= (UF_ECHO | UF_SGA);
363 setConMode();
364
365 putiac2(DO, TELOPT_ECHO);
366 putiac2(DO, TELOPT_SGA);
367 iacflush();
368}
369
370static void do_linemode()
371{
372 G.charmode = CHM_TRY;
373 G.telflags &= ~(UF_ECHO | UF_SGA);
374 setConMode();
375
376 putiac2(DONT, TELOPT_ECHO);
377 putiac2(DONT, TELOPT_SGA);
378 iacflush();
379}
380
381/* ******************************* */
382
383static inline void to_notsup(char c)
384{
385 if (G.telwish == WILL) putiac2(DONT, c);
386 else if (G.telwish == DO) putiac2(WONT, c);
387}
388
389static inline void to_echo()
390{
391 /* if server requests ECHO, don't agree */
392 if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; }
393 else if (G.telwish == DONT) return;
394
395 if (G.telflags & UF_ECHO)
396 {
397 if (G.telwish == WILL)
398 return;
399 }
400 else
401 if (G.telwish == WONT)
402 return;
403
404 if (G.charmode != CHM_OFF)
405 G.telflags ^= UF_ECHO;
406
407 if (G.telflags & UF_ECHO)
408 putiac2(DO, TELOPT_ECHO);
409 else
410 putiac2(DONT, TELOPT_ECHO);
411
412 setConMode();
413 WriteCS(1, "\r\n"); /* sudden modec */
414}
415
416static inline void to_sga()
417{
418 /* daemon always sends will/wont, client do/dont */
419
420 if (G.telflags & UF_SGA)
421 {
422 if (G.telwish == WILL)
423 return;
424 }
425 else
426 if (G.telwish == WONT)
427 return;
428
429 if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */
430 putiac2(DO, TELOPT_SGA);
431 else
432 putiac2(DONT, TELOPT_SGA);
433
434 return;
435}
436
437static void telopt(byte c)
438{
439 switch (c)
440 {
441 case TELOPT_ECHO: to_echo(c); break;
442 case TELOPT_SGA: to_sga(c); break;
443 default: to_notsup(c); break;
444 }
445}
446
447
448/* ******************************* */
449
450/* subnegotiation -- ignore all */
451
452static int subneg(byte c)
453{
454 switch (G.telstate)
455 {
456 case TS_SUB1:
457 if (c == IAC)
458 G.telstate = TS_SUB2;
459 break;
460 case TS_SUB2:
461 if (c == SE)
462 return TRUE;
463 G.telstate = TS_SUB1;
464 /* break; */
465 }
466 return FALSE;
467}
468
469/* ******************************* */
470
471static void fgotsig(int sig)
472{
473 G.gotsig = sig;
474}
475
476
477static void rawmode()
478{
479 tcsetattr(0, TCSADRAIN, &G.termios_raw);
480}
481
482static void cookmode()
483{
484 tcsetattr(0, TCSADRAIN, &G.termios_def);
485}
486
487extern int telnet_main(int argc, char** argv)
488{
489 struct in_addr host;
490 int port;
491#ifdef USE_POLL
492 struct pollfd ufds[2];
493#else
494 fd_set readfds;
495 int maxfd;
496#endif
497
498
499 memset(&G, 0, sizeof G);
500
501 if (tcgetattr(0, &G.termios_def) < 0)
502 exit(1);
503
504 G.termios_raw = G.termios_def;
505
506 cfmakeraw(&G.termios_raw);
507
508 if (argc < 2) usage(telnet_usage);
509 port = (argc > 2)? getport(argv[2]): 23;
510
511 G.buf = xmalloc(DATABUFSIZE);
512 G.iacbuf = xmalloc(IACBUFSIZE);
513
514 host = getserver(argv[1]);
515
516 G.netfd = remote_connect(host, port);
517
518 signal(SIGINT, fgotsig);
519
520#ifdef USE_POLL
521 ufds[0].fd = 0; ufds[1].fd = G.netfd;
522 ufds[0].events = ufds[1].events = POLLIN;
523#else
524 FD_ZERO(&readfds);
525 FD_SET(0, &readfds);
526 FD_SET(G.netfd, &readfds);
527 maxfd = G.netfd + 1;
528#endif
529
530 while (1)
531 {
532#ifndef USE_POLL
533 fd_set rfds = readfds;
534
535 switch (select(maxfd, &rfds, NULL, NULL, NULL))
536#else
537 switch (poll(ufds, 2, -1))
538#endif
539 {
540 case 0:
541 /* timeout */
542 case -1:
543 /* error, ignore and/or log something, bay go to loop */
544 if (G.gotsig)
545 conescape();
546 else
547 sleep(1);
548 break;
549 default:
550
551#ifdef USE_POLL
552 if (ufds[0].revents) /* well, should check POLLIN, but ... */
553#else
554 if (FD_ISSET(0, &rfds))
555#endif
556 {
557 G.len = read(0, G.buf, DATABUFSIZE);
558
559 if (G.len <= 0)
560 doexit(0);
561
562 TRACE(0, ("Read con: %d\n", G.len));
563
564 handlenetoutput();
565 }
566
567#ifdef USE_POLL
568 if (ufds[1].revents) /* well, should check POLLIN, but ... */
569#else
570 if (FD_ISSET(G.netfd, &rfds))
571#endif
572 {
573 G.len = read(G.netfd, G.buf, DATABUFSIZE);
574
575 if (G.len <= 0)
576 {
577 WriteCS(1, "Connection closed by foreign host.\r\n");
578 doexit(1);
579 }
580 TRACE(0, ("Read netfd (%d): %d\n", G.netfd, G.len));
581
582 handlenetinput();
583 }
584 }
585 }
586}
587
588static int getport(char * p)
589{
590 unsigned int port = atoi(p);
591
592 if ((unsigned)(port - 1 ) > 65534)
593 {
594 fatalError("%s: bad port number\n", p);
595 }
596 return port;
597}
598
599static struct in_addr getserver(char * host)
600{
601 struct in_addr addr;
602
603 struct hostent * he;
604 if ((he = gethostbyname(host)) == NULL)
605 {
606 fatalError("%s: Unkonwn host\n", host);
607 }
608 memcpy(&addr, he->h_addr, sizeof addr);
609
610 TRACE(1, ("addr: %s\n", inet_ntoa(addr)));
611
612 return addr;
613}
614
615static int create_socket()
616{
617 return socket(AF_INET, SOCK_STREAM, 0);
618}
619
620static void setup_sockaddr_in(struct sockaddr_in * addr, int port)
621{
622 memset(addr, 0, sizeof addr);
623 addr->sin_family = AF_INET;
624 addr->sin_port = htons(port);
625}
626
627#if 0
628static int local_bind(int port)
629{
630 struct sockaddr_in s_addr;
631 int s = create_socket();
632
633 setup_sockaddr_in(&s_addr, port);
634
635 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
636
637 if (bind(s, &s_addr, sizeof s_addr) < 0)
638 {
639 char * e = sys_errlist[errno];
640 syserrorexit("bind");
641 exit(1);
642 }
643 listen(s, 1);
644
645 return s;
646}
647#endif
648
649static int remote_connect(struct in_addr addr, int port)
650{
651 struct sockaddr_in s_addr;
652 int s = create_socket();
653
654 setup_sockaddr_in(&s_addr, port);
655 s_addr.sin_addr = addr;
656
657 setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one);
658
659 if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0)
660 {
661 fatalError("Unable to connect to remote host: %s\n", strerror(errno));
662 }
663 return s;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000664}
665
666/*
Eric Andersen28c70b32000-06-14 20:42:57 +0000667Local Variables:
668c-file-style: "linux"
669c-basic-offset: 4
670tab-width: 4
671End:
672*/
673