ESP8266-based WiFi serial modem emulator ROM
1/*
2 * WiFiPPP
3 * Copyright (c) 2021 joshua stein <jcs@jcs.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18#include "wifippp.h"
19
20WiFiClient telnet;
21
22enum {
23 TELNET_STATE_DISCONNECTED = 0,
24 TELNET_STATE_CONNECTED,
25 TELNET_STATE_IAC,
26 TELNET_STATE_IAC_WILL,
27 TELNET_STATE_IAC_WONT,
28 TELNET_STATE_IAC_DO,
29 TELNET_STATE_IAC_DONT,
30 TELNET_STATE_IAC_SB,
31};
32
33uint8_t telnet_state = TELNET_STATE_DISCONNECTED;
34uint8_t telnet_sb[64];
35uint8_t telnet_sb_len = 0;
36
37#define SE 240 /* end of sub-negotiation options */
38#define SB 250 /* start of sub-negotiation options */
39#define WILL 251 /* confirm willingness to negotiate */
40#define WONT 252 /* confirm unwillingness to negotiate */
41#define DO 253 /* indicate willingness to negotiate */
42#define DONT 254 /* indicate unwillingness to negotiate */
43#define IAC 255 /* start of a negotiation sequence */
44
45#define IS 0 /* sub-negotiation */
46#define SEND 1 /* sub-negotiation */
47
48#define IAC_BINARY 0 /* Transmit Binary */
49#define IAC_ECHO 1 /* Echo Option */
50#define IAC_SGA 3 /* Suppress Go Ahead Option */
51#define IAC_STATUS 5 /* Status Option */
52#define IAC_TM 6 /* Timing Mark Option */
53#define IAC_NAOCRD 10 /* Output Carriage-Return Disposition Option */
54#define IAC_NAOHTS 11 /* Output Horizontal Tabstops Option */
55#define IAC_NAOHTD 12 /* Output Horizontal Tab Disposition Option */
56#define IAC_NAOFFD 13 /* Output Formfeed Disposition Option */
57#define IAC_NAOVTS 14 /* Output Vertical Tabstops Option */
58#define IAC_NAOVTD 15 /* Output Vertical Tab Disposition Option */
59#define IAC_NAOLFD 16 /* Output Linefeed Disposition */
60#define IAC_XASCII 17 /* Extended Ascii Option */
61#define IAC_LOGOUT 18 /* Logout Option */
62#define IAC_BM 19 /* Byte Macro Option */
63#define IAC_SUPDUP 22 /* SUPDUP-OUTPUT Option */
64#define IAC_SENDLOC 23 /* SEND-LOCATION Option */
65#define IAC_TTYPE 24 /* Terminal Type Option */
66#define IAC_EOR 25 /* End of Record Option */
67#define IAC_OUTMRK 27 /* Marking Telnet Option */
68#define IAC_TTYLOC 28 /* Terminal Location Number Option */
69#define IAC_DET 20 /* Data Entry Terminal Option DODIIS */
70#define IAC_X3PAD 30 /* X.3 PAD Option */
71#define IAC_NAWS 31 /* Window Size Option */
72#define IAC_TSPEED 32 /* Terminal Speed Option */
73#define IAC_FLOWCTRL 33 /* Remote Flow Control Option */
74#define IAC_LINEMODE 34 /* Linemode Option */
75#define IAC_XDISPLOC 35 /* X Display Location Option */
76#define IAC_ENVIRON 36 /* Environment Option */
77#define IAC_AUTH 37 /* Authentication */
78#define IAC_ENCRYPT 38 /* Encryption Option */
79#define IAC_NEWENV 39 /* Environment Option */
80#define IAC_CHARSET 42 /* Charset Option */
81#define IAC_COMPORT 44 /* Com Port Control Option */
82
83#ifdef TELNET_IAC_TRACE
84#define TELNET_IAC_DEBUG(...) { syslog.logf(LOG_INFO, __VA_ARGS__); delay(1); }
85#else
86#define TELNET_IAC_DEBUG(...) {}
87#endif
88
89#ifdef TELNET_DATA_TRACE
90#define TELNET_DATA_DEBUG(...) { syslog.logf(LOG_INFO, __VA_ARGS__); delay(1); }
91#else
92#define TELNET_DATA_DEBUG(...) {}
93#endif
94
95int
96telnet_connect(char *host, uint16_t port)
97{
98 if (telnet_state != TELNET_STATE_DISCONNECTED)
99 telnet_disconnect();
100
101 if (!telnet.connect(host, port))
102 return 1;
103
104 telnet.setNoDelay(true);
105
106 telnet_state = TELNET_STATE_CONNECTED;
107 serial_dcd(true);
108
109 if (settings->telnet) {
110 /* start by sending things we support */
111 TELNET_IAC_DEBUG("%s: -> IAC DO SUPPRESS GO AHEAD", __func__);
112 telnet.printf("%c%c%c", IAC, DO, IAC_SGA);
113 TELNET_IAC_DEBUG("%s: -> IAC WILL TTYPE", __func__);
114 telnet.printf("%c%c%c", IAC, WILL, IAC_TTYPE);
115 TELNET_IAC_DEBUG("%s: -> IAC WILL NAWS", __func__);
116 telnet.printf("%c%c%c", IAC, WILL, IAC_NAWS);
117 TELNET_IAC_DEBUG("%s: -> IAC WILL TSPEED", __func__);
118 telnet.printf("%c%c%c", IAC, WILL, IAC_TSPEED);
119 TELNET_IAC_DEBUG("%s: -> IAC WONT LINEMODE", __func__);
120 telnet.printf("%c%c%c", IAC, WONT, IAC_LINEMODE);
121 TELNET_IAC_DEBUG("%s: -> IAC DO STATUS", __func__);
122 telnet.printf("%c%c%c", IAC, DO, IAC_STATUS);
123 }
124
125 return 0;
126}
127
128bool
129telnet_connected(void)
130{
131 if (telnet_state == TELNET_STATE_DISCONNECTED)
132 return false;
133
134 if (!telnet.connected()) {
135 if (telnet_state != TELNET_STATE_DISCONNECTED)
136 telnet_disconnect();
137 return false;
138 }
139
140 return true;
141}
142
143void
144telnet_disconnect(void)
145{
146 telnet.stop();
147 telnet_state = TELNET_STATE_DISCONNECTED;
148 serial_dcd(false);
149}
150
151#ifdef TELNET_IAC_TRACE
152static char iac_name[16];
153char *
154telnet_iac_name(char iac)
155{
156 if (telnet_state == TELNET_STATE_CONNECTED && iac != IAC) {
157 sprintf(iac_name, "%d (%c)", iac, iac);
158 return iac_name;
159 }
160
161 switch (iac) {
162 case IAC_ECHO:
163 sprintf(iac_name, "ECHO");
164 break;
165 case IAC_SGA:
166 sprintf(iac_name, "SGA");
167 break;
168 case IAC_STATUS:
169 sprintf(iac_name, "STATUS");
170 break;
171 case IAC_TTYPE:
172 sprintf(iac_name, "TTYPE");
173 break;
174 case IAC_NAWS:
175 sprintf(iac_name, "NAWS");
176 break;
177 case IAC_TSPEED:
178 sprintf(iac_name, "TSPEED");
179 break;
180 case IAC_FLOWCTRL:
181 sprintf(iac_name, "FLOWCTRL");
182 break;
183 case IAC_LINEMODE:
184 sprintf(iac_name, "LINEMODE");
185 break;
186 case IAC_ENCRYPT:
187 sprintf(iac_name, "ENCRYPT");
188 break;
189 case IAC_AUTH:
190 sprintf(iac_name, "AUTH");
191 break;
192 case IAC_XDISPLOC:
193 sprintf(iac_name, "XDISPLOC");
194 break;
195 case IAC_NEWENV:
196 sprintf(iac_name, "NEWENV");
197 break;
198 case IAC_ENVIRON:
199 sprintf(iac_name, "OLD ENVIRON");
200 break;
201 case SE:
202 sprintf(iac_name, "SE");
203 break;
204 case SB:
205 sprintf(iac_name, "SB");
206 break;
207 case WILL:
208 sprintf(iac_name, "WILL");
209 break;
210 case WONT:
211 sprintf(iac_name, "WONT");
212 break;
213 case DO:
214 sprintf(iac_name, "DO");
215 break;
216 case DONT:
217 sprintf(iac_name, "DONT");
218 break;
219 case IAC:
220 sprintf(iac_name, "IAC");
221 break;
222 default:
223 sprintf(iac_name, "%d", iac);
224 break;
225 }
226 return iac_name;
227}
228#endif
229
230void
231telnet_send_ttype(void)
232{
233 TELNET_IAC_DEBUG("%s: -> IAC SB TTYPE IS %s IAC SE", __func__,
234 settings->telnet_tterm);
235 telnet.printf("%c%c%c%c", IAC, SB, IAC_TTYPE, IS);
236 for (size_t i = 0; i < sizeof(settings->telnet_tterm); i++) {
237 if (settings->telnet_tterm[i] == '\0')
238 break;
239
240 if (settings->telnet_tterm[i] == IAC)
241 telnet.write(IAC);
242 telnet.write(settings->telnet_tterm[i]);
243 }
244 telnet.printf("%c%c", IAC, SE);
245}
246
247void
248telnet_send_naws(void)
249{
250
251 TELNET_IAC_DEBUG("%s: -> IAC SB NAWS IS %dx%d IAC SE", __func__,
252 settings->telnet_tts_w, settings->telnet_tts_h);
253
254 telnet.printf("%c%c%c", IAC, SB, IAC_NAWS);
255
256 /* we only support 8-bit settings, but NAWS is 16-bit * */
257 telnet.write(0);
258 if (settings->telnet_tts_w == IAC)
259 telnet.write(IAC);
260 telnet.write(settings->telnet_tts_w);
261
262 telnet.write(0);
263 if (settings->telnet_tts_h == IAC)
264 telnet.write(IAC);
265 telnet.write(settings->telnet_tts_h);
266
267 telnet.printf("%c%c", IAC, SE);
268}
269
270void
271telnet_send_tspeed(void)
272{
273 TELNET_IAC_DEBUG("%s: -> IAC SB TSPEED IS %d,%d IAC SE", __func__,
274 Serial.baudRate(), Serial.baudRate());
275
276 telnet.printf("%c%c%c%c%d,%d", IAC, SB, IAC_TSPEED, IS,
277 Serial.baudRate(), Serial.baudRate());
278
279 telnet.printf("%c%c", IAC, SE);
280}
281
282int
283telnet_read(void)
284{
285 char b;
286
287 if (!telnet.available())
288 return -1;
289
290 /* when AT$NET=0, just pass everything as-is */
291 if (!settings->telnet)
292 return telnet.read();
293
294 b = telnet.peek();
295 if (telnet_state != TELNET_STATE_CONNECTED || b == IAC)
296 TELNET_IAC_DEBUG("telnet_read[%s]: %s",
297 (telnet_state == TELNET_STATE_CONNECTED ? "connected" :
298 (telnet_state == TELNET_STATE_IAC ? "IAC" :
299 (telnet_state == TELNET_STATE_IAC_WILL ? "WILL" :
300 (telnet_state == TELNET_STATE_IAC_WONT ? "WONT" :
301 (telnet_state == TELNET_STATE_IAC_DO ? "DO" :
302 (telnet_state == TELNET_STATE_IAC_DONT ? "DONT" :
303 (telnet_state == TELNET_STATE_IAC_SB ? "SB" : "?"))))))),
304 telnet_iac_name(b));
305
306 switch (telnet_state) {
307 case TELNET_STATE_CONNECTED:
308 b = telnet.read();
309 switch (b) {
310 case IAC:
311 telnet_state = TELNET_STATE_IAC;
312 break;
313 default:
314 return b;
315 }
316 break;
317 case TELNET_STATE_IAC:
318 /* don't consume byte yet */
319 switch (b) {
320 case IAC:
321 /* escaped IAC, return one IAC */
322 telnet_state = TELNET_STATE_CONNECTED;
323 return telnet.read();
324 case WILL:
325 /* server can do something */
326 telnet_state = TELNET_STATE_IAC_WILL;
327 break;
328 case WONT:
329 /* server will not do something */
330 telnet_state = TELNET_STATE_IAC_WONT;
331 break;
332 case DO:
333 /* server wants us to do something */
334 telnet_state = TELNET_STATE_IAC_DO;
335 break;
336 case DONT:
337 /* server wants us to not do something */
338 telnet_state = TELNET_STATE_IAC_DONT;
339 break;
340 case SB:
341 /* sub-negotiate */
342 telnet_state = TELNET_STATE_IAC_SB;
343 telnet_sb_len = 0;
344 break;
345 default:
346 /* something else, return the original IAC */
347 telnet_state = TELNET_STATE_CONNECTED;
348 return IAC;
349 /* this next non-IAC byte will get returned next */
350 }
351 /* consume byte */
352 telnet.read();
353 break;
354 case TELNET_STATE_IAC_SB:
355 /* keep reading until we see [^IAC] IAC SE */
356 b = telnet.read();
357 TELNET_IAC_DEBUG("telnet_read: SB[%d] %s",
358 telnet_sb_len, telnet_iac_name(b));
359 if (b == SE && telnet_sb_len > 0 &&
360 telnet_sb[telnet_sb_len - 1] == IAC) {
361 TELNET_IAC_DEBUG("telnet_read: processing SB");
362 if (telnet_sb[1] == SEND) {
363 switch (telnet_sb[0]) {
364 case IAC_TTYPE:
365 telnet_send_ttype();
366 break;
367 case IAC_NAWS:
368 telnet_send_naws();
369 break;
370 case IAC_TSPEED:
371 telnet_send_tspeed();
372 break;
373 default:
374 TELNET_IAC_DEBUG("unsupported IAC SB %s",
375 telnet_iac_name(telnet_sb[0]));
376 }
377 } else {
378 syslog.logf(LOG_WARNING, "%s: server is "
379 "telling us SB %d?", __func__, telnet_sb[0]);
380 }
381 telnet_state = TELNET_STATE_CONNECTED;
382 } else {
383 if (telnet_sb_len < sizeof(telnet_sb))
384 telnet_sb[telnet_sb_len++] = b;
385 else {
386 syslog.logf(LOG_ERR, "IAC SB overflow!");
387 telnet_state = TELNET_STATE_CONNECTED;
388 telnet_sb_len = 0;
389 }
390 }
391 break;
392 case TELNET_STATE_IAC_WILL:
393 switch (b = telnet.read()) {
394 case IAC_ECHO:
395 TELNET_IAC_DEBUG("telnet_read: -> IAC DO ECHO");
396 telnet.printf("%c%c%c", IAC, DO, b);
397 break;
398 case IAC_SGA:
399 TELNET_IAC_DEBUG("telnet_read: -> IAC DO SGA");
400 telnet.printf("%c%c%c", IAC, DO, b);
401 break;
402 case IAC_ENCRYPT:
403 /* refuse with DONT to satisfy NetBSD's telnetd */
404 TELNET_IAC_DEBUG("telnet_read: -> IAC DONT ENCRYPT");
405 telnet.printf("%c%c%c", IAC, DONT, b);
406 break;
407 }
408 telnet_state = TELNET_STATE_CONNECTED;
409 break;
410 case TELNET_STATE_IAC_WONT:
411 switch (b = telnet.read()) {
412 default:
413 /* we don't care about any of these yet */
414 break;
415 }
416 telnet_state = TELNET_STATE_CONNECTED;
417 break;
418 case TELNET_STATE_IAC_DO:
419 b = telnet.read();
420 switch (b) {
421 case IAC_BINARY:
422 TELNET_IAC_DEBUG("telnet_read: -> IAC WILL BINARY");
423 telnet.printf("%c%c%c", IAC, WILL, b);
424 break;
425 case IAC_NAWS:
426 case IAC_TSPEED:
427 case IAC_TTYPE:
428 case IAC_FLOWCTRL:
429 break;
430 case IAC_LINEMODE:
431 /* refuse this, we want the server to handle input */
432 /* FALLTHROUGH */
433 default:
434 TELNET_IAC_DEBUG("telnet_read: -> IAC WONT %s",
435 telnet_iac_name(b));
436 telnet.printf("%c%c%c", IAC, WONT, b);
437 break;
438 }
439 telnet_state = TELNET_STATE_CONNECTED;
440 break;
441 case TELNET_STATE_IAC_DONT:
442 switch (b = telnet.read()) {
443 default:
444 TELNET_IAC_DEBUG("telnet_read: IAC DONT %s",
445 telnet_iac_name(b));
446 break;
447 }
448 telnet_state = TELNET_STATE_CONNECTED;
449 break;
450 default:
451 b = telnet.read();
452 TELNET_IAC_DEBUG("telnet_read: read 0x%x but in state %d", b,
453 telnet_state);
454 break;
455 }
456
457 return -1;
458}
459
460int
461telnet_write(char b)
462{
463 /* escape */
464 if (settings->telnet && b == IAC) {
465 TELNET_DATA_DEBUG("telnet_write: escaped IAC");
466 telnet.write(b);
467 }
468
469 TELNET_DATA_DEBUG("telnet_write: 0x%x", b);
470 return telnet.write(b);
471}
472
473int
474telnet_write(String s)
475{
476 String s2 = "";
477
478 for (size_t i = 0; i < s.length(); i++) {
479 /* escape */
480 if (settings->telnet && s.charAt(i) == IAC) {
481 TELNET_DATA_DEBUG("telnet_write: escaped IAC");
482 s2 += IAC;
483 }
484 s2 += s.charAt(i);
485 TELNET_DATA_DEBUG("telnet_write: 0x%x", s.charAt(i));
486 }
487
488 return telnet.print(s2);
489}