add session handling
[libi2ncommon] / src / daemonfunc.cpp
1 /*
2 The software in this package is distributed under the GNU General
3 Public License version 2 (with a special exception described below).
4
5 A copy of GNU General Public License (GPL) is included in this distribution,
6 in the file COPYING.GPL.
7
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.
13
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.
16
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.
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 #include <errno.h>
29 #include <string.h>
30
31 #include <string>
32 #include <stdexcept>
33 #include "daemonfunc.hpp"
34 #include "stringfunc.hxx"
35 #include "filefunc.hxx"
36
37
38 namespace I2n
39 {
40 namespace Daemon
41 {
42
43 using namespace std;
44
45 /**
46  * Fork into the background.
47  */
48 void daemonize()
49 {
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
62 }
63
64 /**
65  * Drop root privileges
66  * @param username User to become. Don't change user if empty
67  * @param group Group to become. Don't change group if empty
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
70  */
71 bool drop_root_privileges(const std::string &username,
72                           const std::string &group, bool get_group_from_user)
73 {
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
96       // Initialize additional groups the user is a member of
97       if (initgroups(username.c_str(), getgid()) == -1)
98          return false;
99
100       if (setuid(my_user.Uid))
101          return false;
102    }
103
104     return true;
105 }
106
107 static 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  */
124 static inline std::string
125 strip_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
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  */
154 bool pid_of(const std::string& name, std::vector< pid_t >& result)
155 {
156    std::vector< std::string > entries;
157    std::vector< pid_t > fuzz1_result;
158    std::vector< pid_t > fuzz2_result;
159    result.clear();
160
161    if (name.empty()) return false;
162    if (!get_dir("/proc", entries)) return false;
163
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);
175       if (!real_exe.empty())
176       {
177          // we got the path of the exe
178          if (real_exe == name || strip_deleted_marker (real_exe) == name)
179          {
180                  result.push_back( pid );
181                  continue;
182          }
183          
184          std::string proc_stat= read_file( base_path + "/stat");
185          if (proc_stat.empty()) continue; // process vanished
186          
187          //TODO some more fuzz tests here?! (cmdline, stat(us))
188          
189          real_exe = basename (real_exe);
190          if (real_exe == name || strip_deleted_marker (real_exe) == name)
191          {
192                  fuzz2_result.push_back(pid);
193                  continue;
194          }
195       }
196       else
197       {
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          }
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;
230 } // eo pidOf(const std::string&,std::vector< pid_t >&)
231
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  */
240 pid_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
257 }
258 }