1*cf5a6c84SAndroid Build Coastguard Worker /* telnet.c - Telnet client.
2*cf5a6c84SAndroid Build Coastguard Worker *
3*cf5a6c84SAndroid Build Coastguard Worker * Copyright 2012 Madhur Verma <[email protected]>
4*cf5a6c84SAndroid Build Coastguard Worker * Copyright 2013 Kyungwan Han <[email protected]>
5*cf5a6c84SAndroid Build Coastguard Worker * Modified by Ashwini Kumar <[email protected]>
6*cf5a6c84SAndroid Build Coastguard Worker *
7*cf5a6c84SAndroid Build Coastguard Worker * Not in SUSv4.
8*cf5a6c84SAndroid Build Coastguard Worker
9*cf5a6c84SAndroid Build Coastguard Worker USE_TELNET(NEWTOY(telnet, "<1>2", TOYFLAG_BIN))
10*cf5a6c84SAndroid Build Coastguard Worker
11*cf5a6c84SAndroid Build Coastguard Worker config TELNET
12*cf5a6c84SAndroid Build Coastguard Worker bool "telnet"
13*cf5a6c84SAndroid Build Coastguard Worker default n
14*cf5a6c84SAndroid Build Coastguard Worker help
15*cf5a6c84SAndroid Build Coastguard Worker usage: telnet HOST [PORT]
16*cf5a6c84SAndroid Build Coastguard Worker
17*cf5a6c84SAndroid Build Coastguard Worker Connect to telnet server.
18*cf5a6c84SAndroid Build Coastguard Worker */
19*cf5a6c84SAndroid Build Coastguard Worker
20*cf5a6c84SAndroid Build Coastguard Worker #define FOR_telnet
21*cf5a6c84SAndroid Build Coastguard Worker #include "toys.h"
22*cf5a6c84SAndroid Build Coastguard Worker #include <arpa/telnet.h>
23*cf5a6c84SAndroid Build Coastguard Worker
GLOBALS(int sock;char buf[2048];struct termios old_term;struct termios raw_term;uint8_t mode;int echo,sga;int state,request;)24*cf5a6c84SAndroid Build Coastguard Worker GLOBALS(
25*cf5a6c84SAndroid Build Coastguard Worker int sock;
26*cf5a6c84SAndroid Build Coastguard Worker char buf[2048]; // Half sizeof(toybuf) allows a buffer full of IACs.
27*cf5a6c84SAndroid Build Coastguard Worker struct termios old_term;
28*cf5a6c84SAndroid Build Coastguard Worker struct termios raw_term;
29*cf5a6c84SAndroid Build Coastguard Worker uint8_t mode;
30*cf5a6c84SAndroid Build Coastguard Worker int echo, sga;
31*cf5a6c84SAndroid Build Coastguard Worker int state, request;
32*cf5a6c84SAndroid Build Coastguard Worker )
33*cf5a6c84SAndroid Build Coastguard Worker
34*cf5a6c84SAndroid Build Coastguard Worker #define NORMAL 0
35*cf5a6c84SAndroid Build Coastguard Worker #define SAW_IAC 1
36*cf5a6c84SAndroid Build Coastguard Worker #define SAW_WWDD 2
37*cf5a6c84SAndroid Build Coastguard Worker #define SAW_SB 3
38*cf5a6c84SAndroid Build Coastguard Worker #define SAW_SB_TTYPE 4
39*cf5a6c84SAndroid Build Coastguard Worker #define WANT_IAC 5
40*cf5a6c84SAndroid Build Coastguard Worker #define WANT_SE 6
41*cf5a6c84SAndroid Build Coastguard Worker #define SAW_CR 10
42*cf5a6c84SAndroid Build Coastguard Worker
43*cf5a6c84SAndroid Build Coastguard Worker #define CM_TRY 0
44*cf5a6c84SAndroid Build Coastguard Worker #define CM_ON 1
45*cf5a6c84SAndroid Build Coastguard Worker #define CM_OFF 2
46*cf5a6c84SAndroid Build Coastguard Worker
47*cf5a6c84SAndroid Build Coastguard Worker static void raw(int raw)
48*cf5a6c84SAndroid Build Coastguard Worker {
49*cf5a6c84SAndroid Build Coastguard Worker tcsetattr(0, TCSADRAIN, raw ? &TT.raw_term : &TT.old_term);
50*cf5a6c84SAndroid Build Coastguard Worker }
51*cf5a6c84SAndroid Build Coastguard Worker
slc(int line)52*cf5a6c84SAndroid Build Coastguard Worker static void slc(int line)
53*cf5a6c84SAndroid Build Coastguard Worker {
54*cf5a6c84SAndroid Build Coastguard Worker TT.mode = line ? CM_OFF : CM_ON;
55*cf5a6c84SAndroid Build Coastguard Worker xprintf("Entering %s mode\r\nEscape character is '^%c'.\r\n",
56*cf5a6c84SAndroid Build Coastguard Worker line ? "line" : "character", line ? 'C' : ']');
57*cf5a6c84SAndroid Build Coastguard Worker raw(!line);
58*cf5a6c84SAndroid Build Coastguard Worker }
59*cf5a6c84SAndroid Build Coastguard Worker
set_mode(void)60*cf5a6c84SAndroid Build Coastguard Worker static void set_mode(void)
61*cf5a6c84SAndroid Build Coastguard Worker {
62*cf5a6c84SAndroid Build Coastguard Worker if (TT.echo) {
63*cf5a6c84SAndroid Build Coastguard Worker if (TT.mode == CM_TRY) slc(0);
64*cf5a6c84SAndroid Build Coastguard Worker } else if (TT.mode != CM_OFF) slc(1);
65*cf5a6c84SAndroid Build Coastguard Worker }
66*cf5a6c84SAndroid Build Coastguard Worker
handle_esc(void)67*cf5a6c84SAndroid Build Coastguard Worker static void handle_esc(void)
68*cf5a6c84SAndroid Build Coastguard Worker {
69*cf5a6c84SAndroid Build Coastguard Worker char input;
70*cf5a6c84SAndroid Build Coastguard Worker
71*cf5a6c84SAndroid Build Coastguard Worker if (toys.signal) raw(1);
72*cf5a6c84SAndroid Build Coastguard Worker
73*cf5a6c84SAndroid Build Coastguard Worker // This matches busybox telnet, not BSD telnet.
74*cf5a6c84SAndroid Build Coastguard Worker xputsn("\r\n"
75*cf5a6c84SAndroid Build Coastguard Worker "Console escape. Commands are:\r\n"
76*cf5a6c84SAndroid Build Coastguard Worker "\r\n"
77*cf5a6c84SAndroid Build Coastguard Worker " l go to line mode\r\n"
78*cf5a6c84SAndroid Build Coastguard Worker " c go to character mode\r\n"
79*cf5a6c84SAndroid Build Coastguard Worker " z suspend telnet\r\n"
80*cf5a6c84SAndroid Build Coastguard Worker " e exit telnet\r\n"
81*cf5a6c84SAndroid Build Coastguard Worker "\r\n"
82*cf5a6c84SAndroid Build Coastguard Worker "telnet> ");
83*cf5a6c84SAndroid Build Coastguard Worker // In particular, the boxes only read a single character, not a line.
84*cf5a6c84SAndroid Build Coastguard Worker if (read(0, &input, 1) <= 0 || input == 4 || input == 'e') {
85*cf5a6c84SAndroid Build Coastguard Worker xprintf("Connection closed.\r\n");
86*cf5a6c84SAndroid Build Coastguard Worker xexit();
87*cf5a6c84SAndroid Build Coastguard Worker }
88*cf5a6c84SAndroid Build Coastguard Worker
89*cf5a6c84SAndroid Build Coastguard Worker if (input == 'l') {
90*cf5a6c84SAndroid Build Coastguard Worker if (!toys.signal) {
91*cf5a6c84SAndroid Build Coastguard Worker TT.mode = CM_TRY;
92*cf5a6c84SAndroid Build Coastguard Worker TT.echo = TT.sga = 0;
93*cf5a6c84SAndroid Build Coastguard Worker set_mode();
94*cf5a6c84SAndroid Build Coastguard Worker dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DONT,TELOPT_ECHO,IAC,DONT,TELOPT_SGA);
95*cf5a6c84SAndroid Build Coastguard Worker goto ret;
96*cf5a6c84SAndroid Build Coastguard Worker }
97*cf5a6c84SAndroid Build Coastguard Worker } else if (input == 'c') {
98*cf5a6c84SAndroid Build Coastguard Worker if (toys.signal) {
99*cf5a6c84SAndroid Build Coastguard Worker TT.mode = CM_TRY;
100*cf5a6c84SAndroid Build Coastguard Worker TT.echo = TT.sga = 1;
101*cf5a6c84SAndroid Build Coastguard Worker set_mode();
102*cf5a6c84SAndroid Build Coastguard Worker dprintf(TT.sock,"%c%c%c%c%c%c",IAC,DO,TELOPT_ECHO,IAC,DO,TELOPT_SGA);
103*cf5a6c84SAndroid Build Coastguard Worker goto ret;
104*cf5a6c84SAndroid Build Coastguard Worker }
105*cf5a6c84SAndroid Build Coastguard Worker } else if (input == 'z') {
106*cf5a6c84SAndroid Build Coastguard Worker raw(0);
107*cf5a6c84SAndroid Build Coastguard Worker kill(0, SIGTSTP);
108*cf5a6c84SAndroid Build Coastguard Worker raw(1);
109*cf5a6c84SAndroid Build Coastguard Worker }
110*cf5a6c84SAndroid Build Coastguard Worker
111*cf5a6c84SAndroid Build Coastguard Worker xprintf("telnet %s %s\r\n", toys.optargs[0], toys.optargs[1] ?: "23");
112*cf5a6c84SAndroid Build Coastguard Worker if (toys.signal) raw(0);
113*cf5a6c84SAndroid Build Coastguard Worker
114*cf5a6c84SAndroid Build Coastguard Worker ret:
115*cf5a6c84SAndroid Build Coastguard Worker toys.signal = 0;
116*cf5a6c84SAndroid Build Coastguard Worker }
117*cf5a6c84SAndroid Build Coastguard Worker
118*cf5a6c84SAndroid Build Coastguard Worker // Handle WILL WONT DO DONT requests from the server.
handle_wwdd(char opt)119*cf5a6c84SAndroid Build Coastguard Worker static void handle_wwdd(char opt)
120*cf5a6c84SAndroid Build Coastguard Worker {
121*cf5a6c84SAndroid Build Coastguard Worker if (opt == TELOPT_ECHO) {
122*cf5a6c84SAndroid Build Coastguard Worker if (TT.request == DO) dprintf(TT.sock, "%c%c%c", IAC, WONT, TELOPT_ECHO);
123*cf5a6c84SAndroid Build Coastguard Worker if (TT.request == DONT) return;
124*cf5a6c84SAndroid Build Coastguard Worker if (TT.echo) {
125*cf5a6c84SAndroid Build Coastguard Worker if (TT.request == WILL) return;
126*cf5a6c84SAndroid Build Coastguard Worker } else if (TT.request == WONT) return;
127*cf5a6c84SAndroid Build Coastguard Worker if (TT.mode != CM_OFF) TT.echo ^= 1;
128*cf5a6c84SAndroid Build Coastguard Worker dprintf(TT.sock, "%c%c%c", IAC, TT.echo ? DO : DONT, TELOPT_ECHO);
129*cf5a6c84SAndroid Build Coastguard Worker set_mode();
130*cf5a6c84SAndroid Build Coastguard Worker } else if (opt == TELOPT_SGA) { // Suppress Go Ahead
131*cf5a6c84SAndroid Build Coastguard Worker if (TT.sga) {
132*cf5a6c84SAndroid Build Coastguard Worker if (TT.request == WILL) return;
133*cf5a6c84SAndroid Build Coastguard Worker } else if (TT.request == WONT) return;
134*cf5a6c84SAndroid Build Coastguard Worker TT.sga ^= 1;
135*cf5a6c84SAndroid Build Coastguard Worker dprintf(TT.sock, "%c%c%c", IAC, TT.sga ? DO : DONT, TELOPT_SGA);
136*cf5a6c84SAndroid Build Coastguard Worker } else if (opt == TELOPT_TTYPE) { // Terminal TYPE
137*cf5a6c84SAndroid Build Coastguard Worker dprintf(TT.sock, "%c%c%c", IAC, WILL, TELOPT_TTYPE);
138*cf5a6c84SAndroid Build Coastguard Worker } else if (opt == TELOPT_NAWS) { // Negotiate About Window Size
139*cf5a6c84SAndroid Build Coastguard Worker unsigned cols = 80, rows = 24;
140*cf5a6c84SAndroid Build Coastguard Worker
141*cf5a6c84SAndroid Build Coastguard Worker terminal_size(&cols, &rows);
142*cf5a6c84SAndroid Build Coastguard Worker dprintf(TT.sock, "%c%c%c%c%c%c%c%c%c%c%c%c", IAC, WILL, TELOPT_NAWS,
143*cf5a6c84SAndroid Build Coastguard Worker IAC, SB, TELOPT_NAWS, cols>>8, cols, rows>>8, rows,
144*cf5a6c84SAndroid Build Coastguard Worker IAC, SE);
145*cf5a6c84SAndroid Build Coastguard Worker } else {
146*cf5a6c84SAndroid Build Coastguard Worker // Say "no" to anything we don't understand.
147*cf5a6c84SAndroid Build Coastguard Worker dprintf(TT.sock, "%c%c%c", IAC, (TT.request == WILL) ? DONT : WONT, opt);
148*cf5a6c84SAndroid Build Coastguard Worker }
149*cf5a6c84SAndroid Build Coastguard Worker }
150*cf5a6c84SAndroid Build Coastguard Worker
handle_server_output(int n)151*cf5a6c84SAndroid Build Coastguard Worker static void handle_server_output(int n)
152*cf5a6c84SAndroid Build Coastguard Worker {
153*cf5a6c84SAndroid Build Coastguard Worker char *p = TT.buf, *end = TT.buf + n, ch;
154*cf5a6c84SAndroid Build Coastguard Worker int i = 0;
155*cf5a6c84SAndroid Build Coastguard Worker
156*cf5a6c84SAndroid Build Coastguard Worker // Possibilities:
157*cf5a6c84SAndroid Build Coastguard Worker //
158*cf5a6c84SAndroid Build Coastguard Worker // 1. Regular character
159*cf5a6c84SAndroid Build Coastguard Worker // 2. IAC [WILL|WONT|DO|DONT] option
160*cf5a6c84SAndroid Build Coastguard Worker // 3. IAC SB option arg... IAC SE
161*cf5a6c84SAndroid Build Coastguard Worker //
162*cf5a6c84SAndroid Build Coastguard Worker // The only subnegotiation we support is IAC SB TTYPE SEND IAC SE, so we just
163*cf5a6c84SAndroid Build Coastguard Worker // hard-code that into our state machine rather than having a more general
164*cf5a6c84SAndroid Build Coastguard Worker // "collect the subnegotation into a buffer and handle it after we've seen
165*cf5a6c84SAndroid Build Coastguard Worker // the IAC SE at the end". It's 2021, so we're unlikely to need more.
166*cf5a6c84SAndroid Build Coastguard Worker
167*cf5a6c84SAndroid Build Coastguard Worker while (p < end) {
168*cf5a6c84SAndroid Build Coastguard Worker ch = *p++;
169*cf5a6c84SAndroid Build Coastguard Worker if (TT.state == SAW_IAC) {
170*cf5a6c84SAndroid Build Coastguard Worker if (ch >= WILL && ch <= DONT) {
171*cf5a6c84SAndroid Build Coastguard Worker TT.state = SAW_WWDD;
172*cf5a6c84SAndroid Build Coastguard Worker TT.request = ch;
173*cf5a6c84SAndroid Build Coastguard Worker } else if (ch == SB) {
174*cf5a6c84SAndroid Build Coastguard Worker TT.state = SAW_SB;
175*cf5a6c84SAndroid Build Coastguard Worker } else {
176*cf5a6c84SAndroid Build Coastguard Worker TT.state = NORMAL;
177*cf5a6c84SAndroid Build Coastguard Worker }
178*cf5a6c84SAndroid Build Coastguard Worker } else if (TT.state == SAW_WWDD) {
179*cf5a6c84SAndroid Build Coastguard Worker handle_wwdd(ch);
180*cf5a6c84SAndroid Build Coastguard Worker TT.state = NORMAL;
181*cf5a6c84SAndroid Build Coastguard Worker } else if (TT.state == SAW_SB) {
182*cf5a6c84SAndroid Build Coastguard Worker if (ch == TELOPT_TTYPE) TT.state = SAW_SB_TTYPE;
183*cf5a6c84SAndroid Build Coastguard Worker else TT.state = WANT_IAC;
184*cf5a6c84SAndroid Build Coastguard Worker } else if (TT.state == SAW_SB_TTYPE) {
185*cf5a6c84SAndroid Build Coastguard Worker if (ch == TELQUAL_SEND) {
186*cf5a6c84SAndroid Build Coastguard Worker dprintf(TT.sock, "%c%c%c%c%s%c%c", IAC, SB, TELOPT_TTYPE, TELQUAL_IS,
187*cf5a6c84SAndroid Build Coastguard Worker getenv("TERM") ?: "NVT", IAC, SE);
188*cf5a6c84SAndroid Build Coastguard Worker }
189*cf5a6c84SAndroid Build Coastguard Worker TT.state = WANT_IAC;
190*cf5a6c84SAndroid Build Coastguard Worker } else if (TT.state == WANT_IAC) {
191*cf5a6c84SAndroid Build Coastguard Worker if (ch == IAC) TT.state = WANT_SE;
192*cf5a6c84SAndroid Build Coastguard Worker } else if (TT.state == WANT_SE) {
193*cf5a6c84SAndroid Build Coastguard Worker if (ch == SE) TT.state = NORMAL;
194*cf5a6c84SAndroid Build Coastguard Worker } else if (ch == IAC) {
195*cf5a6c84SAndroid Build Coastguard Worker TT.state = SAW_IAC;
196*cf5a6c84SAndroid Build Coastguard Worker } else {
197*cf5a6c84SAndroid Build Coastguard Worker if (TT.state == SAW_CR && ch == '\0') {
198*cf5a6c84SAndroid Build Coastguard Worker // CR NUL -> CR
199*cf5a6c84SAndroid Build Coastguard Worker } else toybuf[i++] = ch;
200*cf5a6c84SAndroid Build Coastguard Worker if (ch == '\r') TT.state = SAW_CR;
201*cf5a6c84SAndroid Build Coastguard Worker TT.state = NORMAL;
202*cf5a6c84SAndroid Build Coastguard Worker }
203*cf5a6c84SAndroid Build Coastguard Worker }
204*cf5a6c84SAndroid Build Coastguard Worker if (i) xwrite(0, toybuf, i);
205*cf5a6c84SAndroid Build Coastguard Worker }
206*cf5a6c84SAndroid Build Coastguard Worker
handle_user_input(int n)207*cf5a6c84SAndroid Build Coastguard Worker static void handle_user_input(int n)
208*cf5a6c84SAndroid Build Coastguard Worker {
209*cf5a6c84SAndroid Build Coastguard Worker char *p = TT.buf, ch;
210*cf5a6c84SAndroid Build Coastguard Worker int i = 0;
211*cf5a6c84SAndroid Build Coastguard Worker
212*cf5a6c84SAndroid Build Coastguard Worker while (n--) {
213*cf5a6c84SAndroid Build Coastguard Worker ch = *p++;
214*cf5a6c84SAndroid Build Coastguard Worker if (ch == 0x1d) {
215*cf5a6c84SAndroid Build Coastguard Worker handle_esc();
216*cf5a6c84SAndroid Build Coastguard Worker return;
217*cf5a6c84SAndroid Build Coastguard Worker }
218*cf5a6c84SAndroid Build Coastguard Worker toybuf[i++] = ch;
219*cf5a6c84SAndroid Build Coastguard Worker if (ch == IAC) toybuf[i++] = IAC; // IAC -> IAC IAC
220*cf5a6c84SAndroid Build Coastguard Worker else if (ch == '\r') toybuf[i++] = '\n'; // CR -> CR LF
221*cf5a6c84SAndroid Build Coastguard Worker else if (ch == '\n') { // LF -> CR LF
222*cf5a6c84SAndroid Build Coastguard Worker toybuf[i-1] = '\r';
223*cf5a6c84SAndroid Build Coastguard Worker toybuf[i++] = '\n';
224*cf5a6c84SAndroid Build Coastguard Worker }
225*cf5a6c84SAndroid Build Coastguard Worker }
226*cf5a6c84SAndroid Build Coastguard Worker if (i) xwrite(TT.sock, toybuf, i);
227*cf5a6c84SAndroid Build Coastguard Worker }
228*cf5a6c84SAndroid Build Coastguard Worker
reset_terminal(void)229*cf5a6c84SAndroid Build Coastguard Worker static void reset_terminal(void)
230*cf5a6c84SAndroid Build Coastguard Worker {
231*cf5a6c84SAndroid Build Coastguard Worker raw(0);
232*cf5a6c84SAndroid Build Coastguard Worker }
233*cf5a6c84SAndroid Build Coastguard Worker
telnet_main(void)234*cf5a6c84SAndroid Build Coastguard Worker void telnet_main(void)
235*cf5a6c84SAndroid Build Coastguard Worker {
236*cf5a6c84SAndroid Build Coastguard Worker struct pollfd pfds[2];
237*cf5a6c84SAndroid Build Coastguard Worker int n = 1;
238*cf5a6c84SAndroid Build Coastguard Worker
239*cf5a6c84SAndroid Build Coastguard Worker tcgetattr(0, &TT.old_term);
240*cf5a6c84SAndroid Build Coastguard Worker TT.raw_term = TT.old_term;
241*cf5a6c84SAndroid Build Coastguard Worker cfmakeraw(&TT.raw_term);
242*cf5a6c84SAndroid Build Coastguard Worker
243*cf5a6c84SAndroid Build Coastguard Worker TT.sock = xconnectany(xgetaddrinfo(*toys.optargs, toys.optargs[1] ?: "23", 0,
244*cf5a6c84SAndroid Build Coastguard Worker SOCK_STREAM, IPPROTO_TCP, 0));
245*cf5a6c84SAndroid Build Coastguard Worker xsetsockopt(TT.sock, SOL_SOCKET, SO_KEEPALIVE, &n, sizeof(n));
246*cf5a6c84SAndroid Build Coastguard Worker
247*cf5a6c84SAndroid Build Coastguard Worker xprintf("Connected to %s.\r\n", *toys.optargs);
248*cf5a6c84SAndroid Build Coastguard Worker
249*cf5a6c84SAndroid Build Coastguard Worker sigatexit(reset_terminal);
250*cf5a6c84SAndroid Build Coastguard Worker signal(SIGINT, generic_signal);
251*cf5a6c84SAndroid Build Coastguard Worker
252*cf5a6c84SAndroid Build Coastguard Worker pfds[0].fd = 0;
253*cf5a6c84SAndroid Build Coastguard Worker pfds[0].events = POLLIN;
254*cf5a6c84SAndroid Build Coastguard Worker pfds[1].fd = TT.sock;
255*cf5a6c84SAndroid Build Coastguard Worker pfds[1].events = POLLIN;
256*cf5a6c84SAndroid Build Coastguard Worker for (;;) {
257*cf5a6c84SAndroid Build Coastguard Worker if (poll(pfds, 2, -1) < 0) {
258*cf5a6c84SAndroid Build Coastguard Worker if (toys.signal) handle_esc();
259*cf5a6c84SAndroid Build Coastguard Worker else perror_exit("poll");
260*cf5a6c84SAndroid Build Coastguard Worker }
261*cf5a6c84SAndroid Build Coastguard Worker if (pfds[0].revents) {
262*cf5a6c84SAndroid Build Coastguard Worker if ((n = read(0, TT.buf, sizeof(TT.buf))) <= 0) xexit();
263*cf5a6c84SAndroid Build Coastguard Worker handle_user_input(n);
264*cf5a6c84SAndroid Build Coastguard Worker }
265*cf5a6c84SAndroid Build Coastguard Worker if (pfds[1].revents) {
266*cf5a6c84SAndroid Build Coastguard Worker if ((n = read(TT.sock, TT.buf, sizeof(TT.buf))) <= 0)
267*cf5a6c84SAndroid Build Coastguard Worker error_exit("Connection closed by foreign host\r");
268*cf5a6c84SAndroid Build Coastguard Worker handle_server_output(n);
269*cf5a6c84SAndroid Build Coastguard Worker }
270*cf5a6c84SAndroid Build Coastguard Worker }
271*cf5a6c84SAndroid Build Coastguard Worker }
272