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