diff --git a/Makefile.am b/Makefile.am index 1852983..b7aa1a3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,7 @@ ## along with this program. If not, see . ## ## Authors: Daniel Kopecek +## Jiri Vymazal ## SUBDIRS=src/Tests/ diff --git a/doc/usbguard-daemon.8 b/doc/usbguard-daemon.8 index 18c72fe..dfdb285 100644 --- a/doc/usbguard-daemon.8 +++ b/doc/usbguard-daemon.8 @@ -1,4 +1,4 @@ -.\" Automatically generated by Pandoc 1.17.0.3 +.\" Automatically generated by Pandoc 1.19.1 .\" .TH "USBGUARD\-DAEMON" "8" "June 2016" "" "" .hy @@ -25,6 +25,11 @@ Enable debugging messages in the log. .RS .RE .TP +.B \f[B]\-f\f[] +Enable classical daemon behavior (fork at start, sysV compliant). +.RS +.RE +.TP .B \f[B]\-s\f[] Log to syslog. .RS @@ -41,7 +46,8 @@ Log to a file at \f[I]path\f[]. .RE .TP .B \f[B]\-p\f[] <\f[I]path\f[]> -Write PID to a file at \f[I]path\f[]. +Write PID to a file at \f[I]path\f[] (default: +\f[I]/var/run/usbguard.pid\f[]). .RS .RE .TP diff --git a/doc/usbguard-daemon.8.md b/doc/usbguard-daemon.8.md index 3e2fcaf..581613d 100644 --- a/doc/usbguard-daemon.8.md +++ b/doc/usbguard-daemon.8.md @@ -19,6 +19,9 @@ The **usbguard-daemon** is the main component of the USBGuard software framework **-d** : Enable debugging messages in the log. +**-f** +: Enable classical daemon behavior (fork at start, sysV compliant). + **-s** : Log to syslog. @@ -29,7 +32,7 @@ The **usbguard-daemon** is the main component of the USBGuard software framework : Log to a file at *path*. **-p** <*path*> -: Write PID to a file at *path*. +: Write PID to a file at *path* (default: */var/run/usbguard.pid*). **-c** <*path*> : Load configuration from a file at *path* (default: */etc/usbguard/usbguard-daemon.conf*). diff --git a/src/Common/Utility.cpp b/src/Common/Utility.cpp index f84d2a8..237acfb 100644 --- a/src/Common/Utility.cpp +++ b/src/Common/Utility.cpp @@ -42,56 +42,6 @@ namespace usbguard { - void daemonize() - { - const ::pid_t pid = fork(); - - switch(pid) { - case 0: /* child */ - break; - case -1: /* error */ - ::exit(EXIT_FAILURE); - default: /* parent */ - ::exit(EXIT_SUCCESS); - } - // - // Decouple from parent environment - // - chdir to / - // - create new process session - // - reset umask - // - cleanup file descriptors - // - ??? - // - consider using libdaemon - // - if (::chdir("/") != 0) { - ::exit(EXIT_FAILURE); - } - const ::pid_t sid = ::setsid(); - if (sid != 0) { - ::exit(EXIT_FAILURE); - } - ::umask(::umask(077)|022); - struct rlimit rlim; - if (::getrlimit(RLIMIT_NOFILE, &rlim) != 0) { - ::exit(EXIT_FAILURE); - } - const int maxfd = (rlim.rlim_max == RLIM_INFINITY ? 1024 : rlim.rlim_max); - for (int fd = 0; fd < maxfd; ++fd) { - ::close(fd); - } - return; - } - - bool writePID(const std::string& filepath) - { - std::ofstream pidstream(filepath, std::ios_base::trunc); - if (!pidstream) { - return false; - } - pidstream << numberToString(getpid()) << std::endl; - return true; - } - static void runCommandExecChild(const std::string& path, const std::vector& args) { struct rlimit rlim; diff --git a/src/Common/Utility.hpp b/src/Common/Utility.hpp index f722b22..54e1ea1 100644 --- a/src/Common/Utility.hpp +++ b/src/Common/Utility.hpp @@ -41,25 +41,6 @@ namespace usbguard { /** - * Create a background process. - * - * Performs the following actions: - * 1) fork a new process (parent process exists with 0) - * 2) chdir to / - * 3) creates a new process session - * 4) resets umask - * 5) closes all file descriptors - * 6) Reinitialize logging for the child - */ - void daemonize(void); - - /** - * Writes the current PID to a file at filepath. - * Returns true on success, otherwise returns false. - */ - bool writePID(const std::string& filepath); - - /** * Wrappers for the __builtin_expect function. */ #if defined(__GNUC__) diff --git a/src/Daemon/Daemon.cpp b/src/Daemon/Daemon.cpp index b317c85..2a9a37c 100644 --- a/src/Daemon/Daemon.cpp +++ b/src/Daemon/Daemon.cpp @@ -15,6 +15,7 @@ // along with this program. If not, see . // // Authors: Daniel Kopecek +// Jiri Vymazal // #ifdef HAVE_BUILD_CONFIG_H #include @@ -27,6 +28,8 @@ #include "usbguard/RuleParser.hpp" #include "usbguard/Audit.hpp" +#include + #include #include #include @@ -112,6 +115,8 @@ namespace usbguard _device_rules_with_port = false; _restore_controller_device_state = false; + + pid_fd = -1; } Daemon::~Daemon() @@ -402,6 +407,10 @@ namespace usbguard } } while(!exit_loop); + if (pid_fd != -1) { + lockf(pid_fd, F_ULOCK, 0); + close(pid_fd); + } IPCServer::stop(); _dm->stop(); USBGUARD_LOG(Trace) << "Leaving main loop."; @@ -411,6 +420,73 @@ namespace usbguard { } + void Daemon::daemonize(const std::string &pid_file) + { + USBGUARD_LOG(Trace) << "Starting daemonization"; + + pid_t pid = 0; + pid_t original_pid = getpid(); + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + sigprocmask(SIG_BLOCK, &mask, nullptr); + USBGUARD_SYSCALL_THROW("Daemonize", (pid = fork()) < 0); + if (pid > 0) { + constexpr int timeout_val = 5; + struct timespec timeout {timeout_val,0}; + const time_t start = time(nullptr); + siginfo_t info; + do { + const int signum = sigtimedwait(&mask, &info, &timeout); + if (signum == SIGUSR1 && info.si_signo == SIGUSR1 && info.si_pid == pid) { + USBGUARD_LOG(Trace) << "Finished daemonization"; + exit(EXIT_SUCCESS); + } + if (signum == -1 && errno == EAGAIN) { + break; /* timed out */ + } + timeout.tv_sec = timeout_val - difftime(time(nullptr), start); /* avoid potentially endless loop */ + } while(true); + throw Exception("Deamonize", "signal", "Waiting on pid file write timeout!"); + } + + /* Now we are forked */ + USBGUARD_SYSCALL_THROW("Daemonize", setsid() < 0); + signal(SIGCHLD, SIG_IGN); + + USBGUARD_SYSCALL_THROW("Daemonize", (pid_fd = open(pid_file.c_str(), O_RDWR|O_CREAT, 0640)) < 0); + USBGUARD_SYSCALL_THROW("Daemonize", (lockf(pid_fd, F_TLOCK, 0)) < 0); + USBGUARD_SYSCALL_THROW("Daemonize", (pid = fork()) < 0); + if (pid > 0) { + try { + std::string pid_str = std::to_string(pid); + USBGUARD_SYSCALL_THROW("Daemonize", write(pid_fd, pid_str.c_str(), pid_str.size()) != static_cast(pid_str.size())); + kill(original_pid, SIGUSR1); + exit(EXIT_SUCCESS); + } + catch(...) { + kill(pid, SIGKILL); + throw; + } + } + + /* Now we are forked 2nd time */ + umask(0047); /* no need for world-accessible or executable files */ + chdir("/"); + const std::array std_fds {{STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}}; + int fd_null; + USBGUARD_SYSCALL_THROW("Daemonize", (fd_null = open("/dev/null", O_RDWR)) < 0); + /* We do not need to close all fds because there is only logging open at this point */ + for (auto fd : std_fds) { + USBGUARD_SYSCALL_THROW("Daemonize", close(fd)); + USBGUARD_SYSCALL_THROW("Daemonize", (dup2(fd_null, fd)) < 0); + } + close(fd_null); + + USBGUARD_SYSCALL_THROW("Daemonize", (lockf(pid_fd, F_LOCK, 0)) < 0); + } + uint32_t Daemon::assignID() { return _ruleset.assignID(); diff --git a/src/Daemon/Daemon.hpp b/src/Daemon/Daemon.hpp index cfd02d9..065deaf 100644 --- a/src/Daemon/Daemon.hpp +++ b/src/Daemon/Daemon.hpp @@ -15,6 +15,7 @@ // along with this program. If not, see . // // Authors: Daniel Kopecek +// Jiri Vymazal // #pragma once #ifdef HAVE_BUILD_CONFIG_H @@ -72,6 +73,8 @@ namespace usbguard void run(); /* Stop the daemon */ void quit(); + /* Handle process daemonization */ + void daemonize(const std::string& pid_file); uint32_t assignID(); uint32_t upsertRule(const std::string& match_spec, const std::string& rule_spec, bool parent_insensitive = false); @@ -112,6 +115,8 @@ namespace usbguard ConfigFile _config; RuleSet _ruleset; + int pid_fd; + std::string _device_manager_backend; std::shared_ptr _dm; diff --git a/src/Daemon/main.cpp b/src/Daemon/main.cpp index 869c2e2..4b9b351 100644 --- a/src/Daemon/main.cpp +++ b/src/Daemon/main.cpp @@ -15,6 +15,7 @@ // along with this program. If not, see . // // Authors: Daniel Kopecek +// Jiri Vymazal // #ifdef HAVE_BUILD_CONFIG_H #include @@ -37,9 +38,13 @@ static void setupCapabilities(void); #endif +#ifndef USBGUARD_PID_FILE +#define USBGUARD_PID_FILE "/var/run/usbguard.pid" +#endif + using namespace usbguard; -const char * const G_optstring = "dskl:p:c:hWC"; +const char * const G_optstring = "dfskl:p:c:hWC"; static void printUsage(std::ostream& stream, const char *arg0) { @@ -47,6 +52,7 @@ static void printUsage(std::ostream& stream, const char *arg0) stream << "Usage: " << filenameFromPath(std::string(arg0), true) << " [OPTIONS]" << std::endl; stream << std::endl; stream << " -d Enable debugging messages in the log." << std::endl; + stream << " -f Enable classical daemon forking behavior." << std::endl; stream << " -s Log to syslog." << std::endl; stream << " -k Log to console." << std::endl; stream << " -l Log to a file at `path'." << std::endl; @@ -68,8 +74,9 @@ int main(int argc, char *argv[]) bool log_file = false; bool use_seccomp_whitelist = false; bool drop_capabilities = false; + bool daemonize = false; std::string log_file_path; - std::string pid_file; + std::string pid_file = USBGUARD_PID_FILE; std::string conf_file = "/etc/usbguard/usbguard-daemon.conf"; int opt; @@ -79,6 +86,9 @@ int main(int argc, char *argv[]) case 'd': debug_mode = true; break; + case 'f': + daemonize = true; + break; case 's': log_syslog = true; break; @@ -144,6 +154,13 @@ int main(int argc, char *argv[]) if (!conf_file.empty()) { daemon.loadConfiguration(conf_file); } + if (daemonize) { + if (log_console && !log_syslog && !log_file) { + USBGUARD_LOG(Warning) << "You have selected to fork and log only to \ + console, nothing will be logged after forking!"; + } + daemon.daemonize(pid_file); + } daemon.run(); ret = EXIT_SUCCESS; } diff --git a/src/Tests/Makefile.am b/src/Tests/Makefile.am index 7d93474..a952d18 100644 --- a/src/Tests/Makefile.am +++ b/src/Tests/Makefile.am @@ -40,6 +40,7 @@ EXTRA_DIST=\ $(top_srcdir)/src/Tests/UseCase/001_cli_policy.sh \ $(top_srcdir)/src/Tests/UseCase/002_cli_devices.sh \ $(top_srcdir)/src/Tests/UseCase/003_cli_devices_dummy.sh \ + $(top_srcdir)/src/Tests/UseCase/004_daemonize.sh \ $(top_srcdir)/src/Tests/UseCase/DummyDevices LOG_DRIVER=\ @@ -62,7 +63,8 @@ TESTS=\ UseCase/000_executable.sh \ UseCase/001_cli_policy.sh \ UseCase/002_cli_devices.sh \ - UseCase/003_cli_devices_dummy.sh + UseCase/003_cli_devices_dummy.sh \ + UseCase/004_daemonize.sh check_PROGRAMS=\ test-unit \ diff --git a/src/Tests/UseCase/004_daemonize.sh b/src/Tests/UseCase/004_daemonize.sh new file mode 100755 index 0000000..d59dad1 --- /dev/null +++ b/src/Tests/UseCase/004_daemonize.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# +# +# Copyright (C) 2016 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Authors: Jiri Vymazal +# +# Test whether the binaries are executable as expected (no linker errors, etc.) +# +source "${USBGUARD_TESTLIB_BASH}" || exit 129 + +# TODO? Move to testlib +export USBGUARD_TESTLIB_TMPDIR="$(mktemp -d --tmpdir usbguard-test.XXXXXX)" + +export config_path="${USBGUARD_TESTLIB_TMPDIR}/daemon.conf" +export pidfile_path="${USBGUARD_TESTLIB_TMPDIR}/usbguard.pid" +export logfile="${USBGUARD_TESTLIB_TMPDIR}/daemon.log" + +function test_cli_daemonize() +{ + sleep 5 + + if [ ! -f "$pidfile_path" ]; then + echo "Test error: PID file for usbguard not present" + exit 1 + fi + + if [ ! `pgrep usbguard` == `cat $pidfile_path` ]; then + echo "Test error: PID of usbguard daemon not present in PID file" + exit 1 + fi +} + +cat > "$config_path" <