diff options
Diffstat (limited to 'src/daemon.c')
-rw-r--r-- | src/daemon.c | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/src/daemon.c b/src/daemon.c new file mode 100644 index 0000000..a635053 --- /dev/null +++ b/src/daemon.c @@ -0,0 +1,551 @@ +#include "port/stdio.h" /* for snprintf */ +#include "port/string.h" /* for strdup */ +#include "port/limits.h" /* for PATH_MAX */ +#include "port/noreturn.h" /* for NORETURN */ + +#include "daemon.h" +#include "log.h" +#include "signals.h" +#include "pidfile.h" + +#include <sys/types.h> /* for pid_t, ssize_t, off_t */ +#include <unistd.h> /* for getppid, geteuid, fork, + chdir, pipe, sysconf */ +#include <stdlib.h> /* for malloc, free */ +#include <errno.h> /* for errno */ +#include <sys/stat.h> /* for umask */ +#include <fcntl.h> /* for O_RDWR */ +#include <grp.h> /* for getgrnam, setgid */ +#include <pwd.h> /* for getpwnam, setuid */ + +static int exit_code_pipe[2] = { -1, -1 }; + +int daemon_parent_pipe[2] = { -1, -1 }; + +#define TERMINATE_EXIT_CODE 99 +#define TERMINATE_PARENT 98 + +struct daemon_t { + daemon_params_t params; /**< the parameters for creation */ + error_t error; /**< error of last start */ + struct pidfile_t pidfile; /**< pid/lock file of the daemon */ + struct group *groupent; /**< group entry */ + struct passwd *userent; /**< password entry */ +}; + +daemon_p daemon_new( daemon_params_t params, + error_t *error ) { + daemon_p d; + + d = (struct daemon_t *)malloc( sizeof ( struct daemon_t ) ); + if( d == NULL ) { + *error = ERR_OUT_OF_MEMORY; + return NULL; + } + + d->error = -1; + d->params = params; + + if( params.daemon_name == NULL ) { + *error = ERR_INVALID_VALUE; + return NULL; + } + + if( params.pid_filename == NULL ) { + pidfile_set_from_daemon_name( &d->pidfile, params.daemon_name ); + } else { + pidfile_set_from_filename( &d->pidfile, params.pid_filename ); + } + + *error = OK; + return d; +} + +void daemon_free( daemon_p d ) { + free( d ); + d = NULL; +} + +static error_t close_fd( int fd ) { + if( close( fd ) < 0 ) { + switch( errno ) { + case EBADF: + /* skip */ + break; + + default: + LOG( LOG_EMERG, "Error while closing file descriptor %d: %s (%d)", + fd, strerror( errno ), errno ); + return ERR_INTERNAL; + } + } + + return OK; +} + +static error_t daemon_close_standard_fds( void ) { + error_t error; + + if( ( error = close_fd( STDIN_FILENO ) ) != OK ) return error; + if( ( error = close_fd( STDOUT_FILENO ) ) != OK ) return error; + if( ( error = close_fd( STDERR_FILENO ) ) != OK ) return error; + + return OK; + +} + +static error_t daemon_close_all_fds( void ) { + long nof_files; + int i; + error_t error; + + nof_files = sysconf( _SC_OPEN_MAX ); + if( nof_files < 0 ) { + LOG( LOG_EMERG, "Unable to retrieve maximal number of files: %s (%d)", + strerror( errno ), errno ); + return ERR_INTERNAL; + } + LOG( LOG_DEBUG, "Closing all filedescriptors up to %ld", nof_files ); + + for( i = 0; i < nof_files; i++ ) { + if( ( i == STDIN_FILENO ) || + ( i == STDOUT_FILENO ) || + ( i == STDERR_FILENO ) || + ( i == daemon_signal_pipe[0] ) || + ( i == daemon_signal_pipe[1] ) || + ( i == daemon_parent_pipe[0] ) || + ( i == daemon_parent_pipe[1] ) || + ( i == exit_code_pipe[0] ) || + ( i == exit_code_pipe[1] ) ) { + continue; + } + if( ( error = close_fd( i ) ) != OK ) return error; + } + + return OK; +} + +static error_t open_null_fd( int must_fd, int flags ) { + int fd; + + fd = open( "/dev/null", flags ); + if( fd < 0 ) { + LOG( LOG_EMERG, "Unable to open fd %d as /dev/null: %s (%d)", + must_fd, strerror( errno ), errno ); + return ERR_INTERNAL; + } + if( fd != must_fd ) { + LOG( LOG_EMERG, "Something is wrong with the file descriptors (expecting %d,got %d)!", + must_fd, fd ); + return ERR_PROGRAMMING; + } + + return OK; +} + +static void atomar_write( int fd, void *data, size_t data_len ) { + ssize_t res; + +atomar_write_again: + res = write( fd, data, data_len ); + if( res < 0 ) { + if( errno == EINTR ) goto atomar_write_again; + LOG( LOG_EMERG, "Error in atomar write to fd %d: %s (%d)", + fd, strerror( errno ), errno ); + } else if( (size_t)res != data_len ) { + LOG( LOG_EMERG, "Unexpected number of octets %zd in atomar write to fd %d (expected %zd)", + res, fd, data_len ); + } +} + +static void atomar_read( int fd, void *data, size_t data_len ) { + ssize_t res; + +atomar_read_again: + res = read( fd, data, data_len ); + if( res < 0 ) { + if( errno == EINTR ) goto atomar_read_again; + LOG( LOG_EMERG, "Error in atmoar read from fd %d: %s (%d)", + fd, strerror( errno ), errno ); + } else if( (size_t)res != data_len ) { + LOG( LOG_EMERG, "Unexpected number of octets %zd in atomar read from fd %d (expected %zd)", + res, fd, data_len ); + } +} + +error_t daemon_start( daemon_p d ) { + pid_t pid; + mode_t mode; + error_t error; + int exit_code; + + /* Our parent is already the init process (which has pid 1 by + * convention), we are already a daemon. This is a programming + * error.. + */ + if( getppid( ) == 1 ) { + LOG( LOG_EMERG, "Already running as daemon!" ); + return( d->error = ERR_PROGRAMMING ); + } + + /* Are we starting as root? If not, bail out, we need root + * permissions because we have to listen to ports below 1024 + * or we must open logfiles/pidfiles with proper permissions. + * Also we must change to the proper running user/group and + * we must be root (uid 0 by convention) for that. + */ + if( geteuid( ) != 0 ) { + LOG( LOG_EMERG, "Unable to start daemon as not root user!" ); + return( d->error = ERR_INVALID_STATE ); + } + + /* We fork so SIGCHLD signals get to the parent and grandfather. + * We don't want that and we are installing empty signal handlers. + */ +#if defined( SIGCHLD ) && defined( SIGCLD ) +#if SIGCLD != SIGCHLD + signal_install_empty( SIGCLD, 0 ); + signal_install_empty( SIGCHLD, 0 ); +#else +#if defined( SIGCHLD ) + signal_install_empty( SIGCHLD, 0 ); +#endif +#if defined( SIGCLD ) + signal_install_empty( SIGCLD, 0 ); +#endif +#endif +#endif + + /* first pipe communicates from parent of daemon and daemon + * itself back to the grand-parent (exit codes). + */ + if( pipe( exit_code_pipe ) < 0 ) { + LOG( LOG_EMERG, "Unable to create exit code pipe: %s (%d)", + strerror( errno ), errno ); + return ( d->error = ERR_INTERNAL ); + } + LOG( LOG_DEBUG, "Created exit code pipe (%d,%d)", exit_code_pipe[0], exit_code_pipe[1] ); + + /* first fork: make sure we are no longer process group leader. + * So we can get our own process group leader by calling setsid + */ + switch( ( pid = fork( ) ) ) { + case -1: + /* error */ + LOG( LOG_EMERG, "Unable to fork the first time: %s", strerror( errno ) ); + return ( d->error = ERR_INTERNAL ); + + case 0: + /* the child becomes the daemon */ + LOG( LOG_DEBUG, "First fork reached" ); + break; + + default: + /* the parent waits till it gets feedback from + * the child (error pipe) + */ + /* TODO: wait for some time for correct exit + * code from the error pipe + */ + LOG( LOG_DEBUG, "Parent after first fork: child is %d", pid ); + return ( d->error = TERMINATE_EXIT_CODE ); + } + + /* second pipe communicates from the daemon back to its parent + * (for termination and cleanup which can only be done as root) + */ + if( pipe( daemon_parent_pipe ) < 0 ) { + LOG( LOG_EMERG, "Unable to create parent pipe: %s (%d)", + strerror( errno ), errno ); + return ( d->error = ERR_INTERNAL ); + } + LOG( LOG_DEBUG, "Created parent pipe (%d,%d)", daemon_parent_pipe[0], daemon_parent_pipe[1] ); + + /* Put the first child in it's own process group and finally detach it + * from its controlling terminal. This ensure we don't get funny + * signals which could kill the daemon. + */ + if( setsid( ) < 0 ) { + /* should actually never fail */ + LOG( LOG_EMERG, "Starting new process group session for the parent of the daemon failed: %s", strerror( errno ) ); + return ERR_INTERNAL; + } + + /* We fork so SIGCHLD signals get to the parent and grandfather. + * We don't want that and we are installing empty signal handlers. + */ +#if defined( SIGCHLD ) && defined( SIGCLD ) +#if SIGCLD != SIGCHLD + signal_install_empty( SIGCLD, 0 ); + signal_install_empty( SIGCHLD, 0 ); +#else +#if defined( SIGCHLD ) + signal_install_empty( SIGCHLD, 0 ); +#endif +#if defined( SIGCLD ) + signal_install_empty( SIGCLD, 0 ); +#endif +#endif +#endif + + /* Now that the parent is process group leader, fork again. This + * way the final child can not inherit a controlling terminal + */ + switch( ( pid = fork( ) ) ) { + case -1: + /* error */ + LOG( LOG_EMERG, "Unable to fork the second time: %s", strerror( errno ) ); + return ( d->error = ERR_INTERNAL ); + + case 0: + /* the child becomes the daemon */ + LOG( LOG_DEBUG, "Second fork reached" ); + break; + + default: + (void)signal_install_handlers_parent( ); + LOG( LOG_DEBUG, "Parent after second fork: child (and daemon) is %d", pid ); + return ( d->error = TERMINATE_PARENT ); + } + + /* Now install signal handlers */ + if( ( error = signal_initialize( ) ) != OK ) + return ( d->error = error ); + if( ( error = signal_install_handlers_daemon( ) ) != OK ) + return ( d->error = error ); + + /* Put the daemon in it's own process group and finally detach it + * from its controlling terminal. This ensure we don't get funny + * signals which could kill the daemon. + */ + if( setsid( ) < 0 ) { + /* should actually never fail */ + LOG( LOG_EMERG, "Starting new process group for daemon session failed: %s", strerror( errno ) ); + return ERR_INTERNAL; + } + + /* Change to the root directory for several reasons: + * - all but the root directory we may want to unmount without + * stopping the daemon + * - we don't have write permissions there so a core dump + * would not result in DoSA + * - non-priviledged users can't do anything in the root + * directory (especially they can't write files there) + */ + if( chdir( "/" ) < 0 ) { + LOG( LOG_EMERG, "Changing to root diretory failed: %s", strerror( errno ) ); + return ERR_INTERNAL; + } + LOG( LOG_DEBUG, "Changed to root directory /" ); + + /* Change the umask to 0133 temporarily so we don't have to + * chmod the logfiles, pidfiles later. + * + * Create pid files with permissions 0644 (rw-r--r--) + */ + mode = umask( 0133 ); + LOG( LOG_DEBUG, "Switched umask from %04o to %04o", mode, 0133 ); + + /* check if another daemon is already running, if yes, bail out */ + if( is_daemon_running( &d->pidfile, &pid, &error ) || ( error != OK ) ) { + if( error == OK ) { + LOG( LOG_EMERG, "Another daemon is already running with pid '%d', can't start!", pid ); + } + return ERR_INTERNAL; + } + + /* we loose logfile and syslog file descriptors anyway, so close + * them here */ + closelogtosyslog( ); + closelogtofile( ); + closelogtostderr( ); + + /* close all filedescriptors */ + if( daemon_close_all_fds( ) != OK ) + return ERR_INTERNAL; + + /* reopen the logs to the logfile and syslog (not stderr) */ + reopenlogtosyslog( ); + reopenlogtofile( ); + + /* create and lock the pidfile now, write pid of daemon into it */ + if( pidfile_create( &d->pidfile ) != OK ) { + return ERR_INTERNAL; + } + + /* Install the final permissions for new files. The reason is + * simple: We don't want to create files in a defective part + * of the daemon, especially third party code. We don't know + * what kind of plugins we gonna integrate.. + * + * Make sure that no files can be written except the ones belonging + * to us (because now we are no longer root) and that no files can + * be created with executable permission (code injection!) + * + * New files always get the permission 640 (rw-r-----) + */ + mode = umask( 0137 ); + LOG( LOG_DEBUG, "Switched umask from %04o to %04o", mode, 0137 ); + + /* drop permissions to a non-priviledged user now */ + if( ( d->params.group_name != NULL ) && ( d->params.user_name != NULL ) ) { + errno = 0; + d->groupent = getgrnam( d->params.group_name ); + if( d->groupent == NULL ) { + if( errno == 0 ) { + LOG( LOG_EMERG, "No group '%s' found", d->params.group_name ); + } else { + LOG( LOG_EMERG, "Unable to retrieve group information for group '%s': %s (%d)", + d->params.group_name, strerror( errno ), errno ); + } + return ERR_INTERNAL; + } + + errno = 0; + d->userent = getpwnam( d->params.user_name ); + if( d->userent == NULL ) { + if( errno == 0 ) { + LOG( LOG_EMERG, "No user '%s' found", d->params.user_name ); + } else { + LOG( LOG_EMERG, "Unable to retrieve user information for user '%s': %s (%d)", + d->params.user_name, strerror( errno ), errno ); + } + return ERR_INTERNAL; + } + + if( setgid( d->userent->pw_gid ) < 0 ) { + LOG( LOG_EMERG, "Setting unprivileged group failed: %s (%d)", + strerror( errno ), errno ); + return ERR_INTERNAL; + } + + if( setuid( d->userent->pw_uid ) < 0 ) { + LOG( LOG_EMERG, "Setting unprivileged user failed: %s (%d)", + strerror( errno ), errno ); + return ERR_INTERNAL; + } + + /* TODO: setsid and setting all groups of the user */ + /* TODO: also check the permissions of the home dir + * of the unpriviledged user */ + + LOG( LOG_DEBUG, "Switched to user '%s' (%d) and group '%s' (%d)", + d->params.user_name, d->userent->pw_uid, + d->params.group_name, d->userent->pw_gid ); + } + + /* assign stdin/stdout/stderr again to something harmless + * (/dev/null). Do this as late as possible, so somebody + * starting the daemon by hand still gets error messages + * on stderr. + */ + if( ( error = daemon_close_standard_fds( ) ) != OK ) return error; + if( ( error = open_null_fd( STDIN_FILENO, O_RDONLY ) ) != OK ) return error; + if( ( error = open_null_fd( STDOUT_FILENO, O_WRONLY ) ) != OK ) return error; + if( ( error = open_null_fd( STDERR_FILENO, O_WRONLY ) ) != OK ) return error; + + /* signal the grand-parent process, that he can return 0 to + * the calling shell/script + */ + exit_code = EXIT_SUCCESS; + atomar_write( exit_code_pipe[1], &exit_code, sizeof( int ) ); + + return ( d->error = OK ); +} + +NORETURN void daemon_exit( daemon_p d ) { + int exit_code; + int pid_file_fd; + + LOG( LOG_DEBUG, "daemon_exit called with error %d", d->error ); + + switch( d->error ) { + case TERMINATE_EXIT_CODE: + /* wait here for exit code in exit_code_pipe + * so we can return the correct exit code to + * the calling script or shell + */ + LOG( LOG_DEBUG, "Waiting on exit_code pipe for exit code" ); + + atomar_read( exit_code_pipe[0], &exit_code, sizeof( int ) ); + + LOG( LOG_DEBUG, "Terminating grand-parent of daemon with code %d (PID: %lu)", + exit_code, getpid( ) ); + + signal_terminate( ); + + /* Do not close file descriptors of the child! + * We are the father or grand-father process + */ + _exit( exit_code ); + + case TERMINATE_PARENT: + /* TODO: handle houskeeping here which needs + * root rights (log rotation?) + */ + + /* wait for termination signal */ + LOG( LOG_DEBUG, "Waiting on parent pipe for termination signal" ); + + atomar_read( daemon_parent_pipe[0], &pid_file_fd, sizeof( int ) ); + LOG( LOG_DEBUG, "Parent got termination (pidfile fd: %d).. cleaning up now (PID: %lu)", + pid_file_fd, getpid( ) ); + + /* we need root permissions for that! */ + d->pidfile.fd = pid_file_fd; + (void)pidfile_remove( &d->pidfile ); + + signal_terminate( ); + + LOG( LOG_DEBUG, "Terminating parent of daemon pid file fd %d (PID %lu)", + pid_file_fd, getpid( ) ); + + /* exit code is irrelevant here.. */ + exit( EXIT_SUCCESS ); + + case OK: + /* close and unlock the pidfile here */ + (void)pidfile_release( &d->pidfile ); + + /* This is the daemon terminating, signal the + * parent that we are terminating + */ + atomar_write( daemon_parent_pipe[1], &d->pidfile.fd, sizeof( int ) ); + + signal_terminate( ); + + LOG( LOG_DEBUG, "Terminating daemon (PID: %lu)", getpid( ) ); + + /* exit code is irrelevant here */ + _exit( EXIT_SUCCESS ); + + default: + /* no exit_code_pipe exists yet, we are before the + * first fork, so terminate with proper exit code + */ + if( exit_code_pipe[1] == -1 ) { + exit( EXIT_FAILURE ); + } + + /* This is an error case, communicate exit code back + * to grand-parent + */ + exit_code = EXIT_FAILURE; + atomar_write( exit_code_pipe[1], &exit_code, sizeof( int ) ); + + /* also terminate the parent of the daemon */ + atomar_write( daemon_parent_pipe[1], &d->pidfile.fd, sizeof( int ) ); + + signal_terminate( ); + + LOG( LOG_DEBUG, "Terminating daemon (PID: %lu)", getpid( ) ); + + /* exit code is irrelevant here */ + _exit( EXIT_SUCCESS ); + } + + /* silence up some versions of the GCC compiler ("noreturn function returns"), + * because they errornously tread _exit as a return, which is clearly wrong + * (experienced with gcc 3.3.5 on OpenBSD 4.3) */ + exit( EXIT_SUCCESS ); +} |