1
/* Copyright © 2005-2006 Roger Leigh <rleigh@debian.org>
3
* schroot is free software; you can redistribute it and/or modify it
4
* under the terms of the GNU General Public License as published by
5
* the Free Software Foundation; either version 2 of the License, or
6
* (at your option) any later version.
8
* schroot is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
* General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program; if not, write to the Free Software
15
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
18
*********************************************************************/
35
#include <boost/format.hpp>
37
#include <uuid/uuid.h>
42
using namespace sbuild;
48
* Check group membership.
50
* @param group the group to check for.
51
* @returns true if the user is a member of group, otherwise false.
54
is_group_member (std::string const& group)
57
struct group *groupbuf = getgrnam(group.c_str());
61
log_error() << format(_("%1%: group not found")) % group << endl;
63
log_error() << format(_("%1%: group not found: %2%"))
64
% group % strerror(errno)
69
bool group_member = false;
70
if (groupbuf->gr_gid == getgid())
76
int supp_group_count = getgroups(0, NULL);
77
if (supp_group_count < 0)
79
log_error() << format(_("can't get supplementary group count: %1%"))
84
if (supp_group_count > 0)
86
gid_t *supp_groups = new gid_t[supp_group_count];
87
if (getgroups(supp_group_count, supp_groups) < 1)
89
log_error() << format(_("can't get supplementary groups: %1%"))
95
for (int i = 0; i < supp_group_count; ++i)
97
if (groupbuf->gr_gid == supp_groups[i])
100
delete[] supp_groups;
107
volatile bool sighup_called = false;
110
* Handle the SIGALRM signal.
112
* @param ignore the signal number.
115
sighup_handler (int ignore)
117
/* This exists so that system calls get interrupted. */
118
sighup_called = true;
122
volatile bool child_wait = true;
127
session::session (std::string const& service,
130
sbuild::string_list const& chroots):
136
session_operation(operation),
148
session::get_config ()
154
session::set_config (config_ptr& config)
156
this->config = config;
160
session::get_chroots () const
162
return this->chroots;
166
session::set_chroots (string_list const& chroots)
168
this->chroots = chroots;
172
session::get_operation () const
174
return this->session_operation;
178
session::set_operation (operation operation)
180
this->session_operation = operation;
184
session::get_session_id () const
186
return this->session_id;
190
session::set_session_id (std::string const& session_id)
192
this->session_id = session_id;
196
session::get_force () const
202
session::set_force (bool force)
208
session::get_child_status () const
210
return this->child_status;
214
session::get_auth_status () const
216
assert(!this->chroots.empty());
217
if (this->config.get() == 0) return auth::STATUS_FAIL;
220
* Note that the root user can't escape authentication. This is
221
* because pam_rootok.so should be used in the PAM configuration if
222
* root should automatically be granted access. The only exception
223
* is that the root group doesn't need to be added to the groups or
227
auth::status status = auth::STATUS_NONE;
229
/* @todo Use set difference rather than iteration and
231
for (string_list::const_iterator cur = this->chroots.begin();
232
cur != this->chroots.end();
235
const chroot::ptr chroot = this->config->find_alias(*cur);
236
if (!chroot) // Should never happen, but cater for it anyway.
238
log_warning() << format(_("No chroot found matching alias '%1%'"))
241
status = change_auth(status, auth::STATUS_FAIL);
244
string_list const& groups = chroot->get_groups();
245
string_list const& root_groups = chroot->get_root_groups();
249
bool in_groups = false;
250
bool in_root_groups = false;
254
for (string_list::const_iterator gp = groups.begin();
257
if (is_group_member(*gp))
261
if (!root_groups.empty())
263
for (string_list::const_iterator gp = root_groups.begin();
264
gp != root_groups.end();
266
if (is_group_member(*gp))
267
in_root_groups = true;
271
* No auth required if in root groups and changing to root,
272
* or if the uid is not changing. If not in a group,
273
* authentication fails immediately.
275
if (in_groups == true &&
276
((this->get_uid() == 0 && in_root_groups == true) ||
277
(this->get_ruid() == this->get_uid())))
279
status = change_auth(status, auth::STATUS_NONE);
281
else if (in_groups == true) // Auth required if not in root group
283
status = change_auth(status, auth::STATUS_USER);
285
else // Not in any groups
287
if (this->get_ruid() == 0)
288
status = change_auth(status, auth::STATUS_USER);
290
status = change_auth(status, auth::STATUS_FAIL);
293
else // No available groups entries means no access to anyone
295
if (this->get_ruid() == 0)
296
status = change_auth(status, auth::STATUS_USER);
298
status = change_auth(status, auth::STATUS_FAIL);
308
assert(this->config.get() != NULL);
309
assert(!this->chroots.empty());
313
sighup_called = false;
314
set_sighup_handler();
316
for (string_list::const_iterator cur = this->chroots.begin();
317
cur != this->chroots.end();
320
log_debug(DEBUG_NOTICE)
321
<< format("Running session in %1% chroot:") % *cur
323
const chroot::ptr ch = this->config->find_alias(*cur);
324
if (!ch) // Should never happen, but cater for it anyway.
326
format fmt(_("%1%: Failed to find chroot"));
331
chroot::ptr chroot(ch->clone());
333
/* If restoring a session, set the session ID from the
334
chroot name, or else generate it. Only chroots which
335
support session creation append a UUID to the session
337
if (chroot->get_active() ||
338
!(chroot->get_session_flags() & chroot::SESSION_CREATE))
340
set_session_id(chroot->get_name());
347
uuid_unparse(uuid, uuid_str);
349
std::string session_id(chroot->get_name() + "-" + uuid_str);
350
set_session_id(session_id);
353
/* Activate chroot. */
354
chroot->set_active(true);
356
/* If a chroot mount location has not yet been set, and the
357
chroot is not a plain chroot, set a mount location with the
360
chroot_plain *plain = dynamic_cast<chroot_plain *>(chroot.get());
361
if (chroot->get_mount_location().empty() &&
362
(plain == 0 || plain->get_run_setup_scripts() == true))
364
std::string location(std::string(SCHROOT_MOUNT_DIR) + "/" +
366
chroot->set_mount_location(location);
370
/* Chroot types which create a session (e.g. LVM devices)
371
need the chroot name respecifying. */
372
if (chroot->get_session_flags() & chroot::SESSION_CREATE)
374
chroot->set_name(this->session_id);
375
chroot->set_aliases(string_list());
378
/* LVM devices need the snapshot device name specifying. */
379
chroot_lvm_snapshot *snapshot = 0;
380
if ((snapshot = dynamic_cast<chroot_lvm_snapshot *>(chroot.get())) != 0)
382
std::string dir(dirname(snapshot->get_device(), '/'));
383
std::string device(dir + "/" + this->session_id);
384
snapshot->set_snapshot_device(device);
389
/* Run setup-start chroot setup scripts. */
390
setup_chroot(chroot, chroot::SETUP_START);
391
if (this->session_operation == OPERATION_BEGIN)
392
cout << this->session_id << endl;
394
/* Run recover scripts. */
395
setup_chroot(chroot, chroot::SETUP_RECOVER);
399
/* Run exec-start scripts. */
400
setup_chroot(chroot, chroot::EXEC_START);
402
/* Run session if setup succeeded. */
403
if (this->session_operation == OPERATION_AUTOMATIC ||
404
this->session_operation == OPERATION_RUN)
407
/* Run exec-stop scripts whether or not there was an
409
setup_chroot(chroot, chroot::EXEC_STOP);
411
catch (error const& e)
413
setup_chroot(chroot, chroot::EXEC_STOP);
418
catch (error const& e)
422
setup_chroot(chroot, chroot::SETUP_STOP);
424
catch (error const& discard)
427
chroot->set_active(false);
431
/* Run setup-stop chroot setup scripts whether or not there
433
setup_chroot(chroot, chroot::SETUP_STOP);
435
/* Deactivate chroot. */
436
chroot->set_active(false);
439
clear_sighup_handler();
441
catch (error const& e)
443
clear_sighup_handler();
445
/* If a command was not run, but something failed, the exit
446
status still needs setting. */
447
if (this->child_status == 0)
448
this->child_status = EXIT_FAILURE;
454
session::setup_chroot (sbuild::chroot::ptr& session_chroot,
455
sbuild::chroot::setup_type setup_type)
457
assert(!session_chroot->get_name().empty());
459
if (!((this->session_operation == OPERATION_BEGIN &&
460
setup_type == chroot::SETUP_START) ||
461
(this->session_operation == OPERATION_RECOVER &&
462
setup_type == chroot::SETUP_RECOVER) ||
463
(this->session_operation == OPERATION_END &&
464
setup_type == chroot::SETUP_STOP) ||
465
(this->session_operation == OPERATION_RUN &&
466
(setup_type == chroot::EXEC_START ||
467
setup_type == chroot::EXEC_STOP)) ||
468
(this->session_operation == OPERATION_AUTOMATIC &&
469
(setup_type == chroot::SETUP_START ||
470
setup_type == chroot::SETUP_STOP ||
471
setup_type == chroot::EXEC_START ||
472
setup_type == chroot::EXEC_STOP))))
475
if (((setup_type == chroot::SETUP_START ||
476
setup_type == chroot::SETUP_RECOVER ||
477
setup_type == chroot::SETUP_STOP) &&
478
session_chroot->get_run_setup_scripts() == false) ||
479
((setup_type == chroot::EXEC_START ||
480
setup_type == chroot::EXEC_STOP) &&
481
session_chroot->get_run_exec_scripts() == false))
484
if (setup_type == chroot::SETUP_START)
485
this->chroot_status = true;
489
session_chroot->lock(setup_type);
491
catch (chroot::error const& e)
493
this->chroot_status = false;
496
// Release lock, which also removes session metadata.
497
session_chroot->unlock(setup_type, 0);
499
catch (chroot::error const& ignore)
502
format fmt(_("Chroot setup failed to lock chroot: %1%"));
507
std::string setup_type_string;
508
if (setup_type == chroot::SETUP_START)
509
setup_type_string = "setup-start";
510
else if (setup_type == chroot::SETUP_RECOVER)
511
setup_type_string = "setup-recover";
512
else if (setup_type == chroot::SETUP_STOP)
513
setup_type_string = "setup-stop";
514
else if (setup_type == chroot::EXEC_START)
515
setup_type_string = "exec-start";
516
else if (setup_type == chroot::EXEC_STOP)
517
setup_type_string = "exec-stop";
519
std::string chroot_status_string;
520
if (this->chroot_status)
521
chroot_status_string = "ok";
523
chroot_status_string = "fail";
525
string_list arg_list;
526
arg_list.push_back(RUN_PARTS); // Run run-parts(8)
527
if (get_verbosity() == auth::VERBOSITY_VERBOSE)
528
arg_list.push_back("--verbose");
529
arg_list.push_back("--lsbsysinit");
530
arg_list.push_back("--exit-on-error");
531
if (setup_type == chroot::SETUP_STOP ||
532
setup_type == chroot::EXEC_STOP)
533
arg_list.push_back("--reverse");
534
format arg_fmt1("--arg=%1%");
535
arg_fmt1 % setup_type_string;
536
arg_list.push_back(arg_fmt1.str());
537
format arg_fmt2("--arg=%1%");
538
arg_fmt2 % chroot_status_string;
539
arg_list.push_back(arg_fmt2.str());
540
if (setup_type == chroot::SETUP_START ||
541
setup_type == chroot::SETUP_RECOVER ||
542
setup_type == chroot::SETUP_STOP)
543
arg_list.push_back(SCHROOT_CONF_SETUP_D); // Setup directory
545
arg_list.push_back(SCHROOT_CONF_EXEC_D); // Run directory
547
/* Get a complete list of environment variables to set. We need to
548
query the chroot here, since this can vary depending upon the
551
session_chroot->setup_env(env);
552
env.add("AUTH_USER", get_user());
554
const char *verbosity = NULL;
555
switch (get_verbosity())
557
case auth::VERBOSITY_QUIET:
560
case auth::VERBOSITY_NORMAL:
561
verbosity = "normal";
563
case auth::VERBOSITY_VERBOSE:
564
verbosity = "verbose";
567
log_debug(DEBUG_CRITICAL) << format(_("Invalid verbosity level: %1%, falling back to \"normal\""))
568
% static_cast<int>(get_verbosity())
570
verbosity = "normal";
573
env.add("AUTH_VERBOSITY", verbosity);
576
env.add("MOUNT_DIR", SCHROOT_MOUNT_DIR);
577
env.add("LIBEXEC_DIR", SCHROOT_LIBEXEC_DIR);
578
env.add("PID", getpid());
579
env.add("SESSION_ID", this->session_id);
584
if ((pid = fork()) == -1)
586
this->chroot_status = false;
587
format fmt(_("Failed to fork child: %1%"));
588
fmt % strerror(errno);
593
// The setup scripts don't use our syslog fd.
597
/* This is required to ensure the scripts run with uid=0 and gid=0,
598
otherwise setuid programs such as mount(8) will fail. This
599
should always succeed, because our euid=0 and egid=0.*/
602
initgroups("root", 0);
603
if (exec (arg_list[0], arg_list, env))
605
log_error() << format(_("Could not exec \"%1%\": %2%"))
606
% arg_list[0] % strerror(errno)
610
exit (EXIT_FAILURE); /* Should never be reached. */
614
wait_for_child(pid, exit_status);
619
session_chroot->unlock(setup_type, exit_status);
621
catch (chroot::error const& e)
623
this->chroot_status = false;
624
format fmt(_("Chroot setup failed to unlock chroot: %1%"));
629
if (exit_status != 0)
631
this->chroot_status = false;
632
format fmt(_("Chroot setup failed during chroot \"%1%\" stage"));
633
fmt % setup_type_string;
639
session::run_child (sbuild::chroot::ptr& session_chroot)
641
assert(!session_chroot->get_name().empty());
643
assert(!get_user().empty());
644
assert(!get_shell().empty());
645
assert(auth::pam != NULL); // PAM must be initialised
647
std::string location(session_chroot->get_path());
650
char *raw_cwd = getcwd (NULL, 0);
658
/* Child errors result in immediate exit(). Errors are not
659
propagated back via an exception, because there is no longer any
660
higher-level handler to catch them. */
665
catch (auth::error const& e)
667
log_error() << format(_("PAM error: %1%")) % e.what()
672
/* Set group ID and supplementary groups */
673
if (setgid (get_gid()))
675
log_error() << format(_("Could not set gid to '%1%'")) % get_gid()
679
if (initgroups (get_user().c_str(), get_gid()))
681
log_error() << _("Could not set supplementary group IDs") << endl;
685
/* Set the process execution domain. */
688
session_chroot->get_persona().set();
690
catch (personality::error const& e)
692
log_error() << e.what() << endl;
696
/* Enter the chroot */
697
if (chdir (location.c_str()))
699
log_error() << format(_("Could not chdir to '%1%': %2%"))
700
% location % strerror(errno)
704
if (::chroot (location.c_str()))
706
log_error() << format(_("Could not chroot to '%1%': %2%"))
707
% location % strerror(errno)
712
/* Set uid and check we are not still root */
713
if (setuid (get_uid()))
715
log_error() << format(_("Could not set uid to '%1%'")) % get_uid()
719
if (!setuid (0) && get_uid())
721
log_error() << _("Failed to drop root permissions.")
728
string_list command(get_command());
730
/* chdir to current directory */
731
if (chdir (cwd.c_str()))
733
/* Fall back to home directory, but only for a login shell,
734
since for a command we require deterministic behaviour. */
735
if (command.empty() ||
736
command[0].empty()) // No command
738
log_warning() << format(_("Could not chdir to '%1%': %2%"))
739
% cwd % strerror(errno)
742
if (chdir (get_home().c_str()))
743
log_warning() << format(_("Falling back to '%1%'"))
747
log_warning() << format(_("Falling back to home directory '%1%'"))
753
log_error() << format(_("Could not chdir to '%1%': %2%"))
754
% cwd % strerror(errno)
760
/* Set up environment */
761
environment env = get_pam_environment();
762
log_debug(DEBUG_INFO)
763
<< "Set environment:\n" << env;
765
/* Run login shell */
766
if (command.empty() ||
767
command[0].empty()) // No command
769
assert (!get_shell().empty());
772
if (get_environment().empty() &&
773
session_chroot->get_command_prefix().empty())
774
// Not keeping environment and can setup argv correctly; login shell
776
std::string shellbase = basename(get_shell(), '/');
777
std::string loginshell = "-" + shellbase;
778
command.push_back(loginshell);
779
log_debug(DEBUG_INFO)
780
<< format("Login shell: %1%") % command[0] << endl;
784
command.push_back(get_shell());
787
if (get_environment().empty() &&
788
session_chroot->get_command_prefix().empty())
790
log_debug(DEBUG_NOTICE)
791
<< format("Running login shell: %1%") % get_shell() << endl;
792
syslog(LOG_USER|LOG_NOTICE, "[%s chroot] (%s->%s) Running login shell: \"%s\"",
793
session_chroot->get_name().c_str(), get_ruser().c_str(), get_user().c_str(), get_shell().c_str());
797
log_debug(DEBUG_NOTICE)
798
<< format("Running shell: %1%") % get_shell() << endl;
799
syslog(LOG_USER|LOG_NOTICE, "[%s chroot] (%s->%s) Running shell: \"%s\"",
800
session_chroot->get_name().c_str(), get_ruser().c_str(), get_user().c_str(), get_shell().c_str());
803
if (get_verbosity() != auth::VERBOSITY_QUIET)
805
if (get_ruid() == get_uid())
807
<< format((get_environment().empty() &&
808
session_chroot->get_command_prefix().empty() ?
809
_("[%1% chroot] Running login shell: \"%2%\"") :
810
_("[%1% chroot] Running shell: \"%2%\"")))
811
% session_chroot->get_name() % get_shell()
815
<< format((get_environment().empty() &&
816
session_chroot->get_command_prefix().empty() ?
817
_("[%1% chroot] (%2%->%3%) Running login shell: \"%4%\"") :
818
_("[%1% chroot] (%2%->%3%) Running shell: \"%4%\"")))
819
% session_chroot->get_name()
820
% get_ruser() % get_user()
827
/* Search for program in path. */
828
file = find_program_in_path(command[0], getenv("PATH"), "");
831
std::string commandstring = string_list_to_string(command, " ");
832
log_debug(DEBUG_NOTICE)
833
<< format("Running command: %1%") % commandstring << endl;
834
syslog(LOG_USER|LOG_NOTICE, "[%s chroot] (%s->%s) Running command: \"%s\"",
835
session_chroot->get_name().c_str(), get_ruser().c_str(), get_user().c_str(), commandstring.c_str());
836
if (get_verbosity() != auth::VERBOSITY_QUIET)
838
if (get_ruid() == get_uid())
839
log_info() << format(_("[%1% chroot] Running command: \"%2%\""))
840
% session_chroot->get_name() % commandstring
843
log_info() << format(_("[%1% chroot] (%2%->%3%) Running command: \"%4%\""))
844
% session_chroot->get_name()
845
% get_ruser() % get_user()
851
// The user's command does not use our syslog fd.
854
// Add command prefix.
855
string_list full_command(session_chroot->get_command_prefix());
856
if (full_command.size() > 0)
857
file = full_command[0];
858
for (string_list::const_iterator pos = command.begin();
859
pos != command.end();
861
full_command.push_back(*pos);
864
if (exec (file, full_command, env))
866
log_error() << format(_("Could not exec \"%1%\": %2%"))
867
% command[0] % strerror(errno)
871
/* This should never be reached */
876
session::wait_for_child (int pid,
879
child_status = EXIT_FAILURE; // Default exit status
882
bool child_killed = false;
886
if (sighup_called && !child_killed)
888
log_error() << _("caught hangup signal, terminating...")
891
this->chroot_status = false;
895
if (wait(&status) != pid)
897
if (errno == EINTR && sighup_called)
898
continue; // Kill child and wait again.
901
format fmt(_("wait for child failed: %1%"));
902
fmt % strerror(errno);
906
else if (sighup_called)
908
sighup_called = false;
909
throw error(_("caught hangup signal, terminating..."));
919
catch (auth::error const& e)
921
throw error(e.what());
924
if (!WIFEXITED(status))
926
if (WIFSIGNALED(status))
928
format fmt(_("Child terminated by signal %1%"));
929
fmt % strsignal(WTERMSIG(status));
932
else if (WCOREDUMP(status))
933
throw error(_("Child dumped core"));
935
throw error(_("Child exited abnormally (reason unknown; not a signal or core dump)"));
938
child_status = WEXITSTATUS(status);
942
format fmt(_("Child exited abnormally with status '%1%'"));
949
session::run_chroot (sbuild::chroot::ptr& session_chroot)
951
assert(!session_chroot->get_name().empty());
954
if ((pid = fork()) == -1)
956
format fmt(_("Failed to fork child: %1%"));
957
fmt % strerror(errno);
966
run_child(session_chroot);
967
exit (EXIT_FAILURE); /* Should never be reached. */
971
wait_for_child(pid, this->child_status);
976
session::exec (std::string const& file,
977
string_list const& command,
978
environment const& env)
980
char **argv = string_list_to_strv(command);
981
char **envp = env.get_strv();
984
if ((status = execve(file.c_str(), argv, envp)) != 0)
994
session::set_sighup_handler ()
996
struct sigaction new_sa;
997
sigemptyset(&new_sa.sa_mask);
999
new_sa.sa_handler = sighup_handler;
1001
if (sigaction(SIGHUP, &new_sa, &this->saved_signals) != 0)
1003
format fmt(_("failed to set hangup handler: %1%"));
1004
fmt % strerror(errno);
1010
session::clear_sighup_handler ()
1012
/* Restore original handler */
1013
sigaction (SIGHUP, &this->saved_signals, NULL);