summaryrefslogtreecommitdiff
path: root/plugins/telnet
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/telnet')
-rw-r--r--plugins/telnet/CMakeLists.txt26
-rw-r--r--plugins/telnet/telnet.c516
-rw-r--r--plugins/telnet/telnet_config.c125
-rw-r--r--plugins/telnet/telnet_proto.c213
-rw-r--r--plugins/telnet/telnet_proto.h37
5 files changed, 917 insertions, 0 deletions
diff --git a/plugins/telnet/CMakeLists.txt b/plugins/telnet/CMakeLists.txt
new file mode 100644
index 0000000..144ba1d
--- /dev/null
+++ b/plugins/telnet/CMakeLists.txt
@@ -0,0 +1,26 @@
+add_library(hybbx_plugin_telnet STATIC
+ telnet.c
+ telnet_config.c
+ telnet_proto.c
+)
+
+find_package(Threads REQUIRED)
+
+target_include_directories(hybbx_plugin_telnet
+ PRIVATE ${CMAKE_SOURCE_DIR}/include
+)
+
+target_compile_definitions(hybbx_plugin_telnet
+ PRIVATE HYBBX_PLUGIN_BUILD
+)
+
+target_link_libraries(hybbx_plugin_telnet PRIVATE hybbx_core Threads::Threads)
+hybbx_link_plugin_instances(hybbx_plugin_telnet HYBBX_HAVE_PLUGIN_TELNET)
+hybbx_apply_hardening(hybbx_plugin_telnet)
+
+if(HYBBX_INSTALL_DEV)
+install(TARGETS hybbx_plugin_telnet
+ ARCHIVE DESTINATION ${HYBBX_PLUGIN_INSTALL_DIR}
+ LIBRARY DESTINATION ${HYBBX_PLUGIN_INSTALL_DIR}
+)
+endif()
diff --git a/plugins/telnet/telnet.c b/plugins/telnet/telnet.c
new file mode 100644
index 0000000..a857b7d
--- /dev/null
+++ b/plugins/telnet/telnet.c
@@ -0,0 +1,516 @@
+/*
+ * telnet — TCP transport plugin (port 2323). INI: [transport.telnet].
+ */
+#include "hybbx/plugin.h"
+#include "hybbx/service.h"
+#include "hybbx/session.h"
+#include "hybbx/socket.h"
+#include "hybbx/security_ban.h"
+#include "hybbx/telnet.h"
+#include "telnet_proto.h"
+#include "hybbx/log.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+typedef struct telnet_client {
+ int fd;
+ hybbx_telnet_parser_t parser;
+} telnet_client_t;
+
+typedef struct telnet_client_ctx {
+ hybbx_session_t *session;
+ hybbx_result_t last_rc;
+} telnet_client_ctx_t;
+
+extern const hybbx_transport_plugin_t hybbx_plugin_telnet;
+
+static hybbx_service_t *g_service;
+static hybbx_telnet_config_t g_config;
+static pthread_t g_accept_thread;
+static int g_listen_v4 = -1;
+static int g_listen_v6 = -1;
+static volatile int g_telnet_running = 0;
+
+static hybbx_result_t telnet_stop(void);
+
+static int set_socket_options(int fd, int family)
+{
+ int on = 1;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) {
+ return -1;
+ }
+
+ if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)) != 0) {
+ return -1;
+ }
+
+ hybbx_socket_nosigpipe(fd);
+
+#ifdef IPV6_V6ONLY
+ if (family == AF_INET6) {
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) != 0) {
+ return -1;
+ }
+ }
+#else
+ (void)family;
+#endif
+
+ return 0;
+}
+
+static int create_listen_socket(int family, const char *bind_addr, unsigned port)
+{
+ int fd;
+ int rc;
+
+ fd = socket(family, SOCK_STREAM, 0);
+ if (fd < 0) {
+ return -1;
+ }
+
+ if (set_socket_options(fd, family) != 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (family == AF_INET6) {
+ struct sockaddr_in6 addr6;
+
+ memset(&addr6, 0, sizeof(addr6));
+ addr6.sin6_family = AF_INET6;
+ addr6.sin6_port = htons((uint16_t)port);
+
+ if (inet_pton(AF_INET6, bind_addr, &addr6.sin6_addr) != 1) {
+ close(fd);
+ return -1;
+ }
+
+ rc = bind(fd, (struct sockaddr *)&addr6, sizeof(addr6));
+ } else {
+ struct sockaddr_in addr4;
+
+ memset(&addr4, 0, sizeof(addr4));
+ addr4.sin_family = AF_INET;
+ addr4.sin_port = htons((uint16_t)port);
+
+ if (inet_pton(AF_INET, bind_addr, &addr4.sin_addr) != 1) {
+ close(fd);
+ return -1;
+ }
+
+ rc = bind(fd, (struct sockaddr *)&addr4, sizeof(addr4));
+ }
+
+ if (rc != 0) {
+ close(fd);
+ return -1;
+ }
+
+ if (listen(fd, 16) != 0) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static hybbx_result_t telnet_send_byte(int fd, char ch)
+{
+ ssize_t sent;
+ const char *p = &ch;
+
+ sent = send(fd, p, 1, MSG_NOSIGNAL);
+ if (sent < 0) {
+ return HYBBX_ERR_IO;
+ }
+
+ return HYBBX_OK;
+}
+
+static hybbx_result_t telnet_write(hybbx_session_t *session,
+ const char *data, size_t len)
+{
+ telnet_client_t *client;
+ size_t i;
+ hybbx_result_t rc;
+
+ if (session == NULL || data == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ client = (telnet_client_t *)session->transport_data;
+ if (client == NULL || client->fd < 0) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ for (i = 0; i < len; i++) {
+ char ch = data[i];
+
+ if (ch == '\n' && (i == 0 || data[i - 1] != '\r')) {
+ rc = telnet_send_byte(client->fd, '\r');
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+ }
+
+ {
+ ssize_t sent = send(client->fd, &ch, 1, MSG_NOSIGNAL);
+
+ if (sent < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return HYBBX_ERR_IO;
+ }
+ if (sent == 0) {
+ return HYBBX_ERR_IO;
+ }
+ }
+ }
+
+ return HYBBX_OK;
+}
+
+static void telnet_on_user_data(void *ctx, const uint8_t *data, size_t len)
+{
+ telnet_client_ctx_t *tctx = (telnet_client_ctx_t *)ctx;
+ hybbx_result_t rc;
+
+ if (tctx == NULL || tctx->session == NULL || data == NULL || len == 0) {
+ return;
+ }
+
+ rc = hybbx_session_handle_input(tctx->session, data, len);
+ if (rc != HYBBX_OK) {
+ tctx->last_rc = rc;
+ }
+}
+
+#define TELNET_CLIENT_POLL_MS 30000
+
+static void telnet_send_busy(int fd)
+{
+ static const char msg[] = "All nodes in use. Try later.\r\n";
+
+ if (fd < 0) {
+ return;
+ }
+
+ (void)send(fd, msg, sizeof(msg) - 1u, 0);
+}
+
+static void *telnet_client_thread(void *arg)
+{
+ telnet_client_t *client = (telnet_client_t *)arg;
+ hybbx_session_t *session = NULL;
+ telnet_client_ctx_t tctx;
+ uint8_t buf[256];
+ ssize_t n;
+ hybbx_result_t rc;
+
+ if (client == NULL || g_service == NULL) {
+ free(client);
+ return NULL;
+ }
+
+ (void)set_socket_options(client->fd, 0);
+
+ rc = hybbx_telnet_send_greeting(client->fd);
+ if (rc != HYBBX_OK) {
+ close(client->fd);
+ free(client);
+ return NULL;
+ }
+
+ rc = hybbx_session_open(g_service, &hybbx_plugin_telnet, client, &session);
+ if (rc != HYBBX_OK || session == NULL) {
+ if (rc == HYBBX_ERR_BUSY) {
+ telnet_send_busy(client->fd);
+ }
+ close(client->fd);
+ free(client);
+ return NULL;
+ }
+
+ {
+ char remote[64];
+
+ if (hybbx_socket_peer_name(client->fd, remote, sizeof(remote)) == HYBBX_OK) {
+ (void)hybbx_session_set_remote(session, remote);
+ }
+ }
+
+ hybbx_telnet_parser_init(&client->parser);
+ tctx.session = session;
+ tctx.last_rc = HYBBX_OK;
+
+ while (g_telnet_running) {
+ struct pollfd pfd;
+ int pr;
+
+ pfd.fd = client->fd;
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+
+ pr = poll(&pfd, 1, TELNET_CLIENT_POLL_MS);
+ if (pr < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ break;
+ }
+ if (pr == 0) {
+ rc = hybbx_session_tick(session);
+ if (rc == HYBBX_SESSION_END) {
+ tctx.last_rc = HYBBX_SESSION_END;
+ break;
+ }
+ continue;
+ }
+ if ((pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
+ break;
+ }
+ if ((pfd.revents & POLLIN) == 0) {
+ continue;
+ }
+
+ n = recv(client->fd, buf, sizeof(buf), 0);
+ if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ break;
+ }
+ if (n == 0) {
+ break;
+ }
+
+ rc = hybbx_telnet_parser_feed(&client->parser, client->fd, buf, (size_t)n,
+ telnet_on_user_data, &tctx);
+ if (rc != HYBBX_OK) {
+ break;
+ }
+
+ if (tctx.last_rc == HYBBX_SESSION_END) {
+ break;
+ }
+ }
+
+ hybbx_session_close(session);
+ shutdown(client->fd, SHUT_RDWR);
+ close(client->fd);
+ free(client);
+ return NULL;
+}
+
+static void accept_client(int client_fd)
+{
+ telnet_client_t *client;
+ pthread_t thread;
+ pthread_attr_t attr;
+
+ client = calloc(1, sizeof(*client));
+ if (client == NULL) {
+ close(client_fd);
+ return;
+ }
+
+ client->fd = client_fd;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+ if (pthread_create(&thread, &attr, telnet_client_thread, client) != 0) {
+ close(client_fd);
+ free(client);
+ }
+
+ pthread_attr_destroy(&attr);
+}
+
+static void *telnet_accept_thread(void *arg)
+{
+ (void)arg;
+
+ while (g_telnet_running) {
+ struct pollfd fds[2];
+ nfds_t nfds = 0;
+ int i;
+ int ready;
+
+ memset(fds, 0, sizeof(fds));
+
+ if (g_listen_v4 >= 0) {
+ fds[nfds].fd = g_listen_v4;
+ fds[nfds].events = POLLIN;
+ nfds++;
+ }
+
+ if (g_listen_v6 >= 0) {
+ fds[nfds].fd = g_listen_v6;
+ fds[nfds].events = POLLIN;
+ nfds++;
+ }
+
+ if (nfds == 0) {
+ break;
+ }
+
+ ready = poll(fds, nfds, 500);
+ if (ready < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ break;
+ }
+
+ if (ready == 0) {
+ continue;
+ }
+
+ for (i = 0; i < (int)nfds; i++) {
+ if (fds[i].revents & POLLIN) {
+ int client_fd = accept(fds[i].fd, NULL, NULL);
+
+ if (client_fd >= 0) {
+ if (!hybbx_security_ban_accept_fd(client_fd)) {
+ close(client_fd);
+ continue;
+ }
+ accept_client(client_fd);
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static hybbx_result_t telnet_init(hybbx_service_t *service)
+{
+ g_service = service;
+ return HYBBX_OK;
+}
+
+static void telnet_shutdown(void)
+{
+ telnet_stop();
+}
+
+static hybbx_result_t telnet_start(const char *config)
+{
+ hybbx_result_t rc;
+
+ if (g_telnet_running) {
+ return HYBBX_ERR_BUSY;
+ }
+
+ rc = hybbx_telnet_config_parse(config, &g_config);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+
+ g_listen_v4 = -1;
+ g_listen_v6 = -1;
+
+ if (g_config.ipv4) {
+ g_listen_v4 = create_listen_socket(AF_INET, g_config.bind_v4, g_config.port);
+ if (g_listen_v4 < 0) {
+ hybbx_socket_log_bind_failure("telnet", g_config.bind_v4,
+ g_config.port);
+ return HYBBX_ERR_IO;
+ }
+ }
+
+ if (g_config.ipv6) {
+ g_listen_v6 = create_listen_socket(AF_INET6, g_config.bind_v6, g_config.port);
+ if (g_listen_v6 < 0) {
+ hybbx_log_warn("[telnet] IPv6 bind [%s]:%u skipped (%s)",
+ g_config.bind_v6, g_config.port, strerror(errno));
+ }
+ }
+
+ if (g_listen_v4 < 0 && g_listen_v6 < 0) {
+ return HYBBX_ERR_IO;
+ }
+
+ g_telnet_running = 1;
+
+ if (pthread_create(&g_accept_thread, NULL, telnet_accept_thread, NULL) != 0) {
+ g_telnet_running = 0;
+ if (g_listen_v4 >= 0) {
+ close(g_listen_v4);
+ g_listen_v4 = -1;
+ }
+ if (g_listen_v6 >= 0) {
+ close(g_listen_v6);
+ g_listen_v6 = -1;
+ }
+ return HYBBX_ERR_IO;
+ }
+
+ {
+ char msg[256];
+ size_t pos = 0;
+
+ pos += (size_t)snprintf(msg + pos, sizeof(msg) - pos, "[telnet] listening");
+ if (g_listen_v4 >= 0) {
+ pos += (size_t)snprintf(msg + pos, sizeof(msg) - pos, " IPv4 %s:%u",
+ g_config.bind_v4, g_config.port);
+ }
+ if (g_listen_v6 >= 0) {
+ pos += (size_t)snprintf(msg + pos, sizeof(msg) - pos, " IPv6 [%s]:%u",
+ g_config.bind_v6, g_config.port);
+ }
+ hybbx_log_info("%s", msg);
+ }
+
+ return HYBBX_OK;
+}
+
+static hybbx_result_t telnet_stop(void)
+{
+ if (!g_telnet_running) {
+ return HYBBX_OK;
+ }
+
+ g_telnet_running = 0;
+
+ if (g_listen_v4 >= 0) {
+ shutdown(g_listen_v4, SHUT_RDWR);
+ close(g_listen_v4);
+ g_listen_v4 = -1;
+ }
+
+ if (g_listen_v6 >= 0) {
+ shutdown(g_listen_v6, SHUT_RDWR);
+ close(g_listen_v6);
+ g_listen_v6 = -1;
+ }
+
+ pthread_join(g_accept_thread, NULL);
+ hybbx_log_info("[telnet] stop");
+ return HYBBX_OK;
+}
+
+const hybbx_transport_plugin_t hybbx_plugin_telnet = {
+ .name = "telnet",
+ .kind = HYBBX_TRANSPORT_TELNET,
+ .version = 1,
+ .init = telnet_init,
+ .shutdown = telnet_shutdown,
+ .start = telnet_start,
+ .stop = telnet_stop,
+ .write = telnet_write,
+};
diff --git a/plugins/telnet/telnet_config.c b/plugins/telnet/telnet_config.c
new file mode 100644
index 0000000..d154f88
--- /dev/null
+++ b/plugins/telnet/telnet_config.c
@@ -0,0 +1,125 @@
+#include "hybbx/telnet.h"
+#include "hybbx/util.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+static unsigned int parse_port(const char *value)
+{
+ char *end;
+ unsigned long port;
+
+ if (value == NULL || value[0] == '\0') {
+ return HYBBX_TELNET_DEFAULT_PORT;
+ }
+
+ port = strtoul(value, &end, 10);
+ if (end == value || *end != '\0' || port == 0 || port > 65535u) {
+ return HYBBX_TELNET_DEFAULT_PORT;
+ }
+
+ return (unsigned int)port;
+}
+
+static const char *find_kv(const char *config, const char *key,
+ char *scratch, size_t scratch_len)
+{
+ const char *cursor = config;
+ size_t key_len = strlen(key);
+
+ if (config == NULL || key == NULL) {
+ return NULL;
+ }
+
+ while (*cursor != '\0') {
+ const char *sep = strchr(cursor, ';');
+ const char *end = sep != NULL ? sep : cursor + strlen(cursor);
+ const char *eq = strchr(cursor, '=');
+
+ if (eq != NULL && eq < end && (size_t)(eq - cursor) == key_len &&
+ strncmp(cursor, key, key_len) == 0) {
+ const char *value = eq + 1;
+ size_t value_len = (size_t)(end - value);
+
+ if (scratch != NULL && scratch_len > 0) {
+ if (value_len >= scratch_len) {
+ value_len = scratch_len - 1;
+ }
+ memcpy(scratch, value, value_len);
+ scratch[value_len] = '\0';
+ return scratch;
+ }
+
+ return value;
+ }
+
+ if (sep == NULL) {
+ break;
+ }
+ cursor = sep + 1;
+ }
+
+ return NULL;
+}
+
+void hybbx_telnet_config_defaults(hybbx_telnet_config_t *config)
+{
+ if (config == NULL) {
+ return;
+ }
+
+ memset(config, 0, sizeof(*config));
+ hybbx_strlcpy(config->bind_v4, HYBBX_TELNET_DEFAULT_BIND_V4,
+ sizeof(config->bind_v4));
+ hybbx_strlcpy(config->bind_v6, HYBBX_TELNET_DEFAULT_BIND_V6,
+ sizeof(config->bind_v6));
+ config->port = HYBBX_TELNET_DEFAULT_PORT;
+ config->ipv4 = 1;
+ config->ipv6 = 1;
+}
+
+hybbx_result_t hybbx_telnet_config_parse(const char *config,
+ hybbx_telnet_config_t *out)
+{
+ char scratch[HYBBX_TELNET_BIND_V6_MAX];
+ const char *value;
+
+ if (out == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ hybbx_telnet_config_defaults(out);
+
+ if (config == NULL || config[0] == '\0') {
+ return HYBBX_OK;
+ }
+
+ value = find_kv(config, "port", scratch, sizeof(scratch));
+ out->port = parse_port(value);
+
+ value = find_kv(config, "bind", scratch, sizeof(scratch));
+ if (value != NULL && value[0] != '\0') {
+ if (strchr(value, ':') != NULL) {
+ hybbx_strlcpy(out->bind_v6, value, sizeof(out->bind_v6));
+ } else {
+ hybbx_strlcpy(out->bind_v4, value, sizeof(out->bind_v4));
+ }
+ }
+
+ value = find_kv(config, "bind6", scratch, sizeof(scratch));
+ if (value != NULL && value[0] != '\0') {
+ hybbx_strlcpy(out->bind_v6, value, sizeof(out->bind_v6));
+ }
+
+ value = find_kv(config, "ipv4", scratch, sizeof(scratch));
+ if (value != NULL) {
+ out->ipv4 = hybbx_parse_bool(value, 1);
+ }
+
+ value = find_kv(config, "ipv6", scratch, sizeof(scratch));
+ if (value != NULL) {
+ out->ipv6 = hybbx_parse_bool(value, 1);
+ }
+
+ return HYBBX_OK;
+}
diff --git a/plugins/telnet/telnet_proto.c b/plugins/telnet/telnet_proto.c
new file mode 100644
index 0000000..523c68c
--- /dev/null
+++ b/plugins/telnet/telnet_proto.c
@@ -0,0 +1,213 @@
+#include "telnet_proto.h"
+
+#include "hybbx/socket.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#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_BRK 243
+#define TELNET_DM 242
+#define TELNET_NOP 241
+#define TELNET_SE 240
+
+#define TELNET_OPT_ECHO 1
+#define TELNET_OPT_SGA 3
+#define TELNET_OPT_TTYPE 24
+#define TELNET_OPT_NAWS 31
+#define TELNET_OPT_LINEMODE 34
+#define TELNET_OPT_CHARSET 42
+
+enum telnet_parse_state {
+ TS_DATA = 0,
+ TS_IAC,
+ TS_CMD,
+ TS_SUBNEG,
+ TS_SUBNEG_IAC
+};
+
+static hybbx_result_t telnet_send_raw(int fd, const uint8_t *data, size_t len)
+{
+ size_t sent_total = 0;
+
+ while (sent_total < len) {
+ ssize_t sent;
+
+ sent = send(fd, data + sent_total, len - sent_total, MSG_NOSIGNAL);
+ if (sent < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ return HYBBX_ERR_IO;
+ }
+ if (sent == 0) {
+ return HYBBX_ERR_IO;
+ }
+
+ sent_total += (size_t)sent;
+ }
+
+ return HYBBX_OK;
+}
+
+static hybbx_result_t telnet_send_option(int fd, uint8_t cmd, uint8_t option)
+{
+ uint8_t buf[3];
+
+ buf[0] = TELNET_IAC;
+ buf[1] = cmd;
+ buf[2] = option;
+ return telnet_send_raw(fd, buf, sizeof(buf));
+}
+
+static hybbx_result_t telnet_reply_do(int fd, uint8_t option)
+{
+ switch (option) {
+ case TELNET_OPT_ECHO:
+ return telnet_send_option(fd, TELNET_WONT, option);
+ case TELNET_OPT_SGA:
+ return telnet_send_option(fd, TELNET_WILL, option);
+ case TELNET_OPT_TTYPE:
+ case TELNET_OPT_NAWS:
+ case TELNET_OPT_LINEMODE:
+ case TELNET_OPT_CHARSET:
+ return telnet_send_option(fd, TELNET_WONT, option);
+ default:
+ return telnet_send_option(fd, TELNET_WONT, option);
+ }
+}
+
+static hybbx_result_t telnet_reply_will(int fd, uint8_t option)
+{
+ switch (option) {
+ case TELNET_OPT_SGA:
+ return telnet_send_option(fd, TELNET_DO, option);
+ case TELNET_OPT_ECHO:
+ return telnet_send_option(fd, TELNET_DONT, option);
+ default:
+ return telnet_send_option(fd, TELNET_DONT, option);
+ }
+}
+
+static void emit_byte(hybbx_telnet_data_cb on_data, void *ctx, uint8_t byte)
+{
+ if (on_data != NULL) {
+ on_data(ctx, &byte, 1);
+ }
+}
+
+void hybbx_telnet_parser_init(hybbx_telnet_parser_t *parser)
+{
+ if (parser == NULL) {
+ return;
+ }
+
+ parser->state = TS_DATA;
+ parser->command = 0;
+}
+
+hybbx_result_t hybbx_telnet_send_greeting(int fd)
+{
+ static const uint8_t greeting[] = {
+ TELNET_IAC, TELNET_WONT, TELNET_OPT_ECHO,
+ TELNET_IAC, TELNET_WILL, TELNET_OPT_SGA,
+ TELNET_IAC, TELNET_DONT, TELNET_OPT_LINEMODE,
+ };
+
+ if (fd < 0) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ return telnet_send_raw(fd, greeting, sizeof(greeting));
+}
+
+hybbx_result_t hybbx_telnet_parser_feed(hybbx_telnet_parser_t *parser, int fd,
+ const uint8_t *data, size_t len,
+ hybbx_telnet_data_cb on_data,
+ void *ctx)
+{
+ size_t i;
+ hybbx_result_t rc;
+
+ if (parser == NULL || data == NULL) {
+ return HYBBX_ERR_INVALID;
+ }
+
+ for (i = 0; i < len; i++) {
+ uint8_t byte = data[i];
+
+ switch (parser->state) {
+ case TS_DATA:
+ if (byte == TELNET_IAC) {
+ parser->state = TS_IAC;
+ } else {
+ emit_byte(on_data, ctx, byte);
+ }
+ break;
+
+ case TS_IAC:
+ if (byte == TELNET_IAC) {
+ emit_byte(on_data, ctx, TELNET_IAC);
+ parser->state = TS_DATA;
+ } else if (byte == TELNET_WILL || byte == TELNET_WONT ||
+ byte == TELNET_DO || byte == TELNET_DONT) {
+ parser->command = (int)byte;
+ parser->state = TS_CMD;
+ } else if (byte == TELNET_SB) {
+ parser->state = TS_SUBNEG;
+ } else {
+ parser->state = TS_DATA;
+ }
+ break;
+
+ case TS_CMD:
+ if (fd >= 0) {
+ if (parser->command == TELNET_DO) {
+ rc = telnet_reply_do(fd, byte);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+ } else if (parser->command == TELNET_WILL) {
+ rc = telnet_reply_will(fd, byte);
+ if (rc != HYBBX_OK) {
+ return rc;
+ }
+ }
+ }
+ parser->state = TS_DATA;
+ break;
+
+ case TS_SUBNEG:
+ if (byte == TELNET_IAC) {
+ parser->state = TS_SUBNEG_IAC;
+ }
+ break;
+
+ case TS_SUBNEG_IAC:
+ if (byte == TELNET_SE) {
+ parser->state = TS_DATA;
+ } else {
+ parser->state = TS_SUBNEG;
+ }
+ break;
+
+ default:
+ parser->state = TS_DATA;
+ break;
+ }
+ }
+
+ return HYBBX_OK;
+}
diff --git a/plugins/telnet/telnet_proto.h b/plugins/telnet/telnet_proto.h
new file mode 100644
index 0000000..aa29122
--- /dev/null
+++ b/plugins/telnet/telnet_proto.h
@@ -0,0 +1,37 @@
+#ifndef HYBBX_TELNET_PROTO_H
+#define HYBBX_TELNET_PROTO_H
+
+#include "hybbx/types.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct hybbx_telnet_parser {
+ int state;
+ int command;
+} hybbx_telnet_parser_t;
+
+typedef void (*hybbx_telnet_data_cb)(void *ctx, const uint8_t *data, size_t len);
+
+void hybbx_telnet_parser_init(hybbx_telnet_parser_t *parser);
+
+/** Send initial option negotiation (echo, suppress go ahead). */
+hybbx_result_t hybbx_telnet_send_greeting(int fd);
+
+/**
+ * Strip telnet protocol bytes and invoke @p on_data for user payload.
+ */
+hybbx_result_t hybbx_telnet_parser_feed(hybbx_telnet_parser_t *parser, int fd,
+ const uint8_t *data, size_t len,
+ hybbx_telnet_data_cb on_data,
+ void *ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HYBBX_TELNET_PROTO_H */
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com