From 9261fb6c0445e6cd22d71ddc9760156fd841d2a2 Mon Sep 17 00:00:00 2001 From: y4my4my4m <8145020+y4my4my4m@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:48:26 +0900 Subject: [PATCH 1/3] Enhance Telnet functionality and error handling - Added Telnet negotiation support and improved state management for parsing Telnet commands and ANSI escape sequences. - Implemented error handling for malformed IPV4 packets in the network handler. - Updated TCP connection handling to include retransmission logic and improved error reporting. - Refactored various functions for better readability and maintainability, including socket management and buffer handling. - Introduced new constants for TCP maximum retries and enhanced packet parsing validation across protocols. --- src/Home/Net/Drivers/PCNet.ZC | 44 ++-- src/Home/Net/Programs/Telnet/Telnet.ZC | 235 ++++++++++++------ src/Home/Net/Programs/Telnet/TelnetANSI.ZC | 8 +- src/Home/Net/Programs/Telnet/TelnetClass.ZC | 21 ++ src/Home/Net/Programs/Telnet/TelnetHelpers.ZC | 12 +- .../Net/Programs/Telnet/TelnetNegotiation.ZC | 206 ++++++--------- src/Home/Net/Protocols/IPV4.ZC | 34 ++- src/Home/Net/Protocols/Sockets.ZC | 17 +- src/Home/Net/Protocols/TCP/TCP.HH | 2 + src/Home/Net/Protocols/TCP/TCP.ZC | 90 +++++-- src/Home/Net/Protocols/TCP/TCPHandler.ZC | 20 +- src/Home/Net/Utilities/BST.ZC | 7 +- src/Home/Net/Utilities/NetHandler.ZC | 6 +- src/Home/Net/Utilities/NetQueue.ZC | 20 +- 14 files changed, 444 insertions(+), 278 deletions(-) diff --git a/src/Home/Net/Drivers/PCNet.ZC b/src/Home/Net/Drivers/PCNet.ZC index 637956de8..a3fb20114 100755 --- a/src/Home/Net/Drivers/PCNet.ZC +++ b/src/Home/Net/Drivers/PCNet.ZC @@ -79,6 +79,7 @@ #define PCNET_DESCRIPTORf_ENP 24 #define PCNET_DESCRIPTORf_STP 25 +#define PCNET_DESCRIPTORf_ERR 30 // set when FRAM/OFLO/CRC/BUFF error occurred on this frame #define PCNET_DESCRIPTORf_OWN 31 // AMD PCNet datasheet p.1-992, 1-994 class CPCNet @@ -148,7 +149,7 @@ U0 PCNetReset() InU32(PCNetIOBaseGet + PCNET_DW_RESET); InU16(PCNetIOBaseGet + PCNET_WD_RESET); - Busy(5); // OSDev says minimum 1 µS + Busy(5); // OSDev says minimum 1 ┬╡S } U0 PCNet32BitModeEnable() @@ -214,22 +215,22 @@ U32 PCNetCSRRead(U32 csr) } U0 PCNetAutoLinkSelect() -{ - U32 bcr = PCNetCSRRead(PCNET_BCR_FULL_DUPLEX_CTRL); +{ // BCR2 ASEL: let the card auto-select the media interface. + U32 bcr = PCNetBCRRead(PCNET_BCR_MISC_CONFIG); - Bts(&bcr, PCNET_BCR_FULL_DUPLEX_CTRL_FDEN); - Bts(&bcr, PCNET_BCR_FULL_DUPLEX_CTRL_AUIFD); + Bts(&bcr, PCNET_BCR_MISC_CONFIG_ASEL); - PCNetBCRWrite(PCNET_BCR_FULL_DUPLEX_CTRL, bcr); + PCNetBCRWrite(PCNET_BCR_MISC_CONFIG, bcr); } U0 PCNetEnableFullDuplex() -{ - U32 bcr = PCNetCSRRead(PCNET_BCR_MISC_CONFIG); +{ // BCR9 FDEN/AUIFD: enable full-duplex operation. + U32 bcr = PCNetBCRRead(PCNET_BCR_FULL_DUPLEX_CTRL); - Bts(&bcr, PCNET_BCR_MISC_CONFIG_ASEL); + Bts(&bcr, PCNET_BCR_FULL_DUPLEX_CTRL_FDEN); + Bts(&bcr, PCNET_BCR_FULL_DUPLEX_CTRL_AUIFD); - PCNetBCRWrite(PCNET_BCR_MISC_CONFIG, bcr); + PCNetBCRWrite(PCNET_BCR_FULL_DUPLEX_CTRL, bcr); } @@ -676,15 +677,24 @@ interrupt U0 PCNetIRQ() NetLog("$$BD,CYAN$$$$FD,WHITE$$" "PCNET IRQ: Saw owned RX DE index %d.", pcnet.current_rx_de_index); - de_index = PCNetPacketReceive(&packet_buffer, &packet_length); - - if (de_index >= 0) // todo: necessary? check increment logic in PCNetPacketReceive. - { - NetLog("PCNET IRQ: Pushing copy into Net Queue, releasing receive packet."); - // uncached read - NetQueuePush(packet_buffer + dev.uncached_alias, packet_length); + if (PCIBt(&entry[pcnet.current_rx_de_index].status1, PCNET_DESCRIPTORf_ERR)) + { // drop frames the card flagged with FRAM/OFLO/CRC/BUFF errors + NetErr("PCNET IRQ: RX DE %d has error status, dropping frame.", pcnet.current_rx_de_index); + de_index = PCNetPacketReceive(&packet_buffer, &packet_length); PCNetReceivePacketRelease(de_index); } + else + { + de_index = PCNetPacketReceive(&packet_buffer, &packet_length); + + if (de_index >= 0) // todo: necessary? check increment logic in PCNetPacketReceive. + { + NetLog("PCNET IRQ: Pushing copy into Net Queue, releasing receive packet."); + // uncached read + NetQueuePush(packet_buffer + dev.uncached_alias, packet_length); + PCNetReceivePacketRelease(de_index); + } + } Bts(&csr, PCNET_CTRL_RINT); diff --git a/src/Home/Net/Programs/Telnet/Telnet.ZC b/src/Home/Net/Programs/Telnet/Telnet.ZC index 8b7d7a537..37caabd39 100755 --- a/src/Home/Net/Programs/Telnet/Telnet.ZC +++ b/src/Home/Net/Programs/Telnet/Telnet.ZC @@ -18,6 +18,7 @@ Cd(__DIR__);; #include "TelnetClass" #include "TelnetHelpers" +#include "TelnetNegotiation" #include "TelnetANSI" // If you're using a custom palette, the colors might not seem right. @@ -40,9 +41,7 @@ I64 TelnetOpen(U8 *host, U16 port) { return sock; } - // sock(CTCPSocket *)->timeout = 0; - // sock(CTCPSocket *)->timeout = TCP_TIMEOUT; - return sock; + return sock; // caller sets the socket timeout for poll-mode receive } U0 TermBottom() @@ -132,9 +131,9 @@ U0 HandleControlCodes(U8 ch) { SysLog("case 127"); } if (ch == 0x24) { - DocPrint(term.doc, "$$$$"); + DocPrint(term.doc, "$$$$"); // escape DolDoc's '$', printing it raw would start a doc command } - if (ch >= 32 && ch < 256) // ZealOS's ASCII is up to 255 + else if (ch >= 32 && ch < 256) // ZealOS's ASCII is up to 255 { DocPrint(term.doc, "%c", ch); } @@ -144,76 +143,147 @@ U0 HandleControlCodes(U8 ch) { } } -U0 ANSIParse() -{ - // Basic Telnet protocol parser - U8 *ptr = term.buffer; - while (ptr < term.buffer + term.buffer_len) { - // disable all SAUCE00 art signature? dsnt work - // if (StrNCompare(ptr, "\033SAUCE", 6) == 0) - // { - // SysLog("SAUCE found\n"); - // term.buffer_len = ptr - term.buffer; - // } - // Telnet negotiation sequence - if (*ptr == NEGOTIATE) { - /*telnet negotiation seems proper...however i don't really see any BBS systems relying on this too much... - for instance, the screen size tends to be reported using the Curser Report and not Telnet's NAWS */ - if (term.sock_ready) TelnetNegotiate(term.sock, ptr); - ptr += 3; - } - else if (*ptr == ANSI_ESC) { - // ANSI escape sequence - ptr++; - if (*ptr == ANSI_CSI) { +U0 TelnetByteProcess(U8 ch) +{ // Stream state machine: one byte at a time, state persists in term across + // TCPSocketReceive calls, so telnet commands and ANSI escape sequences split + // over TCP segment boundaries are parsed correctly instead of printed as garbage. + switch (term.parse_state) { + case TELNET_STATE_NORMAL: + if (ch == NEGOTIATE) + term.parse_state = TELNET_STATE_IAC; + else if (ch == ANSI_ESC) + term.parse_state = TELNET_STATE_ESC; + else + HandleControlCodes(ch); + break; - ptr++; // Move past '[' + case TELNET_STATE_IAC: + switch (ch) { + case NEGOTIATE: // IAC IAC is an escaped literal 0xFF data byte + HandleControlCodes(NEGOTIATE); + term.parse_state = TELNET_STATE_NORMAL; + break; + case WILL: + case WONT: + case DO: + case DONT: + term.iac_cmd = ch; + term.parse_state = TELNET_STATE_IAC_OPT; + break; + case SB: + term.sb_len = 0; + term.parse_state = TELNET_STATE_SB; + break; + default: // NOP, GA, AYT, ... : consume and ignore + term.parse_state = TELNET_STATE_NORMAL; + break; + } + break; - U8 seqBuffer[64]; - MemSet(seqBuffer, 0, sizeof(seqBuffer)); // Set all bytes in seqBuffer to 0 + case TELNET_STATE_IAC_OPT: + if (term.sock_ready) + TelnetNegotiateOption(term.sock, term.iac_cmd, ch); + term.parse_state = TELNET_STATE_NORMAL; + break; + + case TELNET_STATE_SB: + if (ch == NEGOTIATE) + term.parse_state = TELNET_STATE_SB_IAC; + else if (term.sb_len < TELNET_SB_BUF_SIZE) + term.sb_buf[term.sb_len++] = ch; + break; - U8 *seqPtr = seqBuffer; + case TELNET_STATE_SB_IAC: + if (ch == SE) { // IAC SE terminates the subnegotiation + if (term.sock_ready) + TelnetSubnegotiate(term.sock, term.sb_buf, term.sb_len); + term.parse_state = TELNET_STATE_NORMAL; + } + else if (ch == NEGOTIATE) { // IAC IAC inside SB: literal 0xFF payload byte + if (term.sb_len < TELNET_SB_BUF_SIZE) + term.sb_buf[term.sb_len++] = NEGOTIATE; + term.parse_state = TELNET_STATE_SB; + } + else // malformed subnegotiation, bail out + term.parse_state = TELNET_STATE_NORMAL; + break; - while (*ptr && !IsAlpha(*ptr) && (seqPtr - seqBuffer < sizeof(seqBuffer) - 1)) { - *seqPtr++ = *ptr++; - } - *seqPtr++ = *ptr++; // Append the final letter - *seqPtr = '\0'; // Null-terminate + case TELNET_STATE_ESC: + if (ch == ANSI_CSI) { + term.seq_len = 0; + term.parse_state = TELNET_STATE_CSI; + } + else { // non-CSI escape: drop the ESC, handle the byte normally + term.parse_state = TELNET_STATE_NORMAL; + TelnetByteProcess(ch); + } + break; - HandleANSISequence(seqBuffer); + case TELNET_STATE_CSI: + if (IsAlpha(ch) || term.seq_len >= TELNET_SEQ_BUF_SIZE - 2) { + term.seq_buf[term.seq_len++] = ch; // final byte (or overlong sequence force-cut) + term.seq_buf[term.seq_len] = '\0'; + HandleANSISequence(term.seq_buf); + term.parse_state = TELNET_STATE_NORMAL; } - } - else { - // Print the received character - HandleControlCodes(*ptr); - ptr++; - } + else + term.seq_buf[term.seq_len++] = ch; + break; } } +U0 ANSIParse() +{ + I64 i; + for (i = 0; i < term.buffer_len; i++) + TelnetByteProcess(term.buffer[i]); +} + U0 TerminalTask() { - while (!term.sock_ready) { - Sleep(100); // Avoid busy waiting - } + I64 received; + + while (!force_disconnect) { + // Claim busy BEFORE checking sock_ready: TelnetDisconnect clears sock_ready + // then waits on task_idle, so this order means it can never observe a stale + // idle flag while we are about to touch the socket. + term.task_idle = FALSE; + + if (!term.sock_ready) { + term.task_idle = TRUE; // main task may now safely close/free the socket + Sleep(50); + continue; + } - while (term.sock_ready && !force_disconnect) { -receive_data: - term.buffer_len = TCPSocketReceive(term.sock, term.buffer, BUF_SIZE - 1); - if (term.buffer_len > 0) { - term.buffer[term.buffer_len] = '\0'; - // parse the buffer + received = TCPSocketReceive(term.sock, term.buffer, BUF_SIZE - 1); + + if (received > 0) { + term.buffer_len = received; ANSIParse; - } else { - //SysLog("BUF_SIZE: %d\n", BUF_SIZE); - if (!term.sock_ready || force_disconnect) - DocPrint(term.doc, "Error: Connection closed by the remote host.\n"); - else { - //SysLog("goto received_data\n"); - goto receive_data; - } - break; } + else if (received == 0) { // orderly close or reset by remote + term.sock_ready = 0; + DocPrint(term.doc, "\n$$BG,RED$$$$WHITE$$Connection closed by remote host. Shift-Esc for splash.$$FG$$$$BG$$\n"); + } + else + Sleep(20); // non-blocking receive: nothing pending yet } + + term.task_idle = TRUE; +} + +U0 TelnetDisconnect() +{ // Close the current socket without racing TerminalTask's receive path. + if (term.sock == 0) + return; + + term.sock_ready = 0; + + while (!term.task_idle) + Sleep(10); // wait for TerminalTask to finish its in-flight receive + + term.sock(CTCPSocket *)->timeout = 500; // bounded graceful FIN handshake + TCPSocketClose(term.sock); + term.sock = 0; } U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { @@ -222,12 +292,22 @@ U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) { term.window_height = 25; term.doc = Fs->display_doc; term.waiting_for_input = TRUE; + term.sock = 0; term.sock_ready = 0; + term.task_idle = TRUE; + term.parse_state = TELNET_STATE_NORMAL; + term.buffer_len = 0; + force_disconnect = FALSE; // reset the global so a second run doesn't exit instantly I64 art_path = "::/Home/Net/Programs/Telnet/Art/TelnetSplash.ans"; I64 message_code, arg1, arg2; GrPaletteGet(original_palette); AutoComplete(OFF); + // Single receive task for the whole session; it idles while sock_ready is 0. + // (Previously one was spawned per connection, leaking tasks that kept using + // freed sockets after reconnects.) + term.task = Spawn(&TerminalTask, NULL, "Telnet"); + DocMax; DocCursor(OFF); @@ -281,10 +361,9 @@ show_splash: term.buffer_len = ANSIArtLoad(art_path, term.buffer); if (term.buffer_len > 0) { term.buffer[term.buffer_len] = '\0'; - // parse the buffer + term.parse_state = TELNET_STATE_NORMAL; // don't inherit half-parsed state from a dropped connection ANSIParse; TermBottom; - // Free(term.buffer); } else { DocClear; @@ -294,20 +373,21 @@ show_splash: init_connection: while (!term.waiting_for_input || host != NULL) { - // if(term.sock != NULL) TCPSocketClose(term.sock); - // TODO: should probably kill the task if it already exists too? - - // Spawn a task to receive data from the socket - term.task = Spawn(&TerminalTask, NULL, "Telnet"); - + TelnetDisconnect; // safely drop any previous connection first term.sock = TelnetOpen(host, port); if (term.sock <= 0) { - // return; + term.sock = 0; term.waiting_for_input = TRUE; host = NULL; goto show_splash; } + + // Poll-mode receives: TerminalTask paces itself, and close paths never + // have to wait out a long blocking receive on a soon-to-be-freed socket. + term.sock(CTCPSocket *)->timeout = 0; + term.parse_state = TELNET_STATE_NORMAL; + Fs->border_attr = WHITE << 4 + GREEN; term.sock_ready = 1; // Signal that the socket is ready term.waiting_for_input = FALSE; @@ -416,16 +496,16 @@ init_connection: default: break; } + break; // was falling through and sending backspace codes on every TAB case CH_BACKSPACE: if (term.sock_ready) TCPSocketSendString(term.sock, "\x08\x7F"); break; case CH_SHIFT_ESC: - if (term.sock_ready) - { + if (term.sock) + { // covers both live connections and remote-closed sockets not yet freed term.waiting_for_input = TRUE; - term.sock_ready = 0; host = NULL; - TCPSocketClose(term.sock); + TelnetDisconnect; DocClear; "Telnet connection closed.\n"; Sleep(100); @@ -461,10 +541,9 @@ init_connection: catch PutExcept; - // ideally go back to the splashscreen and wait for another input? - // term.sock_ready = 0; - // term.waiting_for_input = TRUE; - // goto show_splash; + // Tear down: stop the receive task, then free any remaining socket. + force_disconnect = TRUE; + TelnetDisconnect; MenuPop; SettingsPop; diff --git a/src/Home/Net/Programs/Telnet/TelnetANSI.ZC b/src/Home/Net/Programs/Telnet/TelnetANSI.ZC index c62813bd8..df46837c2 100644 --- a/src/Home/Net/Programs/Telnet/TelnetANSI.ZC +++ b/src/Home/Net/Programs/Telnet/TelnetANSI.ZC @@ -21,6 +21,9 @@ U0 HandleANSISequence(U8 *sequence) } } + if (ansi_param_count >= MAX_ANSI_PARAMS) + ansi_param_count = MAX_ANSI_PARAMS - 1; // loops below use m <= ansi_param_count + // Now ptr should be at the final letter of the sequence U8 actionCode = *ptr; // Handle specific ANSI escape sequences @@ -450,12 +453,13 @@ U0 HandleANSISequence(U8 *sequence) break; case '?': - + I64 code = 0; + ptr++; // move past '?' while (IsDigit(*ptr)) { code = code * 10 + (*ptr - '0'); - + ptr++; } switch (code) { case 25: diff --git a/src/Home/Net/Programs/Telnet/TelnetClass.ZC b/src/Home/Net/Programs/Telnet/TelnetClass.ZC index 067ea5e51..36068e271 100755 --- a/src/Home/Net/Programs/Telnet/TelnetClass.ZC +++ b/src/Home/Net/Programs/Telnet/TelnetClass.ZC @@ -1,6 +1,20 @@ +// Telnet stream parser states. State persists across TCPSocketReceive calls, +// so telnet commands and ANSI sequences split across TCP segments parse correctly. +#define TELNET_STATE_NORMAL 0 // plain data +#define TELNET_STATE_IAC 1 // saw IAC, expect command byte +#define TELNET_STATE_IAC_OPT 2 // saw WILL/WONT/DO/DONT, expect option byte +#define TELNET_STATE_SB 3 // inside subnegotiation, collecting payload +#define TELNET_STATE_SB_IAC 4 // saw IAC inside subnegotiation +#define TELNET_STATE_ESC 5 // saw ESC +#define TELNET_STATE_CSI 6 // saw ESC [, collecting until final byte + +#define TELNET_SEQ_BUF_SIZE 64 // ANSI CSI parameter collect buffer +#define TELNET_SB_BUF_SIZE 64 // telnet subnegotiation payload buffer + class Terminal { I64 sock; Bool sock_ready; + Bool task_idle; // TRUE while TerminalTask is not touching sock; gates safe socket close Bool waiting_for_input; I64 window_width; I64 window_height; @@ -12,6 +26,13 @@ class Terminal { I64 current_row; I64 current_col; + I64 parse_state; // TELNET_STATE_* + U8 iac_cmd; // pending WILL/WONT/DO/DONT command byte + U8 seq_buf[TELNET_SEQ_BUF_SIZE]; + I64 seq_len; + U8 sb_buf[TELNET_SB_BUF_SIZE]; + I64 sb_len; + U8 buffer[BUF_SIZE]; I64 buffer_len; } term; diff --git a/src/Home/Net/Programs/Telnet/TelnetHelpers.ZC b/src/Home/Net/Programs/Telnet/TelnetHelpers.ZC index 937b2a501..48c05cb8b 100755 --- a/src/Home/Net/Programs/Telnet/TelnetHelpers.ZC +++ b/src/Home/Net/Programs/Telnet/TelnetHelpers.ZC @@ -1,6 +1,5 @@ -Cd(__DIR__); -#include "TelnetClass" -#include "TelnetNegotiation" +// Include order is managed by Telnet.ZC: +// TelnetClass -> TelnetHelpers -> TelnetNegotiation -> TelnetANSI. U8 IsDigit(U8 ch) { return '0' <= ch <= '9'; @@ -26,10 +25,9 @@ I64 ANSIArtLoad(U8 *filename, U8 *buffer) { return -1; } - // Allocate memory for the buffer based on file size - *buffer = MAlloc(file->de.size); - if (!(*buffer)) { - PrintErr("Failed to allocate memory for the buffer"); + // buffer is the caller's fixed BUF_SIZE array; refuse files that don't fit. + if (file->de.size > BUF_SIZE - 1) { + PrintErr("ANSI art file too big: %d bytes (max %d)", file->de.size, BUF_SIZE - 1); FClose(file); return -1; } diff --git a/src/Home/Net/Programs/Telnet/TelnetNegotiation.ZC b/src/Home/Net/Programs/Telnet/TelnetNegotiation.ZC index 402508397..a5d819dd5 100755 --- a/src/Home/Net/Programs/Telnet/TelnetNegotiation.ZC +++ b/src/Home/Net/Programs/Telnet/TelnetNegotiation.ZC @@ -14,40 +14,9 @@ #define SE 0xF0 #define CHARSET 0x2A -// #define TELNET_IAC 255 /* 0xff - Interpret as command */ -// #define TELNET_DONT 254 /* 0xfe - Don't do option */ -// #define TELNET_DO 253 /* 0xfd - Do option */ -// #define TELNET_WONT 252 /* 0xfc - Won't do option */ -// #define TELNET_WILL 251 /* 0xfb - Will do option */ - -// #define TELNET_SB 250 /* 0xfa - sub-negotiation */ -// #define TELNET_GA 249 /* 0xf9 - Go ahead */ -// #define TELNET_EL 248 /* 0xf8 - Erase line */ -// #define TELNET_EC 247 /* 0xf7 - Erase char */ -// #define TELNET_AYT 246 /* 0xf6 - Are you there? */ -// #define TELNET_AO 245 /* 0xf5 - Abort output */ -// #define TELNET_IP 244 /* 0xf4 - Interrupt process */ -// #define TELNET_BRK 243 /* 0xf3 - Break */ -// #define TELNET_SYNC 242 /* 0xf2 - Data mark */ -// #define TELNET_NOP 241 /* 0xf1 - No operation */ -#define SE_END 240 - -// #define TELNET_NAWS 31 -// #define TELNET_TERMINAL_TYPE 24 -#define SEND_LOCATION 23 +#define SEND_LOCATION 23 #define BINARY_TRANSMISSION 0 - - -// U0 TelnetRequest(I64 sock, U8 option_code) -// { -// U8 request[3]; -// request[0] = IAC; -// request[1] = WILL; -// request[2] = option_code; - -// TCPSocketSend(sock, request, 3); -// } U0 SendWindowSize(I64 sock, U16 cols, U16 rows) { U8 buf[9]; @@ -55,7 +24,7 @@ U0 SendWindowSize(I64 sock, U16 cols, U16 rows) buf[0] = IAC; buf[1] = SB; buf[2] = NAWS; - // if dynamic resolution, make sure to account for 255 AIC (byte stuffing needs to be done) + // if dynamic resolution, make sure to account for 255 IAC (byte stuffing needs to be done) buf[3] = cols >> 8; // High byte of columns buf[4] = cols & 0xFF; // Low byte of columns buf[5] = rows >> 8; // High byte of rows @@ -69,7 +38,6 @@ U0 SendWindowSize(I64 sock, U16 cols, U16 rows) U0 SendTerminalType(I64 sock) { - U8 response[14]; response[0] = IAC; @@ -90,113 +58,87 @@ U0 SendTerminalType(I64 sock) response[13] = SE; SysLog("SendTerminalType: ANSI-BBS\n"); - TCPSocketSend(sock, response, 14); + TCPSocketSend(sock, response, 14); } -U0 TelnetNegotiate(I64 sock, U8 *ptr) -{ - U8 negotiation_code = *(ptr + 1); - U8 option_code = *(ptr + 2); - - - SysLog("Negotiation code: %d | Option code: %d\n", negotiation_code, option_code); - - // Check if this is a subnegotiation request - if (negotiation_code == SB) - { - SysLog("SB: %d\n", option_code); - if (option_code == TERMINAL_TYPE && *(ptr + 3) == SEND) - { - SendTerminalType(sock); - } - else if (option_code == NAWS && *(ptr + 3) == SEND) - { - SendWindowSize(sock, 80, 25); - } - return; - } - - // Otherwise, handle it as a normal negotiation... +U0 TelnetNegotiateOption(I64 sock, U8 negotiation_code, U8 option_code) +{ // Respond to a 3-byte negotiation: IAC