feat: Full featured Telnet implementation (not integrated)
This commit is contained in:
+12
-1
@@ -1,10 +1,21 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
|
frozen-eye/esp_telnet:
|
||||||
|
component_hash: e00a16d6372fa31df46b32e330847d6e09af2ec184d8c7cac4d183b3daac02e5
|
||||||
|
dependencies:
|
||||||
|
- name: idf
|
||||||
|
require: private
|
||||||
|
version: '>=4.0'
|
||||||
|
source:
|
||||||
|
registry_url: https://components.espressif.com/
|
||||||
|
type: service
|
||||||
|
version: 0.0.2
|
||||||
idf:
|
idf:
|
||||||
source:
|
source:
|
||||||
type: idf
|
type: idf
|
||||||
version: 5.4.1
|
version: 5.4.1
|
||||||
direct_dependencies:
|
direct_dependencies:
|
||||||
|
- frozen-eye/esp_telnet
|
||||||
- idf
|
- idf
|
||||||
manifest_hash: 00d8efc8e59d9f2cc6a576ada310175e19e6f7506dd0b59211849f065dadccae
|
manifest_hash: 8e2a908d16408b9cafcd4c4b109b841acfca6e660d2500fc6d1362f0f143376c
|
||||||
target: esp32c6
|
target: esp32c6
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
## IDF Component Manager Manifest File
|
||||||
|
# https://docs.espressif.com/projects/idf-component-manager/en/latest/reference/manifest_file.html
|
||||||
|
# https://components.espressif.com
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
frozen-eye/esp_telnet: "^0.0.2"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
e00a16d6372fa31df46b32e330847d6e09af2ec184d8c7cac4d183b3daac02e5
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
Original Telnet Server code is based on the version created and written by the following people:
|
||||||
|
|
||||||
|
- Sean Middleditch <sean@sourcemud.org>
|
||||||
|
- Jack Kelly <endgame.dos@gmail.com>
|
||||||
|
- Katherine Flavel <kate@elide.org>
|
||||||
|
- Daniel Loffgren (https://github.com/RyuKojiro)
|
||||||
|
|
||||||
|
and taken from here: [https://github.com/seanmiddleditch/libtelnet](https://github.com/seanmiddleditch/libtelnet)
|
||||||
|
|
||||||
|
Midificatons are performed by:
|
||||||
|
|
||||||
|
- FrozenEye (https://github.com/frozen-eye)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version": "1.0", "algorithm": "sha256", "created_at": "2025-05-21T14:59:16.171166+00:00", "files": [{"path": "CMakeLists.txt", "size": 303, "hash": "70671f714f52ac79607119703f1ddab4a0b325f25e8981afe7c93baf7af7cd34"}, {"path": "LICENSE", "size": 1063, "hash": "068129b238141ba0ebcb2158402d73408c798f4d8c5a42f4dafaa41a002b9ea2"}, {"path": "AUTHORS", "size": 452, "hash": "277d45436ae96f30f6ee4b2dcde2661e679f2ded4f790cbee0c726d5f13c8dba"}, {"path": "idf_component.yml", "size": 179, "hash": "39f2a2bd0c96ab90610389356a2cf39842364514e2a69c1241e6ecd84b269d72"}, {"path": "Kconfig", "size": 1114, "hash": "ccc9fc290f957c953bdb3bbc8d1079efe7c5fdb82f10d6c7a2dc9973b4046bd7"}, {"path": ".gitignore", "size": 0, "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}, {"path": "src/libtelnet.h", "size": 21611, "hash": "0798f3706642fdd4520923914ddfa59fb9ded670b7716877c80fa852153b8521"}, {"path": "src/server.c", "size": 9226, "hash": "bf90b0df126da4a16d79c626724ce715fdfb8015dd7ea0030b8a6cbf072c947f"}, {"path": "src/libtelnet.c", "size": 44236, "hash": "de15620edc5492aaf6c273ce4c9181cb99490a2153e56db7d7c3f96f3ea7e2b4"}, {"path": "include/telnet/server.h", "size": 1493, "hash": "ebe6d17a86a1c56c97e263bd4d468cffc5d95a3ca810e79388b2a9598f89f8ba"}]}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
idf_component_register(
|
||||||
|
INCLUDE_DIRS
|
||||||
|
include
|
||||||
|
src
|
||||||
|
.
|
||||||
|
SRCS
|
||||||
|
src/libtelnet.c
|
||||||
|
src/server.c
|
||||||
|
REQUIRES
|
||||||
|
PRIV_REQUIRES
|
||||||
|
)
|
||||||
|
|
||||||
|
# target_compile_options(${COMPONENT_LIB} PRIVATE -Wall -Wextra -Werror)
|
||||||
|
# target_compile_features(${COMPONENT_LIB} PRIVATE cxx_std_17)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
menu "Telnet Server"
|
||||||
|
|
||||||
|
config TELNET_SERVER_DEFAULT_PORT
|
||||||
|
int "Default Telnet Server Port"
|
||||||
|
default 23
|
||||||
|
help
|
||||||
|
Default Telnet Server Port Number
|
||||||
|
|
||||||
|
config TELNET_SERVER_MAX_CONNECTIONS
|
||||||
|
int "Maximum Telnet Server Connections"
|
||||||
|
default 5
|
||||||
|
help
|
||||||
|
Maximum Number of Telnet Server Connections
|
||||||
|
|
||||||
|
config TELNET_SERVER_STACK_SIZE
|
||||||
|
int "Telnet Server Stack Size"
|
||||||
|
default 4096
|
||||||
|
help
|
||||||
|
Telnet Server Stack Size
|
||||||
|
|
||||||
|
config TELNET_SERVER_TASK_PRIORITY
|
||||||
|
int "Telnet Server Task Priority"
|
||||||
|
range 1 25
|
||||||
|
default 24
|
||||||
|
help
|
||||||
|
Telnet Server Task Priority. In case of high events rate, this parameter could be changed up to (configMAX_PRIORITIES-1).
|
||||||
|
|
||||||
|
config TELNET_SERVER_TASK_CORE
|
||||||
|
int "Telnet Server Task Core"
|
||||||
|
range 0 1
|
||||||
|
default 0
|
||||||
|
help
|
||||||
|
Telnet Server Task Core
|
||||||
|
|
||||||
|
config TELNET_SERVER_REDIRECT_LOGS
|
||||||
|
int "Redirect Logs to Telnet Server"
|
||||||
|
range 0 1
|
||||||
|
default 0
|
||||||
|
help
|
||||||
|
Redirect Logs to Telnet Server
|
||||||
|
endmenu
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
Copyright 2024 Frozen Eye
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
dependencies:
|
||||||
|
idf:
|
||||||
|
version: '>=4.0'
|
||||||
|
description: esp_telnet is a small implementation of the telnet server.
|
||||||
|
url: https://github.com/frozen-eye/esp_telnet.git
|
||||||
|
version: 0.0.2
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#include <libtelnet.h>
|
||||||
|
|
||||||
|
static const telnet_telopt_t default_telopts[] = {
|
||||||
|
{TELNET_TELOPT_COMPRESS2, TELNET_WILL, TELNET_DO}, {TELNET_TELOPT_ZMP, TELNET_WILL, TELNET_DO},
|
||||||
|
{TELNET_TELOPT_MSSP, TELNET_WILL, TELNET_DONT}, {TELNET_TELOPT_NEW_ENVIRON, TELNET_WILL, TELNET_DONT},
|
||||||
|
{TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT}, {-1, 0, 0}};
|
||||||
|
|
||||||
|
struct user_t {
|
||||||
|
char* name;
|
||||||
|
int sock;
|
||||||
|
telnet_t* telnet;
|
||||||
|
char linebuf[255];
|
||||||
|
int linepos;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct telnet_server_config {
|
||||||
|
int port;
|
||||||
|
int stack_size;
|
||||||
|
int task_priority;
|
||||||
|
int task_core;
|
||||||
|
int redirect_logs;
|
||||||
|
int max_connections;
|
||||||
|
const telnet_telopt_t *telnet_opts;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Telnet Server Default Configuration
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#define TELNET_SERVER_DEFAULT_CONFIG \
|
||||||
|
{ \
|
||||||
|
.port = CONFIG_TELNET_SERVER_DEFAULT_PORT, \
|
||||||
|
.stack_size = CONFIG_TELNET_SERVER_STACK_SIZE, \
|
||||||
|
.task_priority = CONFIG_TELNET_SERVER_TASK_PRIORITY, \
|
||||||
|
.task_core = CONFIG_TELNET_SERVER_TASK_CORE, \
|
||||||
|
.redirect_logs = CONFIG_TELNET_SERVER_REDIRECT_LOGS, \
|
||||||
|
.max_connections = CONFIG_TELNET_SERVER_MAX_CONNECTIONS, \
|
||||||
|
.telnet_opts = default_telopts, \
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct telnet_server_config telnet_server_config_t;
|
||||||
|
|
||||||
|
esp_err_t telnet_server_create(telnet_server_config_t* config);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,691 @@
|
|||||||
|
/*!
|
||||||
|
* \brief libtelnet - TELNET protocol handling library
|
||||||
|
*
|
||||||
|
* SUMMARY:
|
||||||
|
*
|
||||||
|
* libtelnet is a library for handling the TELNET protocol. It includes
|
||||||
|
* routines for parsing incoming data from a remote peer as well as formatting
|
||||||
|
* data to send to the remote peer.
|
||||||
|
*
|
||||||
|
* libtelnet uses a callback-oriented API, allowing application-specific
|
||||||
|
* handling of various events. The callback system is also used for buffering
|
||||||
|
* outgoing protocol data, allowing the application to maintain control over
|
||||||
|
* the actual socket connection.
|
||||||
|
*
|
||||||
|
* Features supported include the full TELNET protocol, Q-method option
|
||||||
|
* negotiation, ZMP, MCCP2, MSSP, and NEW-ENVIRON.
|
||||||
|
*
|
||||||
|
* CONFORMS TO:
|
||||||
|
*
|
||||||
|
* RFC854 - http://www.faqs.org/rfcs/rfc854.html
|
||||||
|
* RFC855 - http://www.faqs.org/rfcs/rfc855.html
|
||||||
|
* RFC1091 - http://www.faqs.org/rfcs/rfc1091.html
|
||||||
|
* RFC1143 - http://www.faqs.org/rfcs/rfc1143.html
|
||||||
|
* RFC1408 - http://www.faqs.org/rfcs/rfc1408.html
|
||||||
|
* RFC1572 - http://www.faqs.org/rfcs/rfc1572.html
|
||||||
|
*
|
||||||
|
* LICENSE:
|
||||||
|
*
|
||||||
|
* The author or authors of this code dedicate any and all copyright interest
|
||||||
|
* in this code to the public domain. We make this dedication for the benefit
|
||||||
|
* of the public at large and to the detriment of our heirs and successors. We
|
||||||
|
* intend this dedication to be an overt act of relinquishment in perpetuity of
|
||||||
|
* all present and future rights to this code under copyright law.
|
||||||
|
*
|
||||||
|
* \file libtelnet.h
|
||||||
|
*
|
||||||
|
* \version 0.23
|
||||||
|
*
|
||||||
|
* \author Sean Middleditch <sean@sourcemud.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(LIBTELNET_INCLUDE)
|
||||||
|
#define LIBTELNET_INCLUDE 1
|
||||||
|
|
||||||
|
/* standard C headers necessary for the libtelnet API */
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* C++ support */
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* printf type checking feature in GCC and some other compilers */
|
||||||
|
#if __GNUC__
|
||||||
|
# define TELNET_GNU_PRINTF(f,a) __attribute__((format(printf, f, a))) /*!< internal helper */
|
||||||
|
# define TELNET_GNU_SENTINEL __attribute__((sentinel)) /*!< internal helper */
|
||||||
|
#else
|
||||||
|
# define TELNET_GNU_PRINTF(f,a) /*!< internal helper */
|
||||||
|
# define TELNET_GNU_SENTINEL /*!< internal helper */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Disable environ macro for Visual C++ 2015. */
|
||||||
|
#undef environ
|
||||||
|
|
||||||
|
/*! Telnet state tracker object type. */
|
||||||
|
typedef struct telnet_t telnet_t;
|
||||||
|
|
||||||
|
/*! Telnet event object type. */
|
||||||
|
typedef union telnet_event_t telnet_event_t;
|
||||||
|
|
||||||
|
/*! Telnet option table element type. */
|
||||||
|
typedef struct telnet_telopt_t telnet_telopt_t;
|
||||||
|
|
||||||
|
/*! \name Telnet commands */
|
||||||
|
/*@{*/
|
||||||
|
/*! Telnet commands and special values. */
|
||||||
|
#define TELNET_IAC 255
|
||||||
|
#define TELNET_DONT 254
|
||||||
|
#define TELNET_DO 253
|
||||||
|
#define TELNET_WONT 252
|
||||||
|
#define TELNET_WILL 251
|
||||||
|
#define TELNET_SB 250
|
||||||
|
#define TELNET_GA 249
|
||||||
|
#define TELNET_EL 248
|
||||||
|
#define TELNET_EC 247
|
||||||
|
#define TELNET_AYT 246
|
||||||
|
#define TELNET_AO 245
|
||||||
|
#define TELNET_IP 244
|
||||||
|
#define TELNET_BREAK 243
|
||||||
|
#define TELNET_DM 242
|
||||||
|
#define TELNET_NOP 241
|
||||||
|
#define TELNET_SE 240
|
||||||
|
#define TELNET_EOR 239
|
||||||
|
#define TELNET_ABORT 238
|
||||||
|
#define TELNET_SUSP 237
|
||||||
|
#define TELNET_EOF 236
|
||||||
|
/*@}*/
|
||||||
|
|
||||||
|
/*! \name Telnet option values. */
|
||||||
|
/*@{*/
|
||||||
|
/*! Telnet options. */
|
||||||
|
#define TELNET_TELOPT_BINARY 0
|
||||||
|
#define TELNET_TELOPT_ECHO 1
|
||||||
|
#define TELNET_TELOPT_RCP 2
|
||||||
|
#define TELNET_TELOPT_SGA 3
|
||||||
|
#define TELNET_TELOPT_NAMS 4
|
||||||
|
#define TELNET_TELOPT_STATUS 5
|
||||||
|
#define TELNET_TELOPT_TM 6
|
||||||
|
#define TELNET_TELOPT_RCTE 7
|
||||||
|
#define TELNET_TELOPT_NAOL 8
|
||||||
|
#define TELNET_TELOPT_NAOP 9
|
||||||
|
#define TELNET_TELOPT_NAOCRD 10
|
||||||
|
#define TELNET_TELOPT_NAOHTS 11
|
||||||
|
#define TELNET_TELOPT_NAOHTD 12
|
||||||
|
#define TELNET_TELOPT_NAOFFD 13
|
||||||
|
#define TELNET_TELOPT_NAOVTS 14
|
||||||
|
#define TELNET_TELOPT_NAOVTD 15
|
||||||
|
#define TELNET_TELOPT_NAOLFD 16
|
||||||
|
#define TELNET_TELOPT_XASCII 17
|
||||||
|
#define TELNET_TELOPT_LOGOUT 18
|
||||||
|
#define TELNET_TELOPT_BM 19
|
||||||
|
#define TELNET_TELOPT_DET 20
|
||||||
|
#define TELNET_TELOPT_SUPDUP 21
|
||||||
|
#define TELNET_TELOPT_SUPDUPOUTPUT 22
|
||||||
|
#define TELNET_TELOPT_SNDLOC 23
|
||||||
|
#define TELNET_TELOPT_TTYPE 24
|
||||||
|
#define TELNET_TELOPT_EOR 25
|
||||||
|
#define TELNET_TELOPT_TUID 26
|
||||||
|
#define TELNET_TELOPT_OUTMRK 27
|
||||||
|
#define TELNET_TELOPT_TTYLOC 28
|
||||||
|
#define TELNET_TELOPT_3270REGIME 29
|
||||||
|
#define TELNET_TELOPT_X3PAD 30
|
||||||
|
#define TELNET_TELOPT_NAWS 31
|
||||||
|
#define TELNET_TELOPT_TSPEED 32
|
||||||
|
#define TELNET_TELOPT_LFLOW 33
|
||||||
|
#define TELNET_TELOPT_LINEMODE 34
|
||||||
|
#define TELNET_TELOPT_XDISPLOC 35
|
||||||
|
#define TELNET_TELOPT_ENVIRON 36
|
||||||
|
#define TELNET_TELOPT_AUTHENTICATION 37
|
||||||
|
#define TELNET_TELOPT_ENCRYPT 38
|
||||||
|
#define TELNET_TELOPT_NEW_ENVIRON 39
|
||||||
|
#define TELNET_TELOPT_MSSP 70
|
||||||
|
#define TELNET_TELOPT_COMPRESS 85
|
||||||
|
#define TELNET_TELOPT_COMPRESS2 86
|
||||||
|
#define TELNET_TELOPT_ZMP 93
|
||||||
|
#define TELNET_TELOPT_EXOPL 255
|
||||||
|
|
||||||
|
#define TELNET_TELOPT_MCCP2 86
|
||||||
|
/*@}*/
|
||||||
|
|
||||||
|
/*! \name Protocol codes for TERMINAL-TYPE commands. */
|
||||||
|
/*@{*/
|
||||||
|
/*! TERMINAL-TYPE codes. */
|
||||||
|
#define TELNET_TTYPE_IS 0
|
||||||
|
#define TELNET_TTYPE_SEND 1
|
||||||
|
/*@}*/
|
||||||
|
|
||||||
|
/*! \name Protocol codes for NEW-ENVIRON/ENVIRON commands. */
|
||||||
|
/*@{*/
|
||||||
|
/*! NEW-ENVIRON/ENVIRON codes. */
|
||||||
|
#define TELNET_ENVIRON_IS 0
|
||||||
|
#define TELNET_ENVIRON_SEND 1
|
||||||
|
#define TELNET_ENVIRON_INFO 2
|
||||||
|
#define TELNET_ENVIRON_VAR 0
|
||||||
|
#define TELNET_ENVIRON_VALUE 1
|
||||||
|
#define TELNET_ENVIRON_ESC 2
|
||||||
|
#define TELNET_ENVIRON_USERVAR 3
|
||||||
|
/*@}*/
|
||||||
|
|
||||||
|
/*! \name Protocol codes for MSSP commands. */
|
||||||
|
/*@{*/
|
||||||
|
/*! MSSP codes. */
|
||||||
|
#define TELNET_MSSP_VAR 1
|
||||||
|
#define TELNET_MSSP_VAL 2
|
||||||
|
/*@}*/
|
||||||
|
|
||||||
|
/*! \name Telnet state tracker flags. */
|
||||||
|
/*@{*/
|
||||||
|
/*! Control behavior of telnet state tracker. */
|
||||||
|
#define TELNET_FLAG_PROXY (1<<0)
|
||||||
|
#define TELNET_FLAG_NVT_EOL (1<<1)
|
||||||
|
|
||||||
|
/* Internal-only bits in option flags */
|
||||||
|
#define TELNET_FLAG_TRANSMIT_BINARY (1<<5)
|
||||||
|
#define TELNET_FLAG_RECEIVE_BINARY (1<<6)
|
||||||
|
#define TELNET_PFLAG_DEFLATE (1<<7)
|
||||||
|
/*@}*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* error codes
|
||||||
|
*/
|
||||||
|
enum telnet_error_t {
|
||||||
|
TELNET_EOK = 0, /*!< no error */
|
||||||
|
TELNET_EBADVAL, /*!< invalid parameter, or API misuse */
|
||||||
|
TELNET_ENOMEM, /*!< memory allocation failure */
|
||||||
|
TELNET_EOVERFLOW, /*!< data exceeds buffer size */
|
||||||
|
TELNET_EPROTOCOL, /*!< invalid sequence of special bytes */
|
||||||
|
TELNET_ECOMPRESS /*!< error handling compressed streams */
|
||||||
|
};
|
||||||
|
typedef enum telnet_error_t telnet_error_t; /*!< Error code type. */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* event codes
|
||||||
|
*/
|
||||||
|
enum telnet_event_type_t {
|
||||||
|
TELNET_EV_DATA = 0, /*!< raw text data has been received */
|
||||||
|
TELNET_EV_SEND, /*!< data needs to be sent to the peer */
|
||||||
|
TELNET_EV_IAC, /*!< generic IAC code received */
|
||||||
|
TELNET_EV_WILL, /*!< WILL option negotiation received */
|
||||||
|
TELNET_EV_WONT, /*!< WONT option neogitation received */
|
||||||
|
TELNET_EV_DO, /*!< DO option negotiation received */
|
||||||
|
TELNET_EV_DONT, /*!< DONT option negotiation received */
|
||||||
|
TELNET_EV_SUBNEGOTIATION, /*!< sub-negotiation data received */
|
||||||
|
TELNET_EV_COMPRESS, /*!< compression has been enabled */
|
||||||
|
TELNET_EV_ZMP, /*!< ZMP command has been received */
|
||||||
|
TELNET_EV_TTYPE, /*!< TTYPE command has been received */
|
||||||
|
TELNET_EV_ENVIRON, /*!< ENVIRON command has been received */
|
||||||
|
TELNET_EV_MSSP, /*!< MSSP command has been received */
|
||||||
|
TELNET_EV_WARNING, /*!< recoverable error has occured */
|
||||||
|
TELNET_EV_ERROR /*!< non-recoverable error has occured */
|
||||||
|
};
|
||||||
|
typedef enum telnet_event_type_t telnet_event_type_t; /*!< Telnet event type. */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* environ/MSSP command information
|
||||||
|
*/
|
||||||
|
struct telnet_environ_t {
|
||||||
|
unsigned char type; /*!< either TELNET_ENVIRON_VAR or TELNET_ENVIRON_USERVAR */
|
||||||
|
char *var; /*!< name of the variable being set */
|
||||||
|
char *value; /*!< value of variable being set; empty string if no value */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* event information
|
||||||
|
*/
|
||||||
|
union telnet_event_t {
|
||||||
|
/*!
|
||||||
|
* \brief Event type
|
||||||
|
*
|
||||||
|
* The type field will determine which of the other event structure fields
|
||||||
|
* have been filled in. For instance, if the event type is TELNET_EV_ZMP,
|
||||||
|
* then the zmp event field (and ONLY the zmp event field) will be filled
|
||||||
|
* in.
|
||||||
|
*/
|
||||||
|
enum telnet_event_type_t type;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* data event: for DATA and SEND events
|
||||||
|
*/
|
||||||
|
struct data_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
const char *buffer; /*!< byte buffer */
|
||||||
|
size_t size; /*!< number of bytes in buffer */
|
||||||
|
} data; /*!< DATA and SEND */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* WARNING and ERROR events
|
||||||
|
*/
|
||||||
|
struct error_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
const char *file; /*!< file the error occured in */
|
||||||
|
const char *func; /*!< function the error occured in */
|
||||||
|
const char *msg; /*!< error message string */
|
||||||
|
int line; /*!< line of file error occured on */
|
||||||
|
telnet_error_t errcode; /*!< error code */
|
||||||
|
} error; /*!< WARNING and ERROR */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* command event: for IAC
|
||||||
|
*/
|
||||||
|
struct iac_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
unsigned char cmd; /*!< telnet command received */
|
||||||
|
} iac; /*!< IAC */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* negotiation event: WILL, WONT, DO, DONT
|
||||||
|
*/
|
||||||
|
struct negotiate_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
unsigned char telopt; /*!< option being negotiated */
|
||||||
|
} neg; /*!< WILL, WONT, DO, DONT */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* subnegotiation event
|
||||||
|
*/
|
||||||
|
struct subnegotiate_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
const char *buffer; /*!< data of sub-negotiation */
|
||||||
|
size_t size; /*!< number of bytes in buffer */
|
||||||
|
unsigned char telopt; /*!< option code for negotiation */
|
||||||
|
} sub; /*!< SB */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* ZMP event
|
||||||
|
*/
|
||||||
|
struct zmp_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
const char **argv; /*!< array of argument string */
|
||||||
|
size_t argc; /*!< number of elements in argv */
|
||||||
|
} zmp; /*!< ZMP */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* TTYPE event
|
||||||
|
*/
|
||||||
|
struct ttype_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
unsigned char cmd; /*!< TELNET_TTYPE_IS or TELNET_TTYPE_SEND */
|
||||||
|
const char* name; /*!< terminal type name (IS only) */
|
||||||
|
} ttype; /*!< TTYPE */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* COMPRESS event
|
||||||
|
*/
|
||||||
|
struct compress_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
unsigned char state; /*!< 1 if compression is enabled,
|
||||||
|
0 if disabled */
|
||||||
|
} compress; /*!< COMPRESS */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* ENVIRON/NEW-ENVIRON event
|
||||||
|
*/
|
||||||
|
struct environ_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
const struct telnet_environ_t *values; /*!< array of variable values */
|
||||||
|
size_t size; /*!< number of elements in values */
|
||||||
|
unsigned char cmd; /*!< SEND, IS, or INFO */
|
||||||
|
} environ; /*!< ENVIRON, NEW-ENVIRON */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* MSSP event
|
||||||
|
*/
|
||||||
|
struct mssp_t {
|
||||||
|
enum telnet_event_type_t _type; /*!< alias for type */
|
||||||
|
const struct telnet_environ_t *values; /*!< array of variable values */
|
||||||
|
size_t size; /*!< number of elements in values */
|
||||||
|
} mssp; /*!< MSSP */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief event handler
|
||||||
|
*
|
||||||
|
* This is the type of function that must be passed to
|
||||||
|
* telnet_init() when creating a new telnet object. The
|
||||||
|
* function will be invoked once for every event generated
|
||||||
|
* by the libtelnet protocol parser.
|
||||||
|
*
|
||||||
|
* \param telnet The telnet object that generated the event
|
||||||
|
* \param event Event structure with details about the event
|
||||||
|
* \param user_data User-supplied pointer
|
||||||
|
*/
|
||||||
|
typedef void (*telnet_event_handler_t)(telnet_t *telnet,
|
||||||
|
telnet_event_t *event, void *user_data);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* telopt support table element; use telopt of -1 for end marker
|
||||||
|
*/
|
||||||
|
struct telnet_telopt_t {
|
||||||
|
short telopt; /*!< one of the TELOPT codes or -1 */
|
||||||
|
unsigned char us; /*!< TELNET_WILL or TELNET_WONT */
|
||||||
|
unsigned char him; /*!< TELNET_DO or TELNET_DONT */
|
||||||
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* state tracker -- private data structure
|
||||||
|
*/
|
||||||
|
struct telnet_t;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Initialize a telnet state tracker.
|
||||||
|
*
|
||||||
|
* This function initializes a new state tracker, which is used for all
|
||||||
|
* other libtelnet functions. Each connection must have its own
|
||||||
|
* telnet state tracker object.
|
||||||
|
*
|
||||||
|
* \param telopts Table of TELNET options the application supports.
|
||||||
|
* \param eh Event handler function called for every event.
|
||||||
|
* \param flags 0 or TELNET_FLAG_PROXY.
|
||||||
|
* \param user_data Optional data pointer that will be passsed to eh.
|
||||||
|
* \return Telnet state tracker object.
|
||||||
|
*/
|
||||||
|
extern telnet_t* telnet_init(const telnet_telopt_t *telopts,
|
||||||
|
telnet_event_handler_t eh, unsigned char flags, void *user_data);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Free up any memory allocated by a state tracker.
|
||||||
|
*
|
||||||
|
* This function must be called when a telnet state tracker is no
|
||||||
|
* longer needed (such as after the connection has been closed) to
|
||||||
|
* release any memory resources used by the state tracker.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
*/
|
||||||
|
extern void telnet_free(telnet_t *telnet);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Push a byte buffer into the state tracker.
|
||||||
|
*
|
||||||
|
* Passes one or more bytes to the telnet state tracker for
|
||||||
|
* protocol parsing. The byte buffer is most often going to be
|
||||||
|
* the buffer that recv() was called for while handling the
|
||||||
|
* connection.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param buffer Pointer to byte buffer.
|
||||||
|
* \param size Number of bytes pointed to by buffer.
|
||||||
|
*/
|
||||||
|
extern void telnet_recv(telnet_t *telnet, const char *buffer,
|
||||||
|
size_t size);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send a telnet command.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param cmd Command to send.
|
||||||
|
*/
|
||||||
|
extern void telnet_iac(telnet_t *telnet, unsigned char cmd);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send negotiation command.
|
||||||
|
*
|
||||||
|
* Internally, libtelnet uses RFC1143 option negotiation rules.
|
||||||
|
* The negotiation commands sent with this function may be ignored
|
||||||
|
* if they are determined to be redundant.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param cmd TELNET_WILL, TELNET_WONT, TELNET_DO, or TELNET_DONT.
|
||||||
|
* \param opt One of the TELNET_TELOPT_* values.
|
||||||
|
*/
|
||||||
|
extern void telnet_negotiate(telnet_t *telnet, unsigned char cmd,
|
||||||
|
unsigned char opt);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Send non-command data (escapes IAC bytes).
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param buffer Buffer of bytes to send.
|
||||||
|
* \param size Number of bytes to send.
|
||||||
|
*/
|
||||||
|
extern void telnet_send(telnet_t *telnet,
|
||||||
|
const char *buffer, size_t size);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Send non-command text (escapes IAC bytes and translates
|
||||||
|
* \\r -> CR-NUL and \\n -> CR-LF unless in BINARY mode.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param buffer Buffer of bytes to send.
|
||||||
|
* \param size Number of bytes to send.
|
||||||
|
*/
|
||||||
|
extern void telnet_send_text(telnet_t *telnet,
|
||||||
|
const char *buffer, size_t size);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Begin a sub-negotiation command.
|
||||||
|
*
|
||||||
|
* Sends IAC SB followed by the telopt code. All following data sent
|
||||||
|
* will be part of the sub-negotiation, until telnet_finish_sb() is
|
||||||
|
* called.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param telopt One of the TELNET_TELOPT_* values.
|
||||||
|
*/
|
||||||
|
extern void telnet_begin_sb(telnet_t *telnet,
|
||||||
|
unsigned char telopt);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Finish a sub-negotiation command.
|
||||||
|
*
|
||||||
|
* This must be called after a call to telnet_begin_sb() to finish a
|
||||||
|
* sub-negotiation command.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
*/
|
||||||
|
#define telnet_finish_sb(telnet) telnet_iac((telnet), TELNET_SE)
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Shortcut for sending a complete subnegotiation buffer.
|
||||||
|
*
|
||||||
|
* Equivalent to:
|
||||||
|
* telnet_begin_sb(telnet, telopt);
|
||||||
|
* telnet_send(telnet, buffer, size);
|
||||||
|
* telnet_finish_sb(telnet);
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker format.
|
||||||
|
* \param telopt One of the TELNET_TELOPT_* values.
|
||||||
|
* \param buffer Byte buffer for sub-negotiation data.
|
||||||
|
* \param size Number of bytes to use for sub-negotiation data.
|
||||||
|
*/
|
||||||
|
extern void telnet_subnegotiation(telnet_t *telnet, unsigned char telopt,
|
||||||
|
const char *buffer, size_t size);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Begin sending compressed data.
|
||||||
|
*
|
||||||
|
* This function will begein sending data using the COMPRESS2 option,
|
||||||
|
* which enables the use of zlib to compress data sent to the client.
|
||||||
|
* The client must offer support for COMPRESS2 with option negotiation,
|
||||||
|
* and zlib support must be compiled into libtelnet.
|
||||||
|
*
|
||||||
|
* Only the server may call this command.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
*/
|
||||||
|
extern void telnet_begin_compress2(telnet_t *telnet);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send formatted data.
|
||||||
|
*
|
||||||
|
* This function is a wrapper around telnet_send(). It allows using
|
||||||
|
* printf-style formatting.
|
||||||
|
*
|
||||||
|
* Additionally, this function will translate \\r to the CR NUL construct and
|
||||||
|
* \\n with CR LF, as well as automatically escaping IAC bytes like
|
||||||
|
* telnet_send().
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param fmt Format string.
|
||||||
|
* \return Number of bytes sent.
|
||||||
|
*/
|
||||||
|
extern int telnet_printf(telnet_t *telnet, const char *fmt, ...)
|
||||||
|
TELNET_GNU_PRINTF(2, 3);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send formatted data.
|
||||||
|
*
|
||||||
|
* See telnet_printf().
|
||||||
|
*/
|
||||||
|
extern int telnet_vprintf(telnet_t *telnet, const char *fmt, va_list va);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send formatted data (no newline escaping).
|
||||||
|
*
|
||||||
|
* This behaves identically to telnet_printf(), except that the \\r and \\n
|
||||||
|
* characters are not translated. The IAC byte is still escaped as normal
|
||||||
|
* with telnet_send().
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param fmt Format string.
|
||||||
|
* \return Number of bytes sent.
|
||||||
|
*/
|
||||||
|
extern int telnet_raw_printf(telnet_t *telnet, const char *fmt, ...)
|
||||||
|
TELNET_GNU_PRINTF(2, 3);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send formatted data (no newline escaping).
|
||||||
|
*
|
||||||
|
* See telnet_raw_printf().
|
||||||
|
*/
|
||||||
|
extern int telnet_raw_vprintf(telnet_t *telnet, const char *fmt, va_list va);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Begin a new set of NEW-ENVIRON values to request or send.
|
||||||
|
*
|
||||||
|
* This function will begin the sub-negotiation block for sending or
|
||||||
|
* requesting NEW-ENVIRON values.
|
||||||
|
*
|
||||||
|
* The telnet_finish_newenviron() macro must be called after this
|
||||||
|
* function to terminate the NEW-ENVIRON command.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param type One of TELNET_ENVIRON_SEND, TELNET_ENVIRON_IS, or
|
||||||
|
* TELNET_ENVIRON_INFO.
|
||||||
|
*/
|
||||||
|
extern void telnet_begin_newenviron(telnet_t *telnet, unsigned char type);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send a NEW-ENVIRON variable name or value.
|
||||||
|
*
|
||||||
|
* This can only be called between calls to telnet_begin_newenviron() and
|
||||||
|
* telnet_finish_newenviron().
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param type One of TELNET_ENVIRON_VAR, TELNET_ENVIRON_USERVAR, or
|
||||||
|
* TELNET_ENVIRON_VALUE.
|
||||||
|
* \param string Variable name or value.
|
||||||
|
*/
|
||||||
|
extern void telnet_newenviron_value(telnet_t* telnet, unsigned char type,
|
||||||
|
const char *string);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Finish a NEW-ENVIRON command.
|
||||||
|
*
|
||||||
|
* This must be called after a call to telnet_begin_newenviron() to finish a
|
||||||
|
* NEW-ENVIRON variable list.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
*/
|
||||||
|
#define telnet_finish_newenviron(telnet) telnet_finish_sb((telnet))
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send the TERMINAL-TYPE SEND command.
|
||||||
|
*
|
||||||
|
* Sends the sequence IAC TERMINAL-TYPE SEND.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
*/
|
||||||
|
extern void telnet_ttype_send(telnet_t *telnet);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send the TERMINAL-TYPE IS command.
|
||||||
|
*
|
||||||
|
* Sends the sequence IAC TERMINAL-TYPE IS "string".
|
||||||
|
*
|
||||||
|
* According to the RFC, the recipient of a TERMINAL-TYPE SEND shall
|
||||||
|
* send the next possible terminal-type the client supports. Upon sending
|
||||||
|
* the type, the client should switch modes to begin acting as the terminal
|
||||||
|
* type is just sent.
|
||||||
|
*
|
||||||
|
* The server may continue sending TERMINAL-TYPE IS until it receives a
|
||||||
|
* terminal type is understands. To indicate to the server that it has
|
||||||
|
* reached the end of the available optoins, the client must send the last
|
||||||
|
* terminal type a second time. When the server receives the same terminal
|
||||||
|
* type twice in a row, it knows it has seen all available terminal types.
|
||||||
|
*
|
||||||
|
* After the last terminal type is sent, if the client receives another
|
||||||
|
* TERMINAL-TYPE SEND command, it must begin enumerating the available
|
||||||
|
* terminal types from the very beginning. This allows the server to
|
||||||
|
* scan the available types for a preferred terminal type and, if none
|
||||||
|
* is found, to then ask the client to switch to an acceptable
|
||||||
|
* alternative.
|
||||||
|
*
|
||||||
|
* Note that if the client only supports a single terminal type, then
|
||||||
|
* simply sending that one type in response to every SEND will satisfy
|
||||||
|
* the behavior requirements.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param ttype Name of the terminal-type being sent.
|
||||||
|
*/
|
||||||
|
extern void telnet_ttype_is(telnet_t *telnet, const char* ttype);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send a ZMP command.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param argc Number of ZMP commands being sent.
|
||||||
|
* \param argv Array of argument strings.
|
||||||
|
*/
|
||||||
|
extern void telnet_send_zmp(telnet_t *telnet, size_t argc, const char **argv);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send a ZMP command.
|
||||||
|
*
|
||||||
|
* Arguments are listed out in var-args style. After the last argument, a
|
||||||
|
* NULL pointer must be passed in as a sentinel value.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
*/
|
||||||
|
extern void telnet_send_zmpv(telnet_t *telnet, ...) TELNET_GNU_SENTINEL;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send a ZMP command.
|
||||||
|
*
|
||||||
|
* See telnet_send_zmpv().
|
||||||
|
*/
|
||||||
|
extern void telnet_send_vzmpv(telnet_t *telnet, va_list va);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Begin sending a ZMP command
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param cmd The first argument (command name) for the ZMP command.
|
||||||
|
*/
|
||||||
|
extern void telnet_begin_zmp(telnet_t *telnet, const char *cmd);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Send a ZMP command argument.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
* \param arg Telnet argument string.
|
||||||
|
*/
|
||||||
|
extern void telnet_zmp_arg(telnet_t *telnet, const char *arg);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Finish a ZMP command.
|
||||||
|
*
|
||||||
|
* This must be called after a call to telnet_begin_zmp() to finish a
|
||||||
|
* ZMP argument list.
|
||||||
|
*
|
||||||
|
* \param telnet Telnet state tracker object.
|
||||||
|
*/
|
||||||
|
#define telnet_finish_zmp(telnet) telnet_finish_sb((telnet))
|
||||||
|
|
||||||
|
/* C++ support */
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
} /* extern "C" */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* !defined(LIBTELNET_INCLUDE) */
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#define LINEBUFFER_SIZE 256
|
||||||
|
#define AWAIT_TIMEOUT 10
|
||||||
|
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <libtelnet.h>
|
||||||
|
#include <lwip/def.h>
|
||||||
|
#include <lwip/sockets.h>
|
||||||
|
#include <telnet/server.h>
|
||||||
|
|
||||||
|
static const char* TAG = "telnet";
|
||||||
|
static struct user_t users[CONFIG_TELNET_SERVER_MAX_CONNECTIONS + 1];
|
||||||
|
|
||||||
|
static void linebuffer_push(char* buffer, size_t size, int* linepos, char ch,
|
||||||
|
void (*cb)(const char* line, size_t overflow, void* ud), void* ud)
|
||||||
|
{
|
||||||
|
/* CRLF -- line terminator */
|
||||||
|
if (ch == '\n' && *linepos > 0 && buffer[*linepos - 1] == '\r') {
|
||||||
|
/* NUL terminate (replaces \r in buffer), notify app, clear */
|
||||||
|
buffer[*linepos - 1] = 0;
|
||||||
|
cb(buffer, 0, ud);
|
||||||
|
*linepos = 0;
|
||||||
|
|
||||||
|
/* CRNUL -- just a CR */
|
||||||
|
}
|
||||||
|
else if (ch == 0 && *linepos > 0 && buffer[*linepos - 1] == '\r') {
|
||||||
|
/* do nothing, the CR is already in the buffer */
|
||||||
|
|
||||||
|
/* anything else (including technically invalid CR followed by
|
||||||
|
* anything besides LF or NUL -- just buffer if we have room
|
||||||
|
* \r
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
else if (*linepos != (int)size) {
|
||||||
|
buffer[(*linepos)++] = ch;
|
||||||
|
|
||||||
|
/* buffer overflow */
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* terminate (NOTE: eats a byte), notify app, clear buffer */
|
||||||
|
buffer[size - 1] = 0;
|
||||||
|
cb(buffer, size - 1, ud);
|
||||||
|
*linepos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _message(const char* from, const char* msg)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i != CONFIG_TELNET_SERVER_MAX_CONNECTIONS; ++i) {
|
||||||
|
if (users[i].sock != -1 && users[i].name != 0 && strcmp(users[i].name, from) != 0) {
|
||||||
|
telnet_printf(users[i].telnet, "%s: \"%s\"\n", from, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _broadcast(const char* from, const char* msg)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i != CONFIG_TELNET_SERVER_MAX_CONNECTIONS; ++i) {
|
||||||
|
if (users[i].sock != -1) {
|
||||||
|
telnet_printf(users[i].telnet, "%s: \"%s\"\n", from, msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _send(int sock, const char* buffer, size_t size)
|
||||||
|
{
|
||||||
|
int rs;
|
||||||
|
|
||||||
|
/* ignore on invalid socket */
|
||||||
|
if (sock == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* send data */
|
||||||
|
while (size > 0) {
|
||||||
|
if ((rs = send(sock, buffer, (int)size, 0)) == -1) {
|
||||||
|
if (errno != EINTR && errno != ECONNRESET) {
|
||||||
|
ESP_LOGW(TAG, "send() failed: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (rs == 0) {
|
||||||
|
ESP_LOGE(TAG, "send() unexpectedly returned 0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update pointer and size to see if we've got more to send */
|
||||||
|
buffer += rs;
|
||||||
|
size -= rs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* process input line */
|
||||||
|
static void _online(const char* line, size_t overflow, void* ud)
|
||||||
|
{
|
||||||
|
struct user_t* user = (struct user_t*)ud;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
(void)overflow;
|
||||||
|
|
||||||
|
/* if the user has no name, this is his "login" */
|
||||||
|
if (user->name == 0) {
|
||||||
|
/* must not be empty, must be at least 32 chars */
|
||||||
|
if (strlen(line) == 0 || strlen(line) > 32) {
|
||||||
|
telnet_printf(user->telnet, "Invalid name. Enter name: ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* must not already exist */
|
||||||
|
for (i = 0; i != CONFIG_TELNET_SERVER_MAX_CONNECTIONS; ++i) {
|
||||||
|
if (users[i].name != 0 && strcmp(users[i].name, line) == 0) {
|
||||||
|
telnet_printf(user->telnet, "Name already in use. Enter name: ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* keep name */
|
||||||
|
user->name = strdup(line);
|
||||||
|
telnet_printf(user->telnet, "Welcome, %s!\n", line);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* execute a command, need to send to the system */
|
||||||
|
// _message(user->name, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _input(struct user_t* user, const char* buffer, size_t size)
|
||||||
|
{
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; user->sock != -1 && i != size; ++i) {
|
||||||
|
linebuffer_push(user->linebuf, sizeof(user->linebuf), &user->linepos, (char)buffer[i], _online, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _event_handler(telnet_t* telnet, telnet_event_t* ev, void* user_data)
|
||||||
|
{
|
||||||
|
struct user_t* user = (struct user_t*)user_data;
|
||||||
|
|
||||||
|
switch (ev->type) {
|
||||||
|
/* data received */
|
||||||
|
case TELNET_EV_DATA:
|
||||||
|
_input(user, ev->data.buffer, ev->data.size);
|
||||||
|
// telnet_negotiate(telnet, TELNET_WONT, TELNET_TELOPT_ECHO);
|
||||||
|
// telnet_negotiate(telnet, TELNET_WILL, TELNET_TELOPT_ECHO);
|
||||||
|
break;
|
||||||
|
/* data must be sent */
|
||||||
|
case TELNET_EV_SEND: _send(user->sock, ev->data.buffer, ev->data.size); break;
|
||||||
|
/* enable compress2 if accepted by client */
|
||||||
|
case TELNET_EV_DO:
|
||||||
|
if (ev->neg.telopt == TELNET_TELOPT_COMPRESS2)
|
||||||
|
telnet_begin_compress2(telnet);
|
||||||
|
break;
|
||||||
|
/* error */
|
||||||
|
case TELNET_EV_ERROR:
|
||||||
|
close(user->sock);
|
||||||
|
user->sock = -1;
|
||||||
|
if (user->name != 0) {
|
||||||
|
_message(user->name, "** HAS HAD AN ERROR **");
|
||||||
|
free(user->name);
|
||||||
|
user->name = 0;
|
||||||
|
}
|
||||||
|
telnet_free(user->telnet);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* ignore */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void telnet_task(void* arg)
|
||||||
|
{
|
||||||
|
static char buffer[512];
|
||||||
|
static struct sockaddr_in addr;
|
||||||
|
static telnet_server_config_t config;
|
||||||
|
|
||||||
|
// save the configuration
|
||||||
|
memcpy(&config, arg, sizeof(telnet_server_config_t));
|
||||||
|
|
||||||
|
int listen_sock;
|
||||||
|
int client_sock;
|
||||||
|
int rs;
|
||||||
|
int i;
|
||||||
|
socklen_t addrlen;
|
||||||
|
struct pollfd pfd[config.max_connections + 1];
|
||||||
|
|
||||||
|
/* initialize data structures */
|
||||||
|
memset(&pfd, 0, sizeof(pfd));
|
||||||
|
memset(users, 0, sizeof(users));
|
||||||
|
for (i = 0; i != config.max_connections; ++i) {
|
||||||
|
users[i].sock = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* create listening socket */
|
||||||
|
if ((listen_sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
|
||||||
|
ESP_LOGE(TAG, "socket() failed: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reuse address option */
|
||||||
|
rs = 1;
|
||||||
|
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, (char*)&rs, sizeof(rs));
|
||||||
|
|
||||||
|
/* bind to listening addr/port */
|
||||||
|
memset(&addr, 0, sizeof(addr));
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
addr.sin_port = htons(config.port);
|
||||||
|
if (bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
|
||||||
|
ESP_LOGE(TAG, "bind() failed: %s", strerror(errno));
|
||||||
|
close(listen_sock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* listen for clients */
|
||||||
|
if (listen(listen_sock, 3) == -1) {
|
||||||
|
ESP_LOGE(TAG, "listen() failed: %s", strerror(errno));
|
||||||
|
close(listen_sock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Telnet server listening on port %d", config.port);
|
||||||
|
|
||||||
|
/* initialize listening descriptors */
|
||||||
|
pfd[config.max_connections].fd = listen_sock;
|
||||||
|
pfd[config.max_connections].events = POLLIN;
|
||||||
|
|
||||||
|
/* loop for ever */
|
||||||
|
while (true) {
|
||||||
|
/* prepare for poll */
|
||||||
|
for (i = 0; i != config.max_connections; ++i) {
|
||||||
|
if (users[i].sock != -1) {
|
||||||
|
pfd[i].fd = users[i].sock;
|
||||||
|
pfd[i].events = POLLIN;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pfd[i].fd = -1;
|
||||||
|
pfd[i].events = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* poll */
|
||||||
|
rs = poll(pfd, config.max_connections + 1, AWAIT_TIMEOUT);
|
||||||
|
if (rs == -1 && errno != EINTR) {
|
||||||
|
ESP_LOGE(TAG, "poll() failed: %s", strerror(errno));
|
||||||
|
close(listen_sock);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* new connection */
|
||||||
|
if (pfd[config.max_connections].revents & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLERR | POLLHUP)) {
|
||||||
|
/* acept the sock */
|
||||||
|
ESP_LOGW(TAG, "New connection");
|
||||||
|
addrlen = sizeof(addr);
|
||||||
|
if ((client_sock = accept(listen_sock, (struct sockaddr*)&addr, &addrlen)) == -1) {
|
||||||
|
ESP_LOGE(TAG, "accept() failed: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "Connection received");
|
||||||
|
|
||||||
|
/* find a free user */
|
||||||
|
for (i = 0; i != config.max_connections; ++i) {
|
||||||
|
if (users[i].sock == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == config.max_connections) {
|
||||||
|
ESP_LOGV(TAG, " rejected (too many users)");
|
||||||
|
_send(client_sock, "Too many users.\n", 16);
|
||||||
|
close(client_sock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* init, welcome */
|
||||||
|
users[i].sock = client_sock;
|
||||||
|
users[i].telnet = telnet_init(config.telnet_opts, _event_handler, 0, &users[i]);
|
||||||
|
telnet_negotiate(users[i].telnet, TELNET_WILL, TELNET_TELOPT_COMPRESS2);
|
||||||
|
telnet_printf(users[i].telnet, "Enter name: ");
|
||||||
|
|
||||||
|
// telnet_negotiate(users[i].telnet, TELNET_WILL, TELNET_TELOPT_ECHO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read from client */
|
||||||
|
for (i = 0; i != config.max_connections; ++i) {
|
||||||
|
/* skip users that aren't actually connected */
|
||||||
|
if (users[i].sock == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pfd[i].revents & (POLLIN | POLLERR | POLLHUP)) {
|
||||||
|
if ((rs = recv(users[i].sock, buffer, sizeof(buffer), 0)) > 0) {
|
||||||
|
telnet_recv(users[i].telnet, buffer, rs);
|
||||||
|
}
|
||||||
|
else if (rs == 0) {
|
||||||
|
ESP_LOGV(TAG, "Connection closed.");
|
||||||
|
close(users[i].sock);
|
||||||
|
users[i].sock = -1;
|
||||||
|
if (users[i].name != 0) {
|
||||||
|
// _message(users[i].name, "** HAS DISCONNECTED **");
|
||||||
|
free(users[i].name);
|
||||||
|
users[i].name = 0;
|
||||||
|
}
|
||||||
|
telnet_free(users[i].telnet);
|
||||||
|
}
|
||||||
|
else if (errno != EINTR) {
|
||||||
|
ESP_LOGE(TAG, "recv(client) failed: %s", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TaskHandle_t xHandle = NULL;
|
||||||
|
|
||||||
|
esp_err_t telnet_server_create(telnet_server_config_t* config)
|
||||||
|
{
|
||||||
|
if (config == NULL) {
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseType_t xReturned = xTaskCreate(telnet_task, "telnet_task", config->stack_size, config, config->task_priority, &xHandle);
|
||||||
|
if (xReturned == pdPASS) {
|
||||||
|
ESP_LOGV(TAG, "Telnet task created successfully.");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGE(TAG, "Failed to create telnet task.");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user