diff --git a/include/netutils/telnetc.h b/include/netutils/telnetc.h new file mode 100644 index 000000000..bf8a4e8a9 --- /dev/null +++ b/include/netutils/telnetc.h @@ -0,0 +1,850 @@ +/**************************************************************************** + * 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. + * + ****************************************************************************/ + +/* 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. + * + * Author: Sean Middleditch + */ + +#ifndef __APPS_INCLUDE_NETUTILS_TELNETC_H +#define __APPS_INCLUDE_NETUTILS_TELNETC_H 1 + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/* C++ support */ + +#if defined(__cplusplus) +extern "C" +{ +#endif + +/**************************************************************************** + * Pre-proecessor Definitions + ****************************************************************************/ + +/* 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 + +/* 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 + +/* TERMINAL-TYPE codes. */ + +#define TELNET_TTYPE_IS 0 +#define TELNET_TTYPE_SEND 1 + +/* 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 + +/* MSSP codes. */ + +#define TELNET_MSSP_VAR 1 +#define TELNET_MSSP_VAL 2 + +/* Telnet state tracker flags. */ + +#define TELNET_FLAG_PROXY (1 << 0) +#define TELNET_PFLAG_DEFLATE (1 << 7) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Error codes */ + +enum telnet_error_u +{ + 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 */ +}; + +/* Event codes */ + +enum telnet_event_type_e +{ + 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 */ +}; + +/* Environ/MSSP command information */ + +struct telnet_environ_s +{ + 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 */ +}; + +/* State tracker -- private data structure */ + +struct telnet_s; /* Forward reference */ + +/* Event information */ + +union telnet_event_u +{ + /* 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_e type; + + /* Data event: for DATA and SEND events */ + + struct + { + enum telnet_event_type_e _type; /* Alias for type */ + const char *buffer; /* Byte buffer */ + size_t size; /* Number of bytes in buffer */ + } data; + + /* WARNING and ERROR events */ + + struct + { + enum telnet_event_type_e _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 */ + enum telnet_error_u errcode; /* Error code */ + } error; + + /* Command event: for IAC */ + + struct + { + enum telnet_event_type_e _type; /* Alias for type */ + unsigned char cmd; /* Telnet command received */ + } iac; + + /* Negotiation event: WILL, WONT, DO, DONT */ + + struct + { + enum telnet_event_type_e _type; /* Alias for type */ + unsigned char telopt; /* Option being negotiated */ + } neg; + + /* Subnegotiation event */ + + struct + { + enum telnet_event_type_e _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; + + /* ZMP event */ + + struct + { + enum telnet_event_type_e _type; /* Alias for type */ + const char **argv; /* Array of argument string */ + size_t argc; /* Number of elements in argv */ + } zmp; + + /* TTYPE event */ + + struct + { + enum telnet_event_type_e _type; /* Alias for type */ + unsigned char cmd; /* TELNET_TTYPE_IS or TELNET_TTYPE_SEND */ + const char *name; /* Terminal type name (IS only) */ + } ttype; + + /* COMPRESS event */ + + struct + { + enum telnet_event_type_e _type; /* Alias for type */ + unsigned char state; /* 1 if compression is enabled, 0 if + * disabled */ + } compress; + + /* ENVIRON/NEW-ENVIRON event */ + + struct + { + enum telnet_event_type_e _type; /* Alias for type */ + const struct telnet_environ_s *values; /* Array of variable values */ + size_t size; /* Number of elements in values */ + unsigned char cmd; /* SEND, IS, or INFO */ + } envevent; + + /* MSSP event */ + + struct + { + enum telnet_event_type_e _type; /* Alias for type */ + const struct telnet_environ_s *values; /* Array of variable values */ + size_t size; /* Number of elements in values */ + } mssp; +}; + +/* Name: telnet_event_handler_t + * + * Description: + * 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. + * + * Input Parameters: + * telnet The telnet object that generated the event + * event Event structure with details about the event + * user_data User-supplied pointer + */ + +typedef void (*telnet_event_handler_t)(struct telnet_s *telnet, + union telnet_event_u *event, + void *user_data); + +/* telopt support table element; use telopt of -1 for end marker */ + +struct telnet_telopt_s +{ + 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 */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * 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. + * + ****************************************************************************/ + +struct telnet_s *telnet_init(const struct telnet_telopt_s *telopts, + telnet_event_handler_t eh, unsigned char flags, + void *user_data); + +/**************************************************************************** + * 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(struct telnet_s *telnet); + +/**************************************************************************** + * 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(struct telnet_s *telnet, const char *buffer, size_t size); + +/**************************************************************************** + * Name: telnet_iac + * + * Description: + * Send a telnet command. + * + * Input Parameters: + * telnet Telnet state tracker object. + * cmd Command to send. + * + ****************************************************************************/ + +void telnet_iac(struct telnet_s *telnet, unsigned char cmd); + +/**************************************************************************** + * 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(struct telnet_s *telnet, unsigned char cmd, + unsigned char opt); + +/**************************************************************************** + * 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(struct telnet_s *telnet, const char *buffer, size_t size); + +/**************************************************************************** + * 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(struct telnet_s *telnet, unsigned char telopt); + +/**************************************************************************** + * Name: telnet_finish_sb + * + * Description: + * Finish a sub-negotiation command. + * + * This must be called after a call to telnet_begin_sb() to finish a + * sub-negotiation command. + * + * Input Parameters: + * telnet Telnet state tracker object. + * + ****************************************************************************/ + +#define telnet_finish_sb(telnet) telnet_iac((telnet), TELNET_SE) + +/**************************************************************************** + * 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(struct telnet_s *telnet, unsigned char telopt, + const char *buffer, size_t size); + +/**************************************************************************** + * 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(struct telnet_s *telnet); + +/**************************************************************************** + * 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(struct telnet_s *telnet, const char *fmt, ...); + +/**************************************************************************** + * Name: telnet_vprintf + * + * Description: + * Send formatted data with \r and \n translation in addition to IAC IAC + * + * See telnet_printf(). + * + ****************************************************************************/ + +int telnet_vprintf(struct telnet_s *telnet, const char *fmt, va_list va); + +/**************************************************************************** + * 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(struct telnet_s *telnet, const char *fmt, ...); + +/**************************************************************************** + * Name: telnet_raw_vprintf + * + * Description: + * Send formatted data (no newline escaping). + * + * See telnet_raw_printf(). + * + ****************************************************************************/ + +int telnet_raw_vprintf(struct telnet_s *telnet, const char *fmt, va_list va); + +/**************************************************************************** + * 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(struct telnet_s *telnet, unsigned char type); + +/**************************************************************************** + * 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(struct telnet_s *telnet, unsigned char type, + const char *string); + +/**************************************************************************** + * Name: telnet_finish_newenviron + * + * Description: + * Finish a NEW-ENVIRON command. + * + * This must be called after a call to telnet_begin_newenviron() to finish a + * NEW-ENVIRON variable list. + * + * telnet Telnet state tracker object. + * + ****************************************************************************/ + +#define telnet_finish_newenviron(telnet) telnet_finish_sb((telnet)) + +/**************************************************************************** + * 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(struct telnet_s * telnet); + +/**************************************************************************** + * 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 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. + * + * Input Parameters: + * telnet Telnet state tracker object. + * ttype Name of the terminal-type being sent. + * + ****************************************************************************/ + +void telnet_ttype_is(struct telnet_s * telnet, const char *ttype); + +/**************************************************************************** + * 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(struct telnet_s * telnet, size_t argc, + const char **argv); + +/**************************************************************************** + * 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(struct telnet_s * telnet, ...); + +/**************************************************************************** + * Name: telnet_send_vzmpv + * + * Description: + * Send a ZMP command. + * + * See telnet_send_zmpv(). + * + ****************************************************************************/ + +void telnet_send_vzmpv(struct telnet_s * telnet, va_list 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(struct telnet_s * telnet, const char *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(struct telnet_s * telnet, const char *arg); + +/**************************************************************************** + * Name: telnet_finish_zmp + * + * Description: + * Finish a ZMP command. + * + * This must be called after a call to telnet_begin_zmp() to finish a + * ZMP argument list. + * + * Input Parameters: + * telnet Telnet state tracker object. + * + ****************************************************************************/ + +#define telnet_finish_zmp(telnet) telnet_finish_sb((telnet)) + +#if defined(__cplusplus) +} +#endif + +#endif /* __APPS_INCLUDE_NETUTILS_TELNETC_H */ diff --git a/netutils/README.txt b/netutils/README.txt index c2f3c4391..d0822e3bf 100644 --- a/netutils/README.txt +++ b/netutils/README.txt @@ -55,6 +55,12 @@ highly influenced by uIP) include: and adapted for NuttX by Darcy Gong. tftpc - TFTP client. See apps/include/netutils/tftp.h for interface information. + telnetc - This is a port of libtelnet to NuttX. This is a public domain + Telnet client library available from + https://github.com/seanmiddleditch/libtelnet modified for use + with NuttX. Original Authors: Sean Middleditch , + Jack Kelly , and Katherine Flavel + telnetd - TELNET server. This is the Telnet logic adapted from uIP and generalized for use as the front end to any shell. The telnet daemon creates sessions that are diff --git a/netutils/telnetc/.gitignore b/netutils/telnetc/.gitignore new file mode 100644 index 000000000..f21c854d1 --- /dev/null +++ b/netutils/telnetc/.gitignore @@ -0,0 +1,6 @@ +/.built +/.depend +/Make.dep +/*.src +/*.obj +/*.lst diff --git a/netutils/telnetc/telnetc.c b/netutils/telnetc/telnetc.c new file mode 100644 index 000000000..a5ee4e38e --- /dev/null +++ b/netutils/telnetc/telnetc.c @@ -0,0 +1,2410 @@ +/**************************************************************************** + * 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 */ + + void *ud; + + /* Telopt support table */ + + const struct telnet_telopt_s *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_s *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_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_u _error(struct telnet_s *telnet, unsigned line, + const char *func, enum telnet_error_u err, + int fatal, 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_u _init_zlib(struct telnet_s *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 /* HAVE_ZLIB */ + +/* Push bytes out, compressing them first if need be */ + +static void _send(struct telnet_s *telnet, 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(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 unti 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(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(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: %s", strerror(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(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(struct telnet_s *telnet, unsigned char telopt) +{ + union telnet_event_u ev; + struct telnet_rfc1143_s 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); + NEGOTIATE_EVENT(telnet, TELNET_EV_WILL, telopt); + _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); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, 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)); + NEGOTIATE_EVENT(telnet, TELNET_EV_DO, telopt); + _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_WONT, 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_WILL, 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(struct telnet_s *telnet, unsigned char type, + char *buffer, size_t size) +{ + union telnet_event_u ev; + struct telnet_environ_s *values = 0; + char *c; + char *last; + 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 1; + } + + /* 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: %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.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 1; +} + +/* Process an MSSP subnegotiation buffer */ + +static int _mssp_telnet(struct telnet_s *telnet, char *buffer, size_t size) +{ + union telnet_event_u ev; + struct telnet_environ_s *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 */ + + values = (struct telnet_environ_s *) + calloc(count, sizeof(struct telnet_environ_s)); + if (values == 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(struct telnet_s *telnet, const char *buffer, size_t size) +{ + union telnet_event_u ev; + char **argv; + 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: %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(struct telnet_s *telnet, 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: %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(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 (telnet->sb_telopt == 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; + } + return 0; +#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. + * + ****************************************************************************/ + +struct telnet_s *telnet_init(const struct telnet_telopt_s *telopts, + telnet_event_handler_t eh, + unsigned char flags, + void *user_data) +{ + /* Allocate structure */ + + struct telnet_s *telnet = (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(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_u _buffer_byte(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(struct telnet_s *telnet, 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(struct telnet_s *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) + { + 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(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(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(struct telnet_s *telnet, 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(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(struct telnet_s *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) + { + 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(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(struct telnet_s *telnet, const char *fmt, va_list va) +{ + static const char CRLF[] = { '\r', '\n' }; + static const char CRNUL[] = { '\r', '\0' }; + char buffer[1024]; + char *output = buffer; + int rs; + 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: %s", strerror(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(struct telnet_s *telnet, 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(struct telnet_s *telnet, const char *fmt, va_list va) +{ + char buffer[1024]; + 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: %s", strerror(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(struct telnet_s *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; +} + +/**************************************************************************** + * 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(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(struct telnet_s *telnet, unsigned char type, + const char *string) +{ + telnet_send(telnet, (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(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 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. + * + * Input Parameters: + * telnet Telnet state tracker object. + * ttype Name of the terminal-type being sent. + * + ****************************************************************************/ + +void telnet_ttype_is(struct telnet_s *telnet, const char *ttype) +{ + static const unsigned char IS[] = + { + TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS + }; + + _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(struct telnet_s *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); +} + +/**************************************************************************** + * Name: telnet_send_vzmpv + * + * Description: + * Send a ZMP command. + * + * See telnet_send_zmpv(). + * + ****************************************************************************/ + +void telnet_send_vzmpv(struct telnet_s *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); +} + +/**************************************************************************** + * 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(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(struct telnet_s *telnet, 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(struct telnet_s *telnet, const char *arg) +{ + telnet_send(telnet, arg, strlen(arg) + 1); +}