Initial import
This commit is contained in:
commit
9776672e5e
2 changed files with 262 additions and 0 deletions
15
Makefile
Normal file
15
Makefile
Normal file
|
@ -0,0 +1,15 @@
|
|||
LDFLAGS=-lpthread
|
||||
|
||||
all: server ftserver
|
||||
|
||||
ftserver:LDFLAGS+=-lfemtotcp
|
||||
|
||||
ftserver:server.o
|
||||
gcc -o $@ $^ $(LDFLAGS)
|
||||
|
||||
server: server.o
|
||||
gcc -o $@ $^ $(LDFLAGS)
|
||||
|
||||
|
||||
clean:
|
||||
rm -f ftserver server server.o
|
247
server.c
Normal file
247
server.c
Normal file
|
@ -0,0 +1,247 @@
|
|||
#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];
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
close(client->fd);
|
||||
client->fd = -1; /* Mark as disconnected */
|
||||
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, welcome_msg, strlen(welcome_msg));
|
||||
write(client->fd, client->current_slide, strlen(client->current_slide));
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int server_fd;
|
||||
struct sockaddr_in server_addr;
|
||||
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);
|
||||
}
|
||||
|
||||
struct pollfd fds[MAX_CLIENTS + 1];
|
||||
struct Client clients[MAX_CLIENTS] = {0};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_CLIENTS; i++) {
|
||||
clients[i].fd = -1;
|
||||
}
|
||||
|
||||
fds[0].fd = server_fd;
|
||||
fds[0].events = POLLIN;
|
||||
|
||||
printf("Server running on port %d\n", PORT);
|
||||
|
||||
while (1) {
|
||||
int num_fds = 1;
|
||||
|
||||
/* 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];
|
||||
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);
|
||||
|
||||
execute_git_slides(client->repo_path, "prev");
|
||||
load_slide_content(client->slides_path, client->current_slide);
|
||||
|
||||
send_telnet_negotiation(client_fd);
|
||||
send_welcome_message(client);
|
||||
|
||||
printf("Client connected: %d\n", client_fd);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle client events */
|
||||
for (i = 1; i < num_fds; i++) {
|
||||
if (fds[i].revents & POLLIN) {
|
||||
char command;
|
||||
int client_idx = i - 1;
|
||||
struct Client *client = &clients[client_idx];
|
||||
|
||||
ssize_t n = read(client->fd, &command, 1);
|
||||
if (n <= 0) {
|
||||
cleanup_client(client);
|
||||
continue;
|
||||
}
|
||||
|
||||
handle_client_input(client, command);
|
||||
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in a new issue