+/*
+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 *
- * thomas.jarosch@intra2net.com *
- * http://www.intra2net.com *
***************************************************************************/
#include <sys/types.h>
#include <unistd.h>
+#include <stdlib.h>
#include <pwd.h>
#include <grp.h>
#include <string>
#include <stdexcept>
-#include "daemonfunc.hxx"
+#include "daemonfunc.hpp"
#include "stringfunc.hxx"
#include "filefunc.hxx"
namespace I2n
{
-namespace daemon
+namespace Daemon
{
using namespace std;
*/
void daemonize()
{
- int pid=fork();
+ int pid=fork();
- if (pid < 0)
- {
- throw runtime_error("fork() failed");
- }
- if (pid > 0)
- {
- // parent process
- exit (0);
- }
- // pid==0 -> child process: continue
+ 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
*/
-void drop_root_privileges(const std::string &username,
- const std::string &group)
+bool drop_root_privileges(const std::string &username,
+ const std::string &group, bool get_group_from_user)
{
-/*
- if (!group.empty()) {
- if (setgid(daemon::lookup_gid(group))) {
- throw runtime_error("Can't change to group " + group);
- }
+ 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;
}
- if (!username.empty()) {
- if (setuid(daemon::lookup_uid(username))) {
- throw runtime_error("Can't change to user " + username);
- }
+ 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);
}
/**
*/
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 (!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<pid_t>(*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 == 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))
-
- if (basename(real_exe) == name)
- {
- fuzz2_result.push_back(pid);
- continue;
- }
- }
- if (result.empty())
- {
- result.swap(fuzz1_result);
- }
- if (result.empty())
- {
- result.swap(fuzz2_result);
- }
- return true;
+ 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<pid_t>(*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 >&)
}