// SecLink proxy — bridges an 86Box serial connection to a telnet BBS // // 86Box (DOS terminal) ←→ TCP ←→ proxy ←→ TCP ←→ BBS // secLink protocol plain telnet // // Usage: proxy [listen_port] [bbs_host] [bbs_port] // Defaults: 2323 bbs.duensing.digital 23 #include #include #include #include #include #include #include #include #include #include #include #include "sockShim.h" #include "../seclink/secLink.h" #include "../security/security.h" // ======================================================================== // Defines // ======================================================================== #define DEFAULT_LISTEN_PORT 2323 #define DEFAULT_BBS_HOST "bbs.duensing.digital" #define DEFAULT_BBS_PORT 23 #define CHANNEL_TERMINAL 0 #define POLL_TIMEOUT_MS 10 // ======================================================================== // Static globals // ======================================================================== static volatile bool sRunning = true; // ======================================================================== // Static prototypes (alphabetical) // ======================================================================== static int connectToBbs(const char *host, int port); static int createListenSocket(int port); static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel); static void seedRng(void); static void sigHandler(int sig); // ======================================================================== // Static functions (alphabetical) // ======================================================================== static int connectToBbs(const char *host, int port) { struct addrinfo hints; struct addrinfo *res; char portStr[16]; int fd; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; snprintf(portStr, sizeof(portStr), "%d", port); if (getaddrinfo(host, portStr, &hints, &res) != 0) { return -1; } fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd < 0) { freeaddrinfo(res); return -1; } if (connect(fd, res->ai_addr, res->ai_addrlen) < 0) { close(fd); freeaddrinfo(res); return -1; } freeaddrinfo(res); return fd; } static int createListenSocket(int port) { struct sockaddr_in addr; int fd; int opt = 1; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { return -1; } setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port); if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { close(fd); return -1; } if (listen(fd, 1) < 0) { close(fd); return -1; } return fd; } static void onRecvFromDos(void *ctx, const uint8_t *data, int len, uint8_t channel) { int bbsFd = *(int *)ctx; (void)channel; int sent = 0; while (sent < len) { ssize_t n = write(bbsFd, data + sent, len - sent); if (n <= 0) { break; } sent += (int)n; } } static void seedRng(void) { uint8_t entropy[32]; FILE *f; f = fopen("/dev/urandom", "rb"); if (f) { if (fread(entropy, 1, sizeof(entropy), f) < sizeof(entropy)) { fprintf(stderr, "Warning: short read from /dev/urandom\n"); } fclose(f); } secRngSeed(entropy, sizeof(entropy)); } static void sigHandler(int sig) { (void)sig; sRunning = false; } // ======================================================================== // Main // ======================================================================== int main(int argc, char *argv[]) { int listenPort = DEFAULT_LISTEN_PORT; const char *bbsHost = DEFAULT_BBS_HOST; int bbsPort = DEFAULT_BBS_PORT; int listenFd; int clientFd; int bbsFd; SecLinkT *link; struct sockaddr_in clientAddr; socklen_t clientLen; struct pollfd fds[2]; int rc; if (argc > 1 && (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0)) { printf("Usage: %s [listen_port] [bbs_host] [bbs_port]\n", argv[0]); printf("Defaults: %d %s %d\n", DEFAULT_LISTEN_PORT, DEFAULT_BBS_HOST, DEFAULT_BBS_PORT); return 0; } if (argc > 1) { listenPort = atoi(argv[1]); } if (argc > 2) { bbsHost = argv[2]; } if (argc > 3) { bbsPort = atoi(argv[3]); } signal(SIGPIPE, SIG_IGN); signal(SIGINT, sigHandler); signal(SIGTERM, sigHandler); // Listen for 86Box connection listenFd = createListenSocket(listenPort); if (listenFd < 0) { fprintf(stderr, "Failed to listen on port %d: %s\n", listenPort, strerror(errno)); return 1; } printf("Listening on port %d...\n", listenPort); // Accept connection from 86Box clientLen = sizeof(clientAddr); clientFd = accept(listenFd, (struct sockaddr *)&clientAddr, &clientLen); close(listenFd); if (clientFd < 0) { fprintf(stderr, "Accept failed: %s\n", strerror(errno)); return 1; } printf("86Box connected.\n"); // Associate socket with COM0 for the secLink stack sockShimSetFd(0, clientFd); // Connect to BBS printf("Connecting to %s:%d...\n", bbsHost, bbsPort); bbsFd = connectToBbs(bbsHost, bbsPort); if (bbsFd < 0) { fprintf(stderr, "Failed to connect to BBS: %s\n", strerror(errno)); close(clientFd); return 1; } printf("BBS connected.\n"); // Seed RNG from /dev/urandom and open secLink seedRng(); link = secLinkOpen(0, 115200, 8, 'N', 1, 0, onRecvFromDos, &bbsFd); if (!link) { fprintf(stderr, "Failed to open secLink.\n"); close(bbsFd); close(clientFd); return 1; } // DH key exchange (blocks until both sides complete) printf("Waiting for secLink handshake...\n"); rc = secLinkHandshake(link); if (rc != SECLINK_SUCCESS) { fprintf(stderr, "Handshake failed: %d\n", rc); secLinkClose(link); close(bbsFd); return 1; } printf("Handshake complete. Proxying traffic.\n"); // Set BBS socket non-blocking for the main loop int flags = fcntl(bbsFd, F_GETFL, 0); fcntl(bbsFd, F_SETFL, flags | O_NONBLOCK); // Main proxy loop fds[0].fd = clientFd; fds[0].events = POLLIN; fds[1].fd = bbsFd; fds[1].events = POLLIN; while (sRunning) { poll(fds, 2, POLL_TIMEOUT_MS); // Process incoming secLink packets from 86Box // (callback forwards decrypted data to BBS) secLinkPoll(link); // Read from BBS and send encrypted to 86Box if (fds[1].revents & POLLIN) { uint8_t buf[SECLINK_MAX_PAYLOAD]; ssize_t n = read(bbsFd, buf, sizeof(buf)); if (n <= 0) { printf("BBS disconnected.\n"); break; } secLinkSend(link, buf, (int)n, CHANNEL_TERMINAL, true, true); } // Check for disconnects if (fds[0].revents & (POLLERR | POLLHUP)) { printf("86Box disconnected.\n"); break; } if (fds[1].revents & (POLLERR | POLLHUP)) { printf("BBS disconnected.\n"); break; } } printf("Shutting down.\n"); secLinkClose(link); close(bbsFd); close(clientFd); return 0; }