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