account for deletion marker in /proc/pid/exe
[libi2ncommon] / src / daemonfunc.cpp
index 680ef96..248a79b 100644 (file)
@@ -1,23 +1,41 @@
+/*
+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;
@@ -73,9 +91,49 @@ bool drop_root_privileges(const std::string &username,
             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);
 }
 
 /**
@@ -97,7 +155,10 @@ bool pid_of(const std::string& name, std::vector< pid_t >& result)
    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)
@@ -109,21 +170,50 @@ bool pid_of(const std::string& name, std::vector< pid_t >& result)
       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)
+      if (!real_exe.empty())
       {
-         result.push_back( pid );
-         continue;
+         // 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;
+         }
       }
-
-      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)
+      else
       {
-         fuzz2_result.push_back(pid);
-         continue;
+         // 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())