Added Linux proxy server to test seclink.
This commit is contained in:
parent
bc102b7215
commit
b3ae75cf0c
13 changed files with 648 additions and 15 deletions
|
|
@ -22,7 +22,7 @@ does not open or close the serial port itself.
|
||||||
Before byte stuffing:
|
Before byte stuffing:
|
||||||
|
|
||||||
```
|
```
|
||||||
[0x7E] [SEQ] [TYPE] [LEN_LO] [LEN_HI] [PAYLOAD...] [CRC_LO] [CRC_HI]
|
[0x7E] [SEQ] [TYPE] [LEN] [PAYLOAD...] [CRC_LO] [CRC_HI]
|
||||||
```
|
```
|
||||||
|
|
||||||
| Field | Size | Description |
|
| Field | Size | Description |
|
||||||
|
|
@ -30,8 +30,8 @@ Before byte stuffing:
|
||||||
| `0x7E` | 1 byte | Frame delimiter (flag byte) |
|
| `0x7E` | 1 byte | Frame delimiter (flag byte) |
|
||||||
| `SEQ` | 1 byte | Sequence number (wrapping uint8) |
|
| `SEQ` | 1 byte | Sequence number (wrapping uint8) |
|
||||||
| `TYPE` | 1 byte | Frame type (see below) |
|
| `TYPE` | 1 byte | Frame type (see below) |
|
||||||
| `LEN` | 2 bytes | Payload length, little-endian |
|
| `LEN` | 1 byte | Payload length (0-255) |
|
||||||
| Payload | 0-256 | Application data |
|
| Payload | 0-255 | Application data |
|
||||||
| `CRC` | 2 bytes | CRC-16-CCITT over SEQ+TYPE+LEN+payload |
|
| `CRC` | 2 bytes | CRC-16-CCITT over SEQ+TYPE+LEN+payload |
|
||||||
|
|
||||||
### Frame Types
|
### Frame Types
|
||||||
|
|
@ -82,7 +82,7 @@ typedef struct PktConnS PktConnT;
|
||||||
|
|
||||||
| Name | Value | Description |
|
| Name | Value | Description |
|
||||||
|-----------------------|-------|-------------------------------------|
|
|-----------------------|-------|-------------------------------------|
|
||||||
| `PKT_MAX_PAYLOAD` | 256 | Max payload bytes per packet |
|
| `PKT_MAX_PAYLOAD` | 255 | Max payload bytes per packet |
|
||||||
| `PKT_DEFAULT_WINDOW` | 4 | Default sliding window size |
|
| `PKT_DEFAULT_WINDOW` | 4 | Default sliding window size |
|
||||||
| `PKT_MAX_WINDOW` | 8 | Maximum sliding window size |
|
| `PKT_MAX_WINDOW` | 8 | Maximum sliding window size |
|
||||||
| `PKT_SUCCESS` | 0 | Success |
|
| `PKT_SUCCESS` | 0 | Success |
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Packetized serial transport with HDLC-style framing and sliding window
|
// Packetized serial transport with HDLC-style framing and sliding window
|
||||||
//
|
//
|
||||||
// Frame format (before byte stuffing):
|
// Frame format (before byte stuffing):
|
||||||
// [0x7E] [SEQ] [TYPE] [LEN_LO] [LEN_HI] [PAYLOAD...] [CRC_LO] [CRC_HI]
|
// [0x7E] [SEQ] [TYPE] [LEN] [PAYLOAD...] [CRC_LO] [CRC_HI]
|
||||||
//
|
//
|
||||||
// Byte stuffing:
|
// Byte stuffing:
|
||||||
// 0x7E -> 0x7D 0x5E
|
// 0x7E -> 0x7D 0x5E
|
||||||
|
|
@ -32,8 +32,8 @@
|
||||||
#define FRAME_NAK 0x02
|
#define FRAME_NAK 0x02
|
||||||
#define FRAME_RST 0x03
|
#define FRAME_RST 0x03
|
||||||
|
|
||||||
// Header size: SEQ + TYPE + LEN_LO + LEN_HI
|
// Header size: SEQ + TYPE + LEN
|
||||||
#define HEADER_SIZE 4
|
#define HEADER_SIZE 3
|
||||||
// CRC size
|
// CRC size
|
||||||
#define CRC_SIZE 2
|
#define CRC_SIZE 2
|
||||||
// Minimum frame size (header + CRC, no payload)
|
// Minimum frame size (header + CRC, no payload)
|
||||||
|
|
@ -173,13 +173,12 @@ static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t *
|
||||||
uint16_t crc;
|
uint16_t crc;
|
||||||
int out;
|
int out;
|
||||||
|
|
||||||
// Build raw frame: SEQ + TYPE + LEN_LO + LEN_HI + PAYLOAD
|
// Build raw frame: SEQ + TYPE + LEN + PAYLOAD
|
||||||
raw[0] = seq;
|
raw[0] = seq;
|
||||||
raw[1] = type;
|
raw[1] = type;
|
||||||
raw[2] = (uint8_t)(len & 0xFF);
|
raw[2] = (uint8_t)len;
|
||||||
raw[3] = (uint8_t)((len >> 8) & 0xFF);
|
|
||||||
if (payload && len > 0) {
|
if (payload && len > 0) {
|
||||||
memcpy(&raw[4], payload, len);
|
memcpy(&raw[3], payload, len);
|
||||||
}
|
}
|
||||||
rawLen = HEADER_SIZE + len;
|
rawLen = HEADER_SIZE + len;
|
||||||
|
|
||||||
|
|
@ -269,7 +268,7 @@ static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
|
||||||
|
|
||||||
seq = frame[0];
|
seq = frame[0];
|
||||||
type = frame[1];
|
type = frame[1];
|
||||||
payloadLen = frame[2] | ((int)frame[3] << 8);
|
payloadLen = frame[2];
|
||||||
|
|
||||||
// Validate payload length against actual frame size
|
// Validate payload length against actual frame size
|
||||||
if (payloadLen + MIN_FRAME_SIZE != len) {
|
if (payloadLen + MIN_FRAME_SIZE != len) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
// Maximum payload per packet (excluding header/CRC)
|
// Maximum payload per packet (excluding header/CRC)
|
||||||
#define PKT_MAX_PAYLOAD 256
|
#define PKT_MAX_PAYLOAD 255
|
||||||
|
|
||||||
// Default sliding window size (1-8)
|
// Default sliding window size (1-8)
|
||||||
#define PKT_DEFAULT_WINDOW 4
|
#define PKT_DEFAULT_WINDOW 4
|
||||||
|
|
|
||||||
49
proxy/Makefile
Normal file
49
proxy/Makefile
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# SecLink Proxy — Linux build
|
||||||
|
# Compiles the packet, security, and secLink layers against a socket
|
||||||
|
# shim instead of the DJGPP rs232 driver.
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -O2 -Wall -Wextra
|
||||||
|
|
||||||
|
OBJDIR = ../obj/proxy
|
||||||
|
BINDIR = ../bin
|
||||||
|
|
||||||
|
TARGET = $(BINDIR)/secproxy
|
||||||
|
|
||||||
|
OBJS = $(OBJDIR)/sockShim.o $(OBJDIR)/packet.o $(OBJDIR)/security.o \
|
||||||
|
$(OBJDIR)/secLink.o $(OBJDIR)/proxy.o
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
|
all: $(TARGET)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS) | $(BINDIR)
|
||||||
|
$(CC) -o $@ $(OBJS)
|
||||||
|
|
||||||
|
# Local sources
|
||||||
|
$(OBJDIR)/sockShim.o: sockShim.c sockShim.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR)/proxy.o: proxy.c sockShim.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
# Packet layer — block real rs232.h, inject socket shim
|
||||||
|
$(OBJDIR)/packet.o: ../packet/packet.c sockShim.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -I. -Istubs/ -include sockShim.h -c -o $@ $<
|
||||||
|
|
||||||
|
# Security layer — stub DOS-specific headers
|
||||||
|
$(OBJDIR)/security.o: ../security/security.c | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -Istubs/ -c -o $@ $<
|
||||||
|
|
||||||
|
# SecLink layer — block real rs232.h, inject socket shim
|
||||||
|
$(OBJDIR)/secLink.o: ../seclink/secLink.c sockShim.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -I. -include sockShim.h -c -o $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR):
|
||||||
|
mkdir -p $(OBJDIR)
|
||||||
|
|
||||||
|
$(BINDIR):
|
||||||
|
mkdir -p $(BINDIR)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(OBJDIR) $(TARGET)
|
||||||
132
proxy/README.md
Normal file
132
proxy/README.md
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
# SecLink Proxy
|
||||||
|
|
||||||
|
Linux-hosted proxy that bridges an 86Box emulated serial port to a
|
||||||
|
remote telnet BBS. The 86Box side communicates using the secLink
|
||||||
|
protocol (packet framing, DH key exchange, XTEA encryption). The BBS
|
||||||
|
side is plain telnet over TCP.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
86Box (DOS terminal) Remote BBS
|
||||||
|
| |
|
||||||
|
emulated modem telnet:23
|
||||||
|
| |
|
||||||
|
TCP:2323 TCP:23
|
||||||
|
| |
|
||||||
|
+--- secproxy ----------------------------------+
|
||||||
|
secLink ←→ plaintext
|
||||||
|
(encrypted, reliable)
|
||||||
|
```
|
||||||
|
|
||||||
|
The proxy accepts a single TCP connection from 86Box, performs the
|
||||||
|
secLink handshake (DH key exchange), then connects to the BBS. All
|
||||||
|
traffic between 86Box and the proxy is encrypted via XTEA-CTR on
|
||||||
|
channel 0. Traffic between the proxy and the BBS is unencrypted
|
||||||
|
telnet.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
secproxy [listen_port] [bbs_host] [bbs_port]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Argument | Default | Description |
|
||||||
|
|---------------|------------------------|---------------------------------|
|
||||||
|
| `listen_port` | 2323 | TCP port for 86Box connection |
|
||||||
|
| `bbs_host` | bbs.duensing.digital | BBS hostname |
|
||||||
|
| `bbs_port` | 23 | BBS TCP port |
|
||||||
|
|
||||||
|
```
|
||||||
|
secproxy # all defaults
|
||||||
|
secproxy 5000 # listen on port 5000
|
||||||
|
secproxy 2323 bbs.example.com 23 # different BBS
|
||||||
|
secproxy --help # show usage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Startup Sequence
|
||||||
|
|
||||||
|
1. Listen on the configured TCP port
|
||||||
|
2. Wait for 86Box to connect (blocks on accept)
|
||||||
|
3. Connect to the remote BBS
|
||||||
|
4. Seed the RNG from `/dev/urandom`
|
||||||
|
5. Open secLink and perform the DH handshake (blocks until the DOS
|
||||||
|
side completes its handshake)
|
||||||
|
6. Enter the proxy loop
|
||||||
|
|
||||||
|
## Proxy Loop
|
||||||
|
|
||||||
|
The main loop uses `poll()` with a 10ms timeout to multiplex between
|
||||||
|
the two TCP connections:
|
||||||
|
|
||||||
|
- **86Box → BBS**: `secLinkPoll()` reads from the 86Box socket via
|
||||||
|
the socket shim, decrypts incoming packets, and the receive callback
|
||||||
|
writes plaintext to the BBS socket.
|
||||||
|
- **BBS → 86Box**: `read()` from the BBS socket, then
|
||||||
|
`secLinkSend()` encrypts and sends to 86Box via the socket shim.
|
||||||
|
- **Maintenance**: `secLinkPoll()` also handles packet-layer retransmit
|
||||||
|
timers on every iteration.
|
||||||
|
|
||||||
|
The proxy exits cleanly on Ctrl+C (SIGINT), SIGTERM, or when either
|
||||||
|
side disconnects.
|
||||||
|
|
||||||
|
## 86Box Configuration
|
||||||
|
|
||||||
|
Configure the 86Box serial port to use a telnet connection:
|
||||||
|
|
||||||
|
1. In 86Box settings, set a COM port to "TCP (server)" or
|
||||||
|
"TCP (client)" mode pointing at the proxy's listen port
|
||||||
|
2. Enable "No telnet negotiation" to send raw bytes
|
||||||
|
3. The DOS terminal application running inside 86Box uses secLink
|
||||||
|
over this serial port
|
||||||
|
|
||||||
|
## Socket Shim
|
||||||
|
|
||||||
|
The proxy reuses the same packet, security, and secLink source code
|
||||||
|
as the DOS build. A socket shim (`sockShim.h`/`sockShim.c`) provides
|
||||||
|
rs232-compatible `rs232Read()`/`rs232Write()` functions backed by TCP
|
||||||
|
sockets instead of UART hardware:
|
||||||
|
|
||||||
|
| rs232 function | Socket shim behavior |
|
||||||
|
|----------------|-----------------------------------------------|
|
||||||
|
| `rs232Open()` | No-op (socket already connected) |
|
||||||
|
| `rs232Close()` | Marks port closed (socket managed by caller) |
|
||||||
|
| `rs232Read()` | Non-blocking `recv()` with `MSG_DONTWAIT` |
|
||||||
|
| `rs232Write()` | Blocking `send()` loop with `MSG_NOSIGNAL` |
|
||||||
|
|
||||||
|
The shim maps COM port indices (0-3) to socket file descriptors via
|
||||||
|
`sockShimSetFd()`, which must be called before opening the secLink
|
||||||
|
layer.
|
||||||
|
|
||||||
|
DOS-specific headers (`<pc.h>`, `<go32.h>`, `<sys/farptr.h>`) are
|
||||||
|
replaced by minimal stubs in `stubs/` that provide no-op
|
||||||
|
implementations. The security library's hardware entropy function
|
||||||
|
returns zeros on Linux, which is harmless since the proxy seeds the
|
||||||
|
RNG from `/dev/urandom` before the handshake.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```
|
||||||
|
make # builds ../bin/secproxy
|
||||||
|
make clean # removes objects and binary
|
||||||
|
```
|
||||||
|
|
||||||
|
Objects are placed in `../obj/proxy/`, the binary in `../bin/`.
|
||||||
|
|
||||||
|
Requires only a standard Linux C toolchain (gcc, libc). No external
|
||||||
|
dependencies.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
```
|
||||||
|
proxy/
|
||||||
|
proxy.c main proxy program
|
||||||
|
sockShim.h rs232-compatible socket API (header)
|
||||||
|
sockShim.c socket shim implementation
|
||||||
|
Makefile Linux build
|
||||||
|
stubs/
|
||||||
|
pc.h stub for DJGPP <pc.h>
|
||||||
|
go32.h stub for DJGPP <go32.h>
|
||||||
|
sys/
|
||||||
|
farptr.h stub for DJGPP <sys/farptr.h>
|
||||||
|
```
|
||||||
296
proxy/proxy.c
Normal file
296
proxy/proxy.c
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
// 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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
99
proxy/sockShim.c
Normal file
99
proxy/sockShim.c
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Socket shim — rs232-compatible API over TCP sockets
|
||||||
|
|
||||||
|
#include "sockShim.h"
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Internal state
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
#define MAX_PORTS 4
|
||||||
|
|
||||||
|
static int sFds[MAX_PORTS] = { -1, -1, -1, -1 };
|
||||||
|
static bool sOpen[MAX_PORTS] = { false, false, false, false };
|
||||||
|
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Public functions (alphabetical)
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
int rs232Close(int com) {
|
||||||
|
if (com < 0 || com >= MAX_PORTS) {
|
||||||
|
return RS232_ERR_INVALID_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
sOpen[com] = false;
|
||||||
|
// Socket lifecycle is managed by the caller, not the shim
|
||||||
|
return RS232_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake) {
|
||||||
|
(void)bps;
|
||||||
|
(void)dataBits;
|
||||||
|
(void)parity;
|
||||||
|
(void)stopBits;
|
||||||
|
(void)handshake;
|
||||||
|
|
||||||
|
if (com < 0 || com >= MAX_PORTS) {
|
||||||
|
return RS232_ERR_INVALID_PORT;
|
||||||
|
}
|
||||||
|
if (sFds[com] < 0) {
|
||||||
|
return RS232_ERR_NOT_OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
sOpen[com] = true;
|
||||||
|
return RS232_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int rs232Read(int com, char *data, int len) {
|
||||||
|
if (com < 0 || com >= MAX_PORTS || sFds[com] < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t n = recv(sFds[com], data, len, MSG_DONTWAIT);
|
||||||
|
if (n <= 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int rs232Write(int com, const char *data, int len) {
|
||||||
|
if (com < 0 || com >= MAX_PORTS || sFds[com] < 0) {
|
||||||
|
return RS232_ERR_NOT_OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sent = 0;
|
||||||
|
while (sent < len) {
|
||||||
|
ssize_t n = send(sFds[com], data + sent, len - sent, MSG_NOSIGNAL);
|
||||||
|
if (n < 0) {
|
||||||
|
if (errno == EINTR) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return RS232_ERR_NOT_OPEN;
|
||||||
|
}
|
||||||
|
sent += (int)n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RS232_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void sockShimSetFd(int com, int fd) {
|
||||||
|
if (com < 0 || com >= MAX_PORTS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sFds[com] = fd;
|
||||||
|
|
||||||
|
// Set non-blocking so rs232Read returns immediately when empty
|
||||||
|
int flags = fcntl(fd, F_GETFL, 0);
|
||||||
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
}
|
||||||
36
proxy/sockShim.h
Normal file
36
proxy/sockShim.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Socket shim — provides rs232-compatible API backed by TCP sockets
|
||||||
|
// Used by the Linux proxy to reuse the packet and secLink layers.
|
||||||
|
|
||||||
|
#ifndef SOCKSHIM_H
|
||||||
|
#define SOCKSHIM_H
|
||||||
|
|
||||||
|
// Block the real rs232.h from being included
|
||||||
|
#define RS232_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// rs232-compatible constants (subset used by packet and secLink)
|
||||||
|
#define RS232_COM1 0
|
||||||
|
#define RS232_COM2 1
|
||||||
|
#define RS232_COM3 2
|
||||||
|
#define RS232_COM4 3
|
||||||
|
|
||||||
|
#define RS232_HANDSHAKE_NONE 0
|
||||||
|
|
||||||
|
#define RS232_SUCCESS 0
|
||||||
|
#define RS232_ERR_NOT_OPEN -2
|
||||||
|
#define RS232_ERR_INVALID_PORT -5
|
||||||
|
|
||||||
|
|
||||||
|
// Associate a socket fd with a COM index. Must be called before
|
||||||
|
// rs232Open/pktOpen/secLinkOpen. Sets the socket to non-blocking.
|
||||||
|
void sockShimSetFd(int com, int fd);
|
||||||
|
|
||||||
|
// rs232-compatible functions backed by TCP sockets
|
||||||
|
int rs232Close(int com);
|
||||||
|
int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake);
|
||||||
|
int rs232Read(int com, char *data, int len);
|
||||||
|
int rs232Write(int com, const char *data, int len);
|
||||||
|
|
||||||
|
#endif
|
||||||
7
proxy/stubs/go32.h
Normal file
7
proxy/stubs/go32.h
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Stub for DJGPP <go32.h> — Linux proxy build
|
||||||
|
#ifndef GO32_H_STUB
|
||||||
|
#define GO32_H_STUB
|
||||||
|
|
||||||
|
#define _dos_ds 0
|
||||||
|
|
||||||
|
#endif
|
||||||
8
proxy/stubs/pc.h
Normal file
8
proxy/stubs/pc.h
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Stub for DJGPP <pc.h> — Linux proxy build
|
||||||
|
#ifndef PC_H_STUB
|
||||||
|
#define PC_H_STUB
|
||||||
|
|
||||||
|
static inline void outportb(unsigned short port, unsigned char val) { (void)port; (void)val; }
|
||||||
|
static inline unsigned char inportb(unsigned short port) { (void)port; return 0; }
|
||||||
|
|
||||||
|
#endif
|
||||||
7
proxy/stubs/sys/farptr.h
Normal file
7
proxy/stubs/sys/farptr.h
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
// Stub for DJGPP <sys/farptr.h> — Linux proxy build
|
||||||
|
#ifndef FARPTR_H_STUB
|
||||||
|
#define FARPTR_H_STUB
|
||||||
|
|
||||||
|
static inline unsigned long _farpeekl(unsigned short sel, unsigned long ofs) { (void)sel; (void)ofs; return 0; }
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -62,7 +62,7 @@ typedef struct SecLinkS SecLinkT;
|
||||||
|
|
||||||
| Name | Value | Description |
|
| Name | Value | Description |
|
||||||
|-------------------------|-------|----------------------------------------|
|
|-------------------------|-------|----------------------------------------|
|
||||||
| `SECLINK_MAX_PAYLOAD` | 255 | Max bytes per `secLinkSend()` call |
|
| `SECLINK_MAX_PAYLOAD` | 254 | Max bytes per `secLinkSend()` call |
|
||||||
| `SECLINK_MAX_CHANNEL` | 127 | Highest valid channel number |
|
| `SECLINK_MAX_CHANNEL` | 127 | Highest valid channel number |
|
||||||
| `SECLINK_SUCCESS` | 0 | Operation succeeded |
|
| `SECLINK_SUCCESS` | 0 | Operation succeeded |
|
||||||
| `SECLINK_ERR_PARAM` | -1 | Invalid parameter |
|
| `SECLINK_ERR_PARAM` | -1 | Invalid parameter |
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
#define SECLINK_ERR_SEND -6
|
#define SECLINK_ERR_SEND -6
|
||||||
|
|
||||||
// Max plaintext payload per send (packet max minus 1-byte channel header)
|
// Max plaintext payload per send (packet max minus 1-byte channel header)
|
||||||
#define SECLINK_MAX_PAYLOAD 255
|
#define SECLINK_MAX_PAYLOAD 254
|
||||||
|
|
||||||
// Channel limits
|
// Channel limits
|
||||||
#define SECLINK_MAX_CHANNEL 127
|
#define SECLINK_MAX_CHANNEL 127
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue