account for deletion marker in /proc/pid/exe
[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>
28
29#include <string>
30#include <stdexcept>
1eb904be 31#include "daemonfunc.hpp"
6a93d84a
TJ
32#include "stringfunc.hxx"
33#include "filefunc.hxx"
34
a287a306
TJ
35
36namespace I2n
6a93d84a 37{
1eb904be 38namespace Daemon
6a93d84a 39{
0a654ec0
TJ
40
41using namespace std;
42
43/**
44 * Fork into the background.
45 */
6a93d84a 46void daemonize()
0a654ec0 47{
90246b4a
TJ
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
0a654ec0
TJ
60}
61
62/**
09684efc 63 * Drop root privileges
0a654ec0
TJ
64 * @param username User to become. Don't change user if empty
65 * @param group Group to become. Don't change group if empty
90246b4a
TJ
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
0a654ec0 68 */
90246b4a
TJ
69bool drop_root_privileges(const std::string &username,
70 const std::string &group, bool get_group_from_user)
0a654ec0 71{
90246b4a
TJ
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
dcd37d35 94 // Initialize additional groups the user is a member of
a6238e63 95 if (initgroups(username.c_str(), getgid()) == -1)
dcd37d35
TJ
96 return false;
97
90246b4a
TJ
98 if (setuid(my_user.Uid))
99 return false;
100 }
e43beb91
TJ
101
102 return true;
6a93d84a
TJ
103}
104
492f4a6d
PG
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
6a93d84a
TJ
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{
90246b4a
TJ
154 std::vector< std::string > entries;
155 std::vector< pid_t > fuzz1_result;
156 std::vector< pid_t > fuzz2_result;
157 result.clear();
edd749de
GE
158
159 if (name.empty()) return false;
90246b4a 160 if (!get_dir("/proc", entries)) return false;
edd749de 161
90246b4a
TJ
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);
edd749de 173 if (!real_exe.empty())
90246b4a 174 {
edd749de 175 // we got the path of the exe
492f4a6d 176 if (real_exe == name || strip_deleted_marker (real_exe) == name)
edd749de 177 {
492f4a6d
PG
178 result.push_back( pid );
179 continue;
edd749de 180 }
492f4a6d 181
edd749de
GE
182 std::string proc_stat= read_file( base_path + "/stat");
183 if (proc_stat.empty()) continue; // process vanished
492f4a6d 184
edd749de 185 //TODO some more fuzz tests here?! (cmdline, stat(us))
492f4a6d
PG
186
187 real_exe = basename (real_exe);
188 if (real_exe == name || strip_deleted_marker (real_exe) == name)
edd749de 189 {
492f4a6d
PG
190 fuzz2_result.push_back(pid);
191 continue;
edd749de 192 }
90246b4a 193 }
edd749de 194 else
90246b4a 195 {
edd749de
GE
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 }
90246b4a
TJ
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;
6a93d84a
TJ
228} // eo pidOf(const std::string&,std::vector< pid_t >&)
229
230}
0a654ec0 231}