Bringing WiFi to the Cidco Mailstation
at main 488 lines 13 kB view raw
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}