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