/* The software in this package is distributed under the GNU General Public License version 2 (with a special exception described below). A copy of GNU General Public License (GPL) is included in this distribution, in the file COPYING.GPL. As a special exception, if other files instantiate templates or use macros or inline functions from this file, or you compile this file and link it with other works to produce a work based on this file, this file does not by itself cause the resulting work to be covered by the GNU General Public License. However the source code for this file must still be made available in accordance with section (3) of the GNU General Public License. This exception does not invalidate any other reasons why a work based on this file might be covered by the GNU General Public License. */ /*************************************************************************** * Copyright (C) 2008 by Intra2net AG - Thomas Jarosch * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "daemonfunc.hpp" #include "stringfunc.hxx" #include "filefunc.hxx" namespace I2n { namespace Daemon { using namespace std; /** * Fork into the background. */ void daemonize() { int pid=fork(); if (pid < 0) { throw runtime_error("fork() failed"); } if (pid > 0) { // parent process exit (0); } // pid==0 -> child process: continue } /** * Drop root privileges * @param username User to become. Don't change user if empty * @param group Group to become. Don't change group if empty * @param get_group_from_user Get group GID from user information if group is empty. * @return true if all is fine, false otherwise */ bool drop_root_privileges(const std::string &username, const std::string &group, bool get_group_from_user) { if (!group.empty()) { Group my_group(group); if (!my_group.is_valid()) return false; if (setgid((my_group.Gid))) return false; } if (!username.empty()) { User my_user(username); if (!my_user.is_valid()) return false; if (get_group_from_user && group.empty()) { if (setgid((my_user.Gid))) return false; } // Initialize additional groups the user is a member of if (initgroups(username.c_str(), getgid()) == -1) return false; if (setuid(my_user.Uid)) return false; } return true; } static const char *const EXE_DELETED_MARKER = " (deleted)"; /** @brief Remove the deletion marker from a binary name, if applicable. * * @param exe Binary name as obtained from ``/proc/$PID/exe``. * * @return The input binary name with a trailing `` (deleted)`` removed. * * From proc(5): * * /proc/[pid]/exe * […] If the pathname has been unlinked, the symbolic link will * contain the string '(deleted)' appended to the original pathname. * * Which introduces an ambiguity if the path of the actual binary ends in * `` (deleted)``. */ static inline std::string strip_deleted_marker (const std::string &exe) { if (exe.size () <= sizeof (*EXE_DELETED_MARKER)) { /* This binary name can’t possibly contain a deleted marker. */ return exe; } const size_t found = exe.rfind (EXE_DELETED_MARKER); if (found == std::string::npos) { /* Input does not contain the deleted marker. */ return exe; } return exe.substr (0, found); } /** * @brief determine the pids for a given program * @param[in] name name (or full path) of the binary * @param[out] result the pids associated with the name. * @return @a true if the function performed without errors. * * Walk though the /proc/\'s and search for the name. * * @note Since this function uses /proc, it's system specific. Currently: * Linux only! * * @todo check cmdline and stat in /proc/\ dir for the searched name. */ bool pid_of(const std::string& name, std::vector< pid_t >& result) { std::vector< std::string > entries; std::vector< pid_t > fuzz1_result; std::vector< pid_t > fuzz2_result; result.clear(); if (name.empty()) return false; if (!get_dir("/proc", entries)) return false; for (std::vector< std::string >::const_iterator it= entries.begin(); it != entries.end(); ++it) { pid_t pid; if (! string_to(*it, pid)) continue; std::string base_path= std::string("/proc/") + *it; std::string exe_path= base_path + "/exe"; I2n::Stat stat(exe_path, false); if (not stat or not stat.is_link()) continue; std::string real_exe= read_link(exe_path); if (!real_exe.empty()) { // we got the path of the exe if (real_exe == name || strip_deleted_marker (real_exe) == name) { result.push_back( pid ); continue; } std::string proc_stat= read_file( base_path + "/stat"); if (proc_stat.empty()) continue; // process vanished //TODO some more fuzz tests here?! (cmdline, stat(us)) real_exe = basename (real_exe); if (real_exe == name || strip_deleted_marker (real_exe) == name) { fuzz2_result.push_back(pid); continue; } } else { // we haven't got the path of the exe // this can happen e.g. with processes owned by other users // -> parse the commandline instead std::string cmdline = read_file(base_path + "/cmdline"); if (cmdline.empty()) continue; // in /proc/*/cmdline, the parameters are split by nullbytes, we only care for the first parameter if (cmdline.find('\0') != string::npos) cmdline.erase(cmdline.find('\0')); if (cmdline == name) { result.push_back( pid ); continue; } if (basename(cmdline) == name) { fuzz2_result.push_back(pid); continue; } } } if (result.empty()) { result.swap(fuzz1_result); } if (result.empty()) { result.swap(fuzz2_result); } return true; } // eo pidOf(const std::string&,std::vector< pid_t >&) /** * @brief establish a new session for the current process. * * @return the id of the new session. * * This wraps setsid(2); to be called from a child process after forking. * Raises ``runtime_error`` if the call fails. */ pid_t create_session (void) { pid_t sid; errno = 0; if ((sid = setsid ()) == -1) { throw std::runtime_error ((std::string) "create_session: setsid() returned an error (" + I2n::to_string (errno) + "): " + strerror (errno)); } return sid; } /** * @brief reopen standard file descriptors as ``/dev/null``. * * Disable stdin, stdout and stderr to ensure no output can be read from or * written to those descriptors. This assumes the process will interact with * the outside world through other means like syslog and sockets. * * Raises ``runtime_error`` in case of errors. */ void null_fds () { int devnull; errno = 0; if ((devnull = open ("/dev/null", O_RDWR)) == -1) { throw std::runtime_error ((std::string) "null_fds: open(/dev/null) returned an error (" + I2n::to_string (errno) + "): " + strerror (errno)); } for (int fd = 0; fd != 3; ++fd) { errno = 0; if (dup2 (devnull, fd) == -1) { throw std::runtime_error ((std::string) "null_fds: dup2(/dev/null, " + I2n::to_string (fd) + ") returned an error (" + I2n::to_string (errno) + "): " + strerror (errno)); } } errno = 0; if (close (devnull) == -1) { throw std::runtime_error ((std::string) "null_fds: close(/dev/null) returned an error (" + I2n::to_string (errno) + "): " + strerror (errno)); } } /** * @brief convert the current process into a background process. * * This convenience wrapper combines forking, creation of a new session and * disabling stdio. * * Raises ``runtime_error`` in case of errors. */ void daemonize_full (void) { daemonize (); (void)create_session (); null_fds (); } } }