add handling of stdio descriptors
[libi2ncommon] / src / daemonfunc.cpp
CommitLineData
0e23f538
TJ
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*/
0a654ec0
TJ
20/***************************************************************************
21 * Copyright (C) 2008 by Intra2net AG - Thomas Jarosch *
0a654ec0
TJ
22 ***************************************************************************/
23#include <sys/types.h>
24#include <unistd.h>
5efd35b1 25#include <stdlib.h>
0a654ec0
TJ
26#include <pwd.h>
27#include <grp.h>
478afd5e
PG
28#include <errno.h>
29#include <string.h>
f0dfba98 30#include <fcntl.h>
0a654ec0
TJ
31
32#include <string>
33#include <stdexcept>
1eb904be 34#include "daemonfunc.hpp"
6a93d84a
TJ
35#include "stringfunc.hxx"
36#include "filefunc.hxx"
37
a287a306
TJ
38
39namespace I2n
6a93d84a 40{
1eb904be 41namespace Daemon
6a93d84a 42{
0a654ec0
TJ
43
44using namespace std;
45
46/**
47 * Fork into the background.
48 */
6a93d84a 49void daemonize()
0a654ec0 50{
90246b4a
TJ
51 int pid=fork();
52
53 if (pid < 0)
54 {
55 throw runtime_error("fork() failed");
56 }
57 if (pid > 0)
58 {
59 // parent process
60 exit (0);
61 }
62 // pid==0 -> child process: continue
0a654ec0
TJ
63}
64
65/**
09684efc 66 * Drop root privileges
0a654ec0
TJ
67 * @param username User to become. Don't change user if empty
68 * @param group Group to become. Don't change group if empty
90246b4a
TJ
69 * @param get_group_from_user Get group GID from user information if group is empty.
70 * @return true if all is fine, false otherwise
0a654ec0 71 */
90246b4a
TJ
72bool drop_root_privileges(const std::string &username,
73 const std::string &group, bool get_group_from_user)
0a654ec0 74{
90246b4a
TJ
75 if (!group.empty())
76 {
77 Group my_group(group);
78 if (!my_group.is_valid())
79 return false;
80
81 if (setgid((my_group.Gid)))
82 return false;
83 }
84
85 if (!username.empty())
86 {
87 User my_user(username);
88 if (!my_user.is_valid())
89 return false;
90
91 if (get_group_from_user && group.empty())
92 {
93 if (setgid((my_user.Gid)))
94 return false;
95 }
96
dcd37d35 97 // Initialize additional groups the user is a member of
a6238e63 98 if (initgroups(username.c_str(), getgid()) == -1)
dcd37d35
TJ
99 return false;
100
90246b4a
TJ
101 if (setuid(my_user.Uid))
102 return false;
103 }
e43beb91
TJ
104
105 return true;
6a93d84a
TJ
106}
107
492f4a6d
PG
108static const char *const EXE_DELETED_MARKER = " (deleted)";
109
110/** @brief Remove the deletion marker from a binary name, if applicable.
111 *
112 * @param exe Binary name as obtained from ``/proc/$PID/exe``.
113 *
114 * @return The input binary name with a trailing `` (deleted)`` removed.
115 *
116 * From proc(5):
117 *
118 * /proc/[pid]/exe
119 * […] If the pathname has been unlinked, the symbolic link will
120 * contain the string '(deleted)' appended to the original pathname.
121 *
122 * Which introduces an ambiguity if the path of the actual binary ends in
123 * `` (deleted)``.
124 */
125static inline std::string
126strip_deleted_marker (const std::string &exe)
127{
128 if (exe.size () <= sizeof (*EXE_DELETED_MARKER)) {
129 /* This binary name can’t possibly contain a deleted marker. */
130 return exe;
131 }
132
133 const size_t found = exe.rfind (EXE_DELETED_MARKER);
134 if (found == std::string::npos) {
135 /* Input does not contain the deleted marker. */
136 return exe;
137 }
138
139 return exe.substr (0, found);
140}
141
6a93d84a
TJ
142/**
143 * @brief determine the pids for a given program
144 * @param[in] name name (or full path) of the binary
145 * @param[out] result the pids associated with the name.
146 * @return @a true if the function performed without errors.
147 *
148 * Walk though the /proc/\<pid\>'s and search for the name.
149 *
150 * @note Since this function uses /proc, it's system specific. Currently:
151 * Linux only!
152 *
153 * @todo check cmdline and stat in /proc/\<pid\> dir for the searched name.
154 */
155bool pid_of(const std::string& name, std::vector< pid_t >& result)
156{
90246b4a
TJ
157 std::vector< std::string > entries;
158 std::vector< pid_t > fuzz1_result;
159 std::vector< pid_t > fuzz2_result;
160 result.clear();
edd749de
GE
161
162 if (name.empty()) return false;
90246b4a 163 if (!get_dir("/proc", entries)) return false;
edd749de 164
90246b4a
TJ
165 for (std::vector< std::string >::const_iterator it= entries.begin();
166 it != entries.end();
167 ++it)
168 {
169 pid_t pid;
170 if (! string_to<pid_t>(*it, pid)) continue;
171 std::string base_path= std::string("/proc/") + *it;
172 std::string exe_path= base_path + "/exe";
173 I2n::Stat stat(exe_path, false);
174 if (not stat or not stat.is_link()) continue;
175 std::string real_exe= read_link(exe_path);
edd749de 176 if (!real_exe.empty())
90246b4a 177 {
edd749de 178 // we got the path of the exe
492f4a6d 179 if (real_exe == name || strip_deleted_marker (real_exe) == name)
edd749de 180 {
492f4a6d
PG
181 result.push_back( pid );
182 continue;
edd749de 183 }
492f4a6d 184
edd749de
GE
185 std::string proc_stat= read_file( base_path + "/stat");
186 if (proc_stat.empty()) continue; // process vanished
492f4a6d 187
edd749de 188 //TODO some more fuzz tests here?! (cmdline, stat(us))
492f4a6d
PG
189
190 real_exe = basename (real_exe);
191 if (real_exe == name || strip_deleted_marker (real_exe) == name)
edd749de 192 {
492f4a6d
PG
193 fuzz2_result.push_back(pid);
194 continue;
edd749de 195 }
90246b4a 196 }
edd749de 197 else
90246b4a 198 {
edd749de
GE
199 // we haven't got the path of the exe
200 // this can happen e.g. with processes owned by other users
201 // -> parse the commandline instead
202 std::string cmdline = read_file(base_path + "/cmdline");
203 if (cmdline.empty()) continue;
204
205 // in /proc/*/cmdline, the parameters are split by nullbytes, we only care for the first parameter
206 if (cmdline.find('\0') != string::npos)
207 cmdline.erase(cmdline.find('\0'));
208
209 if (cmdline == name)
210 {
211 result.push_back( pid );
212 continue;
213 }
214
215 if (basename(cmdline) == name)
216 {
217 fuzz2_result.push_back(pid);
218 continue;
219 }
90246b4a
TJ
220 }
221 }
222 if (result.empty())
223 {
224 result.swap(fuzz1_result);
225 }
226 if (result.empty())
227 {
228 result.swap(fuzz2_result);
229 }
230 return true;
6a93d84a
TJ
231} // eo pidOf(const std::string&,std::vector< pid_t >&)
232
478afd5e
PG
233/**
234 * @brief establish a new session for the current process.
235 *
236 * @return the id of the new session.
237 *
238 * This wraps setsid(2); to be called from a child process after forking.
239 * Raises ``runtime_error`` if the call fails.
240 */
241pid_t create_session (void)
242{
243 pid_t sid;
244
245 errno = 0;
246 if ((sid = setsid ()) == -1)
247 {
248 throw std::runtime_error
249 ((std::string)
250 "create_session: setsid() returned an error ("
251 + I2n::to_string (errno)
252 + "): " + strerror (errno));
253 }
254
255 return sid;
256}
257
f0dfba98
PG
258/**
259 * @brief reopen standard file descriptors as ``/dev/null``.
260 *
261 * Disable stdin, stdout and stderr to ensure no output can be read from or
262 * written to those descriptors. This assumes the process will interact with
263 * the outside world through other means like syslog and sockets.
264 *
265 * Raises ``runtime_error`` in case of errors.
266 */
267void null_fds () {
268 int devnull;
269
270 errno = 0;
271 if ((devnull = open ("/dev/null", O_RDWR)) == -1)
272 {
273 throw std::runtime_error
274 ((std::string)
275 "null_fds: open(/dev/null) returned an error ("
276 + I2n::to_string (errno)
277 + "): " + strerror (errno));
278 }
279
280 for (int fd = 0; fd != 3; ++fd)
281 {
282 errno = 0;
283 if (dup2 (devnull, fd) == -1)
284 {
285 throw std::runtime_error
286 ((std::string)
287 "null_fds: dup2(/dev/null, "
288 + I2n::to_string (fd)
289 + ") returned an error ("
290 + I2n::to_string (errno)
291 + "): " + strerror (errno));
292 }
293 }
294
295 errno = 0;
296 if (close (devnull) == -1)
297 {
298 throw std::runtime_error
299 ((std::string)
300 "null_fds: close(/dev/null) returned an error ("
301 + I2n::to_string (errno)
302 + "): " + strerror (errno));
303 }
304}
305
306/**
307 * @brief convert the current process into a background process.
308 *
309 * This convenience wrapper combines forking, creation of a new session and
310 * disabling stdio.
311 *
312 * Raises ``runtime_error`` in case of errors.
313 */
314void daemonize_full (void)
315{
316 daemonize ();
317 (void)create_session ();
318 null_fds ();
319}
320
6a93d84a 321}
0a654ec0 322}