//
// libsyd: Rust-based C library for syd interaction via /dev/syd
// lib/examples/shell_server.c: syd API C Library Shell Server Example
//
// Copyright (c) 2023 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

/*
 * Compile this with:
 * cc -fPIC -fPIE shell_server.c -oshell_server -static-pie -lsyd
 * Run with:
 * syd -pcontainer ./shell_server
 * This binds to port 127.0.0.1:65432
 */

/*
 * SAFETY: This server code is insecure.
 * It passes the command without any validation
 * to /bin/sh -c "$command".
 * This is done with the intention to display syd' abilities.
 */

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <err.h>

#include <syd.h>

#define PORT 65432 // Port number for the server
#define BUFFER_SIZE 1024 // Size of the buffer for incoming messages

int main()
{
	int server_fd, new_socket;
	struct sockaddr_in address;
	int r, opt = 1;
	int addrlen = sizeof(address);
	char buffer[BUFFER_SIZE] = {0};
	bool should_quit = false; // Flag to indicate if the server should quit

	char *sh = realpath("/bin/busybox", NULL);
	if (!sh) {
		err(1, "Failed to determine real path to /bin/busybox");
	}

	if ((r = syd_check()) != 0) {
		errno = -r;
		err(1, "Not running under syd");
	}

	// Creating socket file descriptor
	if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
		err(1, "socket failed");
	}

	// Forcefully attaching socket to the port 65432
	if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,
	               sizeof(opt))) {
		err(1, "setsockopt");
	}

	address.sin_family = AF_INET;
	address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	address.sin_port = htons(PORT);

	if ((r = syd_allow_net_bind_add("LOOPBACK!65432")) != 0) {
		errno = -r;
		err(1, "Failed to allow LOOPBACK!65432");
	}

	// Binding the socket to the port 65432
	if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
		err(1, "bind failed");
	}

	if ((r = syd_allow_net_bind_del("LOOPBACK!65432")) != 0) {
		errno = -r;
		err(1, "Failed to allow LOOPBACK!65432");
	}
	if ((r = syd_allow_exec_add(sh)) != 0) {
		errno = -r;
		err(1, "Failed to allow %s for exec", sh);
	}
	if ((r = syd_allow_stat_add(sh)) != 0) {
		errno = -r;
		err(1, "Failed to allow %s for stat", sh);
	}

	if ((r = syd_lock(LOCK_ON)) != 0) {
		errno = -r;
		err(1, "Failed to lock syd");
	}

	// Listening for incoming connections
	if (listen(server_fd, 3) < 0) {
		err(1, "listen");
	}

	for (;;) {
		printf("*** Waiting for a connection...\n");

		// Accepting an incoming connection
		if ((new_socket = accept(server_fd, (struct sockaddr *)&address,
		                         (socklen_t *)&addrlen)) < 0) {
			err(1, "accept");
		}

		printf("*** Connection established\n");

		// Read data from the client, execute it, and send back the output
		ssize_t read_bytes;
		while ((read_bytes = read(new_socket, buffer, BUFFER_SIZE - 1)) > 0) {
			buffer[read_bytes] = '\0'; // Null-terminate the string

			if (strncmp(buffer, "QUIT", 4) == 0) {
				printf("Received 'QUIT', exiting.\n");
				should_quit = 1; // Set the flag to break out of the outer loop
				break;
			}

			FILE *fp;
			char command_output[BUFFER_SIZE];
			char command[BUFFER_SIZE + 10];

			snprintf(command, sizeof(command), "/bin/busybox sh -c \"%s\" 2>&1", buffer);

			fp = popen(command, "r");
			if (fp == NULL) {
				printf("Failed to run command\n");
				continue;
			}

			// Read the output of the command
			while (fgets(command_output, sizeof(command_output), fp) != NULL) {
				write(new_socket, command_output, strlen(command_output));
			}

			pclose(fp);
			memset(buffer, 0, BUFFER_SIZE); // Clear the buffer
		}

		if (read_bytes < 0) {
			err(1, "read");
		}

		close(new_socket);
		printf("Connection closed\n");

		if (should_quit) {
			break; // Exit the outer loop if "QUIT" was received
		}
	}

	printf("*** Shutting down server.\n");
	close(server_fd);

	return 0;
}
