2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
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.
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.
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.
20 /***************************************************************************
21 * Copyright (C) 2008 by Intra2net AG - Thomas Jarosch *
22 ***************************************************************************/
23 #include <sys/types.h>
34 #include "daemonfunc.hpp"
35 #include "stringfunc.hxx"
36 #include "filefunc.hxx"
47 * Fork into the background.
55 throw runtime_error("fork() failed");
62 // pid==0 -> child process: continue
66 * Drop root privileges
67 * @param username User to become. Don't change user if empty
68 * @param group Group to become. Don't change group if empty
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
72 bool drop_root_privileges(const std::string &username,
73 const std::string &group, bool get_group_from_user)
77 Group my_group(group);
78 if (!my_group.is_valid())
81 if (setgid((my_group.Gid)))
85 if (!username.empty())
87 User my_user(username);
88 if (!my_user.is_valid())
91 if (get_group_from_user && group.empty())
93 if (setgid((my_user.Gid)))
97 // Initialize additional groups the user is a member of
98 if (initgroups(username.c_str(), getgid()) == -1)
101 if (setuid(my_user.Uid))
108 static const char *const EXE_DELETED_MARKER = " (deleted)";
110 /** @brief Remove the deletion marker from a binary name, if applicable.
112 * @param exe Binary name as obtained from ``/proc/$PID/exe``.
114 * @return The input binary name with a trailing `` (deleted)`` removed.
119 * […] If the pathname has been unlinked, the symbolic link will
120 * contain the string '(deleted)' appended to the original pathname.
122 * Which introduces an ambiguity if the path of the actual binary ends in
125 static inline std::string
126 strip_deleted_marker (const std::string &exe)
128 if (exe.size () <= sizeof (*EXE_DELETED_MARKER)) {
129 /* This binary name can’t possibly contain a deleted marker. */
133 const size_t found = exe.rfind (EXE_DELETED_MARKER);
134 if (found == std::string::npos) {
135 /* Input does not contain the deleted marker. */
139 return exe.substr (0, found);
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.
148 * Walk though the /proc/\<pid\>'s and search for the name.
150 * @note Since this function uses /proc, it's system specific. Currently:
153 * @todo check cmdline and stat in /proc/\<pid\> dir for the searched name.
155 bool pid_of(const std::string& name, std::vector< pid_t >& result)
157 std::vector< std::string > entries;
158 std::vector< pid_t > fuzz1_result;
159 std::vector< pid_t > fuzz2_result;
162 if (name.empty()) return false;
163 if (!get_dir("/proc", entries)) return false;
165 for (std::vector< std::string >::const_iterator it= entries.begin();
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);
176 if (!real_exe.empty())
178 // we got the path of the exe
179 if (real_exe == name || strip_deleted_marker (real_exe) == name)
181 result.push_back( pid );
185 std::string proc_stat= read_file( base_path + "/stat");
186 if (proc_stat.empty()) continue; // process vanished
188 //TODO some more fuzz tests here?! (cmdline, stat(us))
190 real_exe = basename (real_exe);
191 if (real_exe == name || strip_deleted_marker (real_exe) == name)
193 fuzz2_result.push_back(pid);
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;
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'));
211 result.push_back( pid );
215 if (basename(cmdline) == name)
217 fuzz2_result.push_back(pid);
224 result.swap(fuzz1_result);
228 result.swap(fuzz2_result);
231 } // eo pidOf(const std::string&,std::vector< pid_t >&)
234 * @brief establish a new session for the current process.
236 * @return the id of the new session.
238 * This wraps setsid(2); to be called from a child process after forking.
239 * Raises ``runtime_error`` if the call fails.
241 pid_t create_session (void)
246 if ((sid = setsid ()) == -1)
248 throw std::runtime_error
250 "create_session: setsid() returned an error ("
251 + I2n::to_string (errno)
252 + "): " + strerror (errno));
259 * @brief reopen standard file descriptors as ``/dev/null``.
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.
265 * Raises ``runtime_error`` in case of errors.
271 if ((devnull = open ("/dev/null", O_RDWR)) == -1)
273 throw std::runtime_error
275 "null_fds: open(/dev/null) returned an error ("
276 + I2n::to_string (errno)
277 + "): " + strerror (errno));
280 for (int fd = 0; fd != 3; ++fd)
283 if (dup2 (devnull, fd) == -1)
285 throw std::runtime_error
287 "null_fds: dup2(/dev/null, "
288 + I2n::to_string (fd)
289 + ") returned an error ("
290 + I2n::to_string (errno)
291 + "): " + strerror (errno));
296 if (close (devnull) == -1)
298 throw std::runtime_error
300 "null_fds: close(/dev/null) returned an error ("
301 + I2n::to_string (errno)
302 + "): " + strerror (errno));
307 * @brief convert the current process into a background process.
309 * This convenience wrapper combines forking, creation of a new session and
312 * Raises ``runtime_error`` in case of errors.
314 void daemonize_full (void)
317 (void)create_session ();