account for deletion marker in /proc/pid/exe
[libi2ncommon] / src / daemonfunc.cpp
index b1040b2..248a79b 100644 (file)
+/*
+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
+{
 
 using namespace std;
 
 /**
  * Fork into the background.
  */
-void daemon::daemonize()
+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
 }
 
 /**
- * Lookup uid for given username
- * @param username username to convert
- * @return uid of the user
+ * 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
  */
-uid_t daemon::lookup_uid(const std::string &username)
+bool drop_root_privileges(const std::string &username,
+                          const std::string &group, bool get_group_from_user)
 {
-    struct passwd *user = getpwnam(username.c_str());
-    if (user == NULL) {
-        throw runtime_error("user " + username + " not found");
-    }
+   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;
+      }
 
-    return user->pw_uid;
+      // 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;
 }
 
-/**
- * Lookup gid for given group
- * @param group group to convert
- * @return gid of the group
+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)``.
  */
-gid_t daemon::lookup_gid(const std::string &group)
+static inline std::string
+strip_deleted_marker (const std::string &exe)
 {
-    struct group *my_group = getgrnam(group.c_str());
-    if (my_group == NULL) {
-        throw runtime_error("group " + group + " not found");
+    if (exe.size () <= sizeof (*EXE_DELETED_MARKER)) {
+        /* This binary name can’t possibly contain a deleted marker. */
+        return exe;
     }
 
-    return my_group->gr_gid;
+    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);
 }
 
 /**
- * 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
+ * @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/\<pid\>'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/\<pid\> dir for the searched name.
  */
-void daemon::drop_root_privileges(const std::string &username,
-                                                 const std::string &group)
+bool pid_of(const std::string& name, std::vector< pid_t >& result)
 {
-    if (!group.empty()) {
-        if (setgid(daemon::lookup_gid(group))) {
-            throw runtime_error("Can't change to group " + group);
-        }
-    }
+   std::vector< std::string > entries;
+   std::vector< pid_t > fuzz1_result;
+   std::vector< pid_t > fuzz2_result;
+   result.clear();
 
-    if (!username.empty()) {
-        if (setuid(daemon::lookup_uid(username))) {
-            throw runtime_error("Can't change to user " + username);
-        }
-    }
+   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 >&)
+
+}
 }