2022-11-21 10:43:22 +01:00
/*
* 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"
2022-12-11 01:59:08 +01:00
# ifndef COSMOPOLITAN
2022-11-28 20:36:41 +01:00
# include <errno.h> /* for errno, ERANGE, EAGAIN, EFBIG, ENOENT */
2022-11-29 13:25:14 +01:00
# include <grp.h> /* for initgroups */
2022-11-21 10:43:22 +01:00
# 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 */
2022-11-28 20:36:41 +01:00
# include <signal.h> /* for signal, SIGPIPE, SIG_IGN */
# include <stdio.h> /* for puts */
# include <stdlib.h> /* for strtoll, free, realpath, srand */
2022-11-21 10:43:22 +01:00
# 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 */
2022-11-28 20:36:41 +01:00
# include <unistd.h> /* for getuid, access, chdir, chown, chroot, close */
2022-12-11 01:59:08 +01:00
# endif
2022-11-21 10:43:22 +01:00
# 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 = {
2022-12-29 17:05:05 +01:00
. address = " 0.0.0.0 " ,
. url = " http://localhost " ,
. output = " /var/www/feuille " ,
. user = " www " ,
2022-11-21 10:43:22 +01:00
2022-12-29 17:05:05 +01:00
. id_length = 4 ,
. worker_count = 4 ,
. port = 9999 ,
. timeout = 2 ,
. max_size = 1048576 , /* = 1MiB = 1024 * 1024 */
. buffer_size = 131072 , /* = 128KiB = 1024 * 128 */
2022-11-21 10:43:22 +01:00
2022-12-29 17:05:05 +01:00
. verbose = 0 ,
. foreground = 0
2022-11-21 10:43:22 +01:00
} ;
/* functions declarations */
static void usage ( int exit_code ) ;
static void version ( void ) ;
2022-11-29 10:49:39 +01:00
static void accept_loop ( int ) ;
2022-11-21 10:43:22 +01:00
/**
* 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 ) ;
}
2022-11-29 10:53:11 +01:00
/**
* Feuille ' s accept loop .
* server : the server socket .
*/
2022-11-29 10:49:39 +01:00
void accept_loop ( int server )
{
2022-11-29 10:53:11 +01:00
/* get current process' pid */
2022-11-29 10:49:39 +01:00
int pid = getpid ( ) ;
/* feed the random number god */
srand ( time ( 0 ) + pid ) ;
/* accept loop */
int connection ;
while ( ( connection = accept_connection ( server ) ) ) {
2022-12-11 00:41:39 +01:00
/* check if the socket is invalid */
if ( connection = = - 1 ) {
error ( " error while accepting incoming connection: %s " , strerror ( errno ) ) ;
continue ;
}
2022-11-29 10:49:39 +01:00
verbose ( 1 , " --- new incoming connection. connection ID: %d:%d --- " , pid , time ( 0 ) ) ;
2022-11-29 13:57:11 +01:00
unsigned long paste_size = 0 ;
2022-11-29 10:49:39 +01:00
char * paste = NULL ;
char * id = NULL ;
char * url = NULL ;
/* read paste from connection */
verbose ( 1 , " reading paste from incoming connection... " ) ;
2022-11-29 13:57:11 +01:00
if ( ( paste_size = read_paste ( connection , & paste ) ) ! = 0 ) {
2022-11-29 10:49:39 +01:00
/* 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 ) ;
2022-11-29 13:57:11 +01:00
if ( write_paste ( paste , paste_size , id ) = = 0 ) {
2022-11-29 10:49:39 +01:00
/* create URL */
verbose ( 1 , " done. " ) ;
verbose ( 2 , " making the right URL... " ) ;
if ( ( url = create_url ( id ) ) ! = NULL ) {
2022-11-29 10:54:07 +01:00
/* send URL */
2022-11-29 10:49:39 +01:00
verbose ( 2 , " done. " , url ) ;
verbose ( 1 , " sending the link to the client... " ) ;
send_response ( connection , url ) ;
verbose ( 1 , " All done. " ) ;
free ( url ) ;
} else {
error ( " error while making a valid URL. " ) ;
send_response ( connection , " Could not create your paste URL. \n Please try again later. \n " ) ;
}
} else {
error ( " error while writing paste to disk. " ) ;
send_response ( connection , " Could not write your paste to disk. \n Please try again later. \n " ) ;
}
free ( id ) ;
} else {
error ( " error while generating a random ID. " ) ;
send_response ( connection , " Could not generate your paste ID. \n Please try again later. \n " ) ;
}
free ( paste ) ;
} else {
if ( errno = = EFBIG )
send_response ( connection , " Paste too big. \n " ) ;
if ( errno = = ENOENT )
send_response ( connection , " Empty paste. \n " ) ;
if ( errno = = EAGAIN )
send_response ( connection , " Timeout'd. \n " ) ;
error ( " error %d while reading paste from incoming connection. " , errno ) ;
}
/* close connection */
close_connection ( connection ) ;
}
}
2022-11-21 10:43:22 +01:00
/**
* Feuille ' s main function .
* argc : the argument count .
* argv : the argument values .
* - > an exit code .
*/
int main ( int argc , char * argv [ ] )
{
/* locale */
setlocale ( LC_ALL , " " ) ;
2022-11-29 11:07:49 +01:00
/* syslog */
2022-11-29 10:28:56 +01:00
openlog ( " feuille " , LOG_NDELAY | LOG_PERROR , LOG_USER ) ;
2022-11-21 10:43:22 +01:00
2022-11-29 11:07:49 +01:00
/* ignore signals that could kill feuille */
signal ( SIGPIPE , SIG_IGN ) ; /* when send(2) or write(2) fails */
2022-11-21 10:43:22 +01:00
/* settings */
long long tmp ;
/* set number of workers */
2022-11-29 10:27:56 +01:00
if ( ( tmp = sysconf ( _SC_NPROCESSORS_ONLN ) ) > settings . worker_count & & tmp < = USHRT_MAX )
2022-11-21 10:43:22 +01:00
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 ) ;
2022-11-21 20:57:59 +01:00
2022-11-21 10:43:22 +01:00
/* 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 )
2022-11-29 13:28:42 +01:00
die ( errno , " could not get real path of directory `%s': %s. \n " , settings . output , strerror ( errno ) ) ;
2022-11-21 10:43:22 +01:00
if ( access ( path , W_OK ) ! = 0 )
2022-11-29 13:28:42 +01:00
die ( errno , " cannot write to directory `%s': %s. \n " , path , strerror ( errno ) ) ;
2022-11-21 10:43:22 +01:00
chdir ( path ) ;
2022-11-21 20:53:55 +01:00
/* user checks */
2022-11-29 14:31:29 +01:00
uid_t uid = 0 ;
gid_t gid = 0 ;
2022-11-22 06:45:46 +01:00
if ( getuid ( ) = = 0 ) {
if ( strlen ( settings . user ) = = 0 )
settings . user = " nobody " ;
2022-11-21 20:53:55 +01:00
2022-11-22 06:45:46 +01:00
verbose ( 2 , " getting uid and gid of user `%s'... " , settings . user ) ;
2022-11-21 20:53:55 +01:00
2022-11-22 06:45:46 +01:00
struct passwd * user ;
if ( ( user = getpwnam ( settings . user ) ) = = NULL )
2022-11-29 13:28:42 +01:00
die ( 1 , " user `%s' doesn't exist. \n " , settings . user ) ;
2022-11-21 20:53:55 +01:00
2022-11-22 06:45:46 +01:00
uid = user - > pw_uid ;
gid = user - > pw_gid ;
2022-11-29 14:18:38 +01:00
2022-11-22 06:48:00 +01:00
} else {
syslog ( LOG_WARNING , " running as non-root user. " ) ;
syslog ( LOG_WARNING , " `chroot' and user switching have been disabled. " ) ;
puts ( " " ) ;
2022-11-22 06:45:46 +01:00
}
2022-11-21 20:53:55 +01:00
2022-11-21 20:57:59 +01:00
2022-11-21 10:43:22 +01:00
/* server socket creation (before dropping root permissions) */
verbose ( 1 , " initializing server socket... " ) ;
int server ;
if ( ( server = initialize_server ( ) ) = = - 1 )
2022-11-29 13:28:42 +01:00
die ( errno , " failed to initialize server socket: %f. \n " , strerror ( errno ) ) ;
2022-11-21 10:43:22 +01:00
2022-11-29 11:07:49 +01:00
2022-11-21 20:53:55 +01:00
/* make feuille run in the background */
if ( ! settings . foreground ) {
verbose ( 1 , " making feuille run in the background... " ) ;
verbose ( 2 , " closing input / output file descriptors... " ) ;
2022-11-21 10:43:22 +01:00
2022-11-21 20:53:55 +01:00
daemon ( 1 , 0 ) ;
}
2022-11-21 10:43:22 +01:00
2022-11-21 20:57:59 +01:00
2022-11-21 20:53:55 +01:00
/* chroot and drop root permissions */
if ( getuid ( ) = = 0 ) {
2022-11-21 10:43:22 +01:00
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... " ) ;
2022-11-29 13:25:14 +01:00
/* switching groups */
if ( setgid ( gid ) ! = 0 | | getgid ( ) ! = gid )
2022-11-29 13:28:42 +01:00
die ( 1 , " could not switch to group for user `%s'. \n " , settings . user ) ;
2022-11-29 13:25:14 +01:00
2022-12-18 23:46:35 +01:00
# ifndef COSMOPOLITAN
2022-12-11 01:59:08 +01:00
/* initgroups doesn't work on cosmopolitan libc yet */
2022-11-29 13:25:14 +01:00
if ( initgroups ( settings . user , gid ) ! = 0 )
2022-11-29 13:28:42 +01:00
die ( 1 , " could not initialize other groups for user `%s'. \n " , settings . user ) ;
2022-12-18 23:46:35 +01:00
# endif
2022-11-29 13:25:14 +01:00
/* switching user */
if ( setuid ( uid ) ! = 0 | | getuid ( ) ! = uid )
2022-11-29 13:28:42 +01:00
die ( 1 , " could not switch to user `%s'. \n " , settings . user ) ;
2022-11-21 10:43:22 +01:00
}
2022-12-11 02:12:30 +01:00
# if defined __OpenBSD__ || defined COSMOPOLITAN
2022-11-29 10:49:39 +01:00
/* OpenBSD-only security measures */
2022-11-21 19:44:41 +01:00
pledge ( " proc stdio rpath wpath cpath inet " , " stdio rpath wpath cpath inet " ) ;
2022-11-29 10:33:15 +01:00
# endif
2022-11-21 10:43:22 +01:00
2022-11-29 11:07:49 +01:00
2022-11-29 13:25:14 +01:00
# ifdef DEBUG
/* do not create a thread pool if in DEBUG mode */
verbose ( 1 , " running in DEBUG mode, won't create a worker pool. " ) ;
accept_loop ( server ) ;
# else
2022-11-21 10:43:22 +01:00
/* create a thread pool for incoming connections */
verbose ( 1 , " initializing worker pool... " ) ;
2022-11-29 10:49:39 +01:00
int pid ;
2022-11-21 10:43:22 +01:00
for ( int i = 1 ; i < = settings . worker_count ; i + + ) {
if ( ( pid = fork ( ) ) = = 0 ) {
verbose ( 2 , " worker n. %d... " , i ) ;
2022-11-29 10:49:39 +01:00
accept_loop ( server ) ;
2022-11-21 10:43:22 +01:00
} else if ( pid < 0 )
2022-11-29 13:28:42 +01:00
die ( errno , " could not initialize worker n. %d: %s. \n " , i , strerror ( errno ) ) ;
2022-11-21 10:43:22 +01:00
}
2022-11-21 20:57:59 +01:00
2022-11-21 10:43:22 +01:00
sleep ( 1 ) ;
verbose ( 1 , " all workers have been initialized. " ) ;
verbose ( 1 , " beginning to accept incoming connections. " ) ;
2022-11-29 11:30:29 +01:00
/* fork again if a child dies */
int status ;
int child_pid ;
while ( ( child_pid = wait ( & status ) ) > 0 ) {
error ( " child %d unexpectedly died with exit code %d. " , child_pid , WEXITSTATUS ( status ) ) ;
/* do not fork if child was KILL'ed */
if ( WTERMSIG ( status ) = = 9 )
continue ;
if ( ( pid = fork ( ) ) = = 0 ) {
accept_loop ( server ) ;
} else if ( pid < 0 )
error ( " could not fork killed child again: " , strerror ( errno ) ) ;
}
2022-11-29 13:25:14 +01:00
# endif
2022-11-29 11:30:29 +01:00
close ( server ) ;
2022-11-21 10:43:22 +01:00
return 0 ;
}