277 lines
8.2 KiB
C
277 lines
8.2 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <errno.h>
|
|
|
|
#define PORT 31337
|
|
#define BUFFER_SIZE (32 * 1024)
|
|
#define MAX_CLIENTS 10
|
|
#define UID 1000
|
|
#define SLIDES_PATH "femtotcp.gitslides"
|
|
|
|
/* Telnet negotiation commands */
|
|
#define IAC 255
|
|
#define DO 253
|
|
#define WILL 251
|
|
#define SUPPRESS_GO_AHEAD 3
|
|
#define ECHO 1
|
|
#define LINEMODE 34
|
|
#define CMD_SIZE 256
|
|
#define PATH_SIZE 256
|
|
|
|
struct Client {
|
|
int fd;
|
|
char repo_path[PATH_SIZE];
|
|
char slides_path[PATH_SIZE];
|
|
char current_slide[BUFFER_SIZE];
|
|
};
|
|
|
|
/* Telnet negotiation to enable non-canonical mode */
|
|
void send_telnet_negotiation(int client_fd) {
|
|
unsigned char suppress_go_ahead[] = {IAC, WILL, SUPPRESS_GO_AHEAD};
|
|
unsigned char disable_echo[] = {IAC, DO, ECHO};
|
|
unsigned char enable_linemode[] = {IAC, DO, LINEMODE};
|
|
|
|
write(client_fd, suppress_go_ahead, sizeof(suppress_go_ahead));
|
|
write(client_fd, disable_echo, sizeof(disable_echo));
|
|
write(client_fd, enable_linemode, sizeof(enable_linemode));
|
|
}
|
|
|
|
/* Clear the screen */
|
|
void clear_screen(int client_fd) {
|
|
const char *ESC_CLEAR = "\x1b[2J\x1b[H";
|
|
write(client_fd, ESC_CLEAR, strlen(ESC_CLEAR));
|
|
}
|
|
|
|
/* Execute a git-slides command */
|
|
void execute_git_slides(const char *repo_path, const char *slide_command) {
|
|
char command[CMD_SIZE];
|
|
memset(command, 0, CMD_SIZE);
|
|
snprintf(command, sizeof(command), "cd %s && git-slides %s", repo_path, slide_command);
|
|
system(command);
|
|
}
|
|
|
|
/* Load slide content from file */
|
|
void load_slide_content(const char *slides_path, char *output) {
|
|
int fd = open(slides_path, O_RDONLY);
|
|
if (fd < 0) {
|
|
snprintf(output, BUFFER_SIZE, "Error: Could not open slides file: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
ssize_t n = read(fd, output, BUFFER_SIZE - 1);
|
|
if (n < 0) {
|
|
snprintf(output, BUFFER_SIZE, "Error: Could not read slides file: %s\n", strerror(errno));
|
|
} else {
|
|
output[n] = '\0'; /* Null-terminate the buffer */
|
|
}
|
|
|
|
close(fd);
|
|
}
|
|
|
|
/* Clean up client resources */
|
|
void cleanup_client(struct Client *client) {
|
|
if (client->fd != -1) {
|
|
close(client->fd);
|
|
client->fd = -1;
|
|
}
|
|
if (strlen(client->repo_path) > 0) {
|
|
char rm_command[CMD_SIZE];
|
|
snprintf(rm_command, sizeof(rm_command), "rm -rf %s", client->repo_path);
|
|
system(rm_command);
|
|
}
|
|
}
|
|
|
|
/* Handle client input and update slides */
|
|
void handle_client_input(struct Client *client, char command) {
|
|
if (command == ' ') {
|
|
execute_git_slides(client->repo_path, "next");
|
|
} else if (command == '\x08' || command == '\x7f') { /* Backspace or DEL */
|
|
execute_git_slides(client->repo_path, "prev");
|
|
} else if (command == 'q') {
|
|
clear_screen(client->fd);
|
|
cleanup_client(client);
|
|
return;
|
|
} else {
|
|
snprintf(client->current_slide, BUFFER_SIZE, "Unknown command '%c'. Use SPACE, BACKSPACE, or Q.\n", command);
|
|
}
|
|
|
|
load_slide_content(client->slides_path, client->current_slide);
|
|
}
|
|
|
|
/* Send welcome message to the client */
|
|
void send_welcome_message(struct Client *client) {
|
|
clear_screen(client->fd);
|
|
const char *welcome_msg =
|
|
"Welcome to the git-slides presentation!\n"
|
|
"Use SPACE (forward), BACKSPACE (back), or Q (quit).\n\n";
|
|
write(client->fd, client->current_slide, strlen(client->current_slide));
|
|
write(client->fd, welcome_msg, strlen(welcome_msg));
|
|
}
|
|
|
|
|
|
int main() {
|
|
int server_fd;
|
|
struct sockaddr_in server_addr;
|
|
struct pollfd fds[MAX_CLIENTS + 1];
|
|
struct Client clients[MAX_CLIENTS] = {0};
|
|
setuid(UID); /* Drop privileges */
|
|
printf("User ID: %d\n", getuid());
|
|
|
|
server_fd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (server_fd < 0) {
|
|
perror("socket");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
server_addr.sin_family = AF_INET;
|
|
server_addr.sin_port = htons(PORT);
|
|
server_addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
|
|
perror("bind");
|
|
close(server_fd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (listen(server_fd, MAX_CLIENTS) < 0) {
|
|
perror("listen");
|
|
close(server_fd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
|
clients[i].fd = -1;
|
|
}
|
|
|
|
|
|
printf("Server running on port %d\n", PORT);
|
|
|
|
while (1) {
|
|
int num_fds = 1;
|
|
fds[0].fd = server_fd;
|
|
fds[0].events = POLLIN;
|
|
|
|
/* Prepare the pollfd array */
|
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
|
if (clients[i].fd != -1) {
|
|
fds[num_fds].fd = clients[i].fd;
|
|
fds[num_fds].events = POLLIN;
|
|
num_fds++;
|
|
}
|
|
}
|
|
|
|
int ret = poll(fds, num_fds, -1);
|
|
if (ret < 0) {
|
|
perror("poll");
|
|
break;
|
|
}
|
|
|
|
/* Handle new connections */
|
|
if (fds[0].revents & POLLIN) {
|
|
int client_fd = accept(server_fd, NULL, NULL);
|
|
if (client_fd < 0) {
|
|
perror("accept");
|
|
continue;
|
|
}
|
|
|
|
/* Find an empty slot for the client */
|
|
int slot = -1;
|
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
|
if (clients[i].fd == -1) {
|
|
slot = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (slot == -1) {
|
|
const char *msg = "Server full. Try again later.\n";
|
|
write(client_fd, msg, strlen(msg));
|
|
close(client_fd);
|
|
} else {
|
|
struct Client *client = &clients[slot];
|
|
char cp_command[CMD_SIZE];
|
|
char co_command[CMD_SIZE];
|
|
client->fd = client_fd;
|
|
snprintf(client->repo_path, PATH_SIZE, "/tmp/git_repo_%d", client_fd);
|
|
snprintf(client->slides_path, PATH_SIZE, "%s/%s", client->repo_path, SLIDES_PATH);
|
|
|
|
/* Create a temporary copy of the repo */
|
|
snprintf(cp_command, sizeof(cp_command), "cp -a . %s", client->repo_path);
|
|
system(cp_command);
|
|
|
|
/* Check out 'start' branch */
|
|
snprintf(co_command, sizeof(co_command), "cd %s && git checkout start", client->repo_path);
|
|
system(co_command);
|
|
|
|
load_slide_content(client->slides_path, client->current_slide);
|
|
|
|
send_telnet_negotiation(client_fd);
|
|
|
|
printf("Client connected: %d\n", client_fd);
|
|
/* Discard any pending input */
|
|
fds[0].fd = client_fd;
|
|
fds[0].events = POLLIN;
|
|
while (poll(fds, 1, 500) > 0) {
|
|
char command;
|
|
read(client_fd, &command, 1);
|
|
}
|
|
printf("Go!\n");
|
|
send_welcome_message(client);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Handle client events */
|
|
for (i = 1; i < num_fds; i++) {
|
|
if (fds[i].revents & POLLIN) {
|
|
char command;
|
|
int ret;
|
|
int r;
|
|
int client_idx = i - 1;
|
|
char *p;
|
|
struct Client *client = &clients[client_idx];
|
|
|
|
ssize_t n = read(client->fd, &command, 1);
|
|
if (n <= 0) {
|
|
cleanup_client(client);
|
|
continue;
|
|
}
|
|
|
|
printf("Input from client: %d, command: %02x (size: %d)\n", client->fd, (uint8_t)command, n);
|
|
handle_client_input(client, command);
|
|
|
|
/* Detect if the slide has more than 40 rows */
|
|
p = client->current_slide;
|
|
for (r = 0; r < 32; r++) {
|
|
p = strchr(p, '\n');
|
|
if (p == NULL) {
|
|
break;
|
|
}
|
|
p++;
|
|
}
|
|
if ((p) && (r == 32)) {
|
|
printf("BIG SLIDE\n");
|
|
*p = '\0';
|
|
}
|
|
clear_screen(client->fd);
|
|
write(client->fd, client->current_slide, strlen(client->current_slide));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Cleanup */
|
|
for (i = 0; i < MAX_CLIENTS; i++) {
|
|
cleanup_client(&clients[i]);
|
|
}
|
|
close(server_fd);
|
|
return 0;
|
|
}
|
|
|