add session handling
[libi2ncommon] / src / daemonfunc.cpp
index 979920e..6481493 100644 (file)
@@ -1,23 +1,43 @@
+/*
+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 <errno.h>
+#include <string.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;
@@ -27,41 +47,95 @@ 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);
 }
 
 /**
@@ -79,49 +153,106 @@ void drop_root_privileges(const std::string &username,
  */
 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())
+   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 >&)
+
+/**
+ * @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)
     {
-        result.swap(fuzz2_result);
+        throw std::runtime_error
+            ((std::string)
+             "create_session: setsid() returned an error ("
+             + I2n::to_string (errno)
+             + "): " + strerror (errno));
     }
-    return true;
-} // eo pidOf(const std::string&,std::vector< pid_t >&)
+
+    return sid;
+}
 
 }
 }