Line data Source code
1 : // Copyright (c) 2016-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : #pragma once
19 :
20 : /** \file
21 : * \brief Class & Function dealing with changing user ownership.
22 : *
23 : * The class is used to gain a different user and group ownership for a
24 : * short period of time. It is expected to be used on the stack for when
25 : * you need to execute some code as a different user. It is called as_root
26 : * because in most cases you want to become root.
27 : *
28 : * The function drop_root_privileges() is used to completely drop root
29 : * privileges. This is often done just after a fork() and just before
30 : * the following exec() so child processes do not gain root privileges.
31 : * It is also used by servers that spawn child processes and do not want
32 : * those children to have root access.
33 : */
34 :
35 : // self
36 : //
37 : #include <snapdev/not_reached.h>
38 : #include <snapdev/not_used.h>
39 :
40 :
41 : // libexcept
42 : //
43 : #include "libexcept/exception.h"
44 :
45 :
46 : // C++
47 : //
48 : #include <algorithm>
49 : #include <string>
50 :
51 :
52 : // C
53 : //
54 : #include <grp.h>
55 : #include <pwd.h>
56 : #include <unistd.h>
57 :
58 :
59 :
60 : namespace snapdev
61 : {
62 :
63 :
64 :
65 0 : DECLARE_MAIN_EXCEPTION(as_root_exception);
66 :
67 0 : DECLARE_EXCEPTION(as_root_exception, still_root);
68 0 : DECLARE_EXCEPTION(as_root_exception, unknown_user);
69 :
70 :
71 : /** \brief A value representing an invalid group number.
72 : *
73 : * By default, the as_root class does not change the group ID. This value
74 : * represents that fact.
75 : *
76 : * \todo
77 : * Determine whether -1 is really not a valid group number. We may need to
78 : * handle this differently.
79 : */
80 : constexpr gid_t NO_GROUP = static_cast<gid_t>(-1);
81 :
82 :
83 : /** \brief Safely become root (or another user) and back.
84 : *
85 : * This class defines a way to become a different user (as defined by the
86 : * 's' bit of the executable) and then come back as the original user once
87 : * your work requiring that user is done.
88 : *
89 : * The class uses RAII so when an object of that type gets destroyed,
90 : * it automatically restore the user. You can also restore the user
91 : * sooner if safer in your code.
92 : *
93 : * This class is used only when a process is given a different user as
94 : * the owner of the executable file and when the set user identifier flag
95 : * is set on the executable (a.k.a. `chmod u+s /usr/bin/my-tool`).
96 : *
97 : * The class constructor attempts the change to the specified user (default
98 : * is root). If that fails, the is_switched() function returns false. It is
99 : * likely important that you verified whether the effective user ID was
100 : * changed or not to make sure that you can run the commands you expect to
101 : * run as that user.
102 : *
103 : * The class can be used recursively. So if function A uses as_root,
104 : * then calls function B which also uses as_root, the second time
105 : * the switch does nothing since you are already that user. Function A
106 : * will stil properly restore the user to the normal user instead of
107 : * the other user.
108 : *
109 : * The class is only expected to be used on the stack. If you use it
110 : * in an object, then the restore may not work as expected (i.e. it
111 : * could happen in the wrong order).
112 : *
113 : * \note
114 : * Make sure to check that the switch happened. A switch could fail because
115 : * the user cannot have yet another process under his belt or because the
116 : * kernel does not have enough memory to apply the change. It may not always
117 : * be because of unavailable permissions.
118 : *
119 : * \todo
120 : * I think that the f_switched boolean flag may need to be changed to a
121 : * 3 value type because the switch_on() may be able to change the group
122 : * and not the user and same with switch_off(). So we could be in a partial
123 : * state and right now that is not properly represented.
124 : */
125 : class as_root
126 : {
127 : public:
128 : /** \brief Shared pointer.
129 : *
130 : * If you want to create the object within an if() block, then you
131 : * can use this pointer like so:
132 : *
133 : * \code
134 : * ...
135 : * snapdev::as_root::pointer_t other_user;
136 : * if(<expr>)
137 : * {
138 : * other_user = std::make_shared<snapdev::as_root>(uid);
139 : * }
140 : * ...
141 : * // once end of function is reached, user is restored
142 : * \endcode
143 : */
144 : typedef std::shared_ptr<as_root> pointer_t;
145 :
146 : /** \brief Save the current user then switch to another user.
147 : *
148 : * This function switches us to the \p uid user. If that fails,
149 : * then the is_switched() function returns false. You can get
150 : * the exact error using the error_number() function. You can
151 : * also check the user identifier with getuid() and see whether
152 : * it matches what you otherwise expect.
153 : *
154 : * The destructor automatically reverts back to the original user.
155 : *
156 : * \param[in] uid The user identifier you want to become.
157 : * \param[in] gid The group identifier you want to become.
158 : */
159 7 : as_root(uid_t uid = 0, gid_t gid = NO_GROUP)
160 7 : : f_new_uid(uid)
161 7 : , f_new_gid(gid)
162 7 : , f_user_uid(getuid())
163 7 : , f_group_gid(getgid())
164 : {
165 7 : switch_on();
166 7 : }
167 :
168 : /** \brief Save the current user then switch to another user.
169 : *
170 : * This function switches us to the named user. If that fails,
171 : * then the is_switched() function returns false. You can get
172 : * the exact error using the error_number() function.
173 : *
174 : * If you become a different user other than root, you may want to
175 : * define the \p groupname parameter in order to have full permissions
176 : * to do a snappdev::chownnm().
177 : *
178 : * The destructor automatically reverts back to the original user.
179 : *
180 : * \exception unknown_user
181 : * If the named user or group do not exist on this computer, then this
182 : * exception is raised. It is also used in case the \p username
183 : * parameter is the empty string.
184 : *
185 : * \param[in] username The name of the user you want to become.
186 : * \param[in] groupname The name of the group you want to become or an
187 : * empty string to not change the group.
188 : */
189 : as_root(
190 : std::string const & username
191 : , std::string const & groupname = std::string())
192 : : f_user_uid(getuid())
193 : {
194 : if(username.empty())
195 : {
196 : throw unknown_user("user name to switch to cannot be an empty string.");
197 : }
198 : passwd * user(getpwnam(username.c_str()));
199 : if(user == nullptr)
200 : {
201 : unknown_user e(
202 : "user \""
203 : + username
204 : + "\" to switch to was not found on this computer.");
205 : e.set_parameter("username", username);
206 : throw e;
207 : }
208 : if(!groupname.empty())
209 : {
210 : group * grp(getgrnam(groupname.c_str()));
211 : if(grp == nullptr)
212 : {
213 : unknown_user e(
214 : "group \""
215 : + groupname
216 : + "\" to switch to was not found on this computer.");
217 : e.set_parameter("groupname", groupname);
218 : throw e;
219 : }
220 : f_new_gid = grp->gr_gid;
221 : }
222 : f_new_uid = user->pw_uid;
223 :
224 : switch_on();
225 : }
226 :
227 : /** \brief Restore the user as it was just before construction.
228 : *
229 : * This function restores the user as found in the construtor. If
230 : * the constructor could not switch to the other user, then this
231 : * function does nothing.
232 : */
233 7 : ~as_root()
234 : {
235 7 : NOT_USED(switch_off());
236 7 : }
237 :
238 : /** \brief Set the effective user ID back to what it was on construction.
239 : *
240 : * This function reverse the constructor action and returns the effective
241 : * user ID back to the user it was set to on construction. The destructor
242 : * calls this function automatically, but it is publicly available in
243 : * case you wanted to change the effective user ID back sooner.
244 : *
245 : * If the switch fails, then the error_number() function returns the
246 : * errno code returned by the seteuid() function.
247 : *
248 : * \return true if the switch worked as expected.
249 : *
250 : * \sa switch_on()
251 : * \sa error_number()
252 : */
253 7 : bool switch_off()
254 : {
255 7 : if(f_switched)
256 : {
257 1 : f_errno = 0;
258 2 : if(f_group_gid != NO_GROUP
259 1 : && setegid(f_group_gid) != 0)
260 : {
261 0 : f_errno = errno;
262 : }
263 1 : if(seteuid(f_user_uid) != 0)
264 : {
265 0 : f_errno = errno;
266 : }
267 1 : if(f_errno != 0)
268 : {
269 0 : return false;
270 : }
271 1 : f_switched = false;
272 : }
273 :
274 7 : return true;
275 : }
276 :
277 : /** \brief Switch the new user back on.
278 : *
279 : * This function sets the effective user ID back to the one specified
280 : * on the constructor. If that is already the current user ID (as per
281 : * the f_switched internal flag), then nothing happens.
282 : *
283 : * If you have part of the code that you want to execute as the new
284 : * user, then part as the old user, and then back again with the
285 : * new user, you can do so using the switch_on() and switch_off()
286 : * functions.
287 : *
288 : * If an error occurs, the function returns false and you can get
289 : * the errno set by the seteuid() function by calling the error_number()
290 : * function.
291 : *
292 : * \note
293 : * You cannot change the new effective user ID to this function. This
294 : * is because it would be much more difficult to manage that way. If
295 : * you need to switch to yet another user, then consider using another
296 : * as_root object.
297 : *
298 : * \return true if the switch succeeded.
299 : *
300 : * \sa switch_off()
301 : * \sa error_number()
302 : */
303 7 : bool switch_on()
304 : {
305 7 : if(!f_switched)
306 : {
307 7 : if(seteuid(f_new_uid) != 0)
308 : {
309 6 : f_errno = errno;
310 6 : return false;
311 : }
312 1 : if(f_new_gid != NO_GROUP)
313 : {
314 0 : if(setegid(f_new_gid) != 0)
315 : {
316 : // attempt to restore the previous user on error
317 : //
318 0 : NOT_USED(seteuid(f_user_uid));
319 :
320 0 : f_errno = errno;
321 0 : return false;
322 : }
323 : }
324 1 : f_switched = true;
325 1 : f_errno = 0;
326 : }
327 1 : return true;
328 : }
329 :
330 : /** \brief Check whether the as_root object is considered switched.
331 : *
332 : * The constructor of the function attempts to switch to a different
333 : * user. If the function fails, then the errno value is saved and
334 : * the switched flag is set to true. This function returns the value
335 : * of the switch flag. If true, then you are switched. If false, you
336 : * are not switched.
337 : *
338 : * If you are not switched, you may check the error number with
339 : * the error_number() function to know whether it's an invalid
340 : * state or just that you are not the new user at the moment.
341 : *
342 : * \return true if the constructor worked and thus we are root.
343 : *
344 : * \sa error_number()
345 : */
346 6 : bool is_switched() const
347 : {
348 6 : return f_switched;
349 : }
350 :
351 : /** \brief Get the error number.
352 : *
353 : * The switch_on() and switch_off() functions saves the error number
354 : * set by the seteuid() function when it fails. That error number can
355 : * be retrieved using this function.
356 : *
357 : * If you call the switch_on() and switch_off() functions and they
358 : * succeed, then this function returns 0.
359 : *
360 : * \return The errno as set by the seteuid() on an error, otherwise 0.
361 : *
362 : * \sa is_switched()
363 : * \sa switch_on()
364 : * \sa switch_off()
365 : */
366 2 : int error_number() const
367 : {
368 2 : return f_errno;
369 : }
370 :
371 : private:
372 : /** \brief The UID to become.
373 : *
374 : * This value represents the UID to become. It can be set on the
375 : * constructor only. It cannot be changed later. If you need yet
376 : * another user, you will have to create a different as_root object
377 : * while you are that other user.
378 : */
379 : uid_t f_new_uid = 0;
380 :
381 : /** \brief The GID to become.
382 : *
383 : * This value represents the GID to become or NO_GROUP. It can be set on
384 : * the constructor only. It cannot be changed later. If you need yet
385 : * another group, you will have to create a different as_root object
386 : * while you are that other user.
387 : */
388 : gid_t f_new_gid = NO_GROUP;
389 :
390 : /** \brief The user UID on entry.
391 : *
392 : * The constructor saves the current user identifier so we can restore
393 : * it on destruction.
394 : */
395 : uid_t f_user_uid = 0;
396 :
397 : /** \brief The group GID on entry.
398 : *
399 : * The constructor saves the current group identifier so we can restore
400 : * it on destruction.
401 : */
402 : gid_t f_group_gid = NO_GROUP;
403 :
404 : /** \brief The error number.
405 : *
406 : * If the seteuid() found in the constructor fails, then this value is
407 : * set to the errno parameter as set by the seteuid() function.
408 : *
409 : * By default, the value is 0 which means that the seteuid() function
410 : * succeeded in changing the user identifier to the root user identifier.
411 : */
412 : int f_errno = 0;
413 :
414 : /** \brief Whether the switch is still in effect.
415 : *
416 : * This flag is set to true when the constructor succeed in switching
417 : * the user to the specified UID.
418 : *
419 : * You can later switch back and forth and thus the flag may be turned
420 : * back on or off.
421 : */
422 : bool f_switched = false;
423 : };
424 :
425 :
426 : /** \brief Modes available to the drop_root_privileges() function.
427 : *
428 : * These modes define how to act in case the caller cannot be switched to
429 : * a user other than root.
430 : */
431 : enum class drop_privilege_mode_t
432 : {
433 : /** \brief The default mode is to fail.
434 : *
435 : * This mode is the default. In this case, a drop failure means that the
436 : * function throws a snapdev::still_root exception.
437 : */
438 : DROP_PRIVILEGE_MODE_FAIL,
439 :
440 : /** \brief Try swithing to another user.
441 : *
442 : * This mode allows the process to attempt to switch to the specified
443 : * user. By default, that user is "nobody". You can change that to a
444 : * different user by changing the nobody parameter of the
445 : * drop_root_privileges() function.
446 : */
447 : DROP_PRIVILEGE_MODE_TRY_NOBODY,
448 :
449 : /** \brief Allow the root user.
450 : *
451 : * If this mode is used and the drop of privileges fails to go to a
452 : * user other than root, then the function simply returns. That means
453 : * you will be running your code as root. If that is not acceptable,
454 : * then you do not want to use this option.
455 : */
456 : DROP_PRIVILEGE_MODE_ALLOW_ROOT,
457 : };
458 :
459 :
460 :
461 : /** \brief This functionmakes sure your process cannot become root.
462 : *
463 : * To make sure your application is not given root privileges, you can call
464 : * this function when your main() function starts. This is useful for
465 : * services which are not expected to run as root.
466 : *
467 : * By default, the function attempts to switch to the user that started
468 : * the process (`getuid(2)`). If that is still root, then it uses the
469 : * \p mode parameter to know how to react next.
470 : *
471 : * \li drop_privilege_mode_t::DROP_PRIVILEGE_MODE_ALLOW_ROOT
472 : *
473 : * If it is acceptable to run your software as root anyway, then use this
474 : * mode instead of the default.
475 : *
476 : * \li drop_privilege_mode_t::DROP_PRIVILEGE_MODE_TRY_NOBODY
477 : *
478 : * Try dropping privileges to the specified \p user_name user. By default,
479 : * \p user_name is set to "nobody", hence the name of the mode.
480 : *
481 : * \li drop_privilege_mode_t::DROP_PRIVILEGE_MODE_FAIL
482 : *
483 : * When \p mode is set to this value, failure to drop privileges result
484 : * in the `still_root` exception.
485 : *
486 : * \exception still_root
487 : * It is important to note that, by default, if the user is still root after
488 : * the attempt to drop privileges, the function raises this exception.
489 : * You can let your users continue to run as root by changing the mode
490 : * parameter.
491 : *
492 : * \param[in] mode Defines what to do in case the privileges are not dropped.
493 : */
494 1 : inline void drop_root_privileges(
495 : drop_privilege_mode_t const mode = drop_privilege_mode_t::DROP_PRIVILEGE_MODE_FAIL
496 : , std::string const & user_name = "nobody")
497 : {
498 : // try dropping privileges
499 : //
500 1 : int r(setuid(getuid()));
501 1 : if(r == 0)
502 : {
503 : // still root?
504 : //
505 1 : if(getuid() != 0)
506 : {
507 1 : return;
508 : }
509 : }
510 :
511 0 : switch(mode)
512 : {
513 0 : case drop_privilege_mode_t::DROP_PRIVILEGE_MODE_ALLOW_ROOT:
514 : // user allows root anyway
515 : //
516 0 : return;
517 :
518 0 : case drop_privilege_mode_t::DROP_PRIVILEGE_MODE_TRY_NOBODY:
519 : {
520 0 : passwd * user(getpwnam(user_name.c_str()));
521 0 : if(user == nullptr)
522 : {
523 0 : unknown_user e(
524 : "user \""
525 0 : + user_name
526 0 : + "\" to drop privileges to was not found on this computer.");
527 0 : e.set_parameter("username", user_name);
528 0 : throw e;
529 0 : }
530 :
531 0 : if(setuid(user->pw_uid) == 0)
532 : {
533 0 : if(getuid() == user->pw_uid)
534 : {
535 : // we are a nobody process now, we're safe
536 : //
537 0 : return;
538 : }
539 : }
540 : }
541 : [[fallthrough]];
542 : case drop_privilege_mode_t::DROP_PRIVILEGE_MODE_FAIL:
543 0 : throw still_root("this process could not drop root privileges.");
544 :
545 : }
546 0 : snapdev::NOT_REACHED();
547 : }
548 :
549 :
550 :
551 : } // namespace snapdev
552 : // vim: ts=4 sw=4 et
|