blob: ac6813e29e83214c64af152ecb3a789d9a9f0dd5 [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 Andersen67059862001-01-22 22:48:42 +000033#warning This applet has moved to netkit-tiny. After BusyBox 0.49, this
34#warning applet will be removed from BusyBox. All maintainence efforts
35#warning should be done in the netkit-tiny source tree.
36
Eric Andersen28c70b32000-06-14 20:42:57 +000037
Eric Andersen3570a342000-09-25 21:45:58 +000038#include "busybox.h"
Erik Andersenf7c49ef2000-02-22 17:17:45 +000039#include <termios.h>
Eric Andersen28c70b32000-06-14 20:42:57 +000040#include <unistd.h>
41#include <errno.h>
42#include <stdlib.h>
43#include <stdarg.h>
44#include <string.h>
45#include <signal.h>
46#include <arpa/telnet.h>
Erik Andersenf7c49ef2000-02-22 17:17:45 +000047#include <sys/types.h>
48#include <sys/socket.h>
Eric Andersen28c70b32000-06-14 20:42:57 +000049#include <netinet/in.h>
50#include <netdb.h>
Erik Andersenf7c49ef2000-02-22 17:17:45 +000051
Eric Andersen28c70b32000-06-14 20:42:57 +000052#if 0
Mark Whitley59ab0252001-01-23 22:30:04 +000053static const int DOTRACE = 1;
Eric Andersen28c70b32000-06-14 20:42:57 +000054#endif
55
Pavel Roskin616d13b2000-07-28 19:38:27 +000056#ifdef DOTRACE
Eric Andersen28c70b32000-06-14 20:42:57 +000057#include <arpa/inet.h> /* for inet_ntoa()... */
58#define TRACE(x, y) do { if (x) printf y; } while (0)
59#else
60#define TRACE(x, y)
61#endif
62
63#if 0
64#define USE_POLL
65#include <sys/poll.h>
66#else
67#include <sys/time.h>
68#endif
69
Mark Whitley59ab0252001-01-23 22:30:04 +000070static const int DATABUFSIZE = 128;
71static const int IACBUFSIZE = 128;
Eric Andersen28c70b32000-06-14 20:42:57 +000072
Mark Whitley59ab0252001-01-23 22:30:04 +000073static const int CHM_TRY = 0;
74static const int CHM_ON = 1;
75static const int CHM_OFF = 2;
Eric Andersen28c70b32000-06-14 20:42:57 +000076
Mark Whitley59ab0252001-01-23 22:30:04 +000077static const int UF_ECHO = 0x01;
78static const int UF_SGA = 0x02;
Eric Andersen28c70b32000-06-14 20:42:57 +000079
Mark Whitley59ab0252001-01-23 22:30:04 +000080enum {
81 TS_0 = 1,
82 TS_IAC = 2,
83 TS_OPT = 3,
84 TS_SUB1 = 4,
85 TS_SUB2 = 5,
86};
Eric Andersen28c70b32000-06-14 20:42:57 +000087
88#define WriteCS(fd, str) write(fd, str, sizeof str -1)
89
90typedef unsigned char byte;
91
92/* use globals to reduce size ??? */ /* test this hypothesis later */
93struct Globalvars {
94 int netfd; /* console fd:s are 0 and 1 (and 2) */
95 /* same buffer used both for network and console read/write */
96 char * buf; /* allocating so static size is smaller */
97 short len;
98 byte telstate; /* telnet negotiation state from network input */
99 byte telwish; /* DO, DONT, WILL, WONT */
100 byte charmode;
101 byte telflags;
102 byte gotsig;
103 /* buffer to handle telnet negotiations */
104 char * iacbuf;
105 short iaclen; /* could even use byte */
106 struct termios termios_def;
107 struct termios termios_raw;
108} G;
109
110#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */
111
112#ifdef USE_GLOBALVAR_PTR
113struct Globalvars * Gptr;
114#define G (*Gptr)
115#else
116struct Globalvars G;
117#endif
118
119static inline void iacflush()
120{
121 write(G.netfd, G.iacbuf, G.iaclen);
122 G.iaclen = 0;
123}
124
125/* Function prototypes */
126static int getport(char * p);
127static struct in_addr getserver(char * p);
128static int create_socket();
129static void setup_sockaddr_in(struct sockaddr_in * addr, int port);
130static int remote_connect(struct in_addr addr, int port);
131static void rawmode();
132static void cookmode();
133static void do_linemode();
134static void will_charmode();
135static void telopt(byte c);
136static int subneg(byte c);
137#if 0
138static int local_bind(int port);
139#endif
140
141/* Some globals */
142static int one = 1;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000143
Eric Andersen28c70b32000-06-14 20:42:57 +0000144static void doexit(int ev)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000145{
Eric Andersen28c70b32000-06-14 20:42:57 +0000146 cookmode();
147 exit(ev);
148}
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000149
Eric Andersen28c70b32000-06-14 20:42:57 +0000150static void conescape()
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000151{
Eric Andersen28c70b32000-06-14 20:42:57 +0000152 char b;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000153
Eric Andersen28c70b32000-06-14 20:42:57 +0000154 if (G.gotsig) /* came from line mode... go raw */
155 rawmode();
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000156
Eric Andersen28c70b32000-06-14 20:42:57 +0000157 WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n"
158 " l go to line mode\r\n"
159 " c go to character mode\r\n"
160 " z suspend telnet\r\n"
161 " e exit telnet\r\n");
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000162
Eric Andersen28c70b32000-06-14 20:42:57 +0000163 if (read(0, &b, 1) <= 0)
164 doexit(1);
165
166 switch (b)
167 {
168 case 'l':
169 if (!G.gotsig)
170 {
171 do_linemode();
172 goto rrturn;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000173 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000174 break;
175 case 'c':
176 if (G.gotsig)
177 {
178 will_charmode();
179 goto rrturn;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000180 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000181 break;
182 case 'z':
183 cookmode();
184 kill(0, SIGTSTP);
185 rawmode();
186 break;
187 case 'e':
188 doexit(0);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000189 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000190
191 WriteCS(1, "continuing...\r\n");
192
193 if (G.gotsig)
194 cookmode();
195
196 rrturn:
197 G.gotsig = 0;
198
199}
200static void handlenetoutput()
201{
202 /* here we could do smart tricks how to handle 0xFF:s in output
203 * stream like writing twice every sequence of FF:s (thus doing
204 * many write()s. But I think interactive telnet application does
205 * not need to be 100% 8-bit clean, so changing every 0xff:s to
206 * 0x7f:s */
207
208 int i;
209 byte * p = G.buf;
210
211 for (i = G.len; i > 0; i--, p++)
212 {
213 if (*p == 0x1d)
214 {
215 conescape();
216 return;
217 }
218 if (*p == 0xff)
219 *p = 0x7f;
220 }
221 write(G.netfd, G.buf, G.len);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000222}
223
Eric Andersen28c70b32000-06-14 20:42:57 +0000224
225static void handlenetinput()
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000226{
Eric Andersen28c70b32000-06-14 20:42:57 +0000227 int i;
228 int cstart = 0;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000229
Eric Andersen28c70b32000-06-14 20:42:57 +0000230 for (i = 0; i < G.len; i++)
231 {
232 byte c = G.buf[i];
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000233
Eric Andersen28c70b32000-06-14 20:42:57 +0000234 if (G.telstate == 0) /* most of the time state == 0 */
235 {
236 if (c == IAC)
237 {
238 cstart = i;
239 G.telstate = TS_IAC;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000240 }
241 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000242 else
243 switch (G.telstate)
244 {
245 case TS_0:
246 if (c == IAC)
247 G.telstate = TS_IAC;
248 else
249 G.buf[cstart++] = c;
250 break;
251
252 case TS_IAC:
253 if (c == IAC) /* IAC IAC -> 0xFF */
254 {
255 G.buf[cstart++] = c;
256 G.telstate = TS_0;
257 break;
258 }
259 /* else */
260 switch (c)
261 {
262 case SB:
263 G.telstate = TS_SUB1;
264 break;
265 case DO:
266 case DONT:
267 case WILL:
268 case WONT:
269 G.telwish = c;
270 G.telstate = TS_OPT;
271 break;
272 default:
273 G.telstate = TS_0; /* DATA MARK must be added later */
274 }
275 break;
276 case TS_OPT: /* WILL, WONT, DO, DONT */
277 telopt(c);
278 G.telstate = TS_0;
279 break;
280 case TS_SUB1: /* Subnegotiation */
281 case TS_SUB2: /* Subnegotiation */
282 if (subneg(c) == TRUE)
283 G.telstate = TS_0;
284 break;
285 }
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000286 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000287 if (G.telstate)
288 {
289 if (G.iaclen) iacflush();
290 if (G.telstate == TS_0) G.telstate = 0;
291
292 G.len = cstart;
293 }
294
295 if (G.len)
296 write(1, G.buf, G.len);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000297}
298
Eric Andersen28c70b32000-06-14 20:42:57 +0000299
300/* ******************************* */
301
302static inline void putiac(int c)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000303{
Eric Andersen28c70b32000-06-14 20:42:57 +0000304 G.iacbuf[G.iaclen++] = c;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000305}
306
Eric Andersen28c70b32000-06-14 20:42:57 +0000307
308static void putiac2(byte wwdd, byte c)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000309{
Eric Andersen28c70b32000-06-14 20:42:57 +0000310 if (G.iaclen + 3 > IACBUFSIZE)
311 iacflush();
312
313 putiac(IAC);
314 putiac(wwdd);
315 putiac(c);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000316}
317
Eric Andersen28c70b32000-06-14 20:42:57 +0000318#if 0
319static void putiac1(byte c)
320{
321 if (G.iaclen + 2 > IACBUFSIZE)
322 iacflush();
323
324 putiac(IAC);
325 putiac(c);
326}
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000327#endif
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000328
Eric Andersen28c70b32000-06-14 20:42:57 +0000329/* void putiacstring (subneg strings) */
330
331/* ******************************* */
332
333char const escapecharis[] = "\r\nEscape character is ";
334
335static void setConMode()
336{
337 if (G.telflags & UF_ECHO)
338 {
339 if (G.charmode == CHM_TRY) {
340 G.charmode = CHM_ON;
Matt Kraai12f417e2001-01-18 02:57:08 +0000341 printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis);
Eric Andersen28c70b32000-06-14 20:42:57 +0000342 rawmode();
343 }
344 }
345 else
346 {
347 if (G.charmode != CHM_OFF) {
348 G.charmode = CHM_OFF;
Matt Kraai12f417e2001-01-18 02:57:08 +0000349 printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis);
Eric Andersen28c70b32000-06-14 20:42:57 +0000350 cookmode();
351 }
352 }
353}
354
355/* ******************************* */
356
357static void will_charmode()
358{
359 G.charmode = CHM_TRY;
360 G.telflags |= (UF_ECHO | UF_SGA);
361 setConMode();
362
363 putiac2(DO, TELOPT_ECHO);
364 putiac2(DO, TELOPT_SGA);
365 iacflush();
366}
367
368static void do_linemode()
369{
370 G.charmode = CHM_TRY;
371 G.telflags &= ~(UF_ECHO | UF_SGA);
372 setConMode();
373
374 putiac2(DONT, TELOPT_ECHO);
375 putiac2(DONT, TELOPT_SGA);
376 iacflush();
377}
378
379/* ******************************* */
380
381static inline void to_notsup(char c)
382{
383 if (G.telwish == WILL) putiac2(DONT, c);
384 else if (G.telwish == DO) putiac2(WONT, c);
385}
386
387static inline void to_echo()
388{
389 /* if server requests ECHO, don't agree */
390 if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; }
391 else if (G.telwish == DONT) return;
392
393 if (G.telflags & UF_ECHO)
394 {
395 if (G.telwish == WILL)
396 return;
397 }
398 else
399 if (G.telwish == WONT)
400 return;
401
402 if (G.charmode != CHM_OFF)
403 G.telflags ^= UF_ECHO;
404
405 if (G.telflags & UF_ECHO)
406 putiac2(DO, TELOPT_ECHO);
407 else
408 putiac2(DONT, TELOPT_ECHO);
409
410 setConMode();
411 WriteCS(1, "\r\n"); /* sudden modec */
412}
413
414static inline void to_sga()
415{
416 /* daemon always sends will/wont, client do/dont */
417
418 if (G.telflags & UF_SGA)
419 {
420 if (G.telwish == WILL)
421 return;
422 }
423 else
424 if (G.telwish == WONT)
425 return;
426
427 if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */
428 putiac2(DO, TELOPT_SGA);
429 else
430 putiac2(DONT, TELOPT_SGA);
431
432 return;
433}
434
435static void telopt(byte c)
436{
437 switch (c)
438 {
439 case TELOPT_ECHO: to_echo(c); break;
440 case TELOPT_SGA: to_sga(c); break;
441 default: to_notsup(c); break;
442 }
443}
444
445
446/* ******************************* */
447
448/* subnegotiation -- ignore all */
449
450static int subneg(byte c)
451{
452 switch (G.telstate)
453 {
454 case TS_SUB1:
455 if (c == IAC)
456 G.telstate = TS_SUB2;
457 break;
458 case TS_SUB2:
459 if (c == SE)
460 return TRUE;
461 G.telstate = TS_SUB1;
462 /* break; */
463 }
464 return FALSE;
465}
466
467/* ******************************* */
468
469static void fgotsig(int sig)
470{
471 G.gotsig = sig;
472}
473
474
475static void rawmode()
476{
477 tcsetattr(0, TCSADRAIN, &G.termios_raw);
478}
479
480static void cookmode()
481{
482 tcsetattr(0, TCSADRAIN, &G.termios_def);
483}
484
485extern int telnet_main(int argc, char** argv)
486{
487 struct in_addr host;
488 int port;
489#ifdef USE_POLL
490 struct pollfd ufds[2];
491#else
492 fd_set readfds;
493 int maxfd;
494#endif
495
496
497 memset(&G, 0, sizeof G);
498
499 if (tcgetattr(0, &G.termios_def) < 0)
500 exit(1);
501
502 G.termios_raw = G.termios_def;
503
504 cfmakeraw(&G.termios_raw);
505
506 if (argc < 2) usage(telnet_usage);
507 port = (argc > 2)? getport(argv[2]): 23;
508
509 G.buf = xmalloc(DATABUFSIZE);
510 G.iacbuf = xmalloc(IACBUFSIZE);
511
512 host = getserver(argv[1]);
513
514 G.netfd = remote_connect(host, port);
515
516 signal(SIGINT, fgotsig);
517
518#ifdef USE_POLL
519 ufds[0].fd = 0; ufds[1].fd = G.netfd;
520 ufds[0].events = ufds[1].events = POLLIN;
521#else
522 FD_ZERO(&readfds);
523 FD_SET(0, &readfds);
524 FD_SET(G.netfd, &readfds);
525 maxfd = G.netfd + 1;
526#endif
527
528 while (1)
529 {
530#ifndef USE_POLL
531 fd_set rfds = readfds;
532
533 switch (select(maxfd, &rfds, NULL, NULL, NULL))
534#else
535 switch (poll(ufds, 2, -1))
536#endif
537 {
538 case 0:
539 /* timeout */
540 case -1:
541 /* error, ignore and/or log something, bay go to loop */
542 if (G.gotsig)
543 conescape();
544 else
545 sleep(1);
546 break;
547 default:
548
549#ifdef USE_POLL
550 if (ufds[0].revents) /* well, should check POLLIN, but ... */
551#else
552 if (FD_ISSET(0, &rfds))
553#endif
554 {
555 G.len = read(0, G.buf, DATABUFSIZE);
556
557 if (G.len <= 0)
558 doexit(0);
559
560 TRACE(0, ("Read con: %d\n", G.len));
561
562 handlenetoutput();
563 }
564
565#ifdef USE_POLL
566 if (ufds[1].revents) /* well, should check POLLIN, but ... */
567#else
568 if (FD_ISSET(G.netfd, &rfds))
569#endif
570 {
571 G.len = read(G.netfd, G.buf, DATABUFSIZE);
572
573 if (G.len <= 0)
574 {
575 WriteCS(1, "Connection closed by foreign host.\r\n");
576 doexit(1);
577 }
578 TRACE(0, ("Read netfd (%d): %d\n", G.netfd, G.len));
579
580 handlenetinput();
581 }
582 }
583 }
584}
585
586static int getport(char * p)
587{
588 unsigned int port = atoi(p);
589
590 if ((unsigned)(port - 1 ) > 65534)
591 {
Mark Whitleyf57c9442000-12-07 19:56:48 +0000592 error_msg_and_die("%s: bad port number\n", p);
Eric Andersen28c70b32000-06-14 20:42:57 +0000593 }
594 return port;
595}
596
597static struct in_addr getserver(char * host)
598{
599 struct in_addr addr;
600
601 struct hostent * he;
602 if ((he = gethostbyname(host)) == NULL)
603 {
Mark Whitleyf57c9442000-12-07 19:56:48 +0000604 error_msg_and_die("%s: Unknown host\n", host);
Eric Andersen28c70b32000-06-14 20:42:57 +0000605 }
606 memcpy(&addr, he->h_addr, sizeof addr);
607
608 TRACE(1, ("addr: %s\n", inet_ntoa(addr)));
609
610 return addr;
611}
612
613static int create_socket()
614{
615 return socket(AF_INET, SOCK_STREAM, 0);
616}
617
618static void setup_sockaddr_in(struct sockaddr_in * addr, int port)
619{
620 memset(addr, 0, sizeof addr);
621 addr->sin_family = AF_INET;
622 addr->sin_port = htons(port);
623}
624
625#if 0
626static int local_bind(int port)
627{
628 struct sockaddr_in s_addr;
629 int s = create_socket();
630
631 setup_sockaddr_in(&s_addr, port);
632
633 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
634
635 if (bind(s, &s_addr, sizeof s_addr) < 0)
636 {
637 char * e = sys_errlist[errno];
638 syserrorexit("bind");
639 exit(1);
640 }
641 listen(s, 1);
642
643 return s;
644}
645#endif
646
647static int remote_connect(struct in_addr addr, int port)
648{
649 struct sockaddr_in s_addr;
650 int s = create_socket();
651
652 setup_sockaddr_in(&s_addr, port);
653 s_addr.sin_addr = addr;
654
655 setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one);
656
657 if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0)
658 {
Matt Kraai1fa1ade2000-12-18 03:57:16 +0000659 perror_msg_and_die("Unable to connect to remote host");
Eric Andersen28c70b32000-06-14 20:42:57 +0000660 }
661 return s;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000662}
663
664/*
Eric Andersen28c70b32000-06-14 20:42:57 +0000665Local Variables:
666c-file-style: "linux"
667c-basic-offset: 4
668tab-width: 4
669End:
670*/
671