1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#define FOR_telnet
21#include "toys.h"
22#include <arpa/telnet.h>
23
24GLOBALS(
25 int sock;
26 char buf[2048];
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
47static void raw(int raw)
48{
49 tcsetattr(0, TCSADRAIN, raw ? &TT.raw_term : &TT.old_term);
50}
51
52static 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
60static 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
67static void handle_esc(void)
68{
69 char input;
70
71 if (toys.signal) raw(1);
72
73
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
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
114ret:
115 toys.signal = 0;
116}
117
118
119static 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) {
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) {
137 dprintf(TT.sock, "%c%c%c", IAC, WILL, TELOPT_TTYPE);
138 } else if (opt == TELOPT_NAWS) {
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
147 dprintf(TT.sock, "%c%c%c", IAC, (TT.request == WILL) ? DONT : WONT, opt);
148 }
149}
150
151static void handle_server_output(int n)
152{
153 char *p = TT.buf, *end = TT.buf + n, ch;
154 int i = 0;
155
156
157
158
159
160
161
162
163
164
165
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
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
207static 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;
220 else if (ch == '\r') toybuf[i++] = '\n';
221 else if (ch == '\n') {
222 toybuf[i-1] = '\r';
223 toybuf[i++] = '\n';
224 }
225 }
226 if (i) xwrite(TT.sock, toybuf, i);
227}
228
229static void reset_terminal(void)
230{
231 raw(0);
232}
233
234void 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