feat: First stable release
This commit is contained in:
		
							parent
							
								
									845677abe1
								
							
						
					
					
						commit
						8453d41a00
					
				
					 10 changed files with 1042 additions and 0 deletions
				
			
		
							
								
								
									
										6
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,6 @@ | |||
| .clangd | ||||
| 
 | ||||
| *.o | ||||
| 
 | ||||
| feuille | ||||
| web/cgi/feuille.cgi | ||||
							
								
								
									
										80
									
								
								arg.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								arg.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <stdlib.h> | ||||
| #include <stddef.h> | ||||
| 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 | ||||
							
								
								
									
										121
									
								
								bin.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								bin.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,121 @@ | |||
| /*
 | ||||
|  * bin.c | ||||
|  *  Pastes handling. | ||||
|  * | ||||
|  * | ||||
|  * Copyright (c) 2022 | ||||
|  *     Tom MTT. <tom@heimdall.pm> | ||||
|  * | ||||
|  * 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 | ||||
|  * <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
 | ||||
|  */ | ||||
| 
 | ||||
| #include "bin.h" | ||||
| 
 | ||||
| #include <stdio.h>    /* for NULL, fclose, fopen, fputs, snprintf, FILE */ | ||||
| #include <stdlib.h>   /* for calloc, free, malloc, rand, realloc        */ | ||||
| #include <string.h>   /* for strlen                                     */ | ||||
| #include <unistd.h>   /* 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; | ||||
| } | ||||
							
								
								
									
										25
									
								
								bin.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								bin.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,25 @@ | |||
| /*
 | ||||
|  * bin.h | ||||
|  *  bin.c header declarations. | ||||
|  * | ||||
|  * Copyright (c) 2022 | ||||
|  *     Tom MTT. <tom@heimdall.pm> | ||||
|  * | ||||
|  * 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 | ||||
|  * <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
 | ||||
|  */ | ||||
| 
 | ||||
| #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 | ||||
							
								
								
									
										402
									
								
								feuille.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								feuille.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,402 @@ | |||
| /*
 | ||||
|  * feuille.c | ||||
|  *  Main source file. | ||||
|  * | ||||
|  * Copyright (c) 2022 | ||||
|  *     Tom MTT. <tom@heimdall.pm> | ||||
|  * | ||||
|  * 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 | ||||
|  * <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
 | ||||
|  */ | ||||
| 
 | ||||
| #define _DEFAULT_SOURCE | ||||
| 
 | ||||
| #include "feuille.h" | ||||
| 
 | ||||
| #include <errno.h>     /* for errno, ERANGE, EFBIG, ENOENT                      */ | ||||
| #include <limits.h>    /* for USHRT_MAX, ULONG_MAX, CHAR_MAX, PATH_MAX, UCHA... */ | ||||
| #include <locale.h>    /* for NULL, setlocale, LC_ALL                           */ | ||||
| #include <pwd.h>       /* for getpwnam, passwd                                  */ | ||||
| #include <stdio.h>     /* for freopen, puts, stderr, stdin, stdout              */ | ||||
| #include <stdlib.h>    /* for strtoll, exit, free, realpath, srand              */ | ||||
| #include <string.h>    /* for strerror, strlen                                  */ | ||||
| #include <sys/stat.h>  /* for mkdir                                             */ | ||||
| #include <sys/wait.h>  /* for wait                                              */ | ||||
| #include <syslog.h>    /* for syslog, openlog, LOG_WARNING, LOG_NDELAY, LOG_... */ | ||||
| #include <time.h>      /* for time                                              */ | ||||
| #include <unistd.h>    /* 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. <tom@heimdall.pm>\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; | ||||
| } | ||||
							
								
								
									
										36
									
								
								feuille.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								feuille.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,36 @@ | |||
| /*
 | ||||
|  * feuille.h | ||||
|  *  feuille.c header declarations. | ||||
|  * | ||||
|  * Copyright (c) 2022 | ||||
|  *     Tom MTT. <tom@heimdall.pm> | ||||
|  * | ||||
|  * 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 | ||||
|  * <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
 | ||||
|  */ | ||||
| 
 | ||||
| #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 | ||||
							
								
								
									
										240
									
								
								server.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										240
									
								
								server.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,240 @@ | |||
| /*
 | ||||
|  * server.c | ||||
|  *  Server handling. | ||||
|  * | ||||
|  * Copyright (c) 2022 | ||||
|  *     Tom MTT. <tom@heimdall.pm> | ||||
|  * | ||||
|  * 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 | ||||
|  * <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
 | ||||
|  */ | ||||
| 
 | ||||
| #include "server.h" | ||||
| 
 | ||||
| #include <arpa/inet.h>              /* for inet_pton                            */ | ||||
| #include <errno.h>                  /* for errno, EFBIG, ENOENT                 */ | ||||
| #include <netinet/in.h>             /* for sockaddr_in, sockaddr_in6, htons     */ | ||||
| #include <stdio.h>                  /* for puts                                 */ | ||||
| #include <stdlib.h>                 /* for free, NULL, malloc, realloc          */ | ||||
| #include <string.h>                 /* for strcmp, strlen                       */ | ||||
| #include <strings.h>                /* for bzero                                */ | ||||
| #include <syslog.h>                 /* for syslog, LOG_WARNING                  */ | ||||
| #include <sys/socket.h>             /* for setsockopt, bind, socket, AF_INET    */ | ||||
| #include <sys/time.h>               /* for timeval                              */ | ||||
| #include <unistd.h>                 /* 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); | ||||
| } | ||||
							
								
								
									
										27
									
								
								server.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								server.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| /*
 | ||||
|  * server.h | ||||
|  *  server.c header declarations. | ||||
|  * | ||||
|  * Copyright (c) 2022 | ||||
|  *     Tom MTT. <tom@heimdall.pm> | ||||
|  * | ||||
|  * 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 | ||||
|  * <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
 | ||||
|  */ | ||||
| 
 | ||||
| #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 | ||||
							
								
								
									
										84
									
								
								util.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								util.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,84 @@ | |||
| /*
 | ||||
|  * util.c | ||||
|  *  Common utils. | ||||
|  * | ||||
|  * Copyright (c) 2022 | ||||
|  *     Tom MTT. <tom@heimdall.pm> | ||||
|  * | ||||
|  * 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 | ||||
|  * <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
 | ||||
|  */ | ||||
| 
 | ||||
| #define _DEFAULT_SOURCE | ||||
| 
 | ||||
| #include "util.h" | ||||
| 
 | ||||
| #include <stdarg.h>   /* for va_end, va_list, va_start                     */ | ||||
| #include <stdio.h>    /* for snprintf, vsnprintf, vfprintf, BUFSIZ, stderr */ | ||||
| #include <stdlib.h>   /* for exit                                          */ | ||||
| #include <syslog.h>   /* for syslog, LOG_DEBUG, LOG_ERR                    */ | ||||
| #include <unistd.h>   /* 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); | ||||
| } | ||||
							
								
								
									
										21
									
								
								util.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								util.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,21 @@ | |||
| /*
 | ||||
|  * util.h | ||||
|  *  util.c header declarations. | ||||
|  * | ||||
|  * Copyright (c) 2022 | ||||
|  *     Tom MTT. <tom@heimdall.pm> | ||||
|  * | ||||
|  * 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 | ||||
|  * <https://basedwa.re/tmtt/feuille/src/branch/main/LICENSE>.
 | ||||
|  */ | ||||
| 
 | ||||
| #ifndef _UTIL_H_ | ||||
| #define _UTIL_H_ | ||||
| 
 | ||||
| void     die(int, char *, ...); | ||||
| void     error(char *, ...); | ||||
| void     verbose(int, char *, ...); | ||||
| 
 | ||||
| #endif | ||||
		Reference in a new issue
	
	 Tom MTT
						Tom MTT