Bringing WiFi to the Cidco Mailstation
1/*
2 * WiFiStation
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 "wifistation.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
108 if (settings->telnet) {
109 /* start by sending things we support */
110 TELNET_IAC_DEBUG("%s: -> IAC DO SUPPRESS GO AHEAD", __func__);
111 telnet.printf("%c%c%c", IAC, DO, IAC_SGA);
112 TELNET_IAC_DEBUG("%s: -> IAC WILL TTYPE", __func__);
113 telnet.printf("%c%c%c", IAC, WILL, IAC_TTYPE);
114 TELNET_IAC_DEBUG("%s: -> IAC WILL NAWS", __func__);
115 telnet.printf("%c%c%c", IAC, WILL, IAC_NAWS);
116 TELNET_IAC_DEBUG("%s: -> IAC WILL TSPEED", __func__);
117 telnet.printf("%c%c%c", IAC, WILL, IAC_TSPEED);
118 TELNET_IAC_DEBUG("%s: -> IAC WONT LINEMODE", __func__);
119 telnet.printf("%c%c%c", IAC, WONT, IAC_LINEMODE);
120 TELNET_IAC_DEBUG("%s: -> IAC DO STATUS", __func__);
121 telnet.printf("%c%c%c", IAC, DO, IAC_STATUS);
122 }
123
124 return 0;
125}
126
127bool
128telnet_connected(void)
129{
130 if (telnet_state == TELNET_STATE_DISCONNECTED)
131 return false;
132
133 if (!telnet.connected()) {
134 if (telnet_state != TELNET_STATE_DISCONNECTED)
135 telnet_disconnect();
136 return false;
137 }
138
139 return true;
140}
141
142void
143telnet_disconnect(void)
144{
145 telnet.stop();
146 telnet_state = TELNET_STATE_DISCONNECTED;
147}
148
149#ifdef TELNET_IAC_TRACE
150static char iac_name[16];
151char *
152telnet_iac_name(char iac)
153{
154 if (telnet_state == TELNET_STATE_CONNECTED && iac != IAC) {
155 sprintf(iac_name, "%d (%c)", iac, iac);
156 return iac_name;
157 }
158
159 switch (iac) {
160 case IAC_ECHO:
161 sprintf(iac_name, "ECHO");
162 break;
163 case IAC_SGA:
164 sprintf(iac_name, "SGA");
165 break;
166 case IAC_STATUS:
167 sprintf(iac_name, "STATUS");
168 break;
169 case IAC_TTYPE:
170 sprintf(iac_name, "TTYPE");
171 break;
172 case IAC_NAWS:
173 sprintf(iac_name, "NAWS");
174 break;
175 case IAC_TSPEED:
176 sprintf(iac_name, "TSPEED");
177 break;
178 case IAC_FLOWCTRL:
179 sprintf(iac_name, "FLOWCTRL");
180 break;
181 case IAC_LINEMODE:
182 sprintf(iac_name, "LINEMODE");
183 break;
184 case IAC_ENCRYPT:
185 sprintf(iac_name, "ENCRYPT");
186 break;
187 case IAC_AUTH:
188 sprintf(iac_name, "AUTH");
189 break;
190 case IAC_XDISPLOC:
191 sprintf(iac_name, "XDISPLOC");
192 break;
193 case IAC_NEWENV:
194 sprintf(iac_name, "NEWENV");
195 break;
196 case IAC_ENVIRON:
197 sprintf(iac_name, "OLD ENVIRON");
198 break;
199 case SE:
200 sprintf(iac_name, "SE");
201 break;
202 case SB:
203 sprintf(iac_name, "SB");
204 break;
205 case WILL:
206 sprintf(iac_name, "WILL");
207 break;
208 case WONT:
209 sprintf(iac_name, "WONT");
210 break;
211 case DO:
212 sprintf(iac_name, "DO");
213 break;
214 case DONT:
215 sprintf(iac_name, "DONT");
216 break;
217 case IAC:
218 sprintf(iac_name, "IAC");
219 break;
220 default:
221 sprintf(iac_name, "%d", iac);
222 break;
223 }
224 return iac_name;
225}
226#endif
227
228void
229telnet_send_ttype(void)
230{
231 TELNET_IAC_DEBUG("%s: -> IAC SB TTYPE IS %s IAC SE", __func__,
232 settings->telnet_tterm);
233 telnet.printf("%c%c%c%c", IAC, SB, IAC_TTYPE, IS);
234 for (size_t i = 0; i < sizeof(settings->telnet_tterm); i++) {
235 if (settings->telnet_tterm[i] == '\0')
236 break;
237
238 if (settings->telnet_tterm[i] == IAC)
239 telnet.write(IAC);
240 telnet.write(settings->telnet_tterm[i]);
241 }
242 telnet.printf("%c%c", IAC, SE);
243}
244
245void
246telnet_send_naws(void)
247{
248 TELNET_IAC_DEBUG("%s: -> IAC SB NAWS IS %dx%d IAC SE", __func__,
249 settings->telnet_tts_w, settings->telnet_tts_h);
250
251 telnet.printf("%c%c%c", IAC, SB, IAC_NAWS);
252
253 /* we only support 8-bit settings, but NAWS is 16-bit * */
254 telnet.write(0);
255 if (settings->telnet_tts_w == IAC)
256 telnet.write(IAC);
257 telnet.write(settings->telnet_tts_w);
258
259 telnet.write(0);
260 if (settings->telnet_tts_h == IAC)
261 telnet.write(IAC);
262 telnet.write(settings->telnet_tts_h);
263
264 telnet.printf("%c%c", IAC, SE);
265}
266
267void
268telnet_send_tspeed(void)
269{
270 TELNET_IAC_DEBUG("%s: -> IAC SB TSPEED IS %d,%d IAC SE", __func__,
271 Serial.baudRate(), Serial.baudRate());
272
273 telnet.printf("%c%c%c%c%d,%d", IAC, SB, IAC_TSPEED, IS,
274 Serial.baudRate(), Serial.baudRate());
275
276 telnet.printf("%c%c", IAC, SE);
277}
278
279int
280telnet_read(void)
281{
282 char b;
283
284 if (!telnet.available())
285 return -1;
286
287 /* when AT$NET=0, just pass everything as-is */
288 if (!settings->telnet)
289 return telnet.read();
290
291 b = telnet.peek();
292 if (telnet_state != TELNET_STATE_CONNECTED || b == IAC)
293 TELNET_IAC_DEBUG("telnet_read[%s]: %s",
294 (telnet_state == TELNET_STATE_CONNECTED ? "connected" :
295 (telnet_state == TELNET_STATE_IAC ? "IAC" :
296 (telnet_state == TELNET_STATE_IAC_WILL ? "WILL" :
297 (telnet_state == TELNET_STATE_IAC_WONT ? "WONT" :
298 (telnet_state == TELNET_STATE_IAC_DO ? "DO" :
299 (telnet_state == TELNET_STATE_IAC_DONT ? "DONT" :
300 (telnet_state == TELNET_STATE_IAC_SB ? "SB" : "?"))))))),
301 telnet_iac_name(b));
302
303 switch (telnet_state) {
304 case TELNET_STATE_CONNECTED:
305 b = telnet.read();
306 switch (b) {
307 case IAC:
308 telnet_state = TELNET_STATE_IAC;
309 break;
310 default:
311 return b;
312 }
313 break;
314 case TELNET_STATE_IAC:
315 /* don't consume byte yet */
316 switch (b) {
317 case IAC:
318 /* escaped IAC, return one IAC */
319 telnet_state = TELNET_STATE_CONNECTED;
320 return telnet.read();
321 case WILL:
322 /* server can do something */
323 telnet_state = TELNET_STATE_IAC_WILL;
324 break;
325 case WONT:
326 /* server will not do something */
327 telnet_state = TELNET_STATE_IAC_WONT;
328 break;
329 case DO:
330 /* server wants us to do something */
331 telnet_state = TELNET_STATE_IAC_DO;
332 break;
333 case DONT:
334 /* server wants us to not do something */
335 telnet_state = TELNET_STATE_IAC_DONT;
336 break;
337 case SB:
338 /* sub-negotiate */
339 telnet_state = TELNET_STATE_IAC_SB;
340 telnet_sb_len = 0;
341 break;
342 default:
343 /* something else, return the original IAC */
344 telnet_state = TELNET_STATE_CONNECTED;
345 return IAC;
346 /* this next non-IAC byte will get returned next */
347 }
348 /* consume byte */
349 telnet.read();
350 break;
351 case TELNET_STATE_IAC_SB:
352 /* keep reading until we see [^IAC] IAC SE */
353 b = telnet.read();
354 TELNET_IAC_DEBUG("telnet_read: SB[%d] %s",
355 telnet_sb_len, telnet_iac_name(b));
356 if (b == SE && telnet_sb_len > 0 &&
357 telnet_sb[telnet_sb_len - 1] == IAC) {
358 TELNET_IAC_DEBUG("telnet_read: processing SB");
359 if (telnet_sb[1] == SEND) {
360 switch (telnet_sb[0]) {
361 case IAC_TTYPE:
362 telnet_send_ttype();
363 break;
364 case IAC_NAWS:
365 telnet_send_naws();
366 break;
367 case IAC_TSPEED:
368 telnet_send_tspeed();
369 break;
370 default:
371 TELNET_IAC_DEBUG("unsupported IAC SB %s",
372 telnet_iac_name(telnet_sb[0]));
373 }
374 } else {
375 syslog.logf(LOG_WARNING, "%s: server is "
376 "telling us SB %d?", __func__, telnet_sb[0]);
377 }
378 telnet_state = TELNET_STATE_CONNECTED;
379 } else {
380 if (telnet_sb_len < sizeof(telnet_sb))
381 telnet_sb[telnet_sb_len++] = b;
382 else {
383 syslog.logf(LOG_ERR, "IAC SB overflow!");
384 telnet_state = TELNET_STATE_CONNECTED;
385 telnet_sb_len = 0;
386 }
387 }
388 break;
389 case TELNET_STATE_IAC_WILL:
390 switch (b = telnet.read()) {
391 case IAC_ECHO:
392 TELNET_IAC_DEBUG("telnet_read: -> IAC DO ECHO");
393 telnet.printf("%c%c%c", IAC, DO, b);
394 break;
395 case IAC_SGA:
396 TELNET_IAC_DEBUG("telnet_read: -> IAC DO SGA");
397 telnet.printf("%c%c%c", IAC, DO, b);
398 break;
399 case IAC_ENCRYPT:
400 /* refuse with DONT to satisfy NetBSD's telnetd */
401 TELNET_IAC_DEBUG("telnet_read: -> IAC DONT ENCRYPT");
402 telnet.printf("%c%c%c", IAC, DONT, b);
403 break;
404 }
405 telnet_state = TELNET_STATE_CONNECTED;
406 break;
407 case TELNET_STATE_IAC_WONT:
408 switch (b = telnet.read()) {
409 default:
410 /* we don't care about any of these yet */
411 break;
412 }
413 telnet_state = TELNET_STATE_CONNECTED;
414 break;
415 case TELNET_STATE_IAC_DO:
416 b = telnet.read();
417 switch (b) {
418 case IAC_BINARY:
419 TELNET_IAC_DEBUG("telnet_read: -> IAC WILL BINARY");
420 telnet.printf("%c%c%c", IAC, WILL, b);
421 break;
422 case IAC_NAWS:
423 telnet_send_naws();
424 break;
425 case IAC_TSPEED:
426 case IAC_TTYPE:
427 case IAC_FLOWCTRL:
428 break;
429 case IAC_LINEMODE:
430 /* refuse this, we want the server to handle input */
431 /* FALLTHROUGH */
432 default:
433 TELNET_IAC_DEBUG("telnet_read: -> IAC WONT %s",
434 telnet_iac_name(b));
435 telnet.printf("%c%c%c", IAC, WONT, b);
436 break;
437 }
438 telnet_state = TELNET_STATE_CONNECTED;
439 break;
440 case TELNET_STATE_IAC_DONT:
441 switch (b = telnet.read()) {
442 default:
443 TELNET_IAC_DEBUG("telnet_read: IAC DONT %s",
444 telnet_iac_name(b));
445 break;
446 }
447 telnet_state = TELNET_STATE_CONNECTED;
448 break;
449 default:
450 b = telnet.read();
451 TELNET_IAC_DEBUG("telnet_read: read 0x%x but in state %d", b,
452 telnet_state);
453 break;
454 }
455
456 return -1;
457}
458
459int
460telnet_write(char b)
461{
462 /* escape */
463 if (settings->telnet && b == IAC) {
464 TELNET_DATA_DEBUG("telnet_write: escaped IAC");
465 telnet.write(b);
466 }
467
468 TELNET_DATA_DEBUG("telnet_write: 0x%x", b);
469 return telnet.write(b);
470}
471
472int
473telnet_write(String s)
474{
475 String s2 = "";
476
477 for (size_t i = 0; i < s.length(); i++) {
478 /* escape */
479 if (settings->telnet && s.charAt(i) == IAC) {
480 TELNET_DATA_DEBUG("telnet_write: escaped IAC");
481 s2 += IAC;
482 }
483 s2 += s.charAt(i);
484 TELNET_DATA_DEBUG("telnet_write: 0x%x", s.charAt(i));
485 }
486
487 return telnet.print(s2);
488}