From 080ca02768ac3ff3869ac72dfbd1e193108e6a36 Mon Sep 17 00:00:00 2001 From: Pavel Punsky Date: Fri, 7 Oct 2022 14:51:46 -0700 Subject: [PATCH] Update libtelnet to 0.23 (portability issues) (#1005) Split out work on libtelnet from #855 Required to update libtelnet to introduce compatibility with Windows This change contains vanilla code of [libtelnet](https://github.com/seanmiddleditch/libtelnet) v0.23 (latest tag) Test Plan: - run turnserver locally, set cli password, connect to the turnserver cli interface - run a few commands - get output --- src/apps/relay/libtelnet.c | 195 +++++++++++++++++++++++++++++-------- src/apps/relay/libtelnet.h | 40 +++++--- 2 files changed, 184 insertions(+), 51 deletions(-) diff --git a/src/apps/relay/libtelnet.c b/src/apps/relay/libtelnet.c index 2cf2cda..17eca8e 100644 --- a/src/apps/relay/libtelnet.c +++ b/src/apps/relay/libtelnet.c @@ -11,10 +11,6 @@ * all present and future rights to this code under copyright law. */ -/** - * Minor fixes by Oleg Moskalenko - */ - #ifdef HAVE_CONFIG_H #include #endif @@ -29,6 +25,15 @@ # define vsnprintf _vsnprintf # define __func__ __FUNCTION__ # define ZLIB_WINAPI 1 +# if defined(_MSC_VER) +/* va_copy() is directly supported starting in Visual Studio 2013 + * https://msdn.microsoft.com/en-us/library/kb57fad8(v=vs.110).aspx + * https://msdn.microsoft.com/en-us/library/kb57fad8(v=vs.120).aspx + */ +# if _MSC_VER <= 1700 +# define va_copy(dest, src) (dest = src) +# endif +# endif #endif #if defined(HAVE_ZLIB) @@ -58,6 +63,7 @@ /* telnet state codes */ enum telnet_state_t { TELNET_STATE_DATA = 0, + TELNET_STATE_EOL, TELNET_STATE_IAC, TELNET_STATE_WILL, TELNET_STATE_WONT, @@ -96,7 +102,9 @@ struct telnet_t { /* current subnegotiation telopt */ unsigned char sb_telopt; /* length of RFC1143 queue */ - unsigned char q_size; + unsigned int q_size; + /* number of entries in RFC1143 queue */ + unsigned int q_cnt; }; /* RFC1143 option negotiation state */ @@ -113,11 +121,18 @@ typedef struct telnet_rfc1143_t { #define Q_WANTNO_OP 4 #define Q_WANTYES_OP 5 +/* telnet NVT EOL sequences */ +static const char CRLF[] = { '\r', '\n' }; +static const char CRNUL[] = { '\r', '\0' }; + /* buffer sizes */ static const size_t _buffer_sizes[] = { 0, 512, 2048, 8192, 16384, }; static const size_t _buffer_sizes_count = sizeof(_buffer_sizes) / sizeof(_buffer_sizes[0]); +/* RFC1143 option negotiation state table allocation quantum */ +#define Q_BUFFER_GROWTH_QUANTUM 4 + /* error generation function */ static telnet_error_t _error(telnet_t *telnet, unsigned line, const char* func, telnet_error_t err, int fatal, const char *fmt, @@ -138,7 +153,7 @@ static telnet_error_t _error(telnet_t *telnet, unsigned line, ev.error.line = line; ev.error.msg = buffer; telnet->eh(telnet, &ev, telnet->ud); - + return err; } @@ -198,7 +213,7 @@ static void _send(telnet_t *telnet, const char *buffer, /* initialize z state */ telnet->z->next_in = (unsigned char *)buffer; - telnet->z->avail_in = size; + telnet->z->avail_in = (unsigned int)size; telnet->z->next_out = (unsigned char *)deflate_buffer; telnet->z->avail_out = sizeof(deflate_buffer); @@ -251,7 +266,7 @@ static INLINE int _check_telopt(telnet_t *telnet, unsigned char telopt, if (telnet->telopts == 0) return 0; - /* loop unti found or end marker (us and him both 0) */ + /* loop until found or end marker (us and him both 0) */ for (i = 0; telnet->telopts[i].telopt != -1; ++i) { if (telnet->telopts[i].telopt == telopt) { if (us && telnet->telopts[i].us == TELNET_WILL) @@ -274,7 +289,7 @@ static INLINE telnet_rfc1143_t _get_rfc1143(telnet_t *telnet, int i; /* search for entry */ - for (i = 0; i != telnet->q_size; ++i) { + for (i = 0; i != telnet->q_cnt; ++i) { if (telnet->q[i].telopt == telopt) { return telnet->q[i]; } @@ -293,9 +308,17 @@ static INLINE void _set_rfc1143(telnet_t *telnet, unsigned char telopt, int i; /* search for entry */ - for (i = 0; i != telnet->q_size; ++i) { + for (i = 0; i != telnet->q_cnt; ++i) { if (telnet->q[i].telopt == telopt) { telnet->q[i].state = Q_MAKE(us,him); + if (telopt != TELNET_TELOPT_BINARY) + return; + telnet->flags &= ~(TELNET_FLAG_TRANSMIT_BINARY | + TELNET_FLAG_RECEIVE_BINARY); + if (us == Q_YES) + telnet->flags |= TELNET_FLAG_TRANSMIT_BINARY; + if (him == Q_YES) + telnet->flags |= TELNET_FLAG_RECEIVE_BINARY; return; } } @@ -306,17 +329,26 @@ static INLINE void _set_rfc1143(telnet_t *telnet, unsigned char telopt, * to the number of enabled options for most simple code, and it * allows for an acceptable number of reallocations for complex code. */ - if ((qtmp = (telnet_rfc1143_t *)realloc(telnet->q, - sizeof(telnet_rfc1143_t) * (telnet->q_size + 4))) == 0) { - _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, - "realloc() failed: %s", strerror(errno)); - return; + + /* Did we reach the end of the table? */ + if (telnet->q_cnt >= telnet->q_size) { + /* Expand the size */ + if ((qtmp = (telnet_rfc1143_t *)realloc(telnet->q, + sizeof(telnet_rfc1143_t) * + (telnet->q_size + Q_BUFFER_GROWTH_QUANTUM))) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "realloc() failed: %s", strerror(errno)); + return; + } + memset(&qtmp[telnet->q_size], 0, sizeof(telnet_rfc1143_t) * + Q_BUFFER_GROWTH_QUANTUM); + telnet->q = qtmp; + telnet->q_size += Q_BUFFER_GROWTH_QUANTUM; } - memset(&qtmp[telnet->q_size], 0, sizeof(telnet_rfc1143_t) * 4); - telnet->q = qtmp; - telnet->q[telnet->q_size].telopt = telopt; - telnet->q[telnet->q_size].state = Q_MAKE(us, him); - telnet->q_size += 4; + /* Add entry to end of table */ + telnet->q[telnet->q_cnt].telopt = telopt; + telnet->q[telnet->q_cnt].state = Q_MAKE(us, him); + ++telnet->q_cnt; } /* send negotiation bytes */ @@ -503,7 +535,7 @@ static int _environ_telnet(telnet_t *telnet, unsigned char type, /* first byte must be a valid command */ if ((unsigned)buffer[0] != TELNET_ENVIRON_SEND && - (unsigned)buffer[0] != TELNET_ENVIRON_IS && + (unsigned)buffer[0] != TELNET_ENVIRON_IS && (unsigned)buffer[0] != TELNET_ENVIRON_INFO) { _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, "telopt %d subneg has invalid command", type); @@ -712,7 +744,7 @@ static int _mssp_telnet(telnet_t *telnet, char* buffer, size_t size) { /* parse ZMP command subnegotiation buffers */ static int _zmp_telnet(telnet_t *telnet, const char* buffer, size_t size) { telnet_event_t ev; - const char **argv; + char **argv; const char *c; size_t i, argc; @@ -728,7 +760,7 @@ static int _zmp_telnet(telnet_t *telnet, const char* buffer, size_t size) { c += strlen(c) + 1; /* allocate argument array, bail on error */ - if ((argv = (const char **)calloc(argc, sizeof(char *))) == 0) { + if ((argv = (char **)calloc(argc, sizeof(char *))) == 0) { _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, "calloc() failed: %s", strerror(errno)); return 0; @@ -736,13 +768,13 @@ static int _zmp_telnet(telnet_t *telnet, const char* buffer, size_t size) { /* populate argument array */ for (i = 0, c = buffer; i != argc; ++i) { - argv[i] = c; + argv[i] = (char *)c; c += strlen(c) + 1; } /* invoke event with our arguments */ ev.type = TELNET_EV_ZMP; - ev.zmp.argv = argv; + ev.zmp.argv = (const char**)argv; ev.zmp.argc = argc; telnet->eh(telnet, &ev, telnet->ud); @@ -890,8 +922,9 @@ void telnet_free(telnet_t *telnet) { /* free RFC1143 queue */ if (telnet->q) { free(telnet->q); - telnet->q = 0; + telnet->q = NULL; telnet->q_size = 0; + telnet->q_cnt = 0; } /* free the telnet structure itself */ @@ -956,9 +989,38 @@ static void _process(telnet_t *telnet, const char *buffer, size_t size) { telnet->eh(telnet, &ev, telnet->ud); } telnet->state = TELNET_STATE_IAC; + } else if (byte == '\r' && + (telnet->flags & TELNET_FLAG_NVT_EOL) && + !(telnet->flags & TELNET_FLAG_RECEIVE_BINARY)) { + if (i != start) { + ev.type = TELNET_EV_DATA; + ev.data.buffer = buffer + start; + ev.data.size = i - start; + telnet->eh(telnet, &ev, telnet->ud); + } + telnet->state = TELNET_STATE_EOL; } break; + /* NVT EOL to be translated */ + case TELNET_STATE_EOL: + if (byte != '\n') { + byte = '\r'; + ev.type = TELNET_EV_DATA; + ev.data.buffer = (char*)&byte; + ev.data.size = 1; + telnet->eh(telnet, &ev, telnet->ud); + byte = buffer[i]; + } + // any byte following '\r' other than '\n' or '\0' is invalid, + // so pass both \r and the byte + start = i; + if (byte == '\0') + ++start; + /* state update */ + telnet->state = TELNET_STATE_DATA; + break; + /* IAC command */ case TELNET_STATE_IAC: switch (byte) { @@ -1026,6 +1088,15 @@ static void _process(telnet_t *telnet, const char *buffer, size_t size) { /* IAC command in subnegotiation -- either IAC SE or IAC IAC */ if (byte == TELNET_IAC) { telnet->state = TELNET_STATE_SB_DATA_IAC; + } else if (telnet->sb_telopt == TELNET_TELOPT_COMPRESS && byte == TELNET_WILL) { + /* In 1998 MCCP used TELOPT 85 and the protocol defined an invalid + * subnegotiation sequence (IAC SB 85 WILL SE) to start compression. + * Subsequently MCCP version 2 was created in 2000 using TELOPT 86 + * and a valid subnegotiation (IAC SB 86 IAC SE). libtelnet for now + * just captures and discards MCCPv1 sequences. + */ + start = i + 2; + telnet->state = TELNET_STATE_DATA; /* buffer the byte, or bail if we can't */ } else if (_buffer_byte(telnet, byte) != TELNET_EOK) { start = i + 1; @@ -1097,7 +1168,7 @@ static void _process(telnet_t *telnet, const char *buffer, size_t size) { } } - /* pass through any remaining bytes */ + /* pass through any remaining bytes */ if (telnet->state == TELNET_STATE_DATA && i != start) { ev.type = TELNET_EV_DATA; ev.data.buffer = buffer + start; @@ -1117,7 +1188,7 @@ void telnet_recv(telnet_t *telnet, const char *buffer, /* initialize zlib state */ telnet->z->next_in = (unsigned char*)buffer; - telnet->z->avail_in = size; + telnet->z->avail_in = (unsigned int)size; telnet->z->next_out = (unsigned char *)inflate_buffer; telnet->z->avail_out = sizeof(inflate_buffer); @@ -1186,7 +1257,7 @@ void telnet_negotiate(telnet_t *telnet, unsigned char cmd, _sendu(telnet, bytes, 3); return; } - + /* get current option states */ q = _get_rfc1143(telnet, telopt); @@ -1282,6 +1353,49 @@ void telnet_send(telnet_t *telnet, const char *buffer, } } +/* send non-command text (escapes IAC bytes and does NVT translation) */ +void telnet_send_text(telnet_t *telnet, const char *buffer, + size_t size) { + size_t i, l; + + for (l = i = 0; i != size; ++i) { + /* dump prior portion of text, send escaped bytes */ + if (buffer[i] == (char)TELNET_IAC) { + /* dump prior text if any */ + if (i != l) { + _send(telnet, buffer + l, i - l); + } + l = i + 1; + + /* send escape */ + telnet_iac(telnet, TELNET_IAC); + } + /* special characters if not in BINARY mode */ + else if (!(telnet->flags & TELNET_FLAG_TRANSMIT_BINARY) && + (buffer[i] == '\r' || buffer[i] == '\n')) { + /* dump prior portion of text */ + if (i != l) { + _send(telnet, buffer + l, i - l); + } + l = i + 1; + + /* automatic translation of \r -> CRNUL */ + if (buffer[i] == '\r') { + _send(telnet, CRNUL, 2); + } + /* automatic translation of \n -> CRLF */ + else { + _send(telnet, CRLF, 2); + } + } + } + + /* send whatever portion of buffer is left */ + if (i != l) { + _send(telnet, buffer + l, i - l); + } +} + /* send subnegotiation header */ void telnet_begin_sb(telnet_t *telnet, unsigned char telopt) { unsigned char sb[3]; @@ -1326,7 +1440,6 @@ void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, } void telnet_begin_compress2(telnet_t *telnet) { - UNUSED_ARG(telnet); #if defined(HAVE_ZLIB) static const unsigned char compress2[] = { TELNET_IAC, TELNET_SB, TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE }; @@ -1355,23 +1468,25 @@ void telnet_begin_compress2(telnet_t *telnet) { /* send formatted data with \r and \n translation in addition to IAC IAC */ int telnet_vprintf(telnet_t *telnet, const char *fmt, va_list va) { - static const char CRLF[] = { '\r', '\n' }; - static const char CRNUL[] = { '\r', '\0' }; char buffer[1024]; char *output = buffer; int rs, i, l; /* format */ + va_list va2; + va_copy(va2, va); rs = vsnprintf(buffer, sizeof(buffer), fmt, va); - if ((size_t)rs >= sizeof(buffer)) { + if (rs >= sizeof(buffer)) { output = (char*)malloc(rs + 1); if (output == 0) { _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, "malloc() failed: %s", strerror(errno)); return -1; } - rs = vsnprintf(output, rs + 1, fmt, va); + rs = vsnprintf(output, rs + 1, fmt, va2); } + va_end(va2); + va_end(va); /* send */ for (l = i = 0; i != rs; ++i) { @@ -1427,16 +1542,20 @@ int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va) { int rs; /* format; allocate more space if necessary */ + va_list va2; + va_copy(va2, va); rs = vsnprintf(buffer, sizeof(buffer), fmt, va); - if ((size_t)rs >= sizeof(buffer)) { + if (rs >= sizeof(buffer)) { output = (char*)malloc(rs + 1); if (output == 0) { _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, "malloc() failed: %s", strerror(errno)); return -1; } - rs = vsnprintf(output, rs + 1, fmt, va); + rs = vsnprintf(output, rs + 1, fmt, va2); } + va_end(va2); + va_end(va); /* send out the formatted data */ telnet_send(telnet, output, rs); @@ -1464,13 +1583,13 @@ int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...) { /* begin NEW-ENVIRON subnegotation */ void telnet_begin_newenviron(telnet_t *telnet, unsigned char cmd) { telnet_begin_sb(telnet, TELNET_TELOPT_NEW_ENVIRON); - telnet_send(telnet, (char*)&cmd, 1); + telnet_send(telnet, (const char *)&cmd, 1); } /* send a NEW-ENVIRON value */ void telnet_newenviron_value(telnet_t *telnet, unsigned char type, const char *string) { - telnet_send(telnet, (char*)&type, 1); + telnet_send(telnet, (const char*)&type, 1); if (string != 0) { telnet_send(telnet, string, strlen(string)); diff --git a/src/apps/relay/libtelnet.h b/src/apps/relay/libtelnet.h index b36cc87..40030c1 100644 --- a/src/apps/relay/libtelnet.h +++ b/src/apps/relay/libtelnet.h @@ -34,20 +34,17 @@ * * \file libtelnet.h * - * \version 0.21 + * \version 0.23 * * \author Sean Middleditch */ -/** - * Minor fixes by Oleg Moskalenko - */ - #if !defined(LIBTELNET_INCLUDE) #define LIBTELNET_INCLUDE 1 /* standard C headers necessary for the libtelnet API */ #include +#include /* C++ support */ #if defined(__cplusplus) @@ -57,10 +54,15 @@ extern "C" { /* printf type checking feature in GCC and some other compilers */ #if __GNUC__ # define TELNET_GNU_PRINTF(f,a) __attribute__((format(printf, f, a))) /*!< internal helper */ +# define TELNET_GNU_SENTINEL __attribute__((sentinel)) /*!< internal helper */ #else # define TELNET_GNU_PRINTF(f,a) /*!< internal helper */ +# define TELNET_GNU_SENTINEL /*!< internal helper */ #endif +/* Disable environ macro for Visual C++ 2015. */ +#undef environ + /*! Telnet state tracker object type. */ typedef struct telnet_t telnet_t; @@ -139,6 +141,7 @@ typedef struct telnet_telopt_t telnet_telopt_t; #define TELNET_TELOPT_ENCRYPT 38 #define TELNET_TELOPT_NEW_ENVIRON 39 #define TELNET_TELOPT_MSSP 70 +#define TELNET_TELOPT_COMPRESS 85 #define TELNET_TELOPT_COMPRESS2 86 #define TELNET_TELOPT_ZMP 93 #define TELNET_TELOPT_EXOPL 255 @@ -176,14 +179,14 @@ typedef struct telnet_telopt_t telnet_telopt_t; /*@{*/ /*! Control behavior of telnet state tracker. */ #define TELNET_FLAG_PROXY (1<<0) +#define TELNET_FLAG_NVT_EOL (1<<1) +/* Internal-only bits in option flags */ +#define TELNET_FLAG_TRANSMIT_BINARY (1<<5) +#define TELNET_FLAG_RECEIVE_BINARY (1<<6) #define TELNET_PFLAG_DEFLATE (1<<7) /*@}*/ -#if !defined(UNUSED_ARG) -#define UNUSED_ARG(A) do { A=A; } while(0) -#endif - /*! * error codes */ @@ -224,8 +227,8 @@ typedef enum telnet_event_type_t telnet_event_type_t; /*!< Telnet event type. */ */ struct telnet_environ_t { unsigned char type; /*!< either TELNET_ENVIRON_VAR or TELNET_ENVIRON_USERVAR */ - const char *var; /*!< name of the variable being set */ - const char *value; /*!< value of variable being set; empty string if no value */ + char *var; /*!< name of the variable being set */ + char *value; /*!< value of variable being set; empty string if no value */ }; /*! @@ -376,7 +379,7 @@ struct telnet_t; * \param eh Event handler function called for every event. * \param flags 0 or TELNET_FLAG_PROXY. * \param user_data Optional data pointer that will be passsed to eh. - * \return Telent state tracker object. + * \return Telnet state tracker object. */ extern telnet_t* telnet_init(const telnet_telopt_t *telopts, telnet_event_handler_t eh, unsigned char flags, void *user_data); @@ -439,6 +442,17 @@ extern void telnet_negotiate(telnet_t *telnet, unsigned char cmd, extern void telnet_send(telnet_t *telnet, const char *buffer, size_t size); +/*! + * Send non-command text (escapes IAC bytes and translates + * \\r -> CR-NUL and \\n -> CR-LF unless in BINARY mode. + * + * \param telnet Telnet state tracker object. + * \param buffer Buffer of bytes to send. + * \param size Number of bytes to send. + */ +extern void telnet_send_text(telnet_t *telnet, + const char *buffer, size_t size); + /*! * \brief Begin a sub-negotiation command. * @@ -634,7 +648,7 @@ extern void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv); * * \param telnet Telnet state tracker object. */ -extern void telnet_send_zmpv(telnet_t *telnet, ...); +extern void telnet_send_zmpv(telnet_t *telnet, ...) TELNET_GNU_SENTINEL; /*! * \brief Send a ZMP command.