account for deletion marker in /proc/pid/exe
[libi2ncommon] / src / daemonfunc.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
8 As a special exception, if other files instantiate templates or use macros
9 or inline functions from this file, or you compile this file and link it
10 with other works to produce a work based on this file, this file
11 does not by itself cause the resulting work to be covered
12 by the GNU General Public License.
13
14 However the source code for this file must still be made available
15 in accordance with section (3) of the GNU General Public License.
16
17 This exception does not invalidate any other reasons why a work based
18 on this file might be covered by the GNU General Public License.
19 */
20 /***************************************************************************
21  *   Copyright (C) 2008 by Intra2net AG - Thomas Jarosch                   *
22  ***************************************************************************/
23 #include <sys/types.h>
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <pwd.h>
27 #include <grp.h>
28
29 #include <string>
30 #include <stdexcept>
31 #include "daemonfunc.hpp"
32 #include "stringfunc.hxx"
33 #include "filefunc.hxx"
34
35
36 namespace I2n
37 {
38 namespace Daemon
39 {
40
41 using namespace std;
42
43 /**
44  * Fork into the background.
45  */
46 void daemonize()
47 {
48    int pid=fork();
49
50    if (pid < 0)
51    {
52       throw runtime_error("fork() failed");
53    }
54    if (pid > 0)
55    {
56       // parent process
57       exit (0);
58    }
59    // pid==0 -> child process: continue
60 }
61
62 /**
63  * Drop root privileges
64  * @param username User to become. Don't change user if empty
65  * @param group Group to become. Don't change group if empty
66  * @param get_group_from_user Get group GID from user information if group is empty.
67  * @return true if all is fine, false otherwise
68  */
69 bool drop_root_privileges(const std::string &username,
70                           const std::string &group, bool get_group_from_user)
71 {
72    if (!group.empty())
73    {
74       Group my_group(group);
75       if (!my_group.is_valid())
76          return false;
77
78       if (setgid((my_group.Gid)))
79          return false;
80    }
81
82    if (!username.empty())
83    {
84       User my_user(username);
85       if (!my_user.is_valid())
86          return false;
87
88       if (get_group_from_user && group.empty())
89       {
90          if (setgid((my_user.Gid)))
91             return false;
92       }
93
94       // Initialize additional groups the user is a member of
95       if (initgroups(username.c_str(), getgid()) == -1)
96          return false;
97
98       if (setuid(my_user.Uid))
99          return false;
100    }
101
102     return true;
103 }
104
105 static const char *const EXE_DELETED_MARKER = " (deleted)";
106
107 /** @brief      Remove the deletion marker from a binary name, if applicable.
108  *
109  * @param exe   Binary name as obtained from ``/proc/$PID/exe``.
110  *
111  * @return      The input binary name with a trailing `` (deleted)`` removed.
112  *
113  * From proc(5):
114  *
115  *     /proc/[pid]/exe
116  *            […] If the pathname has been unlinked, the symbolic link will
117  *            contain the string '(deleted)' appended to the original pathname.
118  *
119  * Which introduces an ambiguity if the path of the actual binary ends in
120  * `` (deleted)``.
121  */
122 static inline std::string
123 strip_deleted_marker (const std::string &exe)
124 {
125     if (exe.size () <= sizeof (*EXE_DELETED_MARKER)) {
126         /* This binary name can’t possibly contain a deleted marker. */
127         return exe;
128     }
129
130     const size_t found = exe.rfind (EXE_DELETED_MARKER);
131     if (found == std::string::npos) {
132         /* Input does not contain the deleted marker. */
133         return exe;
134     }
135
136     return exe.substr (0, found);
137 }
138
139 /**
140  * @brief determine the pids for a given program
141  * @param[in] name name (or full path) of the binary
142  * @param[out] result the pids associated with the name.
143  * @return @a true if the function performed without errors.
144  *
145  * Walk though the /proc/\<pid\>'s and search for the name.
146  *
147  * @note Since this function uses /proc, it's system specific. Currently:
148  * Linux only!
149  *
150  * @todo check cmdline and stat in /proc/\<pid\> dir for the searched name.
151  */
152 bool pid_of(const std::string& name, std::vector< pid_t >& result)
153 {
154    std::vector< std::string > entries;
155    std::vector< pid_t > fuzz1_result;
156    std::vector< pid_t > fuzz2_result;
157    result.clear();
158
159    if (name.empty()) return false;
160    if (!get_dir("/proc", entries)) return false;
161
162    for (std::vector< std::string >::const_iterator it= entries.begin();
163          it != entries.end();
164          ++it)
165    {
166       pid_t pid;
167       if (! string_to<pid_t>(*it, pid)) continue;
168       std::string base_path= std::string("/proc/") + *it;
169       std::string exe_path= base_path + "/exe";
170       I2n::Stat stat(exe_path, false);
171       if (not stat or not stat.is_link()) continue;
172       std::string real_exe= read_link(exe_path);
173       if (!real_exe.empty())
174       {
175          // we got the path of the exe
176          if (real_exe == name || strip_deleted_marker (real_exe) == name)
177          {
178                  result.push_back( pid );
179                  continue;
180          }
181          
182          std::string proc_stat= read_file( base_path + "/stat");
183          if (proc_stat.empty()) continue; // process vanished
184          
185          //TODO some more fuzz tests here?! (cmdline, stat(us))
186          
187          real_exe = basename (real_exe);
188          if (real_exe == name || strip_deleted_marker (real_exe) == name)
189          {
190                  fuzz2_result.push_back(pid);
191                  continue;
192          }
193       }
194       else
195       {
196          // we haven't got the path of the exe
197          // this can happen e.g. with processes owned by other users
198          // -> parse the commandline instead
199          std::string cmdline = read_file(base_path + "/cmdline");
200          if (cmdline.empty()) continue;
201
202          // in /proc/*/cmdline, the parameters are split by nullbytes, we only care for the first parameter
203          if (cmdline.find('\0') != string::npos)
204             cmdline.erase(cmdline.find('\0'));
205
206          if (cmdline == name)
207          {
208                result.push_back( pid );
209                continue;
210          }
211
212          if (basename(cmdline) == name)
213          {
214                fuzz2_result.push_back(pid);
215                continue;
216          }
217       }
218    }
219    if (result.empty())
220    {
221       result.swap(fuzz1_result);
222    }
223    if (result.empty())
224    {
225       result.swap(fuzz2_result);
226    }
227    return true;
228 } // eo pidOf(const std::string&,std::vector< pid_t >&)
229
230 }
231 }