diff --git a/AUTHORS b/AUTHORS index 9beb0c7..baac695 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,3 +2,4 @@ Sean Middleditch Jack Kelly Katherine Flavel Daniel Loffgren (https://github.com/RyuKojiro) +Jesse Friedman diff --git a/README.md b/README.md index 48e3947..ece811a 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ visit the following websites: * http://www.faqs.org/rfcs/rfc854.html * http://www.faqs.org/rfcs/rfc855.html +* http://www.faqs.org/rfcs/rfc861.html * http://www.faqs.org/rfcs/rfc1091.html * http://www.faqs.org/rfcs/rfc1143.html * http://www.faqs.org/rfcs/rfc1408.html @@ -171,11 +172,12 @@ static const telnet_telopt_t my_telopts[] = { commands (255 249). * `void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char opt);` + int opt);` Sends a TELNET negotiation command. The cmd parameter must be one of TELNET_WILL, TELNET_WONT, TELNET_DO, or TELNET_DONT. The opt - parameter is the option to negotiate. + parameter is the option to negotiate. If greater than 255, it is + interpreted as an "Extended Options List" option, per RFC861. Unless in PROXY mode, the RFC1143 support may delay or ellide the request entirely, as appropriate. It will ignore duplicate @@ -200,12 +202,14 @@ static const telnet_telopt_t my_telopts[] = { For sending regular text it may be more convenient to use telnet_printf(). -* `void telnet_begin_sb(telnet_t *telnet, unsigned char telopt);` +* `void telnet_begin_sb(telnet_t *telnet, int telopt);` Sends the header for a TELNET sub-negotiation command for the specified option. All send data following this command will be part of the sub-negotiation data until a call is made to - telnet_finish_subnegotiation(). + telnet_finish_subnegotiation(). If telopt is greater than 255, + it is interpreted as an "Extended Options List" option, per + RFC861. You should not use telnet_printf() for sending subnegotiation data as it will perform newline translations that usually do not @@ -218,11 +222,12 @@ static const telnet_telopt_t my_telopts[] = { telnet_begin_subnegotiation() and any negotiation data has been sent. -* `void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, +* `void telnet_subnegotiation(telnet_t *telnet, int telopt, const char *buffer, unsigned int size);` Sends a TELNET sub-negotiation command. The telopt parameter is - the sub-negotiation option. + the sub-negotiation option. If greater than 255, it is + interpreted as an "Extended Options List" option, per RFC861. Note that this function is just a shorthand for: ``` @@ -297,6 +302,7 @@ union telnet_event_t { struct negotiate_t { enum telnet_event_type_t _type; + int telopt_extended; unsigned char telopt; } neg; @@ -304,6 +310,7 @@ union telnet_event_t { enum telnet_event_type_t _type; const char *buffer; size_t size; + int telopt_extended; unsigned char telopt; } sub; }; @@ -397,7 +404,7 @@ void my_event_handler(telnet_t *telnet, telnet_event_t *ev, sent a WILL command to them. In either case, the TELNET option under negotiation will be in - event->neg.telopt field. + event->neg.telopt_extended field. libtelnet manages most of the pecularities of negotiation for you. For information on libtelnet's negotiation method, see: @@ -407,6 +414,20 @@ void my_event_handler(telnet_t *telnet, telnet_event_t *ev, Note that in PROXY mode libtelnet will do no processing of its own for you. + Also note the presence of both event->neg.telopt and + event->neg.telopt_extended fields. The former is an unsigned char, + and hence only supports the "regular" TELNET options 0-255. The + latter is a int, and supports TELNET options on the Extended + Options List (256-511) as described in RFC861, as well as the + regular options 0-255. When an negotiation command regarding an + EXOPL option is received, the event->neg.telopt field will be set + to 255, and the event->neg.telopt_extended field + will be set to the true number of the option. + + The event->neg.telopt_extended field should + be suitable for all applications - event->neg.telopt is retained + for legacy reasons only. + * TELNET_EV_WONT / TELNET_EV_DONT The WONT and DONT events are sent when the remote end of the @@ -427,25 +448,40 @@ void my_event_handler(telnet_t *telnet, telnet_event_t *ev, allow your application to disable its own use of the features. In either case, the TELNET option under negotiation will be in - event->neg.telopt field. + event->neg.telopt_extended field. Note that in PROXY mode libtelnet will do no processing of its own for you. + Also note the presence of both event->neg.telopt and + event->neg.telopt_extended fields. The former is an unsigned char, + and hence only supports the "regular" TELNET options 0-255. The + latter is a int, and supports TELNET options on the Extended + Options List (256-511) as described in RFC861, as well as the + regular options 0-255. When an negotiation command regarding an + EXOPL option is received, the event->neg.telopt field will be set + to 255, and the event->neg.telopt_extended field + will be set to the true number of the option. + + The event->neg.telopt_extended field should + be suitable for all applications - event->neg.telopt is retained + for legacy reasons only. + * TELNET_EV_SUBNEGOTIATION Triggered whenever a TELNET sub-negotiation has been received. Sub-negotiations include the NAWS option for communicating - terminal size to a server, the NEW-ENVIRON and TTYPE options for - negotiating terminal features, and MUD-centric protocols such as - ZMP, MSSP, and MCCP2. - - The event->sub->telopt value is the option under sub-negotiation. - The remaining data (if any) is passed in event->sub.buffer and - event->sub.size. Note that most subnegotiation commands can include - embedded NUL bytes in the subnegotiation data, and the data - event->sub.buffer is not NUL terminated, so always use the - event->sub.size value! + terminal size to a server; the NEW-ENVIRON and TTYPE options for + negotiating terminal features; MUD-centric protocols such as + ZMP, MSSP, and MCCP2; and the EXOPL option for extending the + maximum option number from 255 to 511. + + The event->sub->telopt_extended value is the option under + sub-negotiation. The remaining data (if any) is passed in + event->sub.buffer and event->sub.size. Note that most + subnegotiation commands can include embedded NUL bytes in the + subnegotiation data, and the data event->sub.buffer is not NUL + terminated, so always use the event->sub.size value! The meaning and necessary processing for subnegotiations are defined in various TELNET RFCs and other informal specifications. @@ -453,13 +489,27 @@ void my_event_handler(telnet_t *telnet, telnet_event_t *ev, has been enabled through the use of the telnet negotiation feature. - TTYPE/ENVIRON/NEW-ENVIRON/MSSP/ZMP SUPPORT: + TTYPE/ENVIRON/NEW-ENVIRON/MSSP/ZMP/EXOPL SUPPORT: libtelnet parses these subnegotiation commands. A special event will be sent for each, after the SUBNEGOTIATION event is sent. Except in special circumstances, the SUBNEGOTIATION event should be ignored for these options and the special events should be handled explicitly. + Note the presence of both event->sub.telopt and + event->sub.telopt_extended fields. The former is an unsigned char, + and hence only supports the "regular" TELNET options 0-255. The + latter is a int, and supports TELNET options on the Extended + Options List (256-511) as described in RFC861, as well as the + regular options 0-255. When a subnegotiation command regarding an + EXOPL option is received, the event->sub.telopt field will be set + to 255, and the event->sub.telopt_extended field + will be set to the true number of the option. + + The event->sub.telopt_extended field should + be suitable for all applications - event->sub.telopt is retained + for legacy reasons only. + * TELNET_EV_COMPRESS The COMPRESS event notifies the app that COMPRESS2/MCCP2 diff --git a/libtelnet.c b/libtelnet.c index 3d09e53..bbb367e 100644 --- a/libtelnet.c +++ b/libtelnet.c @@ -55,9 +55,22 @@ /* helper for the negotiation routines */ #define NEGOTIATE_EVENT(telnet,cmd,opt) \ ev.type = (cmd); \ - ev.neg.telopt = (opt); \ + if ((opt) > 255) { \ + ev.neg.telopt = 255; \ + } else { \ + ev.neg.telopt = (unsigned char)(opt); \ + } \ + ev.neg.telopt_extended = (opt); \ (telnet)->eh((telnet), &ev, (telnet)->ud); +/* helper for the negotiation routines */ +#define SEND_NEGOTIATION(telnet, cmd, opt) \ + if ((opt) > 255) { \ + _exopl_send_negotiate(telnet, (cmd), (opt)); \ + } else { \ + _send_negotiate(telnet, (cmd), (unsigned char)(opt)); \ + } + /* telnet state codes */ enum telnet_state_t { TELNET_STATE_DATA = 0, @@ -97,7 +110,7 @@ struct telnet_t { enum telnet_state_t state; /* option flags */ unsigned char flags; - /* current subnegotiation telopt */ + /* current subnegotiation telopt (never an EXOPL telopt) */ unsigned char sb_telopt; /* length of RFC1143 queue */ unsigned int q_size; @@ -107,7 +120,7 @@ struct telnet_t { /* RFC1143 option negotiation state */ typedef struct telnet_rfc1143_t { - unsigned char telopt; + int telopt; unsigned char state; } telnet_rfc1143_t; @@ -256,7 +269,7 @@ static void _send(telnet_t *telnet, const char *buffer, * check if we (local) supports it, otherwise we check if he (remote) * supports it. return non-zero if supported, zero if not supported. */ -static INLINE int _check_telopt(telnet_t *telnet, unsigned char telopt, +static INLINE int _check_telopt(telnet_t *telnet, int telopt, int us) { int i; @@ -282,7 +295,7 @@ static INLINE int _check_telopt(telnet_t *telnet, unsigned char telopt, /* retrieve RFC1143 option state */ static INLINE telnet_rfc1143_t _get_rfc1143(telnet_t *telnet, - unsigned char telopt) { + int telopt) { telnet_rfc1143_t empty; unsigned int i; @@ -300,7 +313,7 @@ static INLINE telnet_rfc1143_t _get_rfc1143(telnet_t *telnet, } /* save RFC1143 option state */ -static INLINE void _set_rfc1143(telnet_t *telnet, unsigned char telopt, +static INLINE void _set_rfc1143(telnet_t *telnet, int telopt, char us, char him) { telnet_rfc1143_t *qtmp; unsigned int i; @@ -359,8 +372,25 @@ static INLINE void _send_negotiate(telnet_t *telnet, unsigned char cmd, _sendu(telnet, bytes, 3); } +/* send EXOPL negotiation SB message */ +static INLINE void _exopl_send_negotiate(telnet_t *telnet, unsigned char cmd, + int exopl_telopt) +{ + unsigned char bytes[7]; + bytes[0] = TELNET_IAC; + bytes[1] = TELNET_SB; + bytes[2] = TELNET_TELOPT_EXOPL; + bytes[3] = cmd; + bytes[4] = (unsigned char)(exopl_telopt - 256); + bytes[5] = TELNET_IAC; + bytes[6] = TELNET_SE; + + _sendu(telnet, bytes, 7); +} + /* negotiation handling magic for RFC1143 */ -static void _negotiate(telnet_t *telnet, unsigned char telopt) { +static void _negotiate(telnet_t *telnet, int telopt) +{ telnet_event_t ev; telnet_rfc1143_t q; @@ -394,10 +424,10 @@ static void _negotiate(telnet_t *telnet, unsigned char telopt) { case Q_NO: if (_check_telopt(telnet, telopt, 0)) { _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); - _send_negotiate(telnet, TELNET_DO, telopt); + SEND_NEGOTIATION(telnet, TELNET_DO, telopt); NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); } else - _send_negotiate(telnet, TELNET_DONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_DONT, telopt); break; case Q_WANTNO: _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); @@ -416,7 +446,7 @@ static void _negotiate(telnet_t *telnet, unsigned char telopt) { break; case Q_WANTYES_OP: _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); - _send_negotiate(telnet, TELNET_DONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_DONT, telopt); NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); break; } @@ -427,7 +457,7 @@ static void _negotiate(telnet_t *telnet, unsigned char telopt) { switch (Q_HIM(q)) { case Q_YES: _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); - _send_negotiate(telnet, TELNET_DONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_DONT, telopt); NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); break; case Q_WANTNO: @@ -452,10 +482,10 @@ static void _negotiate(telnet_t *telnet, unsigned char telopt) { case Q_NO: if (_check_telopt(telnet, telopt, 1)) { _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WILL, telopt); + SEND_NEGOTIATION(telnet, TELNET_WILL, telopt); NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); } else - _send_negotiate(telnet, TELNET_WONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_WONT, telopt); break; case Q_WANTNO: _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); @@ -474,7 +504,7 @@ static void _negotiate(telnet_t *telnet, unsigned char telopt) { break; case Q_WANTYES_OP: _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_WONT, telopt); NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); break; } @@ -485,7 +515,7 @@ static void _negotiate(telnet_t *telnet, unsigned char telopt) { switch (Q_US(q)) { case Q_YES: _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_WONT, telopt); NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); break; case Q_WANTNO: @@ -494,8 +524,8 @@ static void _negotiate(telnet_t *telnet, unsigned char telopt) { break; case Q_WANTNO_OP: _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WILL, telopt); - NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_WILL, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); break; case Q_WANTYES: case Q_WANTYES_OP: @@ -829,15 +859,87 @@ static int _ttype_telnet(telnet_t *telnet, const char* buffer, size_t size) { return 0; } +/* parse EXTENDED-OPTIONS-LIST command subnegotiation buffers */ +static int _exopl_telnet(telnet_t *telnet, const char *buffer, size_t size) { + telnet_event_t ev; + unsigned char cmd; + int exopl_telopt; + + /* make sure request is not empty */ + if (size == 0) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "incomplete EXTENDED-OPTIONS-LIST request"); + return 0; + } + + cmd = buffer[0]; + + if (cmd == TELNET_SB) { + /* subnegotiation for an EXOPL telopt */ + char *parameterBuf; + + ev.type = TELNET_EV_SUBNEGOTIATION; + ev.sub.telopt = TELNET_TELOPT_EXOPL; + ev.sub.telopt_extended = 256 + (unsigned char)buffer[1]; + ev.sub.size = size - 3; + + /* allocate space for "subbuffer" */ + if ((parameterBuf = (char *)malloc(ev.sub.size)) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "malloc() failed: %s", strerror(errno)); + return 0; + } + memcpy(parameterBuf, buffer + 2, ev.sub.size); + ev.sub.buffer = parameterBuf; + + telnet->eh(telnet, &ev, telnet->ud); + } else { + /* make sure request is the right length (command and option) */ + if (size != 2) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "incomplete EXTENDED-OPTIONS-LIST request"); + return 0; + } + + exopl_telopt = 256 + (unsigned char)buffer[1]; + + switch (cmd) { + case TELNET_WILL: + telnet->state = TELNET_STATE_WILL; + break; + case TELNET_WONT: + telnet->state = TELNET_STATE_WONT; + break; + case TELNET_DO: + telnet->state = TELNET_STATE_DO; + break; + case TELNET_DONT: + telnet->state = TELNET_STATE_DONT; + break; + + default: + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "bad EXTENDED-OPTIONS-LIST request command"); + return 0; + } + + _negotiate(telnet, exopl_telopt); + telnet->state = TELNET_STATE_DATA; + } + + return 0; +} + /* process a subnegotiation buffer; return non-zero if the current buffer * must be aborted and reprocessed due to COMPRESS2 being activated */ static int _subnegotiate(telnet_t *telnet) { telnet_event_t ev; - /* standard subnegotiation event */ + /* standard subnegotiation event (non-EXOPL) */ ev.type = TELNET_EV_SUBNEGOTIATION; ev.sub.telopt = telnet->sb_telopt; + ev.sub.telopt_extended = telnet->sb_telopt; ev.sub.buffer = telnet->buffer; ev.sub.size = telnet->buffer_pos; telnet->eh(telnet, &ev, telnet->ud); @@ -869,6 +971,8 @@ static int _subnegotiate(telnet_t *telnet) { telnet->buffer_pos); case TELNET_TELOPT_MSSP: return _mssp_telnet(telnet, telnet->buffer, telnet->buffer_pos); + case TELNET_TELOPT_EXOPL: + return _exopl_telnet(telnet, telnet->buffer, telnet->buffer_pos); default: return 0; } @@ -1239,15 +1343,21 @@ void telnet_iac(telnet_t *telnet, unsigned char cmd) { /* send negotiation */ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char telopt) { + int telopt) { telnet_rfc1143_t q; + if (telopt > 511) { + _error(telnet, __LINE__, __func__, TELNET_EBADVAL, 0, + "supplied telopt %d for negotiation is greater than 511", telopt); + return; + } + /* if we're in proxy mode, just send it now */ if (telnet->flags & TELNET_FLAG_PROXY) { unsigned char bytes[3]; bytes[0] = TELNET_IAC; bytes[1] = cmd; - bytes[2] = telopt; + bytes[2] = (unsigned char)telopt; _sendu(telnet, bytes, 3); return; } @@ -1261,7 +1371,7 @@ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, switch (Q_US(q)) { case Q_NO: _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WILL, telopt); + SEND_NEGOTIATION(telnet, TELNET_WILL, telopt); break; case Q_WANTNO: _set_rfc1143(telnet, telopt, Q_WANTNO_OP, Q_HIM(q)); @@ -1277,7 +1387,7 @@ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, switch (Q_US(q)) { case Q_YES: _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); - _send_negotiate(telnet, TELNET_WONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_WONT, telopt); break; case Q_WANTYES: _set_rfc1143(telnet, telopt, Q_WANTYES_OP, Q_HIM(q)); @@ -1293,7 +1403,7 @@ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, switch (Q_HIM(q)) { case Q_NO: _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); - _send_negotiate(telnet, TELNET_DO, telopt); + SEND_NEGOTIATION(telnet, TELNET_DO, telopt); break; case Q_WANTNO: _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO_OP); @@ -1309,7 +1419,7 @@ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, switch (Q_HIM(q)) { case Q_YES: _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); - _send_negotiate(telnet, TELNET_DONT, telopt); + SEND_NEGOTIATION(telnet, TELNET_DONT, telopt); break; case Q_WANTYES: _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES_OP); @@ -1391,8 +1501,29 @@ void telnet_send_text(telnet_t *telnet, const char *buffer, } /* send subnegotiation header */ -void telnet_begin_sb(telnet_t *telnet, unsigned char telopt) { - unsigned char sb[3]; +void telnet_begin_sb(telnet_t *telnet, int telopt) { + unsigned char sb[5]; + + if (telopt > 511) { + _error(telnet, __LINE__, __func__, TELNET_EBADVAL, 0, + "supplied telopt %d for subnegotiation is greater than 511", telopt); + return; + } + + if (telopt > 255) { + /* an EXOPL telopt */ + + sb[0] = TELNET_IAC; + sb[1] = TELNET_SB; + sb[2] = TELNET_TELOPT_EXOPL; + sb[3] = TELNET_SB; + sb[4] = (unsigned char)(telopt - 256); + + _sendu(telnet, sb, 5); + + return; + } + sb[0] = TELNET_IAC; sb[1] = TELNET_SB; sb[2] = telopt; @@ -1401,12 +1532,38 @@ void telnet_begin_sb(telnet_t *telnet, unsigned char telopt) { /* send complete subnegotiation */ -void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, +void telnet_subnegotiation(telnet_t *telnet, int telopt, const char *buffer, size_t size) { - unsigned char bytes[5]; + unsigned char bytes[8]; + + if (telopt > 511) { + _error(telnet, __LINE__, __func__, TELNET_EBADVAL, 0, + "supplied telopt %d for subnegotiation is greater than 511", telopt); + return; + } + + if (telopt > 255) { + /* an EXOPL telopt */ + + bytes[0] = TELNET_IAC; + bytes[1] = TELNET_SB; + bytes[2] = TELNET_TELOPT_EXOPL; + bytes[3] = TELNET_SB; + bytes[4] = (unsigned char)(telopt - 256); + bytes[5] = TELNET_SE; + bytes[6] = TELNET_IAC; + bytes[7] = TELNET_SE; + + _sendu(telnet, bytes, 5); + telnet_send(telnet, buffer, size); + _sendu(telnet, bytes + 5, 3); + + return; + } + bytes[0] = TELNET_IAC; bytes[1] = TELNET_SB; - bytes[2] = telopt; + bytes[2] = (unsigned char)telopt; bytes[3] = TELNET_IAC; bytes[4] = TELNET_SE; diff --git a/libtelnet.h b/libtelnet.h index e3e6048..2407386 100644 --- a/libtelnet.h +++ b/libtelnet.h @@ -279,7 +279,8 @@ union telnet_event_t { */ struct negotiate_t { enum telnet_event_type_t _type; /*!< alias for type */ - unsigned char telopt; /*!< option being negotiated */ + int telopt_extended; /*!< option being negotiated (either EXOPL or regular) */ + unsigned char telopt; /*!< (deprecated) option being negotiated, or TELNET_TELOPT_EXOPL if an EXOPL option */ } neg; /*!< WILL, WONT, DO, DONT */ /*! @@ -289,7 +290,8 @@ union telnet_event_t { enum telnet_event_type_t _type; /*!< alias for type */ const char *buffer; /*!< data of sub-negotiation */ size_t size; /*!< number of bytes in buffer */ - unsigned char telopt; /*!< option code for negotiation */ + int telopt_extended; /*!< option code for subnegotiation (either EXOPL or regular) */ + unsigned char telopt; /*!< (deprecated) option code for subnegotiation, or TELNET_TELOPT_EXOPL if an EXOPL option */ } sub; /*!< SB */ /*! @@ -430,7 +432,7 @@ extern void telnet_iac(telnet_t *telnet, unsigned char cmd); * \param opt One of the TELNET_TELOPT_* values. */ extern void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char opt); + int opt); /*! * Send non-command data (escapes IAC bytes). @@ -464,7 +466,7 @@ extern void telnet_send_text(telnet_t *telnet, * \param telopt One of the TELNET_TELOPT_* values. */ extern void telnet_begin_sb(telnet_t *telnet, - unsigned char telopt); + int telopt); /*! * \brief Finish a sub-negotiation command. @@ -489,7 +491,7 @@ extern void telnet_begin_sb(telnet_t *telnet, * \param buffer Byte buffer for sub-negotiation data. * \param size Number of bytes to use for sub-negotiation data. */ -extern void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, +extern void telnet_subnegotiation(telnet_t *telnet, int telopt, const char *buffer, size_t size); /*! diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ea9762b..c8eae36 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,6 @@ enable_testing() -foreach (test_name environ01 environ02 environ03 mssp01 rfc1143 simple01 simple02 ttype01 zmp01 zmp02 zmp03) +foreach (test_name environ01 environ02 environ03 exopl01 exopl02 mssp01 rfc1143 simple01 simple02 ttype01 zmp01 zmp02 zmp03) add_test( NAME ${test_name} COMMAND telnet-test ${CMAKE_CURRENT_SOURCE_DIR}/${test_name}.input ${CMAKE_CURRENT_SOURCE_DIR}/${test_name}.txt) diff --git a/test/exopl01.input b/test/exopl01.input new file mode 100644 index 0000000..9294150 --- /dev/null +++ b/test/exopl01.input @@ -0,0 +1,12 @@ +# Test EXOPL + +# enable EXOPL (expect DO to be output) +%FF%FD%FF + +# enable our dummy EXOPL option 300 +# expect DO 300 +%FF%FA%FF%FD%2C%FF%F0 + +# send EXOPL subnegotiation for dummy option 300 +# expect SUB 300 [6] +%FF%FA%FF%FA%2CFOOBAR%F0%FF%F0 \ No newline at end of file diff --git a/test/exopl01.txt b/test/exopl01.txt new file mode 100644 index 0000000..c1f01d1 --- /dev/null +++ b/test/exopl01.txt @@ -0,0 +1,3 @@ +DO 255 (EXOPL) +DO 300 (LIBTELNET-TEST-EXOPL-TELOPT-1) +SUB 300 (LIBTELNET-TEST-EXOPL-TELOPT-1) [6] diff --git a/test/exopl02.input b/test/exopl02.input new file mode 100644 index 0000000..7552ab5 --- /dev/null +++ b/test/exopl02.input @@ -0,0 +1,12 @@ +# Test EXOPL option 511 (the max possible EXOPL options number; an edge case) + +# enable EXOPL (expect DO to be output) +%FF%FD%FF + +# enable our dummy EXOPL option 511 +# expect DO 511 +%FF%FA%FF%FD%FF%FF%FF%F0 + +# send EXOPL subnegotiation for dummy option 511 +# expect SUB 511 [6] +%FF%FA%FF%FA%FF%FFFOOBAR%F0%FF%F0 \ No newline at end of file diff --git a/test/exopl02.txt b/test/exopl02.txt new file mode 100644 index 0000000..e881f4a --- /dev/null +++ b/test/exopl02.txt @@ -0,0 +1,3 @@ +DO 255 (EXOPL) +DO 511 (LIBTELNET-TEST-EXOPL-TELOPT-2) +SUB 511 (LIBTELNET-TEST-EXOPL-TELOPT-2) [6] diff --git a/util/telnet-test.c b/util/telnet-test.c index 63b8409..70828b5 100644 --- a/util/telnet-test.c +++ b/util/telnet-test.c @@ -28,6 +28,9 @@ static const telnet_telopt_t telopts[] = { { TELNET_TELOPT_MSSP, TELNET_WILL, TELNET_DONT }, { TELNET_TELOPT_NEW_ENVIRON, TELNET_WILL, TELNET_DONT }, { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, + { TELNET_TELOPT_EXOPL, TELNET_WILL, TELNET_DONT }, + { 300, TELNET_WILL, TELNET_DONT }, /* a dummy EXOPL telopt */ + { 511, TELNET_WILL, TELNET_DONT }, /* another dummy EXOPL telopt */ { -1, 0, 0 } }; @@ -61,7 +64,7 @@ static const char *get_cmd(unsigned char cmd) { } } -static const char *get_opt(unsigned char opt) { +static const char *get_opt(int opt) { switch (opt) { case 0: return "BINARY"; case 1: return "ECHO"; @@ -108,6 +111,8 @@ static const char *get_opt(unsigned char opt) { case 86: return "COMPRESS2"; case 93: return "ZMP"; case 255: return "EXOPL"; + case 300: return "LIBTELNET-TEST-EXOPL-TELOPT-1"; /* our custom dummy EXOPL option */ + case 511: return "LIBTELNET-TEST-EXOPL-TELOPT-2"; /* our other custom dummy EXOPL option */ default: return "unknown"; } } @@ -221,28 +226,29 @@ static void event_print(telnet_t *telnet, telnet_event_t *ev, void *ud) { stprintf(state, "IAC %d (%s)\n", (int)ev->iac.cmd, get_cmd(ev->iac.cmd)); break; case TELNET_EV_WILL: - stprintf(state, "WILL %d (%s)\n", (int)ev->neg.telopt, get_opt(ev->neg.telopt)); + stprintf(state, "WILL %d (%s)\n", (int)ev->neg.telopt_extended, get_opt(ev->neg.telopt_extended)); break; case TELNET_EV_WONT: - stprintf(state, "WONT %d (%s)\n", (int)ev->neg.telopt, get_opt(ev->neg.telopt)); + stprintf(state, "WONT %d (%s)\n", (int)ev->neg.telopt_extended, get_opt(ev->neg.telopt_extended)); break; case TELNET_EV_DO: - stprintf(state, "DO %d (%s)\n", (int)ev->neg.telopt, get_opt(ev->neg.telopt)); + stprintf(state, "DO %d (%s)\n", (int)ev->neg.telopt_extended, get_opt(ev->neg.telopt_extended)); break; case TELNET_EV_DONT: - stprintf(state, "DONT %d (%s)\n", (int)ev->neg.telopt, get_opt(ev->neg.telopt)); + stprintf(state, "DONT %d (%s)\n", (int)ev->neg.telopt_extended, get_opt(ev->neg.telopt_extended)); break; case TELNET_EV_SUBNEGOTIATION: - switch (ev->sub.telopt) { + switch (ev->sub.telopt_extended) { case TELNET_TELOPT_ENVIRON: case TELNET_TELOPT_NEW_ENVIRON: case TELNET_TELOPT_TTYPE: case TELNET_TELOPT_ZMP: case TELNET_TELOPT_MSSP: + case TELNET_TELOPT_EXOPL: /* print nothing */ break; default: - stprintf(state, "SUB %d (%s) [%zi]\n", (int)ev->sub.telopt, get_opt(ev->sub.telopt), ev->sub.size); + stprintf(state, "SUB %d (%s) [%zi]\n", (int)ev->sub.telopt_extended, get_opt(ev->sub.telopt_extended), ev->sub.size); break; } break;