From 20cb29c2f8c5c87bc590896854a20b1473ceb358 Mon Sep 17 00:00:00 2001 From: "info@mode42.com" Date: Sat, 8 Aug 2026 03:54:55 +0000 Subject: #2 --- plugins/telnet/telnet.c | 516 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 516 insertions(+) create mode 100644 plugins/telnet/telnet.c (limited to 'plugins/telnet/telnet.c') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, +}; -- cgit v1.3.1