blob: bfa964460908af6793e55939bf99d2163cee48b8 [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
Eric Andersen3570a342000-09-25 21:45:58 +000034#include "busybox.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
Pavel Roskin616d13b2000-07-28 19:38:27 +000052#ifdef DOTRACE
Eric Andersen28c70b32000-06-14 20:42:57 +000053#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;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000137
Eric Andersen28c70b32000-06-14 20:42:57 +0000138static void doexit(int ev)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000139{
Eric Andersen28c70b32000-06-14 20:42:57 +0000140 cookmode();
141 exit(ev);
142}
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000143
Eric Andersen28c70b32000-06-14 20:42:57 +0000144static void conescape()
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000145{
Eric Andersen28c70b32000-06-14 20:42:57 +0000146 char b;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000147
Eric Andersen28c70b32000-06-14 20:42:57 +0000148 if (G.gotsig) /* came from line mode... go raw */
149 rawmode();
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000150
Eric Andersen28c70b32000-06-14 20:42:57 +0000151 WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n"
152 " l go to line mode\r\n"
153 " c go to character mode\r\n"
154 " z suspend telnet\r\n"
155 " e exit telnet\r\n");
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000156
Eric Andersen28c70b32000-06-14 20:42:57 +0000157 if (read(0, &b, 1) <= 0)
158 doexit(1);
159
160 switch (b)
161 {
162 case 'l':
163 if (!G.gotsig)
164 {
165 do_linemode();
166 goto rrturn;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000167 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000168 break;
169 case 'c':
170 if (G.gotsig)
171 {
172 will_charmode();
173 goto rrturn;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000174 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000175 break;
176 case 'z':
177 cookmode();
178 kill(0, SIGTSTP);
179 rawmode();
180 break;
181 case 'e':
182 doexit(0);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000183 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000184
185 WriteCS(1, "continuing...\r\n");
186
187 if (G.gotsig)
188 cookmode();
189
190 rrturn:
191 G.gotsig = 0;
192
193}
194static void handlenetoutput()
195{
196 /* here we could do smart tricks how to handle 0xFF:s in output
197 * stream like writing twice every sequence of FF:s (thus doing
198 * many write()s. But I think interactive telnet application does
199 * not need to be 100% 8-bit clean, so changing every 0xff:s to
200 * 0x7f:s */
201
202 int i;
203 byte * p = G.buf;
204
205 for (i = G.len; i > 0; i--, p++)
206 {
207 if (*p == 0x1d)
208 {
209 conescape();
210 return;
211 }
212 if (*p == 0xff)
213 *p = 0x7f;
214 }
215 write(G.netfd, G.buf, G.len);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000216}
217
Eric Andersen28c70b32000-06-14 20:42:57 +0000218
219static void handlenetinput()
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000220{
Eric Andersen28c70b32000-06-14 20:42:57 +0000221 int i;
222 int cstart = 0;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000223
Eric Andersen28c70b32000-06-14 20:42:57 +0000224 for (i = 0; i < G.len; i++)
225 {
226 byte c = G.buf[i];
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000227
Eric Andersen28c70b32000-06-14 20:42:57 +0000228 if (G.telstate == 0) /* most of the time state == 0 */
229 {
230 if (c == IAC)
231 {
232 cstart = i;
233 G.telstate = TS_IAC;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000234 }
235 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000236 else
237 switch (G.telstate)
238 {
239 case TS_0:
240 if (c == IAC)
241 G.telstate = TS_IAC;
242 else
243 G.buf[cstart++] = c;
244 break;
245
246 case TS_IAC:
247 if (c == IAC) /* IAC IAC -> 0xFF */
248 {
249 G.buf[cstart++] = c;
250 G.telstate = TS_0;
251 break;
252 }
253 /* else */
254 switch (c)
255 {
256 case SB:
257 G.telstate = TS_SUB1;
258 break;
259 case DO:
260 case DONT:
261 case WILL:
262 case WONT:
263 G.telwish = c;
264 G.telstate = TS_OPT;
265 break;
266 default:
267 G.telstate = TS_0; /* DATA MARK must be added later */
268 }
269 break;
270 case TS_OPT: /* WILL, WONT, DO, DONT */
271 telopt(c);
272 G.telstate = TS_0;
273 break;
274 case TS_SUB1: /* Subnegotiation */
275 case TS_SUB2: /* Subnegotiation */
276 if (subneg(c) == TRUE)
277 G.telstate = TS_0;
278 break;
279 }
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000280 }
Eric Andersen28c70b32000-06-14 20:42:57 +0000281 if (G.telstate)
282 {
283 if (G.iaclen) iacflush();
284 if (G.telstate == TS_0) G.telstate = 0;
285
286 G.len = cstart;
287 }
288
289 if (G.len)
290 write(1, G.buf, G.len);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000291}
292
Eric Andersen28c70b32000-06-14 20:42:57 +0000293
294/* ******************************* */
295
296static inline void putiac(int c)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000297{
Eric Andersen28c70b32000-06-14 20:42:57 +0000298 G.iacbuf[G.iaclen++] = c;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000299}
300
Eric Andersen28c70b32000-06-14 20:42:57 +0000301
302static void putiac2(byte wwdd, byte c)
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000303{
Eric Andersen28c70b32000-06-14 20:42:57 +0000304 if (G.iaclen + 3 > IACBUFSIZE)
305 iacflush();
306
307 putiac(IAC);
308 putiac(wwdd);
309 putiac(c);
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000310}
311
Eric Andersen28c70b32000-06-14 20:42:57 +0000312#if 0
313static void putiac1(byte c)
314{
315 if (G.iaclen + 2 > IACBUFSIZE)
316 iacflush();
317
318 putiac(IAC);
319 putiac(c);
320}
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000321#endif
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000322
Eric Andersen28c70b32000-06-14 20:42:57 +0000323/* void putiacstring (subneg strings) */
324
325/* ******************************* */
326
327char const escapecharis[] = "\r\nEscape character is ";
328
329static void setConMode()
330{
331 if (G.telflags & UF_ECHO)
332 {
333 if (G.charmode == CHM_TRY) {
334 G.charmode = CHM_ON;
335 fprintf(stdout, "\r\nEntering character mode%s'^]'.\r\n", escapecharis);
336 rawmode();
337 }
338 }
339 else
340 {
341 if (G.charmode != CHM_OFF) {
342 G.charmode = CHM_OFF;
343 fprintf(stdout, "\r\nEntering line mode%s'^C'.\r\n", escapecharis);
344 cookmode();
345 }
346 }
347}
348
349/* ******************************* */
350
351static void will_charmode()
352{
353 G.charmode = CHM_TRY;
354 G.telflags |= (UF_ECHO | UF_SGA);
355 setConMode();
356
357 putiac2(DO, TELOPT_ECHO);
358 putiac2(DO, TELOPT_SGA);
359 iacflush();
360}
361
362static void do_linemode()
363{
364 G.charmode = CHM_TRY;
365 G.telflags &= ~(UF_ECHO | UF_SGA);
366 setConMode();
367
368 putiac2(DONT, TELOPT_ECHO);
369 putiac2(DONT, TELOPT_SGA);
370 iacflush();
371}
372
373/* ******************************* */
374
375static inline void to_notsup(char c)
376{
377 if (G.telwish == WILL) putiac2(DONT, c);
378 else if (G.telwish == DO) putiac2(WONT, c);
379}
380
381static inline void to_echo()
382{
383 /* if server requests ECHO, don't agree */
384 if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; }
385 else if (G.telwish == DONT) return;
386
387 if (G.telflags & UF_ECHO)
388 {
389 if (G.telwish == WILL)
390 return;
391 }
392 else
393 if (G.telwish == WONT)
394 return;
395
396 if (G.charmode != CHM_OFF)
397 G.telflags ^= UF_ECHO;
398
399 if (G.telflags & UF_ECHO)
400 putiac2(DO, TELOPT_ECHO);
401 else
402 putiac2(DONT, TELOPT_ECHO);
403
404 setConMode();
405 WriteCS(1, "\r\n"); /* sudden modec */
406}
407
408static inline void to_sga()
409{
410 /* daemon always sends will/wont, client do/dont */
411
412 if (G.telflags & UF_SGA)
413 {
414 if (G.telwish == WILL)
415 return;
416 }
417 else
418 if (G.telwish == WONT)
419 return;
420
421 if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */
422 putiac2(DO, TELOPT_SGA);
423 else
424 putiac2(DONT, TELOPT_SGA);
425
426 return;
427}
428
429static void telopt(byte c)
430{
431 switch (c)
432 {
433 case TELOPT_ECHO: to_echo(c); break;
434 case TELOPT_SGA: to_sga(c); break;
435 default: to_notsup(c); break;
436 }
437}
438
439
440/* ******************************* */
441
442/* subnegotiation -- ignore all */
443
444static int subneg(byte c)
445{
446 switch (G.telstate)
447 {
448 case TS_SUB1:
449 if (c == IAC)
450 G.telstate = TS_SUB2;
451 break;
452 case TS_SUB2:
453 if (c == SE)
454 return TRUE;
455 G.telstate = TS_SUB1;
456 /* break; */
457 }
458 return FALSE;
459}
460
461/* ******************************* */
462
463static void fgotsig(int sig)
464{
465 G.gotsig = sig;
466}
467
468
469static void rawmode()
470{
471 tcsetattr(0, TCSADRAIN, &G.termios_raw);
472}
473
474static void cookmode()
475{
476 tcsetattr(0, TCSADRAIN, &G.termios_def);
477}
478
479extern int telnet_main(int argc, char** argv)
480{
481 struct in_addr host;
482 int port;
483#ifdef USE_POLL
484 struct pollfd ufds[2];
485#else
486 fd_set readfds;
487 int maxfd;
488#endif
489
490
491 memset(&G, 0, sizeof G);
492
493 if (tcgetattr(0, &G.termios_def) < 0)
494 exit(1);
495
496 G.termios_raw = G.termios_def;
497
498 cfmakeraw(&G.termios_raw);
499
500 if (argc < 2) usage(telnet_usage);
501 port = (argc > 2)? getport(argv[2]): 23;
502
503 G.buf = xmalloc(DATABUFSIZE);
504 G.iacbuf = xmalloc(IACBUFSIZE);
505
506 host = getserver(argv[1]);
507
508 G.netfd = remote_connect(host, port);
509
510 signal(SIGINT, fgotsig);
511
512#ifdef USE_POLL
513 ufds[0].fd = 0; ufds[1].fd = G.netfd;
514 ufds[0].events = ufds[1].events = POLLIN;
515#else
516 FD_ZERO(&readfds);
517 FD_SET(0, &readfds);
518 FD_SET(G.netfd, &readfds);
519 maxfd = G.netfd + 1;
520#endif
521
522 while (1)
523 {
524#ifndef USE_POLL
525 fd_set rfds = readfds;
526
527 switch (select(maxfd, &rfds, NULL, NULL, NULL))
528#else
529 switch (poll(ufds, 2, -1))
530#endif
531 {
532 case 0:
533 /* timeout */
534 case -1:
535 /* error, ignore and/or log something, bay go to loop */
536 if (G.gotsig)
537 conescape();
538 else
539 sleep(1);
540 break;
541 default:
542
543#ifdef USE_POLL
544 if (ufds[0].revents) /* well, should check POLLIN, but ... */
545#else
546 if (FD_ISSET(0, &rfds))
547#endif
548 {
549 G.len = read(0, G.buf, DATABUFSIZE);
550
551 if (G.len <= 0)
552 doexit(0);
553
554 TRACE(0, ("Read con: %d\n", G.len));
555
556 handlenetoutput();
557 }
558
559#ifdef USE_POLL
560 if (ufds[1].revents) /* well, should check POLLIN, but ... */
561#else
562 if (FD_ISSET(G.netfd, &rfds))
563#endif
564 {
565 G.len = read(G.netfd, G.buf, DATABUFSIZE);
566
567 if (G.len <= 0)
568 {
569 WriteCS(1, "Connection closed by foreign host.\r\n");
570 doexit(1);
571 }
572 TRACE(0, ("Read netfd (%d): %d\n", G.netfd, G.len));
573
574 handlenetinput();
575 }
576 }
577 }
578}
579
580static int getport(char * p)
581{
582 unsigned int port = atoi(p);
583
584 if ((unsigned)(port - 1 ) > 65534)
585 {
586 fatalError("%s: bad port number\n", p);
587 }
588 return port;
589}
590
591static struct in_addr getserver(char * host)
592{
593 struct in_addr addr;
594
595 struct hostent * he;
596 if ((he = gethostbyname(host)) == NULL)
597 {
Eric Andersena3de24e2000-08-31 23:52:46 +0000598 fatalError("%s: Unknown host\n", host);
Eric Andersen28c70b32000-06-14 20:42:57 +0000599 }
600 memcpy(&addr, he->h_addr, sizeof addr);
601
602 TRACE(1, ("addr: %s\n", inet_ntoa(addr)));
603
604 return addr;
605}
606
607static int create_socket()
608{
609 return socket(AF_INET, SOCK_STREAM, 0);
610}
611
612static void setup_sockaddr_in(struct sockaddr_in * addr, int port)
613{
614 memset(addr, 0, sizeof addr);
615 addr->sin_family = AF_INET;
616 addr->sin_port = htons(port);
617}
618
619#if 0
620static int local_bind(int port)
621{
622 struct sockaddr_in s_addr;
623 int s = create_socket();
624
625 setup_sockaddr_in(&s_addr, port);
626
627 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
628
629 if (bind(s, &s_addr, sizeof s_addr) < 0)
630 {
631 char * e = sys_errlist[errno];
632 syserrorexit("bind");
633 exit(1);
634 }
635 listen(s, 1);
636
637 return s;
638}
639#endif
640
641static int remote_connect(struct in_addr addr, int port)
642{
643 struct sockaddr_in s_addr;
644 int s = create_socket();
645
646 setup_sockaddr_in(&s_addr, port);
647 s_addr.sin_addr = addr;
648
649 setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one);
650
651 if (connect(s, (struct sockaddr *)&s_addr, sizeof s_addr) < 0)
652 {
653 fatalError("Unable to connect to remote host: %s\n", strerror(errno));
654 }
655 return s;
Erik Andersenf7c49ef2000-02-22 17:17:45 +0000656}
657
658/*
Eric Andersen28c70b32000-06-14 20:42:57 +0000659Local Variables:
660c-file-style: "linux"
661c-basic-offset: 4
662tab-width: 4
663End:
664*/
665