add session handling
[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>
0a654ec0
TJ
30
31#include <string>
32#include <stdexcept>
1eb904be 33#include "daemonfunc.hpp"
6a93d84a
TJ
34#include "stringfunc.hxx"
35#include "filefunc.hxx"
36
a287a306
TJ
37
38namespace I2n
6a93d84a 39{
1eb904be 40namespace Daemon
6a93d84a 41{
0a654ec0
TJ
42
43using namespace std;
44
45/**
46 * Fork into the background.
47 */
6a93d84a 48void daemonize()
0a654ec0 49{
90246b4a
TJ
50 int pid=fork();
51
52 if (pid < 0)
53 {
54 throw runtime_error("fork() failed");
55 }
56 if (pid > 0)
57 {
58 // parent process
59 exit (0);
60 }
61 // pid==0 -> child process: continue
0a654ec0
TJ
62}
63
64/**
09684efc 65 * Drop root privileges
0a654ec0
TJ
66 * @param username User to become. Don't change user if empty
67 * @param group Group to become. Don't change group if empty
90246b4a
TJ
68 * @param get_group_from_user Get group GID from user information if group is empty.
69 * @return true if all is fine, false otherwise
0a654ec0 70 */
90246b4a
TJ
71bool drop_root_privileges(const std::string &username,
72 const std::string &group, bool get_group_from_user)
0a654ec0 73{
90246b4a
TJ
74 if (!group.empty())
75 {
76 Group my_group(group);
77 if (!my_group.is_valid())
78 return false;
79
80 if (setgid((my_group.Gid)))
81 return false;
82 }
83
84 if (!username.empty())
85 {
86 User my_user(username);
87 if (!my_user.is_valid())
88 return false;
89
90 if (get_group_from_user && group.empty())
91 {
92 if (setgid((my_user.Gid)))
93 return false;
94 }
95
dcd37d35 96 // Initialize additional groups the user is a member of
a6238e63 97 if (initgroups(username.c_str(), getgid()) == -1)
dcd37d35
TJ
98 return false;
99
90246b4a
TJ
100 if (setuid(my_user.Uid))
101 return false;
102 }
e43beb91
TJ
103
104 return true;
6a93d84a
TJ
105}
106
492f4a6d
PG
107static const char *const EXE_DELETED_MARKER = " (deleted)";
108
109/** @brief Remove the deletion marker from a binary name, if applicable.
110 *
111 * @param exe Binary name as obtained from ``/proc/$PID/exe``.
112 *
113 * @return The input binary name with a trailing `` (deleted)`` removed.
114 *
115 * From proc(5):
116 *
117 * /proc/[pid]/exe
118 * […] If the pathname has been unlinked, the symbolic link will
119 * contain the string '(deleted)' appended to the original pathname.
120 *
121 * Which introduces an ambiguity if the path of the actual binary ends in
122 * `` (deleted)``.
123 */
124static inline std::string
125strip_deleted_marker (const std::string &exe)
126{
127 if (exe.size () <= sizeof (*EXE_DELETED_MARKER)) {
128 /* This binary name can’t possibly contain a deleted marker. */
129 return exe;
130 }
131
132 const size_t found = exe.rfind (EXE_DELETED_MARKER);
133 if (found == std::string::npos) {
134 /* Input does not contain the deleted marker. */
135 return exe;
136 }
137
138 return exe.substr (0, found);
139}
140
6a93d84a
TJ
141/**
142 * @brief determine the pids for a given program
143 * @param[in] name name (or full path) of the binary
144 * @param[out] result the pids associated with the name.
145 * @return @a true if the function performed without errors.
146 *
147 * Walk though the /proc/\<pid\>'s and search for the name.
148 *
149 * @note Since this function uses /proc, it's system specific. Currently:
150 * Linux only!
151 *
152 * @todo check cmdline and stat in /proc/\<pid\> dir for the searched name.
153 */
154bool pid_of(const std::string& name, std::vector< pid_t >& result)
155{
90246b4a
TJ
156 std::vector< std::string > entries;
157 std::vector< pid_t > fuzz1_result;
158 std::vector< pid_t > fuzz2_result;
159 result.clear();
edd749de
GE
160
161 if (name.empty()) return false;
90246b4a 162 if (!get_dir("/proc", entries)) return false;
edd749de 163
90246b4a
TJ
164 for (std::vector< std::string >::const_iterator it= entries.begin();
165 it != entries.end();
166 ++it)
167 {
168 pid_t pid;
169 if (! string_to<pid_t>(*it, pid)) continue;
170 std::string base_path= std::string("/proc/") + *it;
171 std::string exe_path= base_path + "/exe";
172 I2n::Stat stat(exe_path, false);
173 if (not stat or not stat.is_link()) continue;
174 std::string real_exe= read_link(exe_path);
edd749de 175 if (!real_exe.empty())
90246b4a 176 {
edd749de 177 // we got the path of the exe
492f4a6d 178 if (real_exe == name || strip_deleted_marker (real_exe) == name)
edd749de 179 {
492f4a6d
PG
180 result.push_back( pid );
181 continue;
edd749de 182 }
492f4a6d 183
edd749de
GE
184 std::string proc_stat= read_file( base_path + "/stat");
185 if (proc_stat.empty()) continue; // process vanished
492f4a6d 186
edd749de 187 //TODO some more fuzz tests here?! (cmdline, stat(us))
492f4a6d
PG
188
189 real_exe = basename (real_exe);
190 if (real_exe == name || strip_deleted_marker (real_exe) == name)
edd749de 191 {
492f4a6d
PG
192 fuzz2_result.push_back(pid);
193 continue;
edd749de 194 }
90246b4a 195 }
edd749de 196 else
90246b4a 197 {
edd749de
GE
198 // we haven't got the path of the exe
199 // this can happen e.g. with processes owned by other users
200 // -> parse the commandline instead
201 std::string cmdline = read_file(base_path + "/cmdline");
202 if (cmdline.empty()) continue;
203
204 // in /proc/*/cmdline, the parameters are split by nullbytes, we only care for the first parameter
205 if (cmdline.find('\0') != string::npos)
206 cmdline.erase(cmdline.find('\0'));
207
208 if (cmdline == name)
209 {
210 result.push_back( pid );
211 continue;
212 }
213
214 if (basename(cmdline) == name)
215 {
216 fuzz2_result.push_back(pid);
217 continue;
218 }
90246b4a
TJ
219 }
220 }
221 if (result.empty())
222 {
223 result.swap(fuzz1_result);
224 }
225 if (result.empty())
226 {
227 result.swap(fuzz2_result);
228 }
229 return true;
6a93d84a
TJ
230} // eo pidOf(const std::string&,std::vector< pid_t >&)
231
478afd5e
PG
232/**
233 * @brief establish a new session for the current process.
234 *
235 * @return the id of the new session.
236 *
237 * This wraps setsid(2); to be called from a child process after forking.
238 * Raises ``runtime_error`` if the call fails.
239 */
240pid_t create_session (void)
241{
242 pid_t sid;
243
244 errno = 0;
245 if ((sid = setsid ()) == -1)
246 {
247 throw std::runtime_error
248 ((std::string)
249 "create_session: setsid() returned an error ("
250 + I2n::to_string (errno)
251 + "): " + strerror (errno));
252 }
253
254 return sid;
255}
256
6a93d84a 257}
0a654ec0 258}