blob: 76956e9ac9dc756fe3ce62f50db78932ff606c29 [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
53#define DOTRACE 1
54#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
70#define DATABUFSIZE 128
71#define IACBUFSIZE 128
72
73#define CHM_TRY 0
74#define CHM_ON 1
75#define CHM_OFF 2
76
77#define UF_ECHO 0x01
78#define UF_SGA 0x02
79
80#define TS_0 1
81#define TS_IAC 2
82#define TS_OPT 3
83#define TS_SUB1 4
84#define TS_SUB2 5
85
86#define WriteCS(fd, str) write(fd, str, sizeof str -1)
87
88typedef unsigned char byte;
89
90/* use globals to reduce size ??? */ /* test this hypothesis later */
91struct Globalvars {
92 int netfd; /* console fd:s are 0 and 1 (and 2) */
93 /* same buffer used both for network and console read/write */
94 char * buf; /* allocating so static size is smaller */
95 short len;
96 byte telstate; /* telnet negotiation state from network input */
97 byte telwish; /* DO, DONT, WILL, WONT */
98 byte charmode;
99 byte telflags;
100 byte gotsig;
101 /* buffer to handle telnet negotiations */
102 char * iacbuf;
103 short iaclen; /* could even use byte */
104 struct termios termios_def;
105 struct termios termios_raw;
106} G;
107
108#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */
109
110#ifdef USE_GLOBALVAR_PTR
111struct Globalvars * Gptr;
112#define G (*Gptr)
113#else
114struct Globalvars G;
115#endif
116
117static inline void iacflush()
118{
119 write(G.netfd, G.iacbuf, G.iaclen);
120 G.iaclen = 0;
121}
122
123/* Function prototypes */
124static int getport(char * p);
125static struct in_addr getserver(char * p);
126static int create_socket();
127static void setup_sockaddr_in(struct sockaddr_in * addr, int port);
128static int remote_connect(struct in_addr addr, int port);
129static void rawmode();
130static void cookmode();
131static void do_linemode();
132static void will_charmode();
133static void telopt(byte c);
134static int subneg(byte c);
135#if 0
136static int local_bind(int port);
137#endif
138
139/* Some globals */
140static int one = 1;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000141
Eric Andersen28c70b32000-06-14 20:42:57 +0000142static void doexit(int ev)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000143{
Eric Andersen28c70b32000-06-14 20:42:57 +0000144 cookmode();
145 exit(ev);
146}
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000147
Eric Andersen28c70b32000-06-14 20:42:57 +0000148static void conescape()
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000149{
Eric Andersen28c70b32000-06-14 20:42:57 +0000150 char b;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000151
Eric Andersen28c70b32000-06-14 20:42:57 +0000152 if (G.gotsig) /* came from line mode... go raw */
153 rawmode();
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000154
Eric Andersen28c70b32000-06-14 20:42:57 +0000155 WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n"
156 " l go to line mode\r\n"
157 " c go to character mode\r\n"
158 " z suspend telnet\r\n"
159 " e exit telnet\r\n");
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000160
Eric Andersen28c70b32000-06-14 20:42:57 +0000161 if (read(0, &b, 1) <= 0)
162 doexit(1);
163
164 switch (b)
165 {
166 case 'l':
167 if (!G.gotsig)
168 {
169 do_linemode();
170 goto rrturn;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000171 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000172 break;
173 case 'c':
174 if (G.gotsig)
175 {
176 will_charmode();
177 goto rrturn;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000178 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000179 break;
180 case 'z':
181 cookmode();
182 kill(0, SIGTSTP);
183 rawmode();
184 break;
185 case 'e':
186 doexit(0);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000187 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000188
189 WriteCS(1, "continuing...\r\n");
190
191 if (G.gotsig)
192 cookmode();
193
194 rrturn:
195 G.gotsig = 0;
196
197}
198static void handlenetoutput()
199{
200 /* here we could do smart tricks how to handle 0xFF:s in output
201 * stream like writing twice every sequence of FF:s (thus doing
202 * many write()s. But I think interactive telnet application does
203 * not need to be 100% 8-bit clean, so changing every 0xff:s to
204 * 0x7f:s */
205
206 int i;
207 byte * p = G.buf;
208
209 for (i = G.len; i > 0; i--, p++)
210 {
211 if (*p == 0x1d)
212 {
213 conescape();
214 return;
215 }
216 if (*p == 0xff)
217 *p = 0x7f;
218 }
219 write(G.netfd, G.buf, G.len);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000220}
221
Eric Andersen28c70b32000-06-14 20:42:57 +0000222
223static void handlenetinput()
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000224{
Eric Andersen28c70b32000-06-14 20:42:57 +0000225 int i;
226 int cstart = 0;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000227
Eric Andersen28c70b32000-06-14 20:42:57 +0000228 for (i = 0; i < G.len; i++)
229 {
230 byte c = G.buf[i];
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000231
Eric Andersen28c70b32000-06-14 20:42:57 +0000232 if (G.telstate == 0) /* most of the time state == 0 */
233 {
234 if (c == IAC)
235 {
236 cstart = i;
237 G.telstate = TS_IAC;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000238 }
239 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000240 else
241 switch (G.telstate)
242 {
243 case TS_0:
244 if (c == IAC)
245 G.telstate = TS_IAC;
246 else
247 G.buf[cstart++] = c;
248 break;
249
250 case TS_IAC:
251 if (c == IAC) /* IAC IAC -> 0xFF */
252 {
253 G.buf[cstart++] = c;
254 G.telstate = TS_0;
255 break;
256 }
257 /* else */
258 switch (c)
259 {
260 case SB:
261 G.telstate = TS_SUB1;
262 break;
263 case DO:
264 case DONT:
265 case WILL:
266 case WONT:
267 G.telwish = c;
268 G.telstate = TS_OPT;
269 break;
270 default:
271 G.telstate = TS_0; /* DATA MARK must be added later */
272 }
273 break;
274 case TS_OPT: /* WILL, WONT, DO, DONT */
275 telopt(c);
276 G.telstate = TS_0;
277 break;
278 case TS_SUB1: /* Subnegotiation */
279 case TS_SUB2: /* Subnegotiation */
280 if (subneg(c) == TRUE)
281 G.telstate = TS_0;
282 break;
283 }
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000284 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000285 if (G.telstate)
286 {
287 if (G.iaclen) iacflush();
288 if (G.telstate == TS_0) G.telstate = 0;
289
290 G.len = cstart;
291 }
292
293 if (G.len)
294 write(1, G.buf, G.len);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000295}
296
Eric Andersen28c70b32000-06-14 20:42:57 +0000297
298/* ******************************* */
299
300static inline void putiac(int c)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000301{
Eric Andersen28c70b32000-06-14 20:42:57 +0000302 G.iacbuf[G.iaclen++] = c;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000303}
304
Eric Andersen28c70b32000-06-14 20:42:57 +0000305
306static void putiac2(byte wwdd, byte c)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000307{
Eric Andersen28c70b32000-06-14 20:42:57 +0000308 if (G.iaclen + 3 > IACBUFSIZE)
309 iacflush();
310
311 putiac(IAC);
312 putiac(wwdd);
313 putiac(c);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000314}
315
Eric Andersen28c70b32000-06-14 20:42:57 +0000316#if 0
317static void putiac1(byte c)
318{
319 if (G.iaclen + 2 > IACBUFSIZE)
320 iacflush();
321
322 putiac(IAC);
323 putiac(c);
324}
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000325#endif
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000326
Eric Andersen28c70b32000-06-14 20:42:57 +0000327/* void putiacstring (subneg strings) */
328
329/* ******************************* */
330
331char const escapecharis[] = "\r\nEscape character is ";
332
333static void setConMode()
334{
335 if (G.telflags & UF_ECHO)
336 {
337 if (G.charmode == CHM_TRY) {
338 G.charmode = CHM_ON;
Matt Kraai12f417e2001-01-18 02:57:08 +0000339 printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis);
Eric Andersen28c70b32000-06-14 20:42:57 +0000340 rawmode();
341 }
342 }
343 else
344 {
345 if (G.charmode != CHM_OFF) {
346 G.charmode = CHM_OFF;
Matt Kraai12f417e2001-01-18 02:57:08 +0000347 printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis);
Eric Andersen28c70b32000-06-14 20:42:57 +0000348 cookmode();
349 }
350 }
351}
352
353/* ******************************* */
354
355static void will_charmode()
356{
357 G.charmode = CHM_TRY;
358 G.telflags |= (UF_ECHO | UF_SGA);
359 setConMode();
360
361 putiac2(DO, TELOPT_ECHO);
362 putiac2(DO, TELOPT_SGA);
363 iacflush();
364}
365
366static void do_linemode()
367{
368 G.charmode = CHM_TRY;
369 G.telflags &= ~(UF_ECHO | UF_SGA);
370 setConMode();
371
372 putiac2(DONT, TELOPT_ECHO);
373 putiac2(DONT, TELOPT_SGA);
374 iacflush();
375}
376
377/* ******************************* */
378
379static inline void to_notsup(char c)
380{
381 if (G.telwish == WILL) putiac2(DONT, c);
382 else if (G.telwish == DO) putiac2(WONT, c);
383}
384
385static inline void to_echo()
386{
387 /* if server requests ECHO, don't agree */
388 if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; }
389 else if (G.telwish == DONT) return;
390
391 if (G.telflags & UF_ECHO)
392 {
393 if (G.telwish == WILL)
394 return;
395 }
396 else
397 if (G.telwish == WONT)
398 return;
399
400 if (G.charmode != CHM_OFF)
401 G.telflags ^= UF_ECHO;
402
403 if (G.telflags & UF_ECHO)
404 putiac2(DO, TELOPT_ECHO);
405 else
406 putiac2(DONT, TELOPT_ECHO);
407
408 setConMode();
409 WriteCS(1, "\r\n"); /* sudden modec */
410}
411
412static inline void to_sga()
413{
414 /* daemon always sends will/wont, client do/dont */
415
416 if (G.telflags & UF_SGA)
417 {
418 if (G.telwish == WILL)
419 return;
420 }
421 else
422 if (G.telwish == WONT)
423 return;
424
425 if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */
426 putiac2(DO, TELOPT_SGA);
427 else
428 putiac2(DONT, TELOPT_SGA);
429
430 return;
431}
432
433static void telopt(byte c)
434{
435 switch (c)
436 {
437 case TELOPT_ECHO: to_echo(c); break;
438 case TELOPT_SGA: to_sga(c); break;
439 default: to_notsup(c); break;
440 }
441}
442
443
444/* ******************************* */
445
446/* subnegotiation -- ignore all */
447
448static int subneg(byte c)
449{
450 switch (G.telstate)
451 {
452 case TS_SUB1:
453 if (c == IAC)
454 G.telstate = TS_SUB2;
455 break;
456 case TS_SUB2:
457 if (c == SE)
458 return TRUE;
459 G.telstate = TS_SUB1;
460 /* break; */
461 }
462 return FALSE;
463}
464
465/* ******************************* */
466
467static void fgotsig(int sig)
468{
469 G.gotsig = sig;
470}
471
472
473static void rawmode()
474{
475 tcsetattr(0, TCSADRAIN, &G.termios_raw);
476}
477
478static void cookmode()
479{
480 tcsetattr(0, TCSADRAIN, &G.termios_def);
481}
482
483extern int telnet_main(int argc, char** argv)
484{
485 struct in_addr host;
486 int port;
487#ifdef USE_POLL
488 struct pollfd ufds[2];
489#else
490 fd_set readfds;
491 int maxfd;
492#endif
493
494
495 memset(&G, 0, sizeof G);
496
497 if (tcgetattr(0, &G.termios_def) < 0)
498 exit(1);
499
500 G.termios_raw = G.termios_def;
501
502 cfmakeraw(&G.termios_raw);
503
504 if (argc < 2) usage(telnet_usage);
505 port = (argc > 2)? getport(argv[2]): 23;
506
507 G.buf = xmalloc(DATABUFSIZE);
508 G.iacbuf = xmalloc(IACBUFSIZE);
509
510 host = getserver(argv[1]);
511
512 G.netfd = remote_connect(host, port);
513
514 signal(SIGINT, fgotsig);
515
516#ifdef USE_POLL
517 ufds[0].fd = 0; ufds[1].fd = G.netfd;
518 ufds[0].events = ufds[1].events = POLLIN;
519#else
520 FD_ZERO(&readfds);
521 FD_SET(0, &readfds);
522 FD_SET(G.netfd, &readfds);
523 maxfd = G.netfd + 1;
524#endif
525
526 while (1)
527 {
528#ifndef USE_POLL
529 fd_set rfds = readfds;
530
531 switch (select(maxfd, &rfds, NULL, NULL, NULL))
532#else
533 switch (poll(ufds, 2, -1))
534#endif
535 {
536 case 0:
537 /* timeout */
538 case -1:
539 /* error, ignore and/or log something, bay go to loop */
540 if (G.gotsig)
541 conescape();
542 else
543 sleep(1);
544 break;
545 default:
546
547#ifdef USE_POLL
548 if (ufds[0].revents) /* well, should check POLLIN, but ... */
549#else
550 if (FD_ISSET(0, &rfds))
551#endif
552 {
553 G.len = read(0, G.buf, DATABUFSIZE);
554
555 if (G.len <= 0)
556 doexit(0);
557
558 TRACE(0, ("Read con: %d\n", G.len));
559
560 handlenetoutput();
561 }
562
563#ifdef USE_POLL
564 if (ufds[1].revents) /* well, should check POLLIN, but ... */
565#else
566 if (FD_ISSET(G.netfd, &rfds))
567#endif
568 {
569 G.len = read(G.netfd, G.buf, DATABUFSIZE);
570
571 if (G.len <= 0)
572 {
573 WriteCS(1, "Connection closed by foreign host.\r\n");
574 doexit(1);
575 }
576 TRACE(0, ("Read netfd (%d): %d\n", G.netfd, G.len));
577
578 handlenetinput();
579 }
580 }
581 }
582}
583
584static int getport(char * p)
585{
586 unsigned int port = atoi(p);
587
588 if ((unsigned)(port - 1 ) > 65534)
589 {
Mark Whitleyf57c9442000-12-07 19:56:48 +0000590 error_msg_and_die("%s: bad port number\n", p);
Eric Andersen28c70b32000-06-14 20:42:57 +0000591 }
592 return port;
593}
594
595static struct in_addr getserver(char * host)
596{
597 struct in_addr addr;
598
599 struct hostent * he;
600 if ((he = gethostbyname(host)) == NULL)
601 {
Mark Whitleyf57c9442000-12-07 19:56:48 +0000602 error_msg_and_die("%s: Unknown host\n", host);
Eric Andersen28c70b32000-06-14 20:42:57 +0000603 }
604 memcpy(&addr, he->h_addr, sizeof addr);
605
606 TRACE(1, ("addr: %s\n", inet_ntoa(addr)));
607
608 return addr;
609}
610
611static int create_socket()
612{
613 return socket(AF_INET, SOCK_STREAM, 0);
614}
615
616static void setup_sockaddr_in(struct sockaddr_in * addr, int port)
617{
618 memset(addr, 0, sizeof addr);
619 addr->sin_family = AF_INET;
620 addr->sin_port = htons(port);
621}
622
623#if 0
624static int local_bind(int port)
625{
626 struct sockaddr_in s_addr;
627 int s = create_socket();
628
629 setup_sockaddr_in(&s_addr, port);
630
631 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
632
633 if (bind(s, &s_addr, sizeof s_addr) < 0)
634 {
635 char * e = sys_errlist[errno];
636 syserrorexit("bind");
637 exit(1);
638 }
639 listen(s, 1);
640
641 return s;
642}
643#endif
644
645static int remote_connect(struct in_addr addr, int port)
646{
647 struct sockaddr_in s_addr;
648 int s = create_socket();
649
650 setup_sockaddr_in(&s_addr, port);
651 s_addr.sin_addr = addr;
652
653 setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one);
654
655 if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0)
656 {
Matt Kraai1fa1ade2000-12-18 03:57:16 +0000657 perror_msg_and_die("Unable to connect to remote host");
Eric Andersen28c70b32000-06-14 20:42:57 +0000658 }
659 return s;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000660}
661
662/*
Eric Andersen28c70b32000-06-14 20:42:57 +0000663Local Variables:
664c-file-style: "linux"
665c-basic-offset: 4
666tab-width: 4
667End:
668*/
669