From 8453d41a00eba076336956976898ab9b46ae2cb7 Mon Sep 17 00:00:00 2001 From: Tom MTT Date: Mon, 21 Nov 2022 10:43:22 +0100 Subject: [PATCH] feat: First stable release --- .gitignore | 6 + arg.h | 80 +++++++++++ bin.c | 121 ++++++++++++++++ bin.h | 25 ++++ feuille.c | 402 +++++++++++++++++++++++++++++++++++++++++++++++++++++ feuille.h | 36 +++++ server.c | 240 ++++++++++++++++++++++++++++++++ server.h | 27 ++++ util.c | 84 +++++++++++ util.h | 21 +++ 10 files changed, 1042 insertions(+) create mode 100644 .gitignore create mode 100644 arg.h create mode 100644 bin.c create mode 100644 bin.h create mode 100644 feuille.c create mode 100644 feuille.h create mode 100644 server.c create mode 100644 server.h create mode 100644 util.c create mode 100644 util.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..54613a8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.clangd + +*.o + +feuille +web/cgi/feuille.cgi diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..2130a20 --- /dev/null +++ b/arg.h @@ -0,0 +1,80 @@ +/* + * arg.h + * Arg parsing. + * + * Copyright (c) 2016 + * Christoph Lohmann <20h at r-36 dot net> + * + * This file is licensed under the MIT License. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* + * Copy me if you can. + * by 20h + */ + +#ifndef _ARG_H_ +#define _ARG_H_ + +#include +#include +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][0] == '-'\ + && argv[0][1];\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + int i_;\ + for (i_ = 1, brk_ = 0, argv_ = argv;\ + argv[0][i_] && !brk_;\ + i_++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][i_];\ + switch (argc_) + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define EARGF(x) ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][i_+1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][i_+1] != '\0')?\ + (&argv[0][i_+1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/bin.c b/bin.c new file mode 100644 index 0000000..804ad01 --- /dev/null +++ b/bin.c @@ -0,0 +1,121 @@ +/* + * bin.c + * Pastes handling. + * + * + * Copyright (c) 2022 + * Tom MTT. + * + * This file is licensed under the 3-Clause BSD License. + * You should have received a copy of the 3-Clause BSD License + * along with this program. If not, see + * . + */ + +#include "bin.h" + +#include /* for NULL, fclose, fopen, fputs, snprintf, FILE */ +#include /* for calloc, free, malloc, rand, realloc */ +#include /* for strlen */ +#include /* for access, F_OK */ + +#include "feuille.h" /* for Settings, settings */ + +/* symbols used to generate IDs */ +static char *id_symbols = "abcdefghijklmnopqrstuvwxyz0123456789"; + +/** + * Generate a random ID, until one is available on disk. + * min_length: the minimum ID length. Will be increased if a collision occurs. + * -> a pointer to the ID. Needs to be freed. + */ +char *generate_id(int min_length) +{ + int length = min_length; + + /* allocate a buffer to store the ID */ + char *buffer; + if ((buffer = calloc(length + 1, sizeof(char))) == NULL) + return NULL; + + /* for each letter, generate a random one */ + for (int i = 0; i < length; i++) { + if (i > 8 * min_length) + return NULL; + + buffer[i] = id_symbols[rand() % strlen(id_symbols)]; + + /* collision? */ + if (i == length - 1 && paste_exists(buffer)) { + /* add one to the ID length and re-allocate the buffer */ + length++; + + void *tmp; + if ((tmp = realloc(buffer, (length + 1) * sizeof(char))) == NULL) { + free(buffer); + return NULL; + } + + buffer = tmp; + } + } + + buffer[length] = 0; + return buffer; +} + +/** + * Check if an ID is already used. + * id: the ID in question. + * -> 1 if exists, 0 if not. + */ +int paste_exists(char *id) +{ + return access(id, F_OK) == 0; +} + +/** + * Write the paste content to disk. + * paste: the string containing the paste. + * id: the ID of the paste. + * -> 0 if done, -1 if not. + */ +int write_paste(char *paste, char *id) +{ + /* open the file with write access */ + FILE *file; + if ((file = fopen(id, "w")) == NULL) + return -1; + + /* write the content to file */ + if (fputs(paste, file) == -1) { + fclose(file); + return -1; + } + + /* close the file (obviously) */ + fclose(file); + return 0; +} + +/** + * Make the full URL for the paste. + * id: the ID of the paste. + * -> a pointer to the URL. Needs to be freed. + */ +char *create_url(char *id) +{ + /* calculate the length of the URL */ + /* the 3 characters added are the trailing slash, newline and null byte */ + int length = strlen(id) + strlen(settings.url) + 3; + + /* allocate a buffer to store the URL */ + char *buffer; + if ((buffer = malloc(length * sizeof(char))) == NULL) + return NULL; + + /* set the buffer to the actual URL */ + snprintf(buffer, length, "%s/%s\n", settings.url, id); + + return buffer; +} diff --git a/bin.h b/bin.h new file mode 100644 index 0000000..2947615 --- /dev/null +++ b/bin.h @@ -0,0 +1,25 @@ +/* + * bin.h + * bin.c header declarations. + * + * Copyright (c) 2022 + * Tom MTT. + * + * This file is licensed under the 3-Clause BSD License. + * You should have received a copy of the 3-Clause BSD License + * along with this program. If not, see + * . + */ + +#ifndef _BIN_H_ +#define _BIN_H_ + +#include "feuille.h" + +int paste_exists(char *); +int write_paste(char *, char *); + +char *generate_id(int); +char *create_url(char *); + +#endif diff --git a/feuille.c b/feuille.c new file mode 100644 index 0000000..7a2dd8f --- /dev/null +++ b/feuille.c @@ -0,0 +1,402 @@ +/* + * feuille.c + * Main source file. + * + * Copyright (c) 2022 + * Tom MTT. + * + * This file is licensed under the 3-Clause BSD License. + * You should have received a copy of the 3-Clause BSD License + * along with this program. If not, see + * . + */ + +#define _DEFAULT_SOURCE + +#include "feuille.h" + +#include /* for errno, ERANGE, EFBIG, ENOENT */ +#include /* for USHRT_MAX, ULONG_MAX, CHAR_MAX, PATH_MAX, UCHA... */ +#include /* for NULL, setlocale, LC_ALL */ +#include /* for getpwnam, passwd */ +#include /* for freopen, puts, stderr, stdin, stdout */ +#include /* for strtoll, exit, free, realpath, srand */ +#include /* for strerror, strlen */ +#include /* for mkdir */ +#include /* for wait */ +#include /* for syslog, openlog, LOG_WARNING, LOG_NDELAY, LOG_... */ +#include /* for time */ +#include /* for fork, access, chdir, chown, chroot, close, getpid */ + +#include "arg.h" /* for EARGF, ARGBEGIN, ARGEND */ +#include "bin.h" /* for create_url, generate_id, write_paste */ +#include "server.h" /* for send_response, accept_connection, close_connec... */ +#include "util.h" /* for verbose, die, error */ + +char *argv0; + +/* default settings */ +Settings settings = { + .address = "0.0.0.0", + .url = "http://localhost", + .output = "/var/www/htdocs/feuille", + .user = "www", + + .id_length = 4, + .worker_count = 1, + .port = 8888, + .timeout = 4, + .max_size = 2097152, /* = 2MiB = 1024 * 1024 * 2 */ + .buffer_size = 131072, /* = 128KiB = 1024 * 128 */ + + .verbose = 0, + .foreground = 0 +}; + +/* functions declarations */ +static void usage(int exit_code); +static void version(void); + +/** + * Display feuille's basic usage. + * exit_code: the exit code to be used. + */ +void usage(int exit_code) +{ + die(exit_code, "usage: %s [-abfhiopstuUvVw]\n" + " see `man feuille'.\n", argv0); +} + +/** + * Display feuille's author(s) and version. + */ +void version(void) +{ + die(0, "%s %s by Tom MTT. \n", argv0, VERSION); +} + +/** + * Feuille's main function. + * argc: the argument count. + * argv: the argument values. + * -> an exit code. + */ +int main(int argc, char *argv[]) +{ + /* pledge stage 1 */ + #ifdef __OpenBSD__ + pledge("stdio rpath wpath cpath inet chown getpw proc id", "stdio wpath inet"); + #endif + + /* locale */ + setlocale(LC_ALL, ""); + + /* syslog setup */ + openlog("feuille", LOG_NDELAY | LOG_PERROR, LOG_USER); + + /* settings */ + long long tmp; + + /* set number of workers */ + if ((tmp = sysconf(_SC_NPROCESSORS_ONLN)) > 0 && tmp <= USHRT_MAX) + settings.worker_count = tmp; + + ARGBEGIN { + case 'a': + /* set address */ + settings.address = EARGF(usage(1)); + break; + + case 'b': + /* set buffer size */ + tmp = strtoll(EARGF(usage(1)), NULL, 10); + + if (tmp <= 0 || tmp > ULONG_MAX || errno == ERANGE) + die(ERANGE, "invalid buffer size.\n" + "see `man feuille'.\n"); + + settings.buffer_size = tmp; + break; + + case 'f': + /* enable foreground execution */ + settings.foreground = 1; + break; + + case 'h': + /* get help */ + usage(0); + break; + + case 'i': + /* set id length */ + tmp = strtoll(EARGF(usage(1)), NULL, 10) + 1; + + if (tmp - 1 < 4 || tmp > UCHAR_MAX || errno == ERANGE) + die(ERANGE, "invalid id length.\n" + "see `man feuille'.\n"); + + settings.id_length = tmp; + break; + + case 'o': + /* set output folder */ + settings.output = EARGF(usage(1)); + break; + + case 'p': + /* set port */ + tmp = strtoll(EARGF(usage(1)), NULL, 10); + + if (tmp <= 0 || tmp > USHRT_MAX || errno == ERANGE) + die(ERANGE, "invalid port.\n" + "see `man feuille'.\n"); + + settings.port = tmp; + break; + + case 's': + /* set max size */ + tmp = strtoll(EARGF(usage(1)), NULL, 10); + + if (tmp <= 0 || tmp > ULONG_MAX || errno == ERANGE) + die(ERANGE, "invalid maximum size.\n" + "see `man feuille'.\n"); + + settings.max_size = tmp; + break; + + case 't': + /* set timeout */ + tmp = strtoll(EARGF(usage(1)), NULL, 10); + + if (tmp < 0 || tmp > UINT_MAX || errno == ERANGE) + die(ERANGE, "invalid timeout.\n" + "see `man feuille'.\n"); + + settings.timeout = tmp; + break; + + case 'u': + /* set user */ + settings.user = EARGF(usage(1)); + break; + + case 'U': + /* set url */ + settings.url = EARGF(usage(1)); + + if (settings.url[strlen(settings.url) - 1] == '/') + settings.url[strlen(settings.url) - 1] = 0; + + break; + + case 'v': + /* enable verbose mode */ + if (settings.verbose == CHAR_MAX) + die(ERANGE, "why? just why?\n" + "please see `man feuille' and go touch grass.\n"); + + settings.verbose++; + break; + + case 'V': + /* get version */ + version(); + break; + + case 'w': + /* set worker count */ + tmp = strtoll(EARGF(usage(1)), NULL, 10); + + if (tmp <= 0 || tmp > USHRT_MAX || errno == ERANGE) + die(ERANGE, "invalid worker count.\n" + "see `man feuille'.\n"); + + settings.worker_count = tmp; + break; + + default: + usage(1); + } ARGEND; + + if (argc != 0) + usage(1); + + /* output folder checks */ + char path[PATH_MAX]; + + if (mkdir(settings.output, 0755) == 0) + verbose(2, "creating folder `%s'...", settings.output); + + if (realpath(settings.output, path) == NULL) + die(errno, "Could not get real path of directory `%s': %s\n", settings.output, strerror(errno)); + + if (access(path, W_OK) != 0) + die(errno, "Cannot write to directory `%s': %s\n", path, strerror(errno)); + + chdir(path); + + /* server socket creation (before dropping root permissions) */ + verbose(1, "initializing server socket..."); + + int server; + if ((server = initialize_server()) == -1) + die(errno, "Failed to initialize server socket: %s\n", strerror(errno)); + + /* chroot and drop root permissions */ + if (getuid() == 0) { + if (strlen(settings.user) == 0) + settings.user = "nobody"; + + verbose(2, "getting uid and gid of user `%s'...", settings.user); + + struct passwd *user; + if ((user = getpwnam(settings.user)) == NULL) + die(1, "User `%s' doesn't exist\n", settings.user); + + int uid = user->pw_uid; + int gid = user->pw_gid; + + verbose(2, "setting owner of `%s' to `%s'...", path, settings.user); + chown(path, uid, gid); + + /* chroot */ + verbose(2, "chroot'ing into `%s'...", path); + chroot(path); + + /* privileges drop */ + verbose(2, "dropping root privileges..."); + setgid(gid); + setuid(uid); + + } else { + puts(""); + syslog(LOG_WARNING, "running as non-root user."); + syslog(LOG_WARNING, "`chroot' and user switching have been disabled."); + puts(""); + } + + /* run feuille in the background */ + if (!settings.foreground) { + verbose(1, "making feuille run in the background..."); + verbose(2, "closing input / output file descriptors..."); + + int pid; + if ((pid = fork()) < 0) + exit(1); + + else if (pid > 0) + exit(0); + + if (setsid() < 0) + exit(1); + + freopen("/dev/null", "r", stdin); + freopen("/dev/null", "w", stdout); + freopen("/dev/null", "w", stderr); + } + + /* pledge stage 2 */ + #ifdef __OpenBSD__ + pledge("stdio proc inet", NULL); + #endif + + /* create a thread pool for incoming connections */ + verbose(1, "initializing worker pool..."); + + int pid; + for (int i = 1; i <= settings.worker_count; i++) { + if ((pid = fork()) == 0) { + verbose(2, " worker n. %d...", i); + + pid = getpid(); + + /* feed the random number god */ + srand(time(0) + pid); + + /* accept loop */ + int connection; + while ((connection = accept_connection(server))) { + verbose(1, "--- new incoming connection. connection ID: %d:%d ---", pid, time(0)); + + char *paste = NULL; + char *id = NULL; + char *url = NULL; + + /* read paste from connection */ + verbose(1, "reading paste from incoming connection..."); + + if ((paste = read_paste(connection)) != NULL) { + /* generate random ID */ + verbose(1, "done."); + verbose(2, "generating a random ID..."); + + if ((id = generate_id(settings.id_length)) != NULL) { + /* write paste to disk */ + verbose(2, "done."); + verbose(1, "writing paste `%s' to disk...", id); + + if (write_paste(paste, id) == 0) { + /* create URL */ + verbose(1, "done."); + verbose(2, "making the right URL..."); + + if ((url = create_url(id)) != NULL) { + verbose(2, "done.", url); + verbose(1, "sending the link to the client..."); + + send_response(connection, url); + + verbose(1, "All done."); + } else { + error("error while making a valid URL."); + send_response(connection, "Could not create your paste URL.\nPlease try again later.\n"); + } + } else { + error("error while writing paste to disk."); + send_response(connection, "Could not write your paste to disk.\nPlease try again later.\n"); + } + } else { + error("error while generating a random ID."); + send_response(connection, "Could not generate your paste ID.\nPlease try again later.\n"); + } + } else { + if (errno == EFBIG) + send_response(connection, "File too big.\n"); + + if (errno == ENOENT) + send_response(connection, "Timeout'd.\n"); + + error("error %d while reading paste from incoming connection.", errno); + } + + /* free resources */ + free(paste); + free(id); + free(url); + + /* close connection */ + close_connection(connection); + } + + } else if (pid < 0) + die(errno, "Could not initialize worker n. %d: %s\n", i, strerror(errno)); + } + + /* pledge stage 3 */ + #ifdef __OpenBSD__ + pledge("stdio", NULL); + #endif + + sleep(1); + + verbose(1, "all workers have been initialized."); + verbose(1, "beginning to accept incoming connections."); + + /* wait for children to finish */ + while(wait(0) > 0); + close(server); + + return 0; +} diff --git a/feuille.h b/feuille.h new file mode 100644 index 0000000..fa87e0c --- /dev/null +++ b/feuille.h @@ -0,0 +1,36 @@ +/* + * feuille.h + * feuille.c header declarations. + * + * Copyright (c) 2022 + * Tom MTT. + * + * This file is licensed under the 3-Clause BSD License. + * You should have received a copy of the 3-Clause BSD License + * along with this program. If not, see + * . + */ + +#ifndef _FEUILLE_H_ +#define _FEUILLE_H_ + +typedef struct Settings { + char *address; + char *url; + char *output; + char *user; + + unsigned char id_length; + unsigned short worker_count; + unsigned short port; + unsigned int timeout; /* seconds */ + unsigned long max_size; /* bytes */ + unsigned long buffer_size; /* bytes */ + + char verbose; + char foreground; +} Settings; + +extern Settings settings; + +#endif diff --git a/server.c b/server.c new file mode 100644 index 0000000..3fa94f1 --- /dev/null +++ b/server.c @@ -0,0 +1,240 @@ +/* + * server.c + * Server handling. + * + * Copyright (c) 2022 + * Tom MTT. + * + * This file is licensed under the 3-Clause BSD License. + * You should have received a copy of the 3-Clause BSD License + * along with this program. If not, see + * . + */ + +#include "server.h" + +#include /* for inet_pton */ +#include /* for errno, EFBIG, ENOENT */ +#include /* for sockaddr_in, sockaddr_in6, htons */ +#include /* for puts */ +#include /* for free, NULL, malloc, realloc */ +#include /* for strcmp, strlen */ +#include /* for bzero */ +#include /* for syslog, LOG_WARNING */ +#include /* for setsockopt, bind, socket, AF_INET */ +#include /* for timeval */ +#include /* for close */ + +#include "feuille.h" /* for Settings, settings */ +#include "util.h" /* for verbose */ + +/** + * Initialize the server socket. + * -> the actual socket. + */ +int initialize_server() +{ + int server; + + /* initialize socket IPv4 and IPv6 addresses */ + verbose(3, "initializing address structs..."); + + struct sockaddr_in server_address_v4; + bzero(&server_address_v4, sizeof(server_address_v4)); + + int ipv6_only = 1; + struct sockaddr_in6 server_address_v6; + bzero(&server_address_v6, sizeof(server_address_v6)); + + if (strcmp(settings.address, "*") == 0) { + settings.address = "::"; + ipv6_only = 0; + } + + /* dirty hack to detect and convert IPv4 / IPv6 addresses */ + verbose(3, "detecting address family..."); + + if (inet_pton(AF_INET, settings.address, &server_address_v4.sin_addr) == 1) { + verbose(3, "IPv4 address detected."); + + /* set socket family and port */ + server_address_v4.sin_family = AF_INET; + server_address_v4.sin_port = htons(settings.port); + + /* create socket */ + verbose(3, "creating server socket..."); + + if ((server = socket(AF_INET, SOCK_STREAM, 0)) < 0) + return -1; + + /* set socket options */ + verbose(3, "setting socket options..."); + + /* reuse address when restarting feuille */ + verbose(3, " SO_REUSEADDR..."); + + if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)) < 0) + return -1; + + /* bind address and port */ + verbose(3, "binding address on the socket..."); + + if (bind(server, (struct sockaddr *)&server_address_v4, sizeof(server_address_v4)) < 0) + return -1; + + } else if (inet_pton(AF_INET6, settings.address, &server_address_v6.sin6_addr) == 1) { + verbose(3, "IPv6 address detected."); + + /* set socket family and port */ + server_address_v6.sin6_family = AF_INET6; + server_address_v6.sin6_port = htons(settings.port); + + /* create socket */ + verbose(3, "creating server socket..."); + + if ((server = socket(AF_INET6, SOCK_STREAM, 0)) < 0) + return -1; + + /* set socket options */ + verbose(3, "setting socket options..."); + + /* reuse address when restarting feuille */ + verbose(3, " SO_REUSEADDR..."); + + if (setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)) < 0) + return -1; + + /* Enable dual-stack mode on supported platforms */ + #ifndef __OpenBSD__ + verbose(3, " IPV6_V6ONLY..."); + if (setsockopt(server, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, sizeof(ipv6_only)) < 0) + return -1; + #else + if (ipv6_only == 0) { + puts(""); + syslog(LOG_WARNING, "dual-stack mode is disabled on OpenBSD."); + syslog(LOG_WARNING, "feuille will only listen on the `::' IPv6 address."); + puts(""); + } + #endif + + /* bind address and port */ + verbose(3, "binding address on the socket..."); + + if (bind(server, (struct sockaddr *)&server_address_v6, sizeof(server_address_v6)) < 0) + return -1; + + } else + return -1; + + /* start listening to incoming connections */ + verbose(3, "starting to listen on the socket..."); + if (listen(server, 1) < 0) + return -1; + + return server; +} + +/** + * Accept incoming connections. + * socket: the server socket. + * -> the socket associated with the connection. + */ +int accept_connection(int socket) +{ + /* accept the connection */ + /* TODO: maybe retrieve IP address for logging? *maybe* */ + int connection = accept(socket, (struct sockaddr *)NULL, NULL); + + /* set the timeout for the connection */ + struct timeval timeout = { settings.timeout, 0 }; + + if (setsockopt(connection, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) + return -1; + + if (setsockopt(connection, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) + return -1; + + return connection; +} + +/** + * Close a connection. + * connection: the socket associated with the connection. + */ +void close_connection(int connection) +{ + /* prevent reading / writing to the socket */ + shutdown(connection, SHUT_RDWR); + + /* close the socket */ + close(connection); +} + +/** + * Read the incoming data from a connection. + * connection: the socket associated with the connection. + * -> the string containing all data sent, or NULL if an error occured. Needs to be freed. + */ +char *read_paste(int connection) +{ + unsigned long buffer_size = settings.buffer_size; + unsigned long total_size = 0; + + /* allocate buffer to store the data */ + char *buffer; + if ((buffer = malloc((buffer_size + 1) * sizeof(char))) == NULL) + return NULL; + + /* read all data until EOF is received, or max file size is reached, or the socket timeouts... */ + /* each time, the data is appended to the buffer, once it's been reallocated a larger size */ + long size; + while ((size = recv(connection, buffer + total_size, buffer_size - total_size, 0)) > 0) { + total_size += size; + + /* have we reached max file size? */ + if (total_size >= settings.max_size) { + /* yup, free the buffer and return an error */ + free(buffer); + errno = EFBIG; + return NULL; + } + + /* have we reached the end of the buffer? */ + if (total_size == buffer_size) { + /* yup, increase the buffer size */ + buffer_size += settings.buffer_size; + + /* reallocate the buffer with a larger size */ + void *tmp; + if ((tmp = realloc(buffer, (buffer_size + 1) * sizeof(char))) == NULL) { + free(buffer); + return NULL; + } + + buffer = tmp; + } + } + + /* is the buffer empty? */ + if (total_size == 0) { + /* yup, free the buffer and return an error */ + free(buffer); + errno = ENOENT; + return NULL; + } + + /* end the buffer with a null byte */ + buffer[total_size] = 0; + return buffer; +} + +/** + * Send a response to the client. + * connection: the socket associated with the connection. + * data: the string to be sent. + * -> -1 on error, number of bytes sent on success. + */ +int send_response(int connection, char *data) { + return send(connection, data, strlen(data), 0); +} diff --git a/server.h b/server.h new file mode 100644 index 0000000..b5aa896 --- /dev/null +++ b/server.h @@ -0,0 +1,27 @@ +/* + * server.h + * server.c header declarations. + * + * Copyright (c) 2022 + * Tom MTT. + * + * This file is licensed under the 3-Clause BSD License. + * You should have received a copy of the 3-Clause BSD License + * along with this program. If not, see + * . + */ + +#ifndef _SERVER_H_ +#define _SERVER_H_ + +#include "feuille.h" + +int initialize_server(); + +int accept_connection(int); +void close_connection(int); + +char *read_paste(int); +int send_response(int, char *); + +#endif diff --git a/util.c b/util.c new file mode 100644 index 0000000..aef31f5 --- /dev/null +++ b/util.c @@ -0,0 +1,84 @@ +/* + * util.c + * Common utils. + * + * Copyright (c) 2022 + * Tom MTT. + * + * This file is licensed under the 3-Clause BSD License. + * You should have received a copy of the 3-Clause BSD License + * along with this program. If not, see + * . + */ + +#define _DEFAULT_SOURCE + +#include "util.h" + +#include /* for va_end, va_list, va_start */ +#include /* for snprintf, vsnprintf, vfprintf, BUFSIZ, stderr */ +#include /* for exit */ +#include /* for syslog, LOG_DEBUG, LOG_ERR */ +#include /* for getpid */ + +#include "feuille.h" /* for Settings, settings */ + +/* reuse that buffer for syslogs */ +char syslog_buffer[BUFSIZ]; + +/** + * Die with an error message. + * exit_code: the exit code to be used. + * string: the string to be displayed. Printf format is supported. + */ +void die(int exit_code, char *string, ...) +{ + va_list ap; + + va_start(ap, string); + vfprintf(stderr, string, ap); + va_end(ap); + + exit(exit_code); +} + +/** + * Send an error message to syslog. + * string: the string to be displayed. Printf format is supported. + */ +void error(char *string, ...) +{ + va_list ap; + va_start(ap, string); + + /* prepend the PID of the current thread */ + int length = snprintf(syslog_buffer, sizeof(syslog_buffer), "ERROR[%d]: ", getpid()); + + /* send the string to syslog */ + vsnprintf(syslog_buffer + length, sizeof(syslog_buffer) - length, string, ap); + syslog(LOG_ERR, "%s", syslog_buffer); + + va_end(ap); +} + +/** + * Send a message to syslog if in verbose mode. + * string: the string to be displayed. Printf format is supported. + */ +void verbose(int level, char *string, ...) +{ + if (settings.verbose < level) + return; + + va_list ap; + va_start(ap, string); + + /* prepend the PID of the current thread */ + int length = snprintf(syslog_buffer, sizeof(syslog_buffer), "DEBUG%d[%d]: ", level, getpid()); + + /* send the string to syslog */ + vsnprintf(syslog_buffer + length, sizeof(syslog_buffer) - length, string, ap); + syslog(LOG_DEBUG, "%s", syslog_buffer); + + va_end(ap); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..f074d15 --- /dev/null +++ b/util.h @@ -0,0 +1,21 @@ +/* + * util.h + * util.c header declarations. + * + * Copyright (c) 2022 + * Tom MTT. + * + * This file is licensed under the 3-Clause BSD License. + * You should have received a copy of the 3-Clause BSD License + * along with this program. If not, see + * . + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +void die(int, char *, ...); +void error(char *, ...); +void verbose(int, char *, ...); + +#endif