/**************************************************************************** * apps/netutils/telnetc/telnetc.c * * Leveraged from libtelnet, https://github.com/seanmiddleditch/libtelnet. * Modified and re-released under the BSD license: * * Copyright (C) 2017 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * The original authors of libtelnet are listed below. Per their licesne, * "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." * * Author: Sean Middleditch * (Also listed in the AUTHORS file are Jack Kelly * and Katherine Flavel ) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #if defined(HAVE_ZLIB) # include #endif #include "netutils/telnetc.h" /**************************************************************************** * Pre-proecessor Definitions ****************************************************************************/ /* 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); /* 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 /* To send bags of unsigned chars */ #define _sendu(t, d, s) _send((t), (const char*)(d), (s)) /**************************************************************************** * Private Types ****************************************************************************/ /* Telnet state codes */ enum telnet_state_e { TELNET_STATE_DATA = 0, 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 }; /* Telnet state tracker */ struct telnet_s { /* User data */ FAR void *ud; /* Telopt support table */ FAR const struct telnet_telopt_s *telopts; /* Event handler */ telnet_event_handler_t eh; #if defined(HAVE_ZLIB) /* zlib (mccp2) compression */ FAR z_stream *z; #endif /* RFC1143 option negotiation states */ FAR struct telnet_rfc1143_s *q; /* Sub-request buffer */ FAR 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_e state; /* Option flags */ unsigned char flags; /* Current subnegotiation telopt */ unsigned char sb_telopt; /* Length of RFC1143 queue */ unsigned char q_size; }; /* RFC1143 option negotiation state */ struct telnet_rfc1143_s { unsigned char telopt; unsigned char state; }; /**************************************************************************** * Private Data ****************************************************************************/ /* 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]); /**************************************************************************** * Private Functions ****************************************************************************/ /* Error generation function */ static enum telnet_error_e _error(FAR struct telnet_s *telnet, unsigned line, FAR const char *func, enum telnet_error_e err, int fatal, FAR const char *fmt, ...) { union telnet_event_u 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; } /* 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. */ #if defined(HAVE_ZLIB) enum telnet_error_e _init_zlib(FAR struct telnet_s *telnet, int deflate, int err_fatal) { FAR 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: %d", 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 /* HAVE_ZLIB */ /* Push bytes out, compressing them first if need be */ static void _send(FAR struct telnet_s *telnet, FAR const char *buffer, size_t size) { union telnet_event_u 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 /* HAVE_ZLIB */ ev.type = TELNET_EV_SEND; ev.data.buffer = buffer; ev.data.size = size; telnet->eh(telnet, &ev, telnet->ud); } /* 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(FAR struct telnet_s *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 struct telnet_rfc1143_s _get_rfc1143(FAR struct telnet_s *telnet, unsigned char telopt) { struct telnet_rfc1143_s empty; int i; /* Search for entry */ for (i = 0; i != telnet->q_size; ++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(FAR struct telnet_s *telnet, unsigned char telopt, char us, char him) { struct telnet_rfc1143_s *qtmp; int i; /* Search for entry */ for (i = 0; i != telnet->q_size; ++i) { if (telnet->q[i].telopt == telopt) { telnet->q[i].state = Q_MAKE(us, him); 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. */ qtmp = (struct telnet_rfc1143_s *) realloc(telnet->q, sizeof(struct telnet_rfc1143_s) * (telnet->q_size + 4)); if (qtmp == 0) { _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, "realloc() failed: %d", errno); return; } memset(&qtmp[telnet->q_size], 0, sizeof(struct telnet_rfc1143_s) * 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; } /* Send negotiation bytes */ static inline void _send_negotiate(FAR struct telnet_s *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(FAR struct telnet_s *telnet, unsigned char telopt) { union telnet_event_u ev; struct telnet_rfc1143_s q; /* In PROXY mode, just pass it through 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(FAR struct telnet_s *telnet, unsigned char type, FAR char *buffer, size_t size) { union telnet_event_u ev; struct telnet_environ_s *values = 0; FAR char *c; FAR char *last; FAR char *out; size_t index; size_t 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.envevent.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.envevent.values = 0; ev.envevent.size = 0; /* Invoke event with our arguments */ ev.type = TELNET_EV_ENVIRON; telnet->eh(telnet, &ev, telnet->ud); return 0; } /* Every 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 */ values = (struct telnet_environ_s *) calloc(count, sizeof(struct telnet_environ_s)); if (values == 0) { _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, "calloc() failed: %d", 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.envevent.values = values; ev.envevent.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(FAR struct telnet_s *telnet, FAR char *buffer, size_t size) { union telnet_event_u ev; FAR struct telnet_environ_s *values; FAR char *var = 0; FAR char *c; FAR char *last; FAR char *out; size_t count; size_t i; 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 */ values = (struct telnet_environ_s *) calloc(count, sizeof(struct telnet_environ_s)); if (values == 0) { _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, "calloc() failed: %d", 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(FAR struct telnet_s *telnet, FAR const char *buffer, size_t size) { union telnet_event_u ev; FAR char **argv; FAR const char *c; size_t i; size_t 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: %d", 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(FAR struct telnet_s *telnet, FAR const char *buffer, size_t size) { union telnet_event_u 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: %d", 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(FAR struct telnet_s *telnet) { union telnet_event_u 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 /* 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; } } /**************************************************************************** * Name: telnet_init * * Description: * 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. * * Input Parameters: * telopts Table of TELNET options the application supports. * eh Event handler function called for every event. * flags 0 or TELNET_FLAG_PROXY. * user_data Optional data pointer that will be passsed to eh. * * Returned Value: * Telent state tracker object. * ****************************************************************************/ FAR struct telnet_s *telnet_init(FAR const struct telnet_telopt_s *telopts, telnet_event_handler_t eh, unsigned char flags, FAR void *user_data) { /* Allocate structure */ FAR struct telnet_s *telnet = (FAR struct telnet_s *) calloc(1, sizeof(struct telnet_s)); if (telnet == 0) { return 0; } /* Initialize data */ telnet->ud = user_data; telnet->telopts = telopts; telnet->eh = eh; telnet->flags = flags; return telnet; } /**************************************************************************** * Name: telnet_free * * Description: * 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. * * Input Parameters: * telnet Telnet state tracker object. * ****************************************************************************/ void telnet_free(FAR struct telnet_s *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 /* HAVE_ZLIB */ /* Free RFC1143 queue */ if (telnet->q) { free(telnet->q); telnet->q = 0; telnet->q_size = 0; } /* Free the telnet structure itself */ free(telnet); } /* Push a byte into the telnet buffer */ static enum telnet_error_e _buffer_byte(FAR struct telnet_s *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(FAR struct telnet_s *telnet, FAR const char *buffer, size_t size) { union telnet_event_u ev; unsigned char byte; size_t start; size_t i; 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; } 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); } } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: telnet_recv * * Description: * 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. * * Input Parameters: * telnet Telnet state tracker object. * buffer Pointer to byte buffer. * size Number of bytes pointed to by buffer. * ****************************************************************************/ void telnet_recv(FAR struct telnet_s *telnet, FAR 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) { union telnet_event_u 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 /* HAVE_ZLIB */ _process(telnet, buffer, size); } /**************************************************************************** * Name: telnet_iac * * Description: * Send a telnet command. * * Input Parameters: * telnet Telnet state tracker object. * cmd Command to send. * ****************************************************************************/ void telnet_iac(FAR struct telnet_s *telnet, unsigned char cmd) { unsigned char bytes[2]; bytes[0] = TELNET_IAC; bytes[1] = cmd; _sendu(telnet, bytes, 2); } /**************************************************************************** * Name: telnet_negotiate * * Description: * 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. * * Input Parameters: * telnet Telnet state tracker object. * cmd TELNET_WILL, TELNET_WONT, TELNET_DO, or TELNET_DONT. * opt One of the TELNET_TELOPT_* values. * ****************************************************************************/ void telnet_negotiate(FAR struct telnet_s *telnet, unsigned char cmd, unsigned char telopt) { struct telnet_rfc1143_s 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; } } /**************************************************************************** * Name: telnet_send * * Description: * Send non-command data (escapes IAC bytes). * * Input Parameters: * telnet Telnet state tracker object. * buffer Buffer of bytes to send. * size Number of bytes to send. * ****************************************************************************/ void telnet_send(FAR struct telnet_s *telnet, FAR const char *buffer, size_t size) { size_t l; size_t i; 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); } } /**************************************************************************** * Name: telnet_begin_sb * * Description: * 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. * * Input Parameters: * telnet Telnet state tracker object. * telopt One of the TELNET_TELOPT_* values. * ****************************************************************************/ void telnet_begin_sb(FAR struct telnet_s *telnet, unsigned char telopt) { unsigned char sb[3]; sb[0] = TELNET_IAC; sb[1] = TELNET_SB; sb[2] = telopt; _sendu(telnet, sb, 3); } /**************************************************************************** * Name: telnet_subnegotiation * * Description: * Send a complete subnegotiation buffer. * * Equivalent to: * telnet_begin_sb(telnet, telopt); * telnet_send(telnet, buffer, size); * telnet_finish_sb(telnet); * * Input Parameters: * telnet Telnet state tracker format. * telopt One of the TELNET_TELOPT_* values. * buffer Byte buffer for sub-negotiation data. * size Number of bytes to use for sub-negotiation data. * ****************************************************************************/ void telnet_subnegotiation(FAR struct telnet_s *telnet, unsigned char telopt, FAR 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) { union telnet_event_u 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 /* HAVE_ZLIB */ } /**************************************************************************** * Name: telnet_begin_compress2 * * Description: * 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. * * Input Parameters: * telnet Telnet state tracker object. * ****************************************************************************/ void telnet_begin_compress2(FAR struct telnet_s *telnet) { #if defined(HAVE_ZLIB) static const unsigned char compress2[] = { TELNET_IAC, TELNET_SB, TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE }; union telnet_event_u 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); #endif /* HAVE_ZLIB */ } /**************************************************************************** * Name: telnet_vprintf * * Description: * Send formatted data with \r and \n translation in addition to IAC IAC * * See telnet_printf(). * ****************************************************************************/ int telnet_vprintf(FAR struct telnet_s *telnet, FAR const char *fmt, va_list va) { static const char CRLF[] = { '\r', '\n' }; static const char CRNUL[] = { '\r', '\0' }; char buffer[1024]; FAR char *output = buffer; int rs; int l; int i; /* Format */ va_list va2; va_copy(va2, va); rs = vsnprintf(buffer, sizeof(buffer), fmt, va); if (rs >= sizeof(buffer)) { output = (char *)malloc(rs + 1); if (output == 0) { _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, "malloc() failed: %d", errno); return -1; } rs = vsnprintf(output, rs + 1, fmt, va2); } va_end(va2); va_end(va); /* 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; } /**************************************************************************** * Name: telnet_printf * * Description: * 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(). * * Input Parameters: * telnet Telnet state tracker object. * fmt Format string. * * Returned Value: * Number of bytes sent. * ****************************************************************************/ int telnet_printf(FAR struct telnet_s *telnet, FAR const char *fmt, ...) { va_list va; int rs; va_start(va, fmt); rs = telnet_vprintf(telnet, fmt, va); va_end(va); return rs; } /**************************************************************************** * Name: telnet_raw_vprintf * * Description: * Send formatted data (no newline escaping). * * See telnet_raw_printf(). * ****************************************************************************/ int telnet_raw_vprintf(FAR struct telnet_s *telnet, FAR const char *fmt, va_list va) { char buffer[1024]; FAR char *output = buffer; int rs; /* Format; allocate more space if necessary */ va_list va2; va_copy(va2, va); rs = vsnprintf(buffer, sizeof(buffer), fmt, va); if (rs >= sizeof(buffer)) { output = (char *)malloc(rs + 1); if (output == 0) { _error(telnet, __LINE__, __func__, TELNET_ENOMEM, 0, "malloc() failed: %d", errno); return -1; } rs = vsnprintf(output, rs + 1, fmt, va2); } va_end(va2); va_end(va); /* Send out the formatted data */ telnet_send(telnet, output, rs); /* Release allocated memory, if any */ if (output != buffer) { free(output); } return rs; } /**************************************************************************** * Name: telnet_raw_printf * * Description: * 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(). * * Input Parameters: * telnet Telnet state tracker object. * fmt Format string. * * Returned Value: * Number of bytes sent. * ****************************************************************************/ int telnet_raw_printf(FAR struct telnet_s *telnet, FAR const char *fmt, ...) { va_list va; int rs; va_start(va, fmt); rs = telnet_raw_vprintf(telnet, fmt, va); va_end(va); return rs; } /**************************************************************************** * Name: telnet_begin_newenviron * * Description: * 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. * * Input Parameters: * telnet Telnet state tracker object. * type One of TELNET_ENVIRON_SEND, TELNET_ENVIRON_IS, or * TELNET_ENVIRON_INFO. * ****************************************************************************/ void telnet_begin_newenviron(FAR struct telnet_s *telnet, unsigned char cmd) { telnet_begin_sb(telnet, TELNET_TELOPT_NEW_ENVIRON); telnet_send(telnet, (const char *)&cmd, 1); } /**************************************************************************** * Name: telnet_newenviron_value * * Description: * Send a NEW-ENVIRON variable name or value. * * This can only be called between calls to telnet_begin_newenviron() and * telnet_finish_newenviron(). * * Input Parameters: * telnet Telnet state tracker object. * type One of TELNET_ENVIRON_VAR, TELNET_ENVIRON_USERVAR, or * TELNET_ENVIRON_VALUE. * string Variable name or value. * ****************************************************************************/ void telnet_newenviron_value(FAR struct telnet_s *telnet, unsigned char type, const char *string) { telnet_send(telnet, (FAR const char *)&type, 1); if (string != 0) { telnet_send(telnet, string, strlen(string)); } } /**************************************************************************** * Name: telnet_ttype_send * * Description: * Send the TERMINAL-TYPE SEND command. * * Sends the sequence IAC TERMINAL-TYPE SEND. * * telnet Telnet state tracker object. * ****************************************************************************/ void telnet_ttype_send(FAR struct telnet_s *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)); } /**************************************************************************** * Name: telnet_ttype_is * * Description: * 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 options, 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. * * Input Parameters: * telnet Telnet state tracker object. * ttype Name of the terminal-type being sent. * ****************************************************************************/ void telnet_ttype_is(FAR struct telnet_s *telnet, FAR 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); } /**************************************************************************** * Name: telnet_send_zmp * * Description: * Send a ZMP command. * * Input Parameters: * telnet Telnet state tracker object. * argc Number of ZMP commands being sent. * argv Array of argument strings. * ****************************************************************************/ void telnet_send_zmp(FAR struct telnet_s *telnet, size_t argc, FAR 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); } /**************************************************************************** * Name: telnet_send_vzmpv * * Description: * Send a ZMP command. * * See telnet_send_zmpv(). * ****************************************************************************/ void telnet_send_vzmpv(FAR struct telnet_s *telnet, va_list va) { FAR 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); } /**************************************************************************** * Name: telnet_send_zmpv * * Description: * 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. * * Input Parameters: * telnet Telnet state tracker object. * ****************************************************************************/ void telnet_send_zmpv(FAR struct telnet_s *telnet, ...) { va_list va; va_start(va, telnet); telnet_send_vzmpv(telnet, va); va_end(va); } /**************************************************************************** * Name: telnet_begin_zmp * * Description: * Begin sending a ZMP command * * Input Parameters: * telnet Telnet state tracker object. * cmd The first argument (command name) for the ZMP command. * ****************************************************************************/ void telnet_begin_zmp(FAR struct telnet_s *telnet, FAR const char *cmd) { telnet_begin_sb(telnet, TELNET_TELOPT_ZMP); telnet_zmp_arg(telnet, cmd); } /**************************************************************************** * Name: telnet_zmp_arg * * Description: * Send a ZMP command argument. * * Input Parameters: * telnet Telnet state tracker object. * arg Telnet argument string. * ****************************************************************************/ void telnet_zmp_arg(FAR struct telnet_s *telnet, FAR const char *arg) { telnet_send(telnet, arg, strlen(arg) + 1); }