From 119058c9bc28379232b650771cb478d77e76ee36 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Mon, 16 Sep 2024 00:37:51 -0400 Subject: [PATCH 1/5] Create PtyServer.c --- src/c/PtyServer.c | 402 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 src/c/PtyServer.c diff --git a/src/c/PtyServer.c b/src/c/PtyServer.c new file mode 100644 index 0000000..4263a53 --- /dev/null +++ b/src/c/PtyServer.c @@ -0,0 +1,402 @@ +// https://chatgpt.com/share/66e7b5fe-cb08-8004-aa82-66f443479008 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_TCP_PORT 5000 +#define SYMLINK_DIR "/home/project" + +typedef struct PicoDevice { + int picoNumber; + char serialId[256]; + uv_tcp_t *client; + uv_pipe_t *pty_pipe; + int pty_fd_master; + int pty_fd_slave; + char pts_name[256]; + uv_stream_t *stream; + struct PicoDevice *next; +} PicoDevice; + +PicoDevice *picoDevices = NULL; +int nextPicoNumber = 1; +uv_loop_t *loop; + +void logMessage(const char *format, ...) { + va_list args; + va_start(args, format); + + time_t now = time(NULL); + struct tm *t = localtime(&now); + char timeStr[64]; + strftime(timeStr, sizeof(timeStr), "%b %d %H:%M:%S", t); + printf("%s ", timeStr); + vprintf(format, args); + printf("\n"); + fflush(stdout); + + va_end(args); +} + +void daemonize() { + pid_t pid = fork(); + + if (pid < 0) { + // Fork failed + perror("fork"); + exit(EXIT_FAILURE); + } + if (pid > 0) { + // Parent process exits + exit(EXIT_SUCCESS); + } + + // Child process continues + + // Create a new session and set process group + if (setsid() < 0) { + perror("setsid"); + exit(EXIT_FAILURE); + } + + // Ignore the SIGCHLD signal to avoid zombies + signal(SIGCHLD, SIG_IGN); + + // Fork again to ensure the process is not a session leader + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(EXIT_FAILURE); + } + if (pid > 0) { + // Parent exits again + exit(EXIT_SUCCESS); + } + + // Child continues + + // Redirect standard file descriptors to /dev/null + int devnull = open("/dev/null", O_RDWR); + if (devnull < 0) { + perror("open /dev/null"); + exit(EXIT_FAILURE); + } + dup2(devnull, STDIN_FILENO); + dup2(devnull, STDOUT_FILENO); + dup2(devnull, STDERR_FILENO); + if (devnull > STDERR_FILENO) { + close(devnull); + } + + // Change working directory to root + if (chdir("/") < 0) { + perror("chdir"); + exit(EXIT_FAILURE); + } +} + +void createSymlink(int picoNumber, const char *ptsName) { + char symlinkPath[512]; + snprintf(symlinkPath, sizeof(symlinkPath), "%s/pico%d", SYMLINK_DIR, picoNumber); + struct stat st; + if (lstat(symlinkPath, &st) == -1) { + if (symlink(ptsName, symlinkPath) == -1) { + logMessage("Error creating symlink: %s", strerror(errno)); + } else { + logMessage("Created symlink '%s' -> '%s'", symlinkPath, ptsName); + } + } else { + logMessage("Symlink '%s' already exists", symlinkPath); + } +} + +void removeSymlink(int picoNumber) { + char symlinkPath[512]; + snprintf(symlinkPath, sizeof(symlinkPath), "%s/pico%d", SYMLINK_DIR, picoNumber); + if (unlink(symlinkPath) == -1) { + logMessage("Error removing symlink: %s", strerror(errno)); + } else { + logMessage("Removed symlink '%s'", symlinkPath); + } +} + +void cleanPicoResources(PicoDevice *picoDevice) { + if (picoDevice->pty_fd_master != -1) { + close(picoDevice->pty_fd_master); + picoDevice->pty_fd_master = -1; + } + if (picoDevice->pty_fd_slave != -1) { + close(picoDevice->pty_fd_slave); + picoDevice->pty_fd_slave = -1; + } + removeSymlink(picoDevice->picoNumber); + if (picoDevice->client) { + uv_close((uv_handle_t *)picoDevice->client, NULL); + picoDevice->client = NULL; + } + if (picoDevice->pty_pipe) { + uv_close((uv_handle_t *)picoDevice->pty_pipe, NULL); + picoDevice->pty_pipe = NULL; + } + // Remove from picoDevices list + PicoDevice **pp = &picoDevices; + while (*pp && *pp != picoDevice) { + pp = &(*pp)->next; + } + if (*pp == picoDevice) { + *pp = picoDevice->next; + } + logMessage("Pico%d pty and socket destroyed", picoDevice->picoNumber); + free(picoDevice); +} + +void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { + buf->base = (char *)malloc(suggested_size); + buf->len = suggested_size; +} + +void on_pty_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf); +void on_client_write(uv_write_t *req, int status); + +void setupPicoPty(PicoDevice *picoDevice) { + int master_fd, slave_fd; + char pts_name[256]; + + if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) == -1) { + logMessage("Error creating pty: %s", strerror(errno)); + return; + } + + if (ptsname_r(master_fd, pts_name, sizeof(pts_name)) != 0) { + logMessage("Error getting pts name: %s", strerror(errno)); + close(master_fd); + close(slave_fd); + return; + } + + picoDevice->pty_fd_master = master_fd; + picoDevice->pty_fd_slave = slave_fd; + strcpy(picoDevice->pts_name, pts_name); + createSymlink(picoDevice->picoNumber, pts_name); + + picoDevice->pty_pipe = (uv_pipe_t *)malloc(sizeof(uv_pipe_t)); + uv_pipe_init(loop, picoDevice->pty_pipe, 0); + uv_pipe_open(picoDevice->pty_pipe, master_fd); + + picoDevice->pty_pipe->data = picoDevice; + uv_read_start((uv_stream_t *)picoDevice->pty_pipe, alloc_buffer, on_pty_read); +} + +void on_client_write(uv_write_t *req, int status) { + if (status) { + logMessage("Error writing to client: %s", uv_strerror(status)); + } + free(req->data); + free(req); +} + +void on_pty_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { + PicoDevice *picoDevice = stream->data; + if (nread > 0) { + uv_write_t *write_req = (uv_write_t *)malloc(sizeof(uv_write_t)); + uv_buf_t write_buf = uv_buf_init(buf->base, nread); + write_req->data = buf->base; + uv_write(write_req, (uv_stream_t *)picoDevice->client, &write_buf, 1, on_client_write); + char *log_buf = (char *)malloc(nread + 1); + memcpy(log_buf, buf->base, nread); + log_buf[nread] = '\0'; + logMessage("Pico%d command: %s", picoDevice->picoNumber, log_buf); + free(log_buf); + return; + } else if (nread < 0) { + if (nread != UV_EOF) { + logMessage("Error reading from pty: %s", uv_strerror(nread)); + } + uv_close((uv_handle_t *)stream, NULL); + } + free(buf->base); +} + +void on_client_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf); + +void on_client_write_to_pty(uv_write_t *req, int status) { + if (status) { + logMessage("Error writing to pty: %s", uv_strerror(status)); + } + free(req->data); + free(req); +} + +void on_client_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) { + PicoDevice *picoDevice = client->data; + + if (nread > 0) { + buf->base[nread] = '\0'; + char *message = buf->base; + while (isspace(*message)) message++; + char *end = message + strlen(message) - 1; + while (end > message && isspace(*end)) *end-- = '\0'; + + if (strncmp(message, "pico_", 5) == 0) { + char serialId[256]; + strncpy(serialId, message + 5, sizeof(serialId) - 1); + serialId[sizeof(serialId) - 1] = '\0'; + + PicoDevice *existingDevice = picoDevices; + while (existingDevice != NULL) { + if (strcmp(existingDevice->serialId, serialId) == 0) { + picoDevice->picoNumber = existingDevice->picoNumber; + logMessage("Pico%d client reconnected", picoDevice->picoNumber); + break; + } + existingDevice = existingDevice->next; + } + if (existingDevice == NULL) { + picoDevice->picoNumber = nextPicoNumber++; + strcpy(picoDevice->serialId, serialId); + picoDevice->next = picoDevices; + picoDevices = picoDevice; + setupPicoPty(picoDevice); + logMessage("Pico%d client connected", picoDevice->picoNumber); + } + if (picoDevice->pty_pipe) { + picoDevice->pty_pipe->data = picoDevice; + } + } else if (picoDevice->picoNumber > 0) { + uv_write_t *write_req = (uv_write_t *)malloc(sizeof(uv_write_t)); + size_t msg_len = strlen(message); + char *msg_with_cr = (char *)malloc(msg_len + 2); + strcpy(msg_with_cr, message); + strcat(msg_with_cr, "\r"); + uv_buf_t write_buf = uv_buf_init(msg_with_cr, strlen(msg_with_cr)); + write_req->data = msg_with_cr; + uv_write(write_req, (uv_stream_t *)picoDevice->pty_pipe, &write_buf, 1, on_client_write_to_pty); + logMessage("Pico%d response: %s", picoDevice->picoNumber, message); + } + free(buf->base); + return; + } else if (nread < 0) { + if (nread != UV_EOF) { + logMessage("Error reading from client: %s", uv_strerror(nread)); + } + uv_close((uv_handle_t *)client, NULL); + if (picoDevice) { + cleanPicoResources(picoDevice); + } + } + free(buf->base); +} + +void on_new_connection(uv_stream_t *server, int status) { + if (status < 0) { + logMessage("New connection error: %s", uv_strerror(status)); + return; + } + + PicoDevice *picoDevice = (PicoDevice *)malloc(sizeof(PicoDevice)); + memset(picoDevice, 0, sizeof(PicoDevice)); + picoDevice->client = (uv_tcp_t *)malloc(sizeof(uv_tcp_t)); + uv_tcp_init(loop, picoDevice->client); + if (uv_accept(server, (uv_stream_t *)picoDevice->client) == 0) { + picoDevice->client->data = picoDevice; + uv_read_start((uv_stream_t *)picoDevice->client, alloc_buffer, on_client_read); + } else { + uv_close((uv_handle_t *)picoDevice->client, NULL); + } +} + +void on_signal(uv_signal_t *handle, int signum) { + logMessage("Received signal %d, cleaning up...", signum); + PicoDevice *picoDevice = picoDevices; + while (picoDevice != NULL) { + PicoDevice *next = picoDevice->next; + cleanPicoResources(picoDevice); + picoDevice = next; + } + uv_signal_stop(handle); + uv_stop(loop); +} + +void print_usage(const char *prog_name) { + printf("Usage: %s [-p port] [-d]\n", prog_name); + printf(" -p, --port PORT Specify the TCP port to listen on (default: %d)\n", DEFAULT_TCP_PORT); + printf(" -d, --daemon Run as a daemon\n"); + printf(" -h, --help Show this help message\n"); +} + +int main(int argc, char *argv[]) { + loop = uv_default_loop(); + int tcp_port = DEFAULT_TCP_PORT; + int run_as_daemon = 0; + + for (int i = 1; i < argc; i++) { + if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) && i + 1 < argc) { + tcp_port = atoi(argv[++i]); + if (tcp_port <= 0 || tcp_port > 65535) { + fprintf(stderr, "Invalid port number: %s\n", argv[i]); + return 1; + } + } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--daemon") == 0) { + run_as_daemon = 1; + } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { + print_usage(argv[0]); + return 0; + } else { + fprintf(stderr, "Unknown argument: %s\n", argv[i]); + print_usage(argv[0]); + return 1; + } + } + + if (run_as_daemon) { + daemonize(); + } + + uv_tcp_t server; + uv_tcp_init(loop, &server); + + struct sockaddr_in addr; + int r = uv_ip4_addr("0.0.0.0", tcp_port, &addr); + if (r != 0) { + logMessage("Error initializing address: %s", uv_strerror(r)); + return 1; + } + + r = uv_tcp_bind(&server, (const struct sockaddr *)&addr, 0); + if (r != 0) { + logMessage("Error binding server: %s", uv_strerror(r)); + return 1; + } + + r = uv_listen((uv_stream_t *)&server, 128, on_new_connection); + if (r != 0) { + logMessage("Error listening: %s", uv_strerror(r)); + return 1; + } + + logMessage("Server listening on port %d", tcp_port); + + uv_signal_t sigint; + uv_signal_init(loop, &sigint); + uv_signal_start(&sigint, on_signal, SIGINT); + + uv_signal_t sigterm; + uv_signal_init(loop, &sigterm); + uv_signal_start(&sigterm, on_signal, SIGTERM); + + uv_run(loop, UV_RUN_DEFAULT); + + return 0; +} From 0eec04e9a28bc89600fb85cd98a383a6e220508b Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Mon, 16 Sep 2024 00:39:27 -0400 Subject: [PATCH 2/5] Create Makefile --- src/c/Makefile | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/c/Makefile diff --git a/src/c/Makefile b/src/c/Makefile new file mode 100644 index 0000000..d7542cb --- /dev/null +++ b/src/c/Makefile @@ -0,0 +1,30 @@ +# Makefile for PtyServer + +# Compiler and flags +CC = gcc +CFLAGS = -O2 -Wall -Wextra -Wpedantic -luv -lutil + +# Source files and executable +SRCS = PtyServer.c +EXEC = PtyServer + +# Default target +all: $(EXEC) + +# Build the executable +$(EXEC): $(SRCS) + $(CC) $(CFLAGS) -o $(EXEC) $(SRCS) + +# Run the program in the foreground with default port +run: $(EXEC) + ./$(EXEC) -p 5000 + +# Run the program as a daemon with default port +run-daemon: $(EXEC) + ./$(EXEC) -d -p 5000 + +# Clean up build files +clean: + rm -f $(EXEC) + +.PHONY: all clean run run-daemon From ef4f8e07a6722521843e3761bc19f72883374f81 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Mon, 16 Sep 2024 00:41:08 -0400 Subject: [PATCH 3/5] Update Makefile to check for libuv --- src/c/Makefile | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/c/Makefile b/src/c/Makefile index d7542cb..83aca87 100644 --- a/src/c/Makefile +++ b/src/c/Makefile @@ -2,18 +2,24 @@ # Compiler and flags CC = gcc -CFLAGS = -O2 -Wall -Wextra -Wpedantic -luv -lutil +CFLAGS = -O2 -Wall -Wextra -Wpedantic +LIBS = -luv -lutil # Source files and executable SRCS = PtyServer.c EXEC = PtyServer # Default target -all: $(EXEC) +all: check_libuv $(EXEC) # Build the executable $(EXEC): $(SRCS) - $(CC) $(CFLAGS) -o $(EXEC) $(SRCS) + $(CC) $(CFLAGS) -o $(EXEC) $(SRCS) $(LIBS) + +# Check if libuv is installed +check_libuv: + @command -v pkg-config >/dev/null 2>&1 || { echo >&2 "pkg-config is required but not installed. Please install it."; exit 1; } + @pkg-config --exists libuv || { echo >&2 "libuv is not installed. Please install libuv development package (e.g., 'libuv1-dev' on Debian/Ubuntu or 'libuv-devel' on Red Hat/Fedora)."; exit 1; } # Run the program in the foreground with default port run: $(EXEC) @@ -27,4 +33,4 @@ run-daemon: $(EXEC) clean: rm -f $(EXEC) -.PHONY: all clean run run-daemon +.PHONY: all clean run run-daemon check_libuv From 3fb90c5e26f827511e0259353000b795c794fd1c Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Mon, 16 Sep 2024 01:20:49 -0400 Subject: [PATCH 4/5] Update PtyServer.c to support encryption --- src/c/PtyServer.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/c/PtyServer.c b/src/c/PtyServer.c index 4263a53..e681c2d 100644 --- a/src/c/PtyServer.c +++ b/src/c/PtyServer.c @@ -14,6 +14,8 @@ #include #include #include +#include +#include #define DEFAULT_TCP_PORT 5000 #define SYMLINK_DIR "/home/project" @@ -167,6 +169,34 @@ void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { buf->len = suggested_size; } +void decrypt_message(const unsigned char *ciphertext, int ciphertext_len, const unsigned char *key, unsigned char *iv, unsigned char *plaintext) { + AES_KEY aes_key; + + // Initialize decryption key structure + if (AES_set_decrypt_key(key, 256, &aes_key) < 0) { + fprintf(stderr, "Failed to set decryption key.\n"); + exit(EXIT_FAILURE); + } + + // Decrypt the data using AES CFB mode + int num = 0; // Used internally by AES_cfb128_encrypt + AES_cfb128_encrypt(ciphertext, plaintext, ciphertext_len, &aes_key, iv, &num, AES_DECRYPT); +} + +void encrypt_message(const unsigned char *plaintext, int plaintext_len, const unsigned char *key, unsigned char *iv, unsigned char *ciphertext) { + AES_KEY aes_key; + + // Initialize encryption key structure + if (AES_set_encrypt_key(key, 256, &aes_key) < 0) { + fprintf(stderr, "Failed to set encryption key.\n"); + exit(EXIT_FAILURE); + } + + // Encrypt the data using AES CFB mode + int num = 0; // Used internally by AES_cfb128_encrypt + AES_cfb128_encrypt(plaintext, ciphertext, plaintext_len, &aes_key, iv, &num, AES_ENCRYPT); +} + void on_pty_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf); void on_client_write(uv_write_t *req, int status); From 07cfb24420f755dd813b85cfc16dae6618c86304 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Mon, 16 Sep 2024 01:29:50 -0400 Subject: [PATCH 5/5] Update PtyServer.c to support encyption --- src/c/PtyServer.c | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/c/PtyServer.c b/src/c/PtyServer.c index e681c2d..8a9df1d 100644 --- a/src/c/PtyServer.c +++ b/src/c/PtyServer.c @@ -30,6 +30,9 @@ typedef struct PicoDevice { char pts_name[256]; uv_stream_t *stream; struct PicoDevice *next; + int encryption_supported; // 0 = no encryption, 1 = encryption supported + unsigned char encryption_key[32]; // AES-256 key + unsigned char iv[AES_BLOCK_SIZE]; // Initialization Vector for AES } PicoDevice; PicoDevice *picoDevices = NULL; @@ -241,8 +244,17 @@ void on_pty_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { PicoDevice *picoDevice = stream->data; if (nread > 0) { uv_write_t *write_req = (uv_write_t *)malloc(sizeof(uv_write_t)); - uv_buf_t write_buf = uv_buf_init(buf->base, nread); - write_req->data = buf->base; + unsigned char encrypted_data[1024]; + + if (picoDevice->encryption_supported) { + encrypt_message((unsigned char *)buf->base, nread, picoDevice->encryption_key, picoDevice->iv, encrypted_data); + uv_buf_t write_buf = uv_buf_init((char *)encrypted_data, nread); + write_req->data = (void *)encrypted_data; + } else { + uv_buf_t write_buf = uv_buf_init(buf->base, nread); + write_req->data = buf->base; + } + uv_write(write_req, (uv_stream_t *)picoDevice->client, &write_buf, 1, on_client_write); char *log_buf = (char *)malloc(nread + 1); memcpy(log_buf, buf->base, nread); @@ -279,7 +291,15 @@ void on_client_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) { char *end = message + strlen(message) - 1; while (end > message && isspace(*end)) *end-- = '\0'; - if (strncmp(message, "pico_", 5) == 0) { + // Check if the Pico supports encryption + if (strncmp(message, "pico_encrypt", 12) == 0) { + picoDevice->encryption_supported = 1; + // Here we could derive or set an encryption key and IV + RAND_bytes(picoDevice->encryption_key, sizeof(picoDevice->encryption_key)); + RAND_bytes(picoDevice->iv, AES_BLOCK_SIZE); + logMessage("Pico%d supports encryption.", picoDevice->picoNumber); + } else if (strncmp(message, "pico_", 5) == 0) { + // Handle Pico device connection and identification as before char serialId[256]; strncpy(serialId, message + 5, sizeof(serialId) - 1); serialId[sizeof(serialId) - 1] = '\0'; @@ -305,6 +325,11 @@ void on_client_read(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf) { picoDevice->pty_pipe->data = picoDevice; } } else if (picoDevice->picoNumber > 0) { + unsigned char plaintext[1024]; + if (picoDevice->encryption_supported) { + decrypt_message((unsigned char *)message, strlen(message), picoDevice->encryption_key, picoDevice->iv, plaintext); + strcpy(message, (char *)plaintext); // Decrypt message and store in the original buffer + } uv_write_t *write_req = (uv_write_t *)malloc(sizeof(uv_write_t)); size_t msg_len = strlen(message); char *msg_with_cr = (char *)malloc(msg_len + 2);