From b2a2de73d009ca06a6b2891f65b53c33fa1a4d65 Mon Sep 17 00:00:00 2001 From: Jesse Friedman Date: Sat, 13 Apr 2019 02:32:59 -0400 Subject: [PATCH 1/8] Adding EXOPL support to libtelnet --- libtelnet.c | 195 ++++++++++++++++++++++++++++++++++++++++++++-------- libtelnet.h | 12 ++-- 2 files changed, 174 insertions(+), 33 deletions(-) diff --git a/libtelnet.c b/libtelnet.c index 71dc5f3..159a7fa 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; + short 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, short 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) { + short 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, short 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, + short 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, short 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); @@ -417,7 +447,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; } @@ -428,7 +458,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)); @@ -475,7 +505,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; } @@ -486,7 +516,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: @@ -495,7 +525,7 @@ 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); + SEND_NEGOTIATION(telnet, TELNET_WILL, telopt); NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); break; case Q_WANTYES: @@ -830,15 +860,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; + short 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 + 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 + (short)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); @@ -873,6 +975,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; } @@ -1243,7 +1347,7 @@ void telnet_iac(telnet_t *telnet, unsigned char cmd) { /* send negotiation */ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char telopt) { + short telopt) { telnet_rfc1143_t q; /* if we're in proxy mode, just send it now */ @@ -1251,7 +1355,7 @@ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, unsigned char bytes[3]; bytes[0] = TELNET_IAC; bytes[1] = cmd; - bytes[2] = telopt; + bytes[2] = (unsigned char)telopt; _sendu(telnet, bytes, 3); return; } @@ -1265,7 +1369,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)); @@ -1281,7 +1385,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)); @@ -1297,7 +1401,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); @@ -1313,7 +1417,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); @@ -1395,8 +1499,23 @@ 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, short telopt) { + unsigned char sb[5]; + + 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; @@ -1405,12 +1524,32 @@ 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, short telopt, const char *buffer, size_t size) { - unsigned char bytes[5]; + unsigned char bytes[8]; + + 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..d136da3 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 */ + short 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 */ + short 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); + short 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); + short 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, short telopt, const char *buffer, size_t size); /*! From d3474c18fcb164ce414885bc840bf1f990ed8562 Mon Sep 17 00:00:00 2001 From: Jesse Friedman Date: Sat, 13 Apr 2019 02:34:40 -0400 Subject: [PATCH 2/8] Adding EXOPL support to telnet-test --- util/telnet-test.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/util/telnet-test.c b/util/telnet-test.c index 63b8409..e27f5f7 100644 --- a/util/telnet-test.c +++ b/util/telnet-test.c @@ -28,6 +28,8 @@ 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 */ { -1, 0, 0 } }; @@ -61,7 +63,7 @@ static const char *get_cmd(unsigned char cmd) { } } -static const char *get_opt(unsigned char opt) { +static const char *get_opt(short opt) { switch (opt) { case 0: return "BINARY"; case 1: return "ECHO"; @@ -108,6 +110,7 @@ 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"; /* our custom dummy EXOPL option */ default: return "unknown"; } } @@ -221,28 +224,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; From 989849f075d2698f3c820ee5a128f41039ba9ae0 Mon Sep 17 00:00:00 2001 From: Jesse Friedman Date: Sat, 13 Apr 2019 02:37:18 -0400 Subject: [PATCH 3/8] Adding test for EXOPL --- test/CMakeLists.txt | 2 +- test/exopl01.input | 12 ++++++++++++ test/exopl01.txt | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/exopl01.input create mode 100644 test/exopl01.txt diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ea9762b..446ad76 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 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..cd37d86 --- /dev/null +++ b/test/exopl01.txt @@ -0,0 +1,3 @@ +DO 255 (EXOPL) +DO 300 (LIBTELNET-TEST-EXOPL-TELOPT) +SUB 300 (LIBTELNET-TEST-EXOPL-TELOPT) [6] From acd686a4587199ae23bac1db0ea8ce518f523105 Mon Sep 17 00:00:00 2001 From: Jesse Friedman Date: Sat, 13 Apr 2019 02:38:42 -0400 Subject: [PATCH 4/8] Adding notes on EXOPL support to readme --- README.md | 88 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index c3a3700..ff728d4 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,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 @@ -172,11 +173,12 @@ static const telnet_telopt_t my_telopts[] = { commands (255 249). * `void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - unsigned char opt);` + short 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 @@ -201,12 +203,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, short 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 @@ -219,11 +223,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, short 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: ``` @@ -298,6 +303,7 @@ union telnet_event_t { struct negotiate_t { enum telnet_event_type_t _type; + short telopt_extended; unsigned char telopt; } neg; @@ -305,6 +311,7 @@ union telnet_event_t { enum telnet_event_type_t _type; const char *buffer; size_t size; + short telopt_extended; unsigned char telopt; } sub; }; @@ -398,7 +405,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: @@ -408,6 +415,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 short, 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 @@ -428,25 +449,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 short, 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. @@ -454,13 +490,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 short, 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 From 1daedf7776dbb2d03553c88df00b8419500c8354 Mon Sep 17 00:00:00 2001 From: Jesse Friedman Date: Sat, 13 Apr 2019 10:21:00 -0400 Subject: [PATCH 5/8] Change telopt struct members and arguments from short to int --- README.md | 16 +- libtelnet.c | 22 +- libtelnet.h | 10 +- util/telnet-client.c | 518 +++++++++++++++++++++---------------------- util/telnet-test.c | 2 +- 5 files changed, 284 insertions(+), 284 deletions(-) diff --git a/README.md b/README.md index ff728d4..5dce7d9 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ static const telnet_telopt_t my_telopts[] = { commands (255 249). * `void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - short 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 @@ -203,7 +203,7 @@ 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, short 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 @@ -223,7 +223,7 @@ static const telnet_telopt_t my_telopts[] = { telnet_begin_subnegotiation() and any negotiation data has been sent. -* `void telnet_subnegotiation(telnet_t *telnet, short 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 @@ -303,7 +303,7 @@ union telnet_event_t { struct negotiate_t { enum telnet_event_type_t _type; - short telopt_extended; + int telopt_extended; unsigned char telopt; } neg; @@ -311,7 +311,7 @@ union telnet_event_t { enum telnet_event_type_t _type; const char *buffer; size_t size; - short telopt_extended; + int telopt_extended; unsigned char telopt; } sub; }; @@ -418,7 +418,7 @@ void my_event_handler(telnet_t *telnet, telnet_event_t *ev, 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 short, and supports TELNET options on the Extended + 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 @@ -457,7 +457,7 @@ void my_event_handler(telnet_t *telnet, telnet_event_t *ev, 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 short, and supports TELNET options on the Extended + 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 @@ -500,7 +500,7 @@ void my_event_handler(telnet_t *telnet, telnet_event_t *ev, 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 short, and supports TELNET options on the Extended + 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 diff --git a/libtelnet.c b/libtelnet.c index 159a7fa..3776422 100644 --- a/libtelnet.c +++ b/libtelnet.c @@ -120,7 +120,7 @@ struct telnet_t { /* RFC1143 option negotiation state */ typedef struct telnet_rfc1143_t { - short telopt; + int telopt; unsigned char state; } telnet_rfc1143_t; @@ -269,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, short telopt, +static INLINE int _check_telopt(telnet_t *telnet, int telopt, int us) { int i; @@ -295,7 +295,7 @@ static INLINE int _check_telopt(telnet_t *telnet, short telopt, /* retrieve RFC1143 option state */ static INLINE telnet_rfc1143_t _get_rfc1143(telnet_t *telnet, - short telopt) { + int telopt) { telnet_rfc1143_t empty; unsigned int i; @@ -313,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, short telopt, +static INLINE void _set_rfc1143(telnet_t *telnet, int telopt, char us, char him) { telnet_rfc1143_t *qtmp; unsigned int i; @@ -374,7 +374,7 @@ static INLINE void _send_negotiate(telnet_t *telnet, unsigned char cmd, /* send EXOPL negotiation SB message */ static INLINE void _exopl_send_negotiate(telnet_t *telnet, unsigned char cmd, - short exopl_telopt) + int exopl_telopt) { unsigned char bytes[7]; bytes[0] = TELNET_IAC; @@ -389,7 +389,7 @@ static INLINE void _exopl_send_negotiate(telnet_t *telnet, unsigned char cmd, } /* negotiation handling magic for RFC1143 */ -static void _negotiate(telnet_t *telnet, short telopt) +static void _negotiate(telnet_t *telnet, int telopt) { telnet_event_t ev; telnet_rfc1143_t q; @@ -864,7 +864,7 @@ static int _ttype_telnet(telnet_t *telnet, const char* buffer, size_t size) { static int _exopl_telnet(telnet_t *telnet, const char *buffer, size_t size) { telnet_event_t ev; unsigned char cmd; - short exopl_telopt; + int exopl_telopt; /* make sure request is not empty */ if (size == 0) { @@ -902,7 +902,7 @@ static int _exopl_telnet(telnet_t *telnet, const char *buffer, size_t size) { return 0; } - exopl_telopt = 256 + (short)buffer[1]; + exopl_telopt = 256 + (int)buffer[1]; switch (cmd) { case TELNET_WILL: @@ -1347,7 +1347,7 @@ void telnet_iac(telnet_t *telnet, unsigned char cmd) { /* send negotiation */ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, - short telopt) { + int telopt) { telnet_rfc1143_t q; /* if we're in proxy mode, just send it now */ @@ -1499,7 +1499,7 @@ void telnet_send_text(telnet_t *telnet, const char *buffer, } /* send subnegotiation header */ -void telnet_begin_sb(telnet_t *telnet, short telopt) { +void telnet_begin_sb(telnet_t *telnet, int telopt) { unsigned char sb[5]; if (telopt > 255) { @@ -1524,7 +1524,7 @@ void telnet_begin_sb(telnet_t *telnet, short telopt) { /* send complete subnegotiation */ -void telnet_subnegotiation(telnet_t *telnet, short telopt, +void telnet_subnegotiation(telnet_t *telnet, int telopt, const char *buffer, size_t size) { unsigned char bytes[8]; diff --git a/libtelnet.h b/libtelnet.h index d136da3..2407386 100644 --- a/libtelnet.h +++ b/libtelnet.h @@ -279,7 +279,7 @@ union telnet_event_t { */ struct negotiate_t { enum telnet_event_type_t _type; /*!< alias for type */ - short telopt_extended; /*!< option being negotiated (either EXOPL or regular) */ + 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 */ @@ -290,7 +290,7 @@ 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 */ - short telopt_extended; /*!< option code for subnegotiation (either EXOPL or regular) */ + 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 */ @@ -432,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, - short opt); + int opt); /*! * Send non-command data (escapes IAC bytes). @@ -466,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, - short telopt); + int telopt); /*! * \brief Finish a sub-negotiation command. @@ -491,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, short telopt, +extern void telnet_subnegotiation(telnet_t *telnet, int telopt, const char *buffer, size_t size); /*! diff --git a/util/telnet-client.c b/util/telnet-client.c index aff4558..ddb4ad0 100644 --- a/util/telnet-client.c +++ b/util/telnet-client.c @@ -1,259 +1,259 @@ -/* - * Sean Middleditch - * sean@sourcemud.org - * - * The author or authors of this code dedicate any and all copyright interest - * in this code to the public domain. We make this dedication for the benefit - * of the public at large and to the detriment of our heirs and successors. We - * intend this dedication to be an overt act of relinquishment in perpetuity of - * all present and future rights to this code under copyright law. - */ - -#if !defined(_BSD_SOURCE) -# define _BSD_SOURCE -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_ZLIB -#include "zlib.h" -#endif - -#include "libtelnet.h" - -static struct termios orig_tios; -static telnet_t *telnet; -static int do_echo; - -static const telnet_telopt_t telopts[] = { - { TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO }, - { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, - { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO }, - { TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO }, - { -1, 0, 0 } -}; - -static void _cleanup(void) { - tcsetattr(STDOUT_FILENO, TCSADRAIN, &orig_tios); -} - -static void _input(char *buffer, int size) { - static char crlf[] = { '\r', '\n' }; - int i; - - for (i = 0; i != size; ++i) { - /* if we got a CR or LF, replace with CRLF - * NOTE that usually you'd get a CR in UNIX, but in raw - * mode we get LF instead (not sure why) - */ - if (buffer[i] == '\r' || buffer[i] == '\n') { - if (do_echo) - printf("\r\n"); - telnet_send(telnet, crlf, 2); - } else { - if (do_echo) - putchar(buffer[i]); - telnet_send(telnet, buffer + i, 1); - } - } - fflush(stdout); -} - -static void _send(int sock, const char *buffer, size_t size) { - int rs; - - /* send data */ - while (size > 0) { - if ((rs = send(sock, buffer, size, 0)) == -1) { - fprintf(stderr, "send() failed: %s\n", strerror(errno)); - exit(1); - } else if (rs == 0) { - fprintf(stderr, "send() unexpectedly returned 0\n"); - exit(1); - } - - /* update pointer and size to see if we've got more to send */ - buffer += rs; - size -= rs; - } -} - -static void _event_handler(telnet_t *telnet, telnet_event_t *ev, - void *user_data) { - int sock = *(int*)user_data; - - switch (ev->type) { - /* data received */ - case TELNET_EV_DATA: - if (ev->data.size && fwrite(ev->data.buffer, 1, ev->data.size, stdout) != ev->data.size) { - fprintf(stderr, "ERROR: Could not write complete buffer to stdout"); - } - fflush(stdout); - break; - /* data must be sent */ - case TELNET_EV_SEND: - _send(sock, ev->data.buffer, ev->data.size); - break; - /* request to enable remote feature (or receipt) */ - case TELNET_EV_WILL: - /* we'll agree to turn off our echo if server wants us to stop */ - if (ev->neg.telopt == TELNET_TELOPT_ECHO) - do_echo = 0; - break; - /* notification of disabling remote feature (or receipt) */ - case TELNET_EV_WONT: - if (ev->neg.telopt == TELNET_TELOPT_ECHO) - do_echo = 1; - break; - /* request to enable local feature (or receipt) */ - case TELNET_EV_DO: - break; - /* demand to disable local feature (or receipt) */ - case TELNET_EV_DONT: - break; - /* respond to TTYPE commands */ - case TELNET_EV_TTYPE: - /* respond with our terminal type, if requested */ - if (ev->ttype.cmd == TELNET_TTYPE_SEND) { - telnet_ttype_is(telnet, getenv("TERM")); - } - break; - /* respond to particular subnegotiations */ - case TELNET_EV_SUBNEGOTIATION: - break; - /* error */ - case TELNET_EV_ERROR: - fprintf(stderr, "ERROR: %s\n", ev->error.msg); - exit(1); - default: - /* ignore */ - break; - } -} - -int main(int argc, char **argv) { - char buffer[512]; - int rs; - int sock; - struct sockaddr_in addr; - struct pollfd pfd[2]; - struct addrinfo *ai; - struct addrinfo hints; - struct termios tios; - const char *servname; - const char *hostname; - - /* check usage */ - if (argc < 2) { - fprintf(stderr, "Usage:\n ./telnet-client [port]\n"); - return 1; - } - - /* process arguments */ - servname = (argc < 3) ? "23" : argv[2]; - hostname = argv[1]; - - /* look up server host */ - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - if ((rs = getaddrinfo(hostname, servname, &hints, &ai)) != 0) { - fprintf(stderr, "getaddrinfo() failed for %s: %s\n", hostname, - gai_strerror(rs)); - return 1; - } - - /* create server socket */ - if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { - fprintf(stderr, "socket() failed: %s\n", strerror(errno)); - return 1; - } - - /* bind server socket */ - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { - fprintf(stderr, "bind() failed: %s\n", strerror(errno)); - close(sock); - return 1; - } - - /* connect */ - if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { - fprintf(stderr, "connect() failed: %s\n", strerror(errno)); - close(sock); - return 1; - } - - /* free address lookup info */ - freeaddrinfo(ai); - - /* get current terminal settings, set raw mode, make sure we - * register atexit handler to restore terminal settings - */ - tcgetattr(STDOUT_FILENO, &orig_tios); - atexit(_cleanup); - tios = orig_tios; - cfmakeraw(&tios); - tcsetattr(STDOUT_FILENO, TCSADRAIN, &tios); - - /* set input echoing on by default */ - do_echo = 1; - - /* initialize telnet box */ - telnet = telnet_init(telopts, _event_handler, 0, &sock); - - /* initialize poll descriptors */ - memset(pfd, 0, sizeof(pfd)); - pfd[0].fd = STDIN_FILENO; - pfd[0].events = POLLIN; - pfd[1].fd = sock; - pfd[1].events = POLLIN; - - /* loop while both connections are open */ - while (poll(pfd, 2, -1) != -1) { - /* read from stdin */ - if (pfd[0].revents & POLLIN) { - if ((rs = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0) { - _input(buffer, rs); - } else if (rs == 0) { - break; - } else { - fprintf(stderr, "recv(server) failed: %s\n", - strerror(errno)); - exit(1); - } - } - - /* read from client */ - if (pfd[1].revents & POLLIN) { - if ((rs = recv(sock, buffer, sizeof(buffer), 0)) > 0) { - telnet_recv(telnet, buffer, rs); - } else if (rs == 0) { - break; - } else { - fprintf(stderr, "recv(client) failed: %s\n", - strerror(errno)); - exit(1); - } - } - } - - /* clean up */ - telnet_free(telnet); - close(sock); - - return 0; -} +/* + * Sean Middleditch + * sean@sourcemud.org + * + * The author or authors of this code dedicate any and all copyright interest + * in this code to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and successors. We + * intend this dedication to be an overt act of relinquishment in perpetuity of + * all present and future rights to this code under copyright law. + */ + +#if !defined(_BSD_SOURCE) +# define _BSD_SOURCE +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_ZLIB +#include "zlib.h" +#endif + +#include "libtelnet.h" + +static struct termios orig_tios; +static telnet_t *telnet; +static int do_echo; + +static const telnet_telopt_t telopts[] = { + { TELNET_TELOPT_ECHO, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, + { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO }, + { -1, 0, 0 } +}; + +static void _cleanup(void) { + tcsetattr(STDOUT_FILENO, TCSADRAIN, &orig_tios); +} + +static void _input(char *buffer, int size) { + static char crlf[] = { '\r', '\n' }; + int i; + + for (i = 0; i != size; ++i) { + /* if we got a CR or LF, replace with CRLF + * NOTE that usually you'd get a CR in UNIX, but in raw + * mode we get LF instead (not sure why) + */ + if (buffer[i] == '\r' || buffer[i] == '\n') { + if (do_echo) + printf("\r\n"); + telnet_send(telnet, crlf, 2); + } else { + if (do_echo) + putchar(buffer[i]); + telnet_send(telnet, buffer + i, 1); + } + } + fflush(stdout); +} + +static void _send(int sock, const char *buffer, size_t size) { + int rs; + + /* send data */ + while (size > 0) { + if ((rs = send(sock, buffer, size, 0)) == -1) { + fprintf(stderr, "send() failed: %s\n", strerror(errno)); + exit(1); + } else if (rs == 0) { + fprintf(stderr, "send() unexpectedly returned 0\n"); + exit(1); + } + + /* update pointer and size to see if we've got more to send */ + buffer += rs; + size -= rs; + } +} + +static void _event_handler(telnet_t *telnet, telnet_event_t *ev, + void *user_data) { + int sock = *(int*)user_data; + + switch (ev->type) { + /* data received */ + case TELNET_EV_DATA: + if (ev->data.size && fwrite(ev->data.buffer, 1, ev->data.size, stdout) != ev->data.size) { + fprintf(stderr, "ERROR: Could not write complete buffer to stdout"); + } + fflush(stdout); + break; + /* data must be sent */ + case TELNET_EV_SEND: + _send(sock, ev->data.buffer, ev->data.size); + break; + /* request to enable remote feature (or receipt) */ + case TELNET_EV_WILL: + /* we'll agree to turn off our echo if server wants us to stop */ + if (ev->neg.telopt == TELNET_TELOPT_ECHO) + do_echo = 0; + break; + /* notification of disabling remote feature (or receipt) */ + case TELNET_EV_WONT: + if (ev->neg.telopt == TELNET_TELOPT_ECHO) + do_echo = 1; + break; + /* request to enable local feature (or receipt) */ + case TELNET_EV_DO: + break; + /* demand to disable local feature (or receipt) */ + case TELNET_EV_DONT: + break; + /* respond to TTYPE commands */ + case TELNET_EV_TTYPE: + /* respond with our terminal type, if requested */ + if (ev->ttype.cmd == TELNET_TTYPE_SEND) { + telnet_ttype_is(telnet, getenv("TERM")); + } + break; + /* respond to particular subnegotiations */ + case TELNET_EV_SUBNEGOTIATION: + break; + /* error */ + case TELNET_EV_ERROR: + fprintf(stderr, "ERROR: %s\n", ev->error.msg); + exit(1); + default: + /* ignore */ + break; + } +} + +int main(int argc, char **argv) { + char buffer[512]; + int rs; + int sock; + struct sockaddr_in addr; + struct pollfd pfd[2]; + struct addrinfo *ai; + struct addrinfo hints; + struct termios tios; + const char *servname; + const char *hostname; + + /* check usage */ + if (argc < 2) { + fprintf(stderr, "Usage:\n ./telnet-client [port]\n"); + return 1; + } + + /* process arguments */ + servname = (argc < 3) ? "23" : argv[2]; + hostname = argv[1]; + + /* look up server host */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + if ((rs = getaddrinfo(hostname, servname, &hints, &ai)) != 0) { + fprintf(stderr, "getaddrinfo() failed for %s: %s\n", hostname, + gai_strerror(rs)); + return 1; + } + + /* create server socket */ + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "socket() failed: %s\n", strerror(errno)); + return 1; + } + + /* bind server socket */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + fprintf(stderr, "bind() failed: %s\n", strerror(errno)); + close(sock); + return 1; + } + + /* connect */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { + fprintf(stderr, "connect() failed: %s\n", strerror(errno)); + close(sock); + return 1; + } + + /* free address lookup info */ + freeaddrinfo(ai); + + /* get current terminal settings, set raw mode, make sure we + * register atexit handler to restore terminal settings + */ + tcgetattr(STDOUT_FILENO, &orig_tios); + atexit(_cleanup); + tios = orig_tios; + cfmakeraw(&tios); + tcsetattr(STDOUT_FILENO, TCSADRAIN, &tios); + + /* set input echoing on by default */ + do_echo = 1; + + /* initialize telnet box */ + telnet = telnet_init(telopts, _event_handler, 0, &sock); + + /* initialize poll descriptors */ + memset(pfd, 0, sizeof(pfd)); + pfd[0].fd = STDIN_FILENO; + pfd[0].events = POLLIN; + pfd[1].fd = sock; + pfd[1].events = POLLIN; + + /* loop while both connections are open */ + while (poll(pfd, 2, -1) != -1) { + /* read from stdin */ + if (pfd[0].revents & POLLIN) { + if ((rs = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0) { + _input(buffer, rs); + } else if (rs == 0) { + break; + } else { + fprintf(stderr, "recv(server) failed: %s\n", + strerror(errno)); + exit(1); + } + } + + /* read from client */ + if (pfd[1].revents & POLLIN) { + if ((rs = recv(sock, buffer, sizeof(buffer), 0)) > 0) { + telnet_recv(telnet, buffer, rs); + } else if (rs == 0) { + break; + } else { + fprintf(stderr, "recv(client) failed: %s\n", + strerror(errno)); + exit(1); + } + } + } + + /* clean up */ + telnet_free(telnet); + close(sock); + + return 0; +} diff --git a/util/telnet-test.c b/util/telnet-test.c index e27f5f7..866c2ba 100644 --- a/util/telnet-test.c +++ b/util/telnet-test.c @@ -63,7 +63,7 @@ static const char *get_cmd(unsigned char cmd) { } } -static const char *get_opt(short opt) { +static const char *get_opt(int opt) { switch (opt) { case 0: return "BINARY"; case 1: return "ECHO"; From 7ab7b72abc90c331bbcc5c0746f2a628fc25e9bb Mon Sep 17 00:00:00 2001 From: Jesse Friedman Date: Sun, 14 Apr 2019 00:37:39 -0400 Subject: [PATCH 6/8] Fix handling for EXOPL option 511 (an edge case) --- libtelnet.c | 4 ++-- test/CMakeLists.txt | 2 +- test/exopl01.input | 22 +++++++++++----------- test/exopl01.txt | 4 ++-- test/exopl02.input | 12 ++++++++++++ test/exopl02.txt | 3 +++ util/telnet-test.c | 4 +++- 7 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 test/exopl02.input create mode 100644 test/exopl02.txt diff --git a/libtelnet.c b/libtelnet.c index 3776422..784dd6b 100644 --- a/libtelnet.c +++ b/libtelnet.c @@ -881,7 +881,7 @@ static int _exopl_telnet(telnet_t *telnet, const char *buffer, size_t size) { ev.type = TELNET_EV_SUBNEGOTIATION; ev.sub.telopt = TELNET_TELOPT_EXOPL; - ev.sub.telopt_extended = 256 + buffer[1]; + ev.sub.telopt_extended = 256 + (unsigned char)buffer[1]; ev.sub.size = size - 3; /* allocate space for "subbuffer" */ @@ -902,7 +902,7 @@ static int _exopl_telnet(telnet_t *telnet, const char *buffer, size_t size) { return 0; } - exopl_telopt = 256 + (int)buffer[1]; + exopl_telopt = 256 + (unsigned char)buffer[1]; switch (cmd) { case TELNET_WILL: diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 446ad76..c8eae36 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,6 @@ enable_testing() -foreach (test_name environ01 environ02 environ03 exopl01 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 index 9294150..43f3c1f 100644 --- a/test/exopl01.input +++ b/test/exopl01.input @@ -1,12 +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] +# 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 index cd37d86..c1f01d1 100644 --- a/test/exopl01.txt +++ b/test/exopl01.txt @@ -1,3 +1,3 @@ DO 255 (EXOPL) -DO 300 (LIBTELNET-TEST-EXOPL-TELOPT) -SUB 300 (LIBTELNET-TEST-EXOPL-TELOPT) [6] +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 866c2ba..70828b5 100644 --- a/util/telnet-test.c +++ b/util/telnet-test.c @@ -30,6 +30,7 @@ static const telnet_telopt_t telopts[] = { { 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 } }; @@ -110,7 +111,8 @@ static const char *get_opt(int opt) { case 86: return "COMPRESS2"; case 93: return "ZMP"; case 255: return "EXOPL"; - case 300: return "LIBTELNET-TEST-EXOPL-TELOPT"; /* our custom dummy EXOPL option */ + 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"; } } From 97d3a042b74432d1efa2afd80b2f2c60d6a4e12b Mon Sep 17 00:00:00 2001 From: Jesse Friedman Date: Mon, 27 May 2019 13:23:36 -0400 Subject: [PATCH 7/8] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) 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 From 9a9c25d94f72a2f64e5a94cbbc3d963662e9ea11 Mon Sep 17 00:00:00 2001 From: Jesse Friedman Date: Mon, 27 May 2019 14:02:59 -0400 Subject: [PATCH 8/8] Add checks that supplied telopts are < 512 --- libtelnet.c | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/libtelnet.c b/libtelnet.c index 784dd6b..88214c0 100644 --- a/libtelnet.c +++ b/libtelnet.c @@ -1350,6 +1350,12 @@ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, 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]; @@ -1501,7 +1507,13 @@ void telnet_send_text(telnet_t *telnet, const char *buffer, /* send subnegotiation header */ 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 */ @@ -1528,6 +1540,12 @@ void telnet_subnegotiation(telnet_t *telnet, int telopt, const char *buffer, size_t size) { 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 */