diff --git a/dependencies.lock b/dependencies.lock index e9e979e..0bbff7d 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -1,10 +1,21 @@ dependencies: + frozen-eye/esp_telnet: + component_hash: e00a16d6372fa31df46b32e330847d6e09af2ec184d8c7cac4d183b3daac02e5 + dependencies: + - name: idf + require: private + version: '>=4.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 0.0.2 idf: source: type: idf version: 5.4.1 direct_dependencies: +- frozen-eye/esp_telnet - idf -manifest_hash: 00d8efc8e59d9f2cc6a576ada310175e19e6f7506dd0b59211849f065dadccae +manifest_hash: 8e2a908d16408b9cafcd4c4b109b841acfca6e660d2500fc6d1362f0f143376c target: esp32c6 version: 2.0.0 diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..23052c8 --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,6 @@ +## IDF Component Manager Manifest File +# https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/manifest_file.html +# https://components.espressif.com + +dependencies: + frozen-eye/esp_telnet: "^0.0.2" diff --git a/managed_components/frozen-eye__esp_telnet/.component_hash b/managed_components/frozen-eye__esp_telnet/.component_hash new file mode 100644 index 0000000..51bed66 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/.component_hash @@ -0,0 +1 @@ +e00a16d6372fa31df46b32e330847d6e09af2ec184d8c7cac4d183b3daac02e5 \ No newline at end of file diff --git a/managed_components/frozen-eye__esp_telnet/.gitignore b/managed_components/frozen-eye__esp_telnet/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/managed_components/frozen-eye__esp_telnet/AUTHORS b/managed_components/frozen-eye__esp_telnet/AUTHORS new file mode 100644 index 0000000..329745c --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/AUTHORS @@ -0,0 +1,12 @@ +Original Telnet Server code is based on the version created and written by the following people: + +- Sean Middleditch +- Jack Kelly +- Katherine Flavel +- Daniel Loffgren (https://github.com/RyuKojiro) + +and taken from here: [https://github.com/seanmiddleditch/libtelnet](https://github.com/seanmiddleditch/libtelnet) + +Midificatons are performed by: + +- FrozenEye (https://github.com/frozen-eye) diff --git a/managed_components/frozen-eye__esp_telnet/CHECKSUMS.json b/managed_components/frozen-eye__esp_telnet/CHECKSUMS.json new file mode 100644 index 0000000..6f2b39b --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/CHECKSUMS.json @@ -0,0 +1 @@ +{"version": "1.0", "algorithm": "sha256", "created_at": "2025-05-21T14:59:16.171166+00:00", "files": [{"path": "CMakeLists.txt", "size": 303, "hash": "70671f714f52ac79607119703f1ddab4a0b325f25e8981afe7c93baf7af7cd34"}, {"path": "LICENSE", "size": 1063, "hash": "068129b238141ba0ebcb2158402d73408c798f4d8c5a42f4dafaa41a002b9ea2"}, {"path": "AUTHORS", "size": 452, "hash": "277d45436ae96f30f6ee4b2dcde2661e679f2ded4f790cbee0c726d5f13c8dba"}, {"path": "idf_component.yml", "size": 179, "hash": "39f2a2bd0c96ab90610389356a2cf39842364514e2a69c1241e6ecd84b269d72"}, {"path": "Kconfig", "size": 1114, "hash": "ccc9fc290f957c953bdb3bbc8d1079efe7c5fdb82f10d6c7a2dc9973b4046bd7"}, {"path": ".gitignore", "size": 0, "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, {"path": "src/libtelnet.h", "size": 21611, "hash": "0798f3706642fdd4520923914ddfa59fb9ded670b7716877c80fa852153b8521"}, {"path": "src/server.c", "size": 9226, "hash": "bf90b0df126da4a16d79c626724ce715fdfb8015dd7ea0030b8a6cbf072c947f"}, {"path": "src/libtelnet.c", "size": 44236, "hash": "de15620edc5492aaf6c273ce4c9181cb99490a2153e56db7d7c3f96f3ea7e2b4"}, {"path": "include/telnet/server.h", "size": 1493, "hash": "ebe6d17a86a1c56c97e263bd4d468cffc5d95a3ca810e79388b2a9598f89f8ba"}]} \ No newline at end of file diff --git a/managed_components/frozen-eye__esp_telnet/CMakeLists.txt b/managed_components/frozen-eye__esp_telnet/CMakeLists.txt new file mode 100644 index 0000000..db10799 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.10) + +idf_component_register( + INCLUDE_DIRS + include + src + . + SRCS + src/libtelnet.c + src/server.c + REQUIRES + PRIV_REQUIRES +) + +# target_compile_options(${COMPONENT_LIB} PRIVATE -Wall -Wextra -Werror) +# target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17) diff --git a/managed_components/frozen-eye__esp_telnet/Kconfig b/managed_components/frozen-eye__esp_telnet/Kconfig new file mode 100644 index 0000000..bcad548 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/Kconfig @@ -0,0 +1,41 @@ +menu "Telnet Server" + + config TELNET_SERVER_DEFAULT_PORT + int "Default Telnet Server Port" + default 23 + help + Default Telnet Server Port Number + + config TELNET_SERVER_MAX_CONNECTIONS + int "Maximum Telnet Server Connections" + default 5 + help + Maximum Number of Telnet Server Connections + + config TELNET_SERVER_STACK_SIZE + int "Telnet Server Stack Size" + default 4096 + help + Telnet Server Stack Size + + config TELNET_SERVER_TASK_PRIORITY + int "Telnet Server Task Priority" + range 1 25 + default 24 + help + Telnet Server Task Priority. In case of high events rate, this parameter could be changed up to (configMAX_PRIORITIES-1). + + config TELNET_SERVER_TASK_CORE + int "Telnet Server Task Core" + range 0 1 + default 0 + help + Telnet Server Task Core + + config TELNET_SERVER_REDIRECT_LOGS + int "Redirect Logs to Telnet Server" + range 0 1 + default 0 + help + Redirect Logs to Telnet Server +endmenu diff --git a/managed_components/frozen-eye__esp_telnet/LICENSE b/managed_components/frozen-eye__esp_telnet/LICENSE new file mode 100644 index 0000000..3390e92 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/LICENSE @@ -0,0 +1,21 @@ +Copyright 2024 Frozen Eye + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/managed_components/frozen-eye__esp_telnet/idf_component.yml b/managed_components/frozen-eye__esp_telnet/idf_component.yml new file mode 100644 index 0000000..95eb8e5 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/idf_component.yml @@ -0,0 +1,6 @@ +dependencies: + idf: + version: '>=4.0' +description: esp_telnet is a small implementation of the telnet server. +url: https://github.com/frozen-eye/esp_telnet.git +version: 0.0.2 diff --git a/managed_components/frozen-eye__esp_telnet/include/telnet/server.h b/managed_components/frozen-eye__esp_telnet/include/telnet/server.h new file mode 100644 index 0000000..43715a1 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/include/telnet/server.h @@ -0,0 +1,55 @@ +#pragma once + +#include "sdkconfig.h" + +#include + +static const telnet_telopt_t default_telopts[] = { + {TELNET_TELOPT_COMPRESS2, TELNET_WILL, TELNET_DO}, {TELNET_TELOPT_ZMP, TELNET_WILL, TELNET_DO}, + {TELNET_TELOPT_MSSP, TELNET_WILL, TELNET_DONT}, {TELNET_TELOPT_NEW_ENVIRON, TELNET_WILL, TELNET_DONT}, + {TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT}, {-1, 0, 0}}; + +struct user_t { + char* name; + int sock; + telnet_t* telnet; + char linebuf[255]; + int linepos; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +struct telnet_server_config { + int port; + int stack_size; + int task_priority; + int task_core; + int redirect_logs; + int max_connections; + const telnet_telopt_t *telnet_opts; +}; + +/** + * @brief Telnet Server Default Configuration + * + */ +#define TELNET_SERVER_DEFAULT_CONFIG \ +{ \ + .port = CONFIG_TELNET_SERVER_DEFAULT_PORT, \ + .stack_size = CONFIG_TELNET_SERVER_STACK_SIZE, \ + .task_priority = CONFIG_TELNET_SERVER_TASK_PRIORITY, \ + .task_core = CONFIG_TELNET_SERVER_TASK_CORE, \ + .redirect_logs = CONFIG_TELNET_SERVER_REDIRECT_LOGS, \ + .max_connections = CONFIG_TELNET_SERVER_MAX_CONNECTIONS, \ + .telnet_opts = default_telopts, \ +} + +typedef struct telnet_server_config telnet_server_config_t; + +esp_err_t telnet_server_create(telnet_server_config_t* config); + +#ifdef __cplusplus +} +#endif diff --git a/managed_components/frozen-eye__esp_telnet/src/libtelnet.c b/managed_components/frozen-eye__esp_telnet/src/libtelnet.c new file mode 100644 index 0000000..3d09e53 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/src/libtelnet.c @@ -0,0 +1,1668 @@ +/* + * libtelnet - TELNET protocol handling library + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +/* Win32 compatibility */ +#if defined(_WIN32) +# 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) +# include +#endif + +#include "libtelnet.h" + +/* inlinable functions */ +#if defined(__GNUC__) || __STDC_VERSION__ >= 199901L +# define INLINE __inline__ +#else +# define INLINE +#endif + +/* helper for Q-method option tracking */ +#define Q_US(q) ((q).state & 0x0F) +#define Q_HIM(q) (((q).state & 0xF0) >> 4) +#define Q_MAKE(us,him) ((us) | ((him) << 4)) + +/* helper for the negotiation routines */ +#define NEGOTIATE_EVENT(telnet,cmd,opt) \ + ev.type = (cmd); \ + ev.neg.telopt = (opt); \ + (telnet)->eh((telnet), &ev, (telnet)->ud); + +/* telnet state codes */ +enum telnet_state_t { + TELNET_STATE_DATA = 0, + TELNET_STATE_EOL, + TELNET_STATE_IAC, + TELNET_STATE_WILL, + TELNET_STATE_WONT, + TELNET_STATE_DO, + TELNET_STATE_DONT, + TELNET_STATE_SB, + TELNET_STATE_SB_DATA, + TELNET_STATE_SB_DATA_IAC +}; +typedef enum telnet_state_t telnet_state_t; + +/* telnet state tracker */ +struct telnet_t { + /* user data */ + void *ud; + /* telopt support table */ + const telnet_telopt_t *telopts; + /* event handler */ + telnet_event_handler_t eh; +#if defined(HAVE_ZLIB) + /* zlib (mccp2) compression */ + z_stream *z; +#endif + /* RFC1143 option negotiation states */ + struct telnet_rfc1143_t *q; + /* sub-request buffer */ + char *buffer; + /* current size of the buffer */ + size_t buffer_size; + /* current buffer write position (also length of buffer data) */ + size_t buffer_pos; + /* current state */ + enum telnet_state_t state; + /* option flags */ + unsigned char flags; + /* current subnegotiation telopt */ + unsigned char sb_telopt; + /* length of RFC1143 queue */ + unsigned int q_size; + /* number of entries in RFC1143 queue */ + unsigned int q_cnt; +}; + +/* RFC1143 option negotiation state */ +typedef struct telnet_rfc1143_t { + unsigned char telopt; + unsigned char state; +} telnet_rfc1143_t; + +/* RFC1143 state names */ +#define Q_NO 0 +#define Q_YES 1 +#define Q_WANTNO 2 +#define Q_WANTYES 3 +#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, + ...) { + telnet_event_t ev; + char buffer[512]; + va_list va; + + /* format informational text */ + va_start(va, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, va); + va_end(va); + + /* send error event to the user */ + ev.type = fatal ? TELNET_EV_ERROR : TELNET_EV_WARNING; + ev.error.file = __FILE__; + ev.error.func = func; + ev.error.line = line; + ev.error.msg = buffer; + telnet->eh(telnet, &ev, telnet->ud); + + return err; +} + +#if defined(HAVE_ZLIB) +/* initialize the zlib box for a telnet box; if deflate is non-zero, it + * initializes zlib for delating (compression), otherwise for inflating + * (decompression). returns TELNET_EOK on success, something else on + * failure. + */ +telnet_error_t _init_zlib(telnet_t *telnet, int deflate, int err_fatal) { + z_stream *z; + int rs; + + /* if compression is already enabled, fail loudly */ + if (telnet->z != 0) + return _error(telnet, __LINE__, __func__, TELNET_EBADVAL, + err_fatal, "cannot initialize compression twice"); + + /* allocate zstream box */ + if ((z= (z_stream *)calloc(1, sizeof(z_stream))) == 0) + return _error(telnet, __LINE__, __func__, TELNET_ENOMEM, err_fatal, + "malloc() failed: %s", strerror(errno)); + + /* initialize */ + if (deflate) { + if ((rs = deflateInit(z, Z_DEFAULT_COMPRESSION)) != Z_OK) { + free(z); + return _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, + err_fatal, "deflateInit() failed: %s", zError(rs)); + } + telnet->flags |= TELNET_PFLAG_DEFLATE; + } else { + if ((rs = inflateInit(z)) != Z_OK) { + free(z); + return _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, + err_fatal, "inflateInit() failed: %s", zError(rs)); + } + telnet->flags &= ~TELNET_PFLAG_DEFLATE; + } + + telnet->z = z; + + return TELNET_EOK; +} +#endif /* defined(HAVE_ZLIB) */ + +/* push bytes out, compressing them first if need be */ +static void _send(telnet_t *telnet, const char *buffer, + size_t size) { + telnet_event_t ev; + +#if defined(HAVE_ZLIB) + /* if we have a deflate (compression) zlib box, use it */ + if (telnet->z != 0 && telnet->flags & TELNET_PFLAG_DEFLATE) { + char deflate_buffer[1024]; + int rs; + + /* initialize z state */ + telnet->z->next_in = (unsigned char *)buffer; + telnet->z->avail_in = (unsigned int)size; + telnet->z->next_out = (unsigned char *)deflate_buffer; + telnet->z->avail_out = sizeof(deflate_buffer); + + /* deflate until buffer exhausted and all output is produced */ + while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) { + /* compress */ + if ((rs = deflate(telnet->z, Z_SYNC_FLUSH)) != Z_OK) { + _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, 1, + "deflate() failed: %s", zError(rs)); + deflateEnd(telnet->z); + free(telnet->z); + telnet->z = 0; + break; + } + + /* send event */ + ev.type = TELNET_EV_SEND; + ev.data.buffer = deflate_buffer; + ev.data.size = sizeof(deflate_buffer) - telnet->z->avail_out; + telnet->eh(telnet, &ev, telnet->ud); + + /* prepare output buffer for next run */ + telnet->z->next_out = (unsigned char *)deflate_buffer; + telnet->z->avail_out = sizeof(deflate_buffer); + } + + /* do not continue with remaining code */ + return; + } +#endif /* defined(HAVE_ZLIB) */ + + ev.type = TELNET_EV_SEND; + ev.data.buffer = buffer; + ev.data.size = size; + telnet->eh(telnet, &ev, telnet->ud); +} + +/* to send bags of unsigned chars */ +#define _sendu(t, d, s) _send((t), (const char*)(d), (s)) + +/* check if we support a particular telopt; if us is non-zero, we + * 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, + int us) { + int i; + + /* if we have no telopts table, we obviously don't support it */ + if (telnet->telopts == 0) + return 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) + return 1; + else if (!us && telnet->telopts[i].him == TELNET_DO) + return 1; + else + return 0; + } + } + + /* not found, so not supported */ + return 0; +} + +/* retrieve RFC1143 option state */ +static INLINE telnet_rfc1143_t _get_rfc1143(telnet_t *telnet, + unsigned char telopt) { + telnet_rfc1143_t empty; + unsigned int i; + + /* search for entry */ + for (i = 0; i != telnet->q_cnt; ++i) { + if (telnet->q[i].telopt == telopt) { + return telnet->q[i]; + } + } + + /* not found, return empty value */ + empty.telopt = telopt; + empty.state = 0; + return empty; +} + +/* save RFC1143 option state */ +static INLINE void _set_rfc1143(telnet_t *telnet, unsigned char telopt, + char us, char him) { + telnet_rfc1143_t *qtmp; + unsigned int i; + + /* search for entry */ + 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; + } + } + + /* we're going to need to track state for it, so grow the queue + * by 4 (four) elements and put the telopt into it; bail on allocation + * error. we go by four because it seems like a reasonable guess as + * to the number of enabled options for most simple code, and it + * allows for an acceptable number of reallocations for complex code. + */ + + /* 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; + } + /* 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 */ +static INLINE void _send_negotiate(telnet_t *telnet, unsigned char cmd, + unsigned char telopt) { + unsigned char bytes[3]; + bytes[0] = TELNET_IAC; + bytes[1] = cmd; + bytes[2] = telopt; + _sendu(telnet, bytes, 3); +} + +/* negotiation handling magic for RFC1143 */ +static void _negotiate(telnet_t *telnet, unsigned char telopt) { + telnet_event_t ev; + telnet_rfc1143_t q; + + /* in PROXY mode, just pass it thru and do nothing */ + if (telnet->flags & TELNET_FLAG_PROXY) { + switch ((int)telnet->state) { + case TELNET_STATE_WILL: + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + break; + case TELNET_STATE_WONT: + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + break; + case TELNET_STATE_DO: + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + break; + case TELNET_STATE_DONT: + NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); + break; + } + return; + } + + /* lookup the current state of the option */ + q = _get_rfc1143(telnet, telopt); + + /* start processing... */ + switch ((int)telnet->state) { + /* request to enable option on remote end or confirm DO */ + case TELNET_STATE_WILL: + switch (Q_HIM(q)) { + case Q_NO: + if (_check_telopt(telnet, telopt, 0)) { + _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); + _send_negotiate(telnet, TELNET_DO, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + } else + _send_negotiate(telnet, TELNET_DONT, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "DONT answered by WILL"); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "DONT answered by WILL"); + break; + case Q_WANTYES: + _set_rfc1143(telnet, telopt, Q_US(q), Q_YES); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + break; + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); + _send_negotiate(telnet, TELNET_DONT, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + break; + } + break; + + /* request to disable option on remote end, confirm DONT, reject DO */ + case TELNET_STATE_WONT: + switch (Q_HIM(q)) { + case Q_YES: + _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); + _send_negotiate(telnet, TELNET_DONT, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); + _send_negotiate(telnet, TELNET_DO, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_WONT, telopt); + break; + case Q_WANTYES: + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_NO); + break; + } + break; + + /* request to enable option on local end or confirm WILL */ + case TELNET_STATE_DO: + switch (Q_US(q)) { + case Q_NO: + if (_check_telopt(telnet, telopt, 1)) { + _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WILL, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + } else + _send_negotiate(telnet, TELNET_WONT, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); + NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "WONT answered by DO"); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "WONT answered by DO"); + break; + case Q_WANTYES: + _set_rfc1143(telnet, telopt, Q_YES, Q_HIM(q)); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + break; + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WONT, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + break; + } + break; + + /* request to disable option on local end, confirm WONT, reject WILL */ + case TELNET_STATE_DONT: + switch (Q_US(q)) { + case Q_YES: + _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WONT, telopt); + NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); + NEGOTIATE_EVENT(telnet, TELNET_EV_DONT, 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); + break; + case Q_WANTYES: + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_NO, Q_HIM(q)); + break; + } + break; + } +} + +/* process an ENVIRON/NEW-ENVIRON subnegotiation buffer + * + * the algorithm and approach used here is kind of a hack, + * but it reduces the number of memory allocations we have + * to make. + * + * we copy the bytes back into the buffer, starting at the very + * beginning, which makes it easy to handle the ENVIRON ESC + * escape mechanism as well as ensure the variable name and + * value strings are NUL-terminated, all while fitting inside + * of the original buffer. + */ +static int _environ_telnet(telnet_t *telnet, unsigned char type, + char* buffer, size_t size) { + telnet_event_t ev; + struct telnet_environ_t *values = 0; + char *c, *last, *out; + size_t index, count; + + /* if we have no data, just pass it through */ + if (size == 0) { + return 0; + } + + /* 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_INFO) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "telopt %d subneg has invalid command", type); + return 0; + } + + /* store ENVIRON command */ + ev.environ.cmd = buffer[0]; + + /* if we have no arguments, send an event with no data end return */ + if (size == 1) { + /* no list of variables given */ + ev.environ.values = 0; + ev.environ.size = 0; + + /* invoke event with our arguments */ + ev.type = TELNET_EV_ENVIRON; + telnet->eh(telnet, &ev, telnet->ud); + + return 0; + } + + /* very second byte must be VAR or USERVAR, if present */ + if ((unsigned)buffer[1] != TELNET_ENVIRON_VAR && + (unsigned)buffer[1] != TELNET_ENVIRON_USERVAR) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "telopt %d subneg missing variable type", type); + return 0; + } + + /* ensure last byte is not an escape byte (makes parsing later easier) */ + if ((unsigned)buffer[size - 1] == TELNET_ENVIRON_ESC) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "telopt %d subneg ends with ESC", type); + return 0; + } + + /* count arguments; each valid entry starts with VAR or USERVAR */ + count = 0; + for (c = buffer + 1; c < buffer + size; ++c) { + if (*c == TELNET_ENVIRON_VAR || *c == TELNET_ENVIRON_USERVAR) { + ++count; + } else if (*c == TELNET_ENVIRON_ESC) { + /* skip the next byte */ + ++c; + } + } + + /* allocate argument array, bail on error */ + if ((values = (struct telnet_environ_t *)calloc(count, + sizeof(struct telnet_environ_t))) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "calloc() failed: %s", strerror(errno)); + return 0; + } + + /* parse argument array strings */ + out = buffer; + c = buffer + 1; + for (index = 0; index != count; ++index) { + /* remember the variable type (will be VAR or USERVAR) */ + values[index].type = *c++; + + /* scan until we find an end-marker, and buffer up unescaped + * bytes into our buffer */ + last = out; + while (c < buffer + size) { + /* stop at the next variable or at the value */ + if ((unsigned)*c == TELNET_ENVIRON_VAR || + (unsigned)*c == TELNET_ENVIRON_VALUE || + (unsigned)*c == TELNET_ENVIRON_USERVAR) { + break; + } + + /* buffer next byte (taking into account ESC) */ + if (*c == TELNET_ENVIRON_ESC) { + ++c; + } + + *out++ = *c++; + } + *out++ = '\0'; + + /* store the variable name we have just received */ + values[index].var = last; + values[index].value = ""; + + /* if we got a value, find the next end marker and + * store the value; otherwise, store empty string */ + if (c < buffer + size && *c == TELNET_ENVIRON_VALUE) { + ++c; + last = out; + while (c < buffer + size) { + /* stop when we find the start of the next variable */ + if ((unsigned)*c == TELNET_ENVIRON_VAR || + (unsigned)*c == TELNET_ENVIRON_USERVAR) { + break; + } + + /* buffer next byte (taking into account ESC) */ + if (*c == TELNET_ENVIRON_ESC) { + ++c; + } + + *out++ = *c++; + } + *out++ = '\0'; + + /* store the variable value */ + values[index].value = last; + } + } + + /* pass values array and count to event */ + ev.environ.values = values; + ev.environ.size = count; + + /* invoke event with our arguments */ + ev.type = TELNET_EV_ENVIRON; + telnet->eh(telnet, &ev, telnet->ud); + + /* clean up */ + free(values); + return 0; +} + +/* process an MSSP subnegotiation buffer */ +static int _mssp_telnet(telnet_t *telnet, char* buffer, size_t size) { + telnet_event_t ev; + struct telnet_environ_t *values; + char *var = 0; + char *c, *last, *out; + size_t i, count; + unsigned char next_type; + + /* if we have no data, just pass it through */ + if (size == 0) { + return 0; + } + + /* first byte must be a VAR */ + if ((unsigned)buffer[0] != TELNET_MSSP_VAR) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "MSSP subnegotiation has invalid data"); + return 0; + } + + /* count the arguments, any part that starts with VALUE */ + for (count = 0, i = 0; i != size; ++i) { + if ((unsigned)buffer[i] == TELNET_MSSP_VAL) { + ++count; + } + } + + /* allocate argument array, bail on error */ + if ((values = (struct telnet_environ_t *)calloc(count, + sizeof(struct telnet_environ_t))) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "calloc() failed: %s", strerror(errno)); + return 0; + } + + ev.mssp.values = values; + ev.mssp.size = count; + + /* allocate strings in argument array */ + out = last = buffer; + next_type = buffer[0]; + for (i = 0, c = buffer + 1; c < buffer + size;) { + /* search for end marker */ + while (c < buffer + size && (unsigned)*c != TELNET_MSSP_VAR && + (unsigned)*c != TELNET_MSSP_VAL) { + *out++ = *c++; + } + *out++ = '\0'; + + /* if it's a variable name, just store the name for now */ + if (next_type == TELNET_MSSP_VAR) { + var = last; + } else if (next_type == TELNET_MSSP_VAL && var != 0) { + values[i].var = var; + values[i].value = last; + ++i; + } else { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "invalid MSSP subnegotiation data"); + free(values); + return 0; + } + + /* remember our next type and increment c for next loop run */ + last = out; + next_type = *c++; + } + + /* invoke event with our arguments */ + ev.type = TELNET_EV_MSSP; + telnet->eh(telnet, &ev, telnet->ud); + + /* clean up */ + free(values); + + return 0; +} + +/* parse ZMP command subnegotiation buffers */ +static int _zmp_telnet(telnet_t *telnet, const char* buffer, size_t size) { + telnet_event_t ev; + char **argv; + const char *c; + size_t i, argc; + + /* make sure this is a valid ZMP buffer */ + if (size == 0 || buffer[size - 1] != 0) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "incomplete ZMP frame"); + return 0; + } + + /* count arguments */ + for (argc = 0, c = buffer; c != buffer + size; ++argc) + c += strlen(c) + 1; + + /* allocate argument array, bail on error */ + if ((argv = (char **)calloc(argc, sizeof(char *))) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "calloc() failed: %s", strerror(errno)); + return 0; + } + + /* populate argument array */ + for (i = 0, c = buffer; i != argc; ++i) { + argv[i] = (char *)c; + c += strlen(c) + 1; + } + + /* invoke event with our arguments */ + ev.type = TELNET_EV_ZMP; + ev.zmp.argv = (const char**)argv; + ev.zmp.argc = argc; + telnet->eh(telnet, &ev, telnet->ud); + + /* clean up */ + free(argv); + return 0; +} + +/* parse TERMINAL-TYPE command subnegotiation buffers */ +static int _ttype_telnet(telnet_t *telnet, const char* buffer, size_t size) { + telnet_event_t ev; + + /* make sure request is not empty */ + if (size == 0) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "incomplete TERMINAL-TYPE request"); + return 0; + } + + /* make sure request has valid command type */ + if (buffer[0] != TELNET_TTYPE_IS && + buffer[0] != TELNET_TTYPE_SEND) { + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "TERMINAL-TYPE request has invalid type"); + return 0; + } + + /* send proper event */ + if (buffer[0] == TELNET_TTYPE_IS) { + char *name; + + /* allocate space for name */ + if ((name = (char *)malloc(size)) == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "malloc() failed: %s", strerror(errno)); + return 0; + } + memcpy(name, buffer + 1, size - 1); + name[size - 1] = '\0'; + + ev.type = TELNET_EV_TTYPE; + ev.ttype.cmd = TELNET_TTYPE_IS; + ev.ttype.name = name; + telnet->eh(telnet, &ev, telnet->ud); + + /* clean up */ + free(name); + } else { + ev.type = TELNET_EV_TTYPE; + ev.ttype.cmd = TELNET_TTYPE_SEND; + ev.ttype.name = 0; + telnet->eh(telnet, &ev, telnet->ud); + } + + 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 */ + ev.type = TELNET_EV_SUBNEGOTIATION; + ev.sub.telopt = telnet->sb_telopt; + ev.sub.buffer = telnet->buffer; + ev.sub.size = telnet->buffer_pos; + telnet->eh(telnet, &ev, telnet->ud); + + switch (telnet->sb_telopt) { +#if defined(HAVE_ZLIB) + /* received COMPRESS2 begin marker, setup our zlib box and + * start handling the compressed stream if it's not already. + */ + case TELNET_TELOPT_COMPRESS2: + if (_init_zlib(telnet, 0, 1) != TELNET_EOK) + return 0; + + /* notify app that compression was enabled */ + ev.type = TELNET_EV_COMPRESS; + ev.compress.state = 1; + telnet->eh(telnet, &ev, telnet->ud); + return 1; +#endif /* defined(HAVE_ZLIB) */ + + /* specially handled subnegotiation telopt types */ + case TELNET_TELOPT_ZMP: + return _zmp_telnet(telnet, telnet->buffer, telnet->buffer_pos); + case TELNET_TELOPT_TTYPE: + return _ttype_telnet(telnet, telnet->buffer, telnet->buffer_pos); + case TELNET_TELOPT_ENVIRON: + case TELNET_TELOPT_NEW_ENVIRON: + return _environ_telnet(telnet, telnet->sb_telopt, telnet->buffer, + telnet->buffer_pos); + case TELNET_TELOPT_MSSP: + return _mssp_telnet(telnet, telnet->buffer, telnet->buffer_pos); + default: + return 0; + } +} + +/* initialize a telnet state tracker */ +telnet_t *telnet_init(const telnet_telopt_t *telopts, + telnet_event_handler_t eh, unsigned char flags, void *user_data) { + /* allocate structure */ + struct telnet_t *telnet = (telnet_t*)calloc(1, sizeof(telnet_t)); + if (telnet == 0) + return 0; + + /* initialize data */ + telnet->ud = user_data; + telnet->telopts = telopts; + telnet->eh = eh; + telnet->flags = flags; + + return telnet; +} + +/* free up any memory allocated by a state tracker */ +void telnet_free(telnet_t *telnet) { + /* free sub-request buffer */ + if (telnet->buffer != 0) { + free(telnet->buffer); + telnet->buffer = 0; + telnet->buffer_size = 0; + telnet->buffer_pos = 0; + } + +#if defined(HAVE_ZLIB) + /* free zlib box */ + if (telnet->z != 0) { + if (telnet->flags & TELNET_PFLAG_DEFLATE) + deflateEnd(telnet->z); + else + inflateEnd(telnet->z); + free(telnet->z); + telnet->z = 0; + } +#endif /* defined(HAVE_ZLIB) */ + + /* free RFC1143 queue */ + if (telnet->q) { + free(telnet->q); + telnet->q = NULL; + telnet->q_size = 0; + telnet->q_cnt = 0; + } + + /* free the telnet structure itself */ + free(telnet); +} + +/* push a byte into the telnet buffer */ +static telnet_error_t _buffer_byte(telnet_t *telnet, + unsigned char byte) { + char *new_buffer; + size_t i; + + /* check if we're out of room */ + if (telnet->buffer_pos == telnet->buffer_size) { + /* find the next buffer size */ + for (i = 0; i != _buffer_sizes_count; ++i) { + if (_buffer_sizes[i] == telnet->buffer_size) { + break; + } + } + + /* overflow -- can't grow any more */ + if (i >= _buffer_sizes_count - 1) { + _error(telnet, __LINE__, __func__, TELNET_EOVERFLOW, 0, + "subnegotiation buffer size limit reached"); + return TELNET_EOVERFLOW; + } + + /* (re)allocate buffer */ + new_buffer = (char *)realloc(telnet->buffer, _buffer_sizes[i + 1]); + if (new_buffer == 0) { + _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, + "realloc() failed"); + return TELNET_ENOMEM; + } + + telnet->buffer = new_buffer; + telnet->buffer_size = _buffer_sizes[i + 1]; + } + + /* push the byte, all set */ + telnet->buffer[telnet->buffer_pos++] = byte; + return TELNET_EOK; +} + +static void _process(telnet_t *telnet, const char *buffer, size_t size) { + telnet_event_t ev; + unsigned char byte; + size_t i, start; + for (i = start = 0; i != size; ++i) { + byte = buffer[i]; + switch (telnet->state) { + /* regular data */ + case TELNET_STATE_DATA: + /* on an IAC byte, pass through all pending bytes and + * switch states */ + if (byte == TELNET_IAC) { + 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_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) { + /* subnegotiation */ + case TELNET_SB: + telnet->state = TELNET_STATE_SB; + break; + /* negotiation commands */ + 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; + /* IAC escaping */ + case TELNET_IAC: + /* event */ + ev.type = TELNET_EV_DATA; + ev.data.buffer = (char*)&byte; + ev.data.size = 1; + telnet->eh(telnet, &ev, telnet->ud); + + /* state update */ + start = i + 1; + telnet->state = TELNET_STATE_DATA; + break; + /* some other command */ + default: + /* event */ + ev.type = TELNET_EV_IAC; + ev.iac.cmd = byte; + telnet->eh(telnet, &ev, telnet->ud); + + /* state update */ + start = i + 1; + telnet->state = TELNET_STATE_DATA; + } + break; + + /* negotiation commands */ + case TELNET_STATE_WILL: + case TELNET_STATE_WONT: + case TELNET_STATE_DO: + case TELNET_STATE_DONT: + _negotiate(telnet, byte); + start = i + 1; + telnet->state = TELNET_STATE_DATA; + break; + + /* subnegotiation -- determine subnegotiation telopt */ + case TELNET_STATE_SB: + telnet->sb_telopt = byte; + telnet->buffer_pos = 0; + telnet->state = TELNET_STATE_SB_DATA; + break; + + /* subnegotiation -- buffer bytes until end request */ + case TELNET_STATE_SB_DATA: + /* 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; + telnet->state = TELNET_STATE_DATA; + } + break; + + /* IAC escaping inside a subnegotiation */ + case TELNET_STATE_SB_DATA_IAC: + switch (byte) { + /* end subnegotiation */ + case TELNET_SE: + /* return to default state */ + start = i + 1; + telnet->state = TELNET_STATE_DATA; + + /* process subnegotiation */ + if (_subnegotiate(telnet) != 0) { + /* any remaining bytes in the buffer are compressed. + * we have to re-invoke telnet_recv to get those + * bytes inflated and abort trying to process the + * remaining compressed bytes in the current _process + * buffer argument + */ + telnet_recv(telnet, &buffer[start], size - start); + return; + } + break; + /* escaped IAC byte */ + case TELNET_IAC: + /* push IAC into buffer */ + if (_buffer_byte(telnet, TELNET_IAC) != + TELNET_EOK) { + start = i + 1; + telnet->state = TELNET_STATE_DATA; + } else { + telnet->state = TELNET_STATE_SB_DATA; + } + break; + /* something else -- protocol error. attempt to process + * content in subnegotiation buffer, then evaluate the + * given command as an IAC code. + */ + default: + _error(telnet, __LINE__, __func__, TELNET_EPROTOCOL, 0, + "unexpected byte after IAC inside SB: %d", + byte); + + /* enter IAC state */ + start = i + 1; + telnet->state = TELNET_STATE_IAC; + + /* process subnegotiation; see comment in + * TELNET_STATE_SB_DATA_IAC about invoking telnet_recv() + */ + if (_subnegotiate(telnet) != 0) { + telnet_recv(telnet, &buffer[start], size - start); + return; + } else { + /* recursive call to get the current input byte processed + * as a regular IAC command. we could use a goto, but + * that would be gross. + */ + _process(telnet, (char *)&byte, 1); + } + break; + } + break; + } + } + + /* pass through any remaining bytes */ + if (telnet->state == TELNET_STATE_DATA && i != start) { + ev.type = TELNET_EV_DATA; + ev.data.buffer = buffer + start; + ev.data.size = i - start; + telnet->eh(telnet, &ev, telnet->ud); + } +} + +/* push a bytes into the state tracker */ +void telnet_recv(telnet_t *telnet, const char *buffer, + size_t size) { +#if defined(HAVE_ZLIB) + /* if we have an inflate (decompression) zlib stream, use it */ + if (telnet->z != 0 && !(telnet->flags & TELNET_PFLAG_DEFLATE)) { + char inflate_buffer[1024]; + int rs; + + /* initialize zlib state */ + telnet->z->next_in = (unsigned char*)buffer; + telnet->z->avail_in = (unsigned int)size; + telnet->z->next_out = (unsigned char *)inflate_buffer; + telnet->z->avail_out = sizeof(inflate_buffer); + + /* inflate until buffer exhausted and all output is produced */ + while (telnet->z->avail_in > 0 || telnet->z->avail_out == 0) { + /* reset output buffer */ + + /* decompress */ + rs = inflate(telnet->z, Z_SYNC_FLUSH); + + /* process the decompressed bytes on success */ + if (rs == Z_OK || rs == Z_STREAM_END) + _process(telnet, inflate_buffer, sizeof(inflate_buffer) - + telnet->z->avail_out); + else + _error(telnet, __LINE__, __func__, TELNET_ECOMPRESS, 1, + "inflate() failed: %s", zError(rs)); + + /* prepare output buffer for next run */ + telnet->z->next_out = (unsigned char *)inflate_buffer; + telnet->z->avail_out = sizeof(inflate_buffer); + + /* on error (or on end of stream) disable further inflation */ + if (rs != Z_OK) { + telnet_event_t ev; + + /* disable compression */ + inflateEnd(telnet->z); + free(telnet->z); + telnet->z = 0; + + /* send event */ + ev.type = TELNET_EV_COMPRESS; + ev.compress.state = 0; + telnet->eh(telnet, &ev, telnet->ud); + + break; + } + } + + /* COMPRESS2 is not negotiated, just process */ + } else +#endif /* defined(HAVE_ZLIB) */ + _process(telnet, buffer, size); +} + +/* send an iac command */ +void telnet_iac(telnet_t *telnet, unsigned char cmd) { + unsigned char bytes[2]; + bytes[0] = TELNET_IAC; + bytes[1] = cmd; + _sendu(telnet, bytes, 2); +} + +/* send negotiation */ +void telnet_negotiate(telnet_t *telnet, unsigned char cmd, + unsigned char telopt) { + telnet_rfc1143_t q; + + /* 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; + _sendu(telnet, bytes, 3); + return; + } + + /* get current option states */ + q = _get_rfc1143(telnet, telopt); + + switch (cmd) { + /* advertise willingess to support an option */ + case TELNET_WILL: + switch (Q_US(q)) { + case Q_NO: + _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WILL, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_WANTNO_OP, Q_HIM(q)); + break; + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_WANTYES, Q_HIM(q)); + break; + } + break; + + /* force turn-off of locally enabled option */ + case TELNET_WONT: + switch (Q_US(q)) { + case Q_YES: + _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); + _send_negotiate(telnet, TELNET_WONT, telopt); + break; + case Q_WANTYES: + _set_rfc1143(telnet, telopt, Q_WANTYES_OP, Q_HIM(q)); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_WANTNO, Q_HIM(q)); + break; + } + break; + + /* ask remote end to enable an option */ + case TELNET_DO: + switch (Q_HIM(q)) { + case Q_NO: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); + _send_negotiate(telnet, TELNET_DO, telopt); + break; + case Q_WANTNO: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO_OP); + break; + case Q_WANTYES_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES); + break; + } + break; + + /* demand remote end disable an option */ + case TELNET_DONT: + switch (Q_HIM(q)) { + case Q_YES: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); + _send_negotiate(telnet, TELNET_DONT, telopt); + break; + case Q_WANTYES: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTYES_OP); + break; + case Q_WANTNO_OP: + _set_rfc1143(telnet, telopt, Q_US(q), Q_WANTNO); + break; + } + break; + } +} + +/* send non-command data (escapes IAC bytes) */ +void telnet_send(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); + } + } + + /* send whatever portion of buffer is left */ + if (i != l) { + _send(telnet, buffer + l, i - l); + } +} + +/* 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]; + sb[0] = TELNET_IAC; + sb[1] = TELNET_SB; + sb[2] = telopt; + _sendu(telnet, sb, 3); +} + + +/* send complete subnegotiation */ +void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt, + const char *buffer, size_t size) { + unsigned char bytes[5]; + bytes[0] = TELNET_IAC; + bytes[1] = TELNET_SB; + bytes[2] = telopt; + bytes[3] = TELNET_IAC; + bytes[4] = TELNET_SE; + + _sendu(telnet, bytes, 3); + telnet_send(telnet, buffer, size); + _sendu(telnet, bytes + 3, 2); + +#if defined(HAVE_ZLIB) + /* if we're a proxy and we just sent the COMPRESS2 marker, we must + * make sure all further data is compressed if not already. + */ + if (telnet->flags & TELNET_FLAG_PROXY && + telopt == TELNET_TELOPT_COMPRESS2) { + telnet_event_t ev; + + if (_init_zlib(telnet, 1, 1) != TELNET_EOK) + return; + + /* notify app that compression was enabled */ + ev.type = TELNET_EV_COMPRESS; + ev.compress.state = 1; + telnet->eh(telnet, &ev, telnet->ud); + } +#endif /* defined(HAVE_ZLIB) */ +} + +void telnet_begin_compress2(telnet_t *telnet) { +#if defined(HAVE_ZLIB) + static const unsigned char compress2[] = { TELNET_IAC, TELNET_SB, + TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE }; + + telnet_event_t ev; + + /* attempt to create output stream first, bail if we can't */ + if (_init_zlib(telnet, 1, 0) != TELNET_EOK) + return; + + /* send compression marker. we send directly to the event handler + * instead of passing through _send because _send would result in + * the compress marker itself being compressed. + */ + ev.type = TELNET_EV_SEND; + ev.data.buffer = (const char*)compress2; + ev.data.size = sizeof(compress2); + telnet->eh(telnet, &ev, telnet->ud); + + /* notify app that compression was successfully enabled */ + ev.type = TELNET_EV_COMPRESS; + ev.compress.state = 1; + telnet->eh(telnet, &ev, telnet->ud); +#else + (void)telnet; +#endif /* defined(HAVE_ZLIB) */ +} + +/* 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) { + va_list va_temp; + char buffer[1024]; + char *output = buffer; + unsigned int rs, i, l; + + /* format */ + va_copy(va_temp, va); + rs = vsnprintf(buffer, sizeof(buffer), fmt, va_temp); + va_end(va_temp); + + 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; + } + + va_copy(va_temp, va); + rs = vsnprintf(output, rs + 1, fmt, va_temp); + va_end(va_temp); + } + + /* send */ + for (l = i = 0; i != rs; ++i) { + /* special characters */ + if (output[i] == (char)TELNET_IAC || output[i] == '\r' || + output[i] == '\n') { + /* dump prior portion of text */ + if (i != l) + _send(telnet, output + l, i - l); + l = i + 1; + + /* IAC -> IAC IAC */ + if (output[i] == (char)TELNET_IAC) + telnet_iac(telnet, TELNET_IAC); + /* automatic translation of \r -> CRNUL */ + else if (output[i] == '\r') + _send(telnet, CRNUL, 2); + /* automatic translation of \n -> CRLF */ + else if (output[i] == '\n') + _send(telnet, CRLF, 2); + } + } + + /* send whatever portion of output is left */ + if (i != l) { + _send(telnet, output + l, i - l); + } + + /* free allocated memory, if any */ + if (output != buffer) { + free(output); + } + + return rs; +} + +/* see telnet_vprintf */ +int telnet_printf(telnet_t *telnet, const char *fmt, ...) { + va_list va; + int rs; + + va_start(va, fmt); + rs = telnet_vprintf(telnet, fmt, va); + va_end(va); + + return rs; +} + +/* send formatted data through telnet_send */ +int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va) { + va_list va_temp; + char buffer[1024]; + char *output = buffer; + unsigned int rs; + + /* format; allocate more space if necessary */ + va_copy(va_temp, va); + rs = vsnprintf(buffer, sizeof(buffer), fmt, va_temp); + va_end(va_temp); + + 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; + } + + va_copy(va_temp, va); + rs = vsnprintf(output, rs + 1, fmt, va_temp); + va_end(va_temp); + } + + /* send out the formatted data */ + telnet_send(telnet, output, rs); + + /* release allocated memory, if any */ + if (output != buffer) { + free(output); + } + + return rs; +} + +/* see telnet_raw_vprintf */ +int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...) { + va_list va; + int rs; + + va_start(va, fmt); + rs = telnet_raw_vprintf(telnet, fmt, va); + va_end(va); + + return rs; +} + +/* 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, (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, (const char*)&type, 1); + + if (string != 0) { + telnet_send(telnet, string, strlen(string)); + } +} + +/* send TERMINAL-TYPE SEND command */ +void telnet_ttype_send(telnet_t *telnet) { + static const unsigned char SEND[] = { TELNET_IAC, TELNET_SB, + TELNET_TELOPT_TTYPE, TELNET_TTYPE_SEND, TELNET_IAC, TELNET_SE }; + _sendu(telnet, SEND, sizeof(SEND)); +} + +/* send TERMINAL-TYPE IS command */ +void telnet_ttype_is(telnet_t *telnet, const char* ttype) { + static const unsigned char IS[] = { TELNET_IAC, TELNET_SB, + TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS }; + if (!ttype) { + ttype = "NVT"; + } + _sendu(telnet, IS, sizeof(IS)); + _send(telnet, ttype, strlen(ttype)); + telnet_finish_sb(telnet); +} + +/* send ZMP data */ +void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv) { + size_t i; + + /* ZMP header */ + telnet_begin_zmp(telnet, argv[0]); + + /* send out each argument, including trailing NUL byte */ + for (i = 1; i != argc; ++i) + telnet_zmp_arg(telnet, argv[i]); + + /* ZMP footer */ + telnet_finish_zmp(telnet); +} + +/* send ZMP data using varargs */ +void telnet_send_vzmpv(telnet_t *telnet, va_list va) { + const char* arg; + + /* ZMP header */ + telnet_begin_sb(telnet, TELNET_TELOPT_ZMP); + + /* send out each argument, including trailing NUL byte */ + while ((arg = va_arg(va, const char *)) != 0) + telnet_zmp_arg(telnet, arg); + + /* ZMP footer */ + telnet_finish_zmp(telnet); +} + +/* see telnet_send_vzmpv */ +void telnet_send_zmpv(telnet_t *telnet, ...) { + va_list va; + + va_start(va, telnet); + telnet_send_vzmpv(telnet, va); + va_end(va); +} + +/* begin a ZMP command */ +void telnet_begin_zmp(telnet_t *telnet, const char *cmd) { + telnet_begin_sb(telnet, TELNET_TELOPT_ZMP); + telnet_zmp_arg(telnet, cmd); +} + +/* send a ZMP argument */ +void telnet_zmp_arg(telnet_t *telnet, const char* arg) { + telnet_send(telnet, arg, strlen(arg) + 1); +} diff --git a/managed_components/frozen-eye__esp_telnet/src/libtelnet.h b/managed_components/frozen-eye__esp_telnet/src/libtelnet.h new file mode 100644 index 0000000..e3e6048 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/src/libtelnet.h @@ -0,0 +1,691 @@ +/*! + * \brief libtelnet - TELNET protocol handling library + * + * SUMMARY: + * + * libtelnet is a library for handling the TELNET protocol. It includes + * routines for parsing incoming data from a remote peer as well as formatting + * data to send to the remote peer. + * + * libtelnet uses a callback-oriented API, allowing application-specific + * handling of various events. The callback system is also used for buffering + * outgoing protocol data, allowing the application to maintain control over + * the actual socket connection. + * + * Features supported include the full TELNET protocol, Q-method option + * negotiation, ZMP, MCCP2, MSSP, and NEW-ENVIRON. + * + * CONFORMS TO: + * + * RFC854 - http://www.faqs.org/rfcs/rfc854.html + * RFC855 - http://www.faqs.org/rfcs/rfc855.html + * RFC1091 - http://www.faqs.org/rfcs/rfc1091.html + * RFC1143 - http://www.faqs.org/rfcs/rfc1143.html + * RFC1408 - http://www.faqs.org/rfcs/rfc1408.html + * RFC1572 - http://www.faqs.org/rfcs/rfc1572.html + * + * LICENSE: + * + * 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. + * + * \file libtelnet.h + * + * \version 0.23 + * + * \author Sean Middleditch + */ + +#if !defined(LIBTELNET_INCLUDE) +#define LIBTELNET_INCLUDE 1 + +/* standard C headers necessary for the libtelnet API */ +#include +#include + +/* C++ support */ +#if defined(__cplusplus) +extern "C" { +#endif + +/* 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; + +/*! Telnet event object type. */ +typedef union telnet_event_t telnet_event_t; + +/*! Telnet option table element type. */ +typedef struct telnet_telopt_t telnet_telopt_t; + +/*! \name Telnet commands */ +/*@{*/ +/*! Telnet commands and special values. */ +#define TELNET_IAC 255 +#define TELNET_DONT 254 +#define TELNET_DO 253 +#define TELNET_WONT 252 +#define TELNET_WILL 251 +#define TELNET_SB 250 +#define TELNET_GA 249 +#define TELNET_EL 248 +#define TELNET_EC 247 +#define TELNET_AYT 246 +#define TELNET_AO 245 +#define TELNET_IP 244 +#define TELNET_BREAK 243 +#define TELNET_DM 242 +#define TELNET_NOP 241 +#define TELNET_SE 240 +#define TELNET_EOR 239 +#define TELNET_ABORT 238 +#define TELNET_SUSP 237 +#define TELNET_EOF 236 +/*@}*/ + +/*! \name Telnet option values. */ +/*@{*/ +/*! Telnet options. */ +#define TELNET_TELOPT_BINARY 0 +#define TELNET_TELOPT_ECHO 1 +#define TELNET_TELOPT_RCP 2 +#define TELNET_TELOPT_SGA 3 +#define TELNET_TELOPT_NAMS 4 +#define TELNET_TELOPT_STATUS 5 +#define TELNET_TELOPT_TM 6 +#define TELNET_TELOPT_RCTE 7 +#define TELNET_TELOPT_NAOL 8 +#define TELNET_TELOPT_NAOP 9 +#define TELNET_TELOPT_NAOCRD 10 +#define TELNET_TELOPT_NAOHTS 11 +#define TELNET_TELOPT_NAOHTD 12 +#define TELNET_TELOPT_NAOFFD 13 +#define TELNET_TELOPT_NAOVTS 14 +#define TELNET_TELOPT_NAOVTD 15 +#define TELNET_TELOPT_NAOLFD 16 +#define TELNET_TELOPT_XASCII 17 +#define TELNET_TELOPT_LOGOUT 18 +#define TELNET_TELOPT_BM 19 +#define TELNET_TELOPT_DET 20 +#define TELNET_TELOPT_SUPDUP 21 +#define TELNET_TELOPT_SUPDUPOUTPUT 22 +#define TELNET_TELOPT_SNDLOC 23 +#define TELNET_TELOPT_TTYPE 24 +#define TELNET_TELOPT_EOR 25 +#define TELNET_TELOPT_TUID 26 +#define TELNET_TELOPT_OUTMRK 27 +#define TELNET_TELOPT_TTYLOC 28 +#define TELNET_TELOPT_3270REGIME 29 +#define TELNET_TELOPT_X3PAD 30 +#define TELNET_TELOPT_NAWS 31 +#define TELNET_TELOPT_TSPEED 32 +#define TELNET_TELOPT_LFLOW 33 +#define TELNET_TELOPT_LINEMODE 34 +#define TELNET_TELOPT_XDISPLOC 35 +#define TELNET_TELOPT_ENVIRON 36 +#define TELNET_TELOPT_AUTHENTICATION 37 +#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 + +#define TELNET_TELOPT_MCCP2 86 +/*@}*/ + +/*! \name Protocol codes for TERMINAL-TYPE commands. */ +/*@{*/ +/*! TERMINAL-TYPE codes. */ +#define TELNET_TTYPE_IS 0 +#define TELNET_TTYPE_SEND 1 +/*@}*/ + +/*! \name Protocol codes for NEW-ENVIRON/ENVIRON commands. */ +/*@{*/ +/*! NEW-ENVIRON/ENVIRON codes. */ +#define TELNET_ENVIRON_IS 0 +#define TELNET_ENVIRON_SEND 1 +#define TELNET_ENVIRON_INFO 2 +#define TELNET_ENVIRON_VAR 0 +#define TELNET_ENVIRON_VALUE 1 +#define TELNET_ENVIRON_ESC 2 +#define TELNET_ENVIRON_USERVAR 3 +/*@}*/ + +/*! \name Protocol codes for MSSP commands. */ +/*@{*/ +/*! MSSP codes. */ +#define TELNET_MSSP_VAR 1 +#define TELNET_MSSP_VAL 2 +/*@}*/ + +/*! \name Telnet state tracker flags. */ +/*@{*/ +/*! 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) +/*@}*/ + +/*! + * error codes + */ +enum telnet_error_t { + TELNET_EOK = 0, /*!< no error */ + TELNET_EBADVAL, /*!< invalid parameter, or API misuse */ + TELNET_ENOMEM, /*!< memory allocation failure */ + TELNET_EOVERFLOW, /*!< data exceeds buffer size */ + TELNET_EPROTOCOL, /*!< invalid sequence of special bytes */ + TELNET_ECOMPRESS /*!< error handling compressed streams */ +}; +typedef enum telnet_error_t telnet_error_t; /*!< Error code type. */ + +/*! + * event codes + */ +enum telnet_event_type_t { + TELNET_EV_DATA = 0, /*!< raw text data has been received */ + TELNET_EV_SEND, /*!< data needs to be sent to the peer */ + TELNET_EV_IAC, /*!< generic IAC code received */ + TELNET_EV_WILL, /*!< WILL option negotiation received */ + TELNET_EV_WONT, /*!< WONT option neogitation received */ + TELNET_EV_DO, /*!< DO option negotiation received */ + TELNET_EV_DONT, /*!< DONT option negotiation received */ + TELNET_EV_SUBNEGOTIATION, /*!< sub-negotiation data received */ + TELNET_EV_COMPRESS, /*!< compression has been enabled */ + TELNET_EV_ZMP, /*!< ZMP command has been received */ + TELNET_EV_TTYPE, /*!< TTYPE command has been received */ + TELNET_EV_ENVIRON, /*!< ENVIRON command has been received */ + TELNET_EV_MSSP, /*!< MSSP command has been received */ + TELNET_EV_WARNING, /*!< recoverable error has occured */ + TELNET_EV_ERROR /*!< non-recoverable error has occured */ +}; +typedef enum telnet_event_type_t telnet_event_type_t; /*!< Telnet event type. */ + +/*! + * environ/MSSP command information + */ +struct telnet_environ_t { + unsigned char type; /*!< either TELNET_ENVIRON_VAR or TELNET_ENVIRON_USERVAR */ + char *var; /*!< name of the variable being set */ + char *value; /*!< value of variable being set; empty string if no value */ +}; + +/*! + * event information + */ +union telnet_event_t { + /*! + * \brief Event type + * + * The type field will determine which of the other event structure fields + * have been filled in. For instance, if the event type is TELNET_EV_ZMP, + * then the zmp event field (and ONLY the zmp event field) will be filled + * in. + */ + enum telnet_event_type_t type; + + /*! + * data event: for DATA and SEND events + */ + struct data_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const char *buffer; /*!< byte buffer */ + size_t size; /*!< number of bytes in buffer */ + } data; /*!< DATA and SEND */ + + /*! + * WARNING and ERROR events + */ + struct error_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const char *file; /*!< file the error occured in */ + const char *func; /*!< function the error occured in */ + const char *msg; /*!< error message string */ + int line; /*!< line of file error occured on */ + telnet_error_t errcode; /*!< error code */ + } error; /*!< WARNING and ERROR */ + + /*! + * command event: for IAC + */ + struct iac_t { + enum telnet_event_type_t _type; /*!< alias for type */ + unsigned char cmd; /*!< telnet command received */ + } iac; /*!< IAC */ + + /*! + * negotiation event: WILL, WONT, DO, DONT + */ + struct negotiate_t { + enum telnet_event_type_t _type; /*!< alias for type */ + unsigned char telopt; /*!< option being negotiated */ + } neg; /*!< WILL, WONT, DO, DONT */ + + /*! + * subnegotiation event + */ + struct subnegotiate_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 */ + } sub; /*!< SB */ + + /*! + * ZMP event + */ + struct zmp_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const char **argv; /*!< array of argument string */ + size_t argc; /*!< number of elements in argv */ + } zmp; /*!< ZMP */ + + /*! + * TTYPE event + */ + struct ttype_t { + enum telnet_event_type_t _type; /*!< alias for type */ + unsigned char cmd; /*!< TELNET_TTYPE_IS or TELNET_TTYPE_SEND */ + const char* name; /*!< terminal type name (IS only) */ + } ttype; /*!< TTYPE */ + + /*! + * COMPRESS event + */ + struct compress_t { + enum telnet_event_type_t _type; /*!< alias for type */ + unsigned char state; /*!< 1 if compression is enabled, + 0 if disabled */ + } compress; /*!< COMPRESS */ + + /*! + * ENVIRON/NEW-ENVIRON event + */ + struct environ_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const struct telnet_environ_t *values; /*!< array of variable values */ + size_t size; /*!< number of elements in values */ + unsigned char cmd; /*!< SEND, IS, or INFO */ + } environ; /*!< ENVIRON, NEW-ENVIRON */ + + /*! + * MSSP event + */ + struct mssp_t { + enum telnet_event_type_t _type; /*!< alias for type */ + const struct telnet_environ_t *values; /*!< array of variable values */ + size_t size; /*!< number of elements in values */ + } mssp; /*!< MSSP */ +}; + +/*! + * \brief event handler + * + * This is the type of function that must be passed to + * telnet_init() when creating a new telnet object. The + * function will be invoked once for every event generated + * by the libtelnet protocol parser. + * + * \param telnet The telnet object that generated the event + * \param event Event structure with details about the event + * \param user_data User-supplied pointer + */ +typedef void (*telnet_event_handler_t)(telnet_t *telnet, + telnet_event_t *event, void *user_data); + +/*! + * telopt support table element; use telopt of -1 for end marker + */ +struct telnet_telopt_t { + short telopt; /*!< one of the TELOPT codes or -1 */ + unsigned char us; /*!< TELNET_WILL or TELNET_WONT */ + unsigned char him; /*!< TELNET_DO or TELNET_DONT */ +}; + +/*! + * state tracker -- private data structure + */ +struct telnet_t; + +/*! + * \brief Initialize a telnet state tracker. + * + * This function initializes a new state tracker, which is used for all + * other libtelnet functions. Each connection must have its own + * telnet state tracker object. + * + * \param telopts Table of TELNET options the application supports. + * \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 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); + +/*! + * \brief Free up any memory allocated by a state tracker. + * + * This function must be called when a telnet state tracker is no + * longer needed (such as after the connection has been closed) to + * release any memory resources used by the state tracker. + * + * \param telnet Telnet state tracker object. + */ +extern void telnet_free(telnet_t *telnet); + +/*! + * \brief Push a byte buffer into the state tracker. + * + * Passes one or more bytes to the telnet state tracker for + * protocol parsing. The byte buffer is most often going to be + * the buffer that recv() was called for while handling the + * connection. + * + * \param telnet Telnet state tracker object. + * \param buffer Pointer to byte buffer. + * \param size Number of bytes pointed to by buffer. + */ +extern void telnet_recv(telnet_t *telnet, const char *buffer, + size_t size); + +/*! + * \brief Send a telnet command. + * + * \param telnet Telnet state tracker object. + * \param cmd Command to send. + */ +extern void telnet_iac(telnet_t *telnet, unsigned char cmd); + +/*! + * \brief Send negotiation command. + * + * Internally, libtelnet uses RFC1143 option negotiation rules. + * The negotiation commands sent with this function may be ignored + * if they are determined to be redundant. + * + * \param telnet Telnet state tracker object. + * \param cmd TELNET_WILL, TELNET_WONT, TELNET_DO, or TELNET_DONT. + * \param opt One of the TELNET_TELOPT_* values. + */ +extern void telnet_negotiate(telnet_t *telnet, unsigned char cmd, + unsigned char opt); + +/*! + * Send non-command data (escapes IAC bytes). + * + * \param telnet Telnet state tracker object. + * \param buffer Buffer of bytes to send. + * \param size Number of bytes to send. + */ +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. + * + * Sends IAC SB followed by the telopt code. All following data sent + * will be part of the sub-negotiation, until telnet_finish_sb() is + * called. + * + * \param telnet Telnet state tracker object. + * \param telopt One of the TELNET_TELOPT_* values. + */ +extern void telnet_begin_sb(telnet_t *telnet, + unsigned char telopt); + +/*! + * \brief Finish a sub-negotiation command. + * + * This must be called after a call to telnet_begin_sb() to finish a + * sub-negotiation command. + * + * \param telnet Telnet state tracker object. + */ +#define telnet_finish_sb(telnet) telnet_iac((telnet), TELNET_SE) + +/*! + * \brief Shortcut for sending a complete subnegotiation buffer. + * + * Equivalent to: + * telnet_begin_sb(telnet, telopt); + * telnet_send(telnet, buffer, size); + * telnet_finish_sb(telnet); + * + * \param telnet Telnet state tracker format. + * \param telopt One of the TELNET_TELOPT_* values. + * \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, + const char *buffer, size_t size); + +/*! + * \brief Begin sending compressed data. + * + * This function will begein sending data using the COMPRESS2 option, + * which enables the use of zlib to compress data sent to the client. + * The client must offer support for COMPRESS2 with option negotiation, + * and zlib support must be compiled into libtelnet. + * + * Only the server may call this command. + * + * \param telnet Telnet state tracker object. + */ +extern void telnet_begin_compress2(telnet_t *telnet); + +/*! + * \brief Send formatted data. + * + * This function is a wrapper around telnet_send(). It allows using + * printf-style formatting. + * + * Additionally, this function will translate \\r to the CR NUL construct and + * \\n with CR LF, as well as automatically escaping IAC bytes like + * telnet_send(). + * + * \param telnet Telnet state tracker object. + * \param fmt Format string. + * \return Number of bytes sent. + */ +extern int telnet_printf(telnet_t *telnet, const char *fmt, ...) + TELNET_GNU_PRINTF(2, 3); + +/*! + * \brief Send formatted data. + * + * See telnet_printf(). + */ +extern int telnet_vprintf(telnet_t *telnet, const char *fmt, va_list va); + +/*! + * \brief Send formatted data (no newline escaping). + * + * This behaves identically to telnet_printf(), except that the \\r and \\n + * characters are not translated. The IAC byte is still escaped as normal + * with telnet_send(). + * + * \param telnet Telnet state tracker object. + * \param fmt Format string. + * \return Number of bytes sent. + */ +extern int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...) + TELNET_GNU_PRINTF(2, 3); + +/*! + * \brief Send formatted data (no newline escaping). + * + * See telnet_raw_printf(). + */ +extern int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va); + +/*! + * \brief Begin a new set of NEW-ENVIRON values to request or send. + * + * This function will begin the sub-negotiation block for sending or + * requesting NEW-ENVIRON values. + * + * The telnet_finish_newenviron() macro must be called after this + * function to terminate the NEW-ENVIRON command. + * + * \param telnet Telnet state tracker object. + * \param type One of TELNET_ENVIRON_SEND, TELNET_ENVIRON_IS, or + * TELNET_ENVIRON_INFO. + */ +extern void telnet_begin_newenviron(telnet_t *telnet, unsigned char type); + +/*! + * \brief Send a NEW-ENVIRON variable name or value. + * + * This can only be called between calls to telnet_begin_newenviron() and + * telnet_finish_newenviron(). + * + * \param telnet Telnet state tracker object. + * \param type One of TELNET_ENVIRON_VAR, TELNET_ENVIRON_USERVAR, or + * TELNET_ENVIRON_VALUE. + * \param string Variable name or value. + */ +extern void telnet_newenviron_value(telnet_t* telnet, unsigned char type, + const char *string); + +/*! + * \brief Finish a NEW-ENVIRON command. + * + * This must be called after a call to telnet_begin_newenviron() to finish a + * NEW-ENVIRON variable list. + * + * \param telnet Telnet state tracker object. + */ +#define telnet_finish_newenviron(telnet) telnet_finish_sb((telnet)) + +/*! + * \brief Send the TERMINAL-TYPE SEND command. + * + * Sends the sequence IAC TERMINAL-TYPE SEND. + * + * \param telnet Telnet state tracker object. + */ +extern void telnet_ttype_send(telnet_t *telnet); + +/*! + * \brief Send the TERMINAL-TYPE IS command. + * + * Sends the sequence IAC TERMINAL-TYPE IS "string". + * + * According to the RFC, the recipient of a TERMINAL-TYPE SEND shall + * send the next possible terminal-type the client supports. Upon sending + * the type, the client should switch modes to begin acting as the terminal + * type is just sent. + * + * The server may continue sending TERMINAL-TYPE IS until it receives a + * terminal type is understands. To indicate to the server that it has + * reached the end of the available optoins, the client must send the last + * terminal type a second time. When the server receives the same terminal + * type twice in a row, it knows it has seen all available terminal types. + * + * After the last terminal type is sent, if the client receives another + * TERMINAL-TYPE SEND command, it must begin enumerating the available + * terminal types from the very beginning. This allows the server to + * scan the available types for a preferred terminal type and, if none + * is found, to then ask the client to switch to an acceptable + * alternative. + * + * Note that if the client only supports a single terminal type, then + * simply sending that one type in response to every SEND will satisfy + * the behavior requirements. + * + * \param telnet Telnet state tracker object. + * \param ttype Name of the terminal-type being sent. + */ +extern void telnet_ttype_is(telnet_t *telnet, const char* ttype); + +/*! + * \brief Send a ZMP command. + * + * \param telnet Telnet state tracker object. + * \param argc Number of ZMP commands being sent. + * \param argv Array of argument strings. + */ +extern void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv); + +/*! + * \brief Send a ZMP command. + * + * Arguments are listed out in var-args style. After the last argument, a + * NULL pointer must be passed in as a sentinel value. + * + * \param telnet Telnet state tracker object. + */ +extern void telnet_send_zmpv(telnet_t *telnet, ...) TELNET_GNU_SENTINEL; + +/*! + * \brief Send a ZMP command. + * + * See telnet_send_zmpv(). + */ +extern void telnet_send_vzmpv(telnet_t *telnet, va_list va); + +/*! + * \brief Begin sending a ZMP command + * + * \param telnet Telnet state tracker object. + * \param cmd The first argument (command name) for the ZMP command. + */ +extern void telnet_begin_zmp(telnet_t *telnet, const char *cmd); + +/*! + * \brief Send a ZMP command argument. + * + * \param telnet Telnet state tracker object. + * \param arg Telnet argument string. + */ +extern void telnet_zmp_arg(telnet_t *telnet, const char *arg); + +/*! + * \brief Finish a ZMP command. + * + * This must be called after a call to telnet_begin_zmp() to finish a + * ZMP argument list. + * + * \param telnet Telnet state tracker object. + */ +#define telnet_finish_zmp(telnet) telnet_finish_sb((telnet)) + +/* C++ support */ +#if defined(__cplusplus) +} /* extern "C" */ +#endif + +#endif /* !defined(LIBTELNET_INCLUDE) */ diff --git a/managed_components/frozen-eye__esp_telnet/src/server.c b/managed_components/frozen-eye__esp_telnet/src/server.c new file mode 100644 index 0000000..7780f71 --- /dev/null +++ b/managed_components/frozen-eye__esp_telnet/src/server.c @@ -0,0 +1,336 @@ +#include "freertos/FreeRTOS.h" +#include "sdkconfig.h" + +#define LINEBUFFER_SIZE 256 +#define AWAIT_TIMEOUT 10 + +#include +#include +#include +#include +#include + +static const char* TAG = "telnet"; +static struct user_t users[CONFIG_TELNET_SERVER_MAX_CONNECTIONS + 1]; + +static void linebuffer_push(char* buffer, size_t size, int* linepos, char ch, + void (*cb)(const char* line, size_t overflow, void* ud), void* ud) +{ + /* CRLF -- line terminator */ + if (ch == '\n' && *linepos > 0 && buffer[*linepos - 1] == '\r') { + /* NUL terminate (replaces \r in buffer), notify app, clear */ + buffer[*linepos - 1] = 0; + cb(buffer, 0, ud); + *linepos = 0; + + /* CRNUL -- just a CR */ + } + else if (ch == 0 && *linepos > 0 && buffer[*linepos - 1] == '\r') { + /* do nothing, the CR is already in the buffer */ + + /* anything else (including technically invalid CR followed by + * anything besides LF or NUL -- just buffer if we have room + * \r + */ + } + else if (*linepos != (int)size) { + buffer[(*linepos)++] = ch; + + /* buffer overflow */ + } + else { + /* terminate (NOTE: eats a byte), notify app, clear buffer */ + buffer[size - 1] = 0; + cb(buffer, size - 1, ud); + *linepos = 0; + } +} + +static void _message(const char* from, const char* msg) +{ + int i; + for (i = 0; i != CONFIG_TELNET_SERVER_MAX_CONNECTIONS; ++i) { + if (users[i].sock != -1 && users[i].name != 0 && strcmp(users[i].name, from) != 0) { + telnet_printf(users[i].telnet, "%s: \"%s\"\n", from, msg); + } + } +} + +static void _broadcast(const char* from, const char* msg) +{ + int i; + for (i = 0; i != CONFIG_TELNET_SERVER_MAX_CONNECTIONS; ++i) { + if (users[i].sock != -1) { + telnet_printf(users[i].telnet, "%s: \"%s\"\n", from, msg); + } + } +} + +static void _send(int sock, const char* buffer, size_t size) +{ + int rs; + + /* ignore on invalid socket */ + if (sock == -1) + return; + + /* send data */ + while (size > 0) { + if ((rs = send(sock, buffer, (int)size, 0)) == -1) { + if (errno != EINTR && errno != ECONNRESET) { + ESP_LOGW(TAG, "send() failed: %s", strerror(errno)); + return; + } + else { + return; + } + } + else if (rs == 0) { + ESP_LOGE(TAG, "send() unexpectedly returned 0"); + return; + } + + /* update pointer and size to see if we've got more to send */ + buffer += rs; + size -= rs; + } +} + +/* process input line */ +static void _online(const char* line, size_t overflow, void* ud) +{ + struct user_t* user = (struct user_t*)ud; + int i; + + (void)overflow; + + /* if the user has no name, this is his "login" */ + if (user->name == 0) { + /* must not be empty, must be at least 32 chars */ + if (strlen(line) == 0 || strlen(line) > 32) { + telnet_printf(user->telnet, "Invalid name. Enter name: "); + return; + } + + /* must not already exist */ + for (i = 0; i != CONFIG_TELNET_SERVER_MAX_CONNECTIONS; ++i) { + if (users[i].name != 0 && strcmp(users[i].name, line) == 0) { + telnet_printf(user->telnet, "Name already in use. Enter name: "); + return; + } + } + + /* keep name */ + user->name = strdup(line); + telnet_printf(user->telnet, "Welcome, %s!\n", line); + return; + } + + /* execute a command, need to send to the system */ + // _message(user->name, line); +} + +static void _input(struct user_t* user, const char* buffer, size_t size) +{ + unsigned int i; + for (i = 0; user->sock != -1 && i != size; ++i) { + linebuffer_push(user->linebuf, sizeof(user->linebuf), &user->linepos, (char)buffer[i], _online, user); + } +} + +static void _event_handler(telnet_t* telnet, telnet_event_t* ev, void* user_data) +{ + struct user_t* user = (struct user_t*)user_data; + + switch (ev->type) { + /* data received */ + case TELNET_EV_DATA: + _input(user, ev->data.buffer, ev->data.size); + // telnet_negotiate(telnet, TELNET_WONT, TELNET_TELOPT_ECHO); + // telnet_negotiate(telnet, TELNET_WILL, TELNET_TELOPT_ECHO); + break; + /* data must be sent */ + case TELNET_EV_SEND: _send(user->sock, ev->data.buffer, ev->data.size); break; + /* enable compress2 if accepted by client */ + case TELNET_EV_DO: + if (ev->neg.telopt == TELNET_TELOPT_COMPRESS2) + telnet_begin_compress2(telnet); + break; + /* error */ + case TELNET_EV_ERROR: + close(user->sock); + user->sock = -1; + if (user->name != 0) { + _message(user->name, "** HAS HAD AN ERROR **"); + free(user->name); + user->name = 0; + } + telnet_free(user->telnet); + break; + default: + /* ignore */ + break; + } +} + +void telnet_task(void* arg) +{ + static char buffer[512]; + static struct sockaddr_in addr; + static telnet_server_config_t config; + + // save the configuration + memcpy(&config, arg, sizeof(telnet_server_config_t)); + + int listen_sock; + int client_sock; + int rs; + int i; + socklen_t addrlen; + struct pollfd pfd[config.max_connections + 1]; + + /* initialize data structures */ + memset(&pfd, 0, sizeof(pfd)); + memset(users, 0, sizeof(users)); + for (i = 0; i != config.max_connections; ++i) { + users[i].sock = -1; + } + + /* create listening socket */ + if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + ESP_LOGE(TAG, "socket() failed: %s", strerror(errno)); + return; + } + + /* reuse address option */ + rs = 1; + setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&rs, sizeof(rs)); + + /* bind to listening addr/port */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(config.port); + if (bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + ESP_LOGE(TAG, "bind() failed: %s", strerror(errno)); + close(listen_sock); + return; + } + + /* listen for clients */ + if (listen(listen_sock, 3) == -1) { + ESP_LOGE(TAG, "listen() failed: %s", strerror(errno)); + close(listen_sock); + return; + } + + ESP_LOGI(TAG, "Telnet server listening on port %d", config.port); + + /* initialize listening descriptors */ + pfd[config.max_connections].fd = listen_sock; + pfd[config.max_connections].events = POLLIN; + + /* loop for ever */ + while (true) { + /* prepare for poll */ + for (i = 0; i != config.max_connections; ++i) { + if (users[i].sock != -1) { + pfd[i].fd = users[i].sock; + pfd[i].events = POLLIN; + } + else { + pfd[i].fd = -1; + pfd[i].events = 0; + } + } + + /* poll */ + rs = poll(pfd, config.max_connections + 1, AWAIT_TIMEOUT); + if (rs == -1 && errno != EINTR) { + ESP_LOGE(TAG, "poll() failed: %s", strerror(errno)); + close(listen_sock); + return; + } + + /* new connection */ + if (pfd[config.max_connections].revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP)) { + /* acept the sock */ + ESP_LOGW(TAG, "New connection"); + addrlen = sizeof(addr); + if ((client_sock = accept(listen_sock, (struct sockaddr*)&addr, &addrlen)) == -1) { + ESP_LOGE(TAG, "accept() failed: %s", strerror(errno)); + return; + } + + ESP_LOGV(TAG, "Connection received"); + + /* find a free user */ + for (i = 0; i != config.max_connections; ++i) { + if (users[i].sock == -1) { + break; + } + } + + if (i == config.max_connections) { + ESP_LOGV(TAG, " rejected (too many users)"); + _send(client_sock, "Too many users.\n", 16); + close(client_sock); + } + + /* init, welcome */ + users[i].sock = client_sock; + users[i].telnet = telnet_init(config.telnet_opts, _event_handler, 0, &users[i]); + telnet_negotiate(users[i].telnet, TELNET_WILL, TELNET_TELOPT_COMPRESS2); + telnet_printf(users[i].telnet, "Enter name: "); + + // telnet_negotiate(users[i].telnet, TELNET_WILL, TELNET_TELOPT_ECHO); + } + + /* read from client */ + for (i = 0; i != config.max_connections; ++i) { + /* skip users that aren't actually connected */ + if (users[i].sock == -1) { + continue; + } + + if (pfd[i].revents & (POLLIN | POLLERR | POLLHUP)) { + if ((rs = recv(users[i].sock, buffer, sizeof(buffer), 0)) > 0) { + telnet_recv(users[i].telnet, buffer, rs); + } + else if (rs == 0) { + ESP_LOGV(TAG, "Connection closed."); + close(users[i].sock); + users[i].sock = -1; + if (users[i].name != 0) { + // _message(users[i].name, "** HAS DISCONNECTED **"); + free(users[i].name); + users[i].name = 0; + } + telnet_free(users[i].telnet); + } + else if (errno != EINTR) { + ESP_LOGE(TAG, "recv(client) failed: %s", strerror(errno)); + return; + } + } + } + } +} + +static TaskHandle_t xHandle = NULL; + +esp_err_t telnet_server_create(telnet_server_config_t* config) +{ + if (config == NULL) { + return ESP_ERR_INVALID_ARG; + } + + BaseType_t xReturned = xTaskCreate(telnet_task, "telnet_task", config->stack_size, config, config->task_priority, &xHandle); + if (xReturned == pdPASS) { + ESP_LOGV(TAG, "Telnet task created successfully."); + return ESP_OK; + } + + ESP_LOGE(TAG, "Failed to create telnet task."); + return ESP_FAIL; +}