account for deletion marker in /proc/pid/exe
[libi2ncommon] / src / daemonfunc.cpp
... / ...
CommitLineData
1/*
2The software in this package is distributed under the GNU General
3Public License version 2 (with a special exception described below).
4
5A copy of GNU General Public License (GPL) is included in this distribution,
6in the file COPYING.GPL.
7
8As a special exception, if other files instantiate templates or use macros
9or inline functions from this file, or you compile this file and link it
10with other works to produce a work based on this file, this file
11does not by itself cause the resulting work to be covered
12by the GNU General Public License.
13
14However the source code for this file must still be made available
15in accordance with section (3) of the GNU General Public License.
16
17This exception does not invalidate any other reasons why a work based
18on 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
36namespace I2n
37{
38namespace Daemon
39{
40
41using namespace std;
42
43/**
44 * Fork into the background.
45 */
46void 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 */
69bool 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
105static 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 */
122static inline std::string
123strip_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 */
152bool 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}