summaryrefslogtreecommitdiff
path: root/src/daemon.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/daemon.c')
-rw-r--r--src/daemon.c551
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 );
+}