ESP8266-based WiFi serial modem emulator ROM
at main 489 lines 13 kB view raw
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}