/* PUBLIC DOMAIN - March 20, 2007 - Jon Mayo * Last Update: April 6, 2007 */ /* TODO: * + handle TCP Urgent/OOB state changes (like SYNCH) */ #define NDEBUG /* References: * RFC 854 - Telnet Protocol Specification * RFC 855 - Telnet Option Specification * RFC 857 - Telnet Echo Option * RFC 859 - Telnet Status Option * RFC 861 - Telnet Extended Options: List Options * RFC 1143 - The Q Method of Implementing TELNET Option Negotiation * RFC 1184 - Telnet Linemode Option * RFC 1572 - Telnet Environment Option * * others? * RFC 1080 * RFC 2066 * RFC 1647 * * special characters to consider * 255 IAC * * commands that don't accept options * 244 IP * 245 AO * 246 AYT * 247 EC * 248 EL * 249 GA * * requests: * DO ----- WILL .: initiator begins using option * \ * --- WONT .: responder must not use option * * WILL --- DO .: responder begins using after sending DO * \ * - DONT .: initiator must not use option * * DONT --- WONT .: notification of initiator deactivated option * * WONT --- DONT .: notification that responder should deactivate option * * (avoid loops of DONT-WONT) * * more info: * http://www.ics.uci.edu/~rohit/IEEE-L7-v2.html * http://www.garlic.com/~lynn/rfcietff.htm */ #include #include #include /* optional macros & tables of arpa-telnet.h to define */ #define TELCMDS #define TELOPTS #include "arpa-telnet.h" #include "telnet.h" enum telnet_event_type { TelnetEventText, TelnetEventControl }; enum telnet_state { TelnetStateText, TelnetStateIacIac, /* IAC escape */ TelnetStateIacCommand, /* WILL WONT DO DONT ... */ TelnetStateIacOption, /* TELOPT_xxx */ TelnetStateSb, /* SB ... */ TelnetStateSbIac /* IAC inside Sb */ }; struct telnet_event { enum telnet_event_type type; }; struct telnet_info { enum telnet_state telnet_state; const char *inbuf; size_t inbuf_len, inbuf_current; char command, option; size_t extra_len; size_t extra_max; #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) char extra[]; #else char extra[1]; /* hack to do flex arrays in C89 */ #endif }; struct telnet_info *telnet_create(size_t extra_max) { struct telnet_info *ret; /* if extra_max is 0 pick a reasonable size */ if(!extra_max) extra_max=16; #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 FAM */ ret=malloc(sizeof *ret + extra_max); #else /* hack to do flex arrays in C89 */ ret=malloc(sizeof *ret + extra_max - 1); #endif ret->telnet_state=TelnetStateText; ret->extra_len=0; ret->extra_max=extra_max; ret->inbuf_len=0; ret->inbuf_current=0; return ret; } /* loads a buffer to the telnet engine */ int telnet_begin(struct telnet_info *ts, size_t inbuf_len, const char *inbuf) { assert(ts != NULL); assert(ts->inbuf == NULL); ts->inbuf=inbuf; ts->inbuf_len=inbuf_len; ts->inbuf_current=0; return 1; } #ifndef NDEBUG static void hexdump(size_t n, const char *d) { size_t i; for(i=0;iinbuf_current>=ts->inbuf_len) { /* no more data */ return 0; } switch(ts->telnet_state) { case TelnetStateIacIac: case TelnetStateText: *ptr=(const char*)ts->inbuf+ts->inbuf_current; current=ts->inbuf_current; newlen=0; if(ts->telnet_state==TelnetStateIacIac) { ts->telnet_state=TelnetStateText; /* skip the IAC in the loop below */ current++; newlen++; } for(;currentinbuf_len;current++,newlen++) { if(ts->inbuf[current]==IAC) { ts->telnet_state=TelnetStateIacCommand; #ifndef NDEBUG fprintf(stderr, "command: "); hexdump(8, &ts->inbuf[current]); fprintf(stderr, "\n"); #endif current++; break; } } #ifndef NDEBUG fprintf(stderr, "curr: %d %d len: %d\n", current, ts->inbuf_current, ts->inbuf_len); #endif *len=newlen; assert(*len >= 0); ts->inbuf_current=current; return 1; case TelnetStateIacCommand: case TelnetStateIacOption: case TelnetStateSb: case TelnetStateSbIac: return 0; } fprintf(stderr, "Invalid telnet state %d in %p\n", ts->telnet_state, (void*)ts); abort(); } /* get the next control item from the telnet engine. * ts - telnet info/state * command - pointer to a single char * option - pointer to a single char * extra_len - pointer to write the length of the extra data * extra - extra data buffer (for SB) */ int telnet_getcontrol(struct telnet_info *ts, char *command, char *option, size_t *extra_len, const char **extra) { char tmp; again: if(ts->inbuf_current>=ts->inbuf_len) { /* no more data */ return 0; } switch(ts->telnet_state) { case TelnetStateIacCommand: /* look for command parameter to IAC */ tmp=ts->inbuf[ts->inbuf_current]; #ifndef NDEBUG fprintf(stderr, "IAC %s\n", TELCMD(tmp)); #endif switch(tmp) { case IAC: ts->telnet_state=TelnetStateIacIac; return 0; case DONT: case DO: case WONT: case WILL: /* the following is for 3-byte IAC codes */ ts->telnet_state=TelnetStateIacOption; ts->command=tmp; ts->inbuf_current++; goto again; case EOR: /* End of Record - RFC 885 */ /* This only shows up if TELOPT_EOR was negotiated */ case ABORT: /* Abort - RFC 1184 */ /* This only shows up if TELOPT_LINEMODE was negotiated */ /* should be treated the same as IAC IP in most cases */ case SUSP: /* Suspend - RFC 1184 */ /* This only shows up if TELOPT_LINEMODE was negotiated */ case xEOF: /* End of File - RFC 1184 */ /* This only shows up if TELOPT_LINEMODE was negotiated */ /* user sent an EOF "character"/signal */ case EC: /* Erase Character */ case EL: /* Erase Line */ case GA: /* Go Ahead */ case AYT: /* Are You There */ case AO: /* Abort Output */ case IP: /* Interrupt Process */ case NOP: /* No Operations */ case BREAK: /* special key with a vague definition - RFC 854 */ /* the following is for 2-byte IAC codes */ ts->command=tmp; ts->inbuf_current++; ts->telnet_state=TelnetStateText; /* go back to text */ ts->option=0; if(command) *command=ts->command; if(option) *option=0; if(extra_len) *extra_len=0; if(extra) *extra=0; return 1; case DM: /* data mark */ /* marks the location in the stream a high priority * telnet urgent OOB message was sent. * normally all unprocessed data is discarded from the * point of the urgent message to the DM. * if an IAC DM is received but no urgent messages are * pending then the DM is ignored (treated as an IAC NOP) */ /* the following is for 2-byte IAC codes */ ts->command=tmp; ts->inbuf_current++; ts->telnet_state=TelnetStateText; /* go back to text */ ts->option=0; if(command) *command=ts->command; if(option) *option=0; if(extra_len) *extra_len=0; if(extra) *extra=0; return 1; case SB: /* Subnegotiation Begin */ /* format: IAC SB