sbuild-session.cc

Go to the documentation of this file.
00001 /* Copyright © 2005-2006  Roger Leigh <rleigh@debian.org>
00002  *
00003  * schroot is free software; you can redistribute it and/or modify it
00004  * under the terms of the GNU General Public License as published by
00005  * the Free Software Foundation; either version 2 of the License, or
00006  * (at your option) any later version.
00007  *
00008  * schroot is distributed in the hope that it will be useful, but
00009  * WITHOUT ANY WARRANTY; without even the implied warranty of
00010  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011  * General Public License for more details.
00012  *
00013  * You should have received a copy of the GNU General Public License
00014  * along with this program; if not, write to the Free Software
00015  * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
00016  * MA  02111-1307  USA
00017  *
00018  *********************************************************************/
00019 
00020 #include <config.h>
00021 
00022 #include "sbuild-chroot-plain.h"
00023 #include "sbuild-chroot-lvm-snapshot.h"
00024 #include "sbuild-run-parts.h"
00025 #include "sbuild-session.h"
00026 #include "sbuild-util.h"
00027 
00028 #include <cassert>
00029 #include <cerrno>
00030 #include <cstdlib>
00031 #include <cstring>
00032 #include <iostream>
00033 #include <memory>
00034 
00035 #include <sys/types.h>
00036 #include <sys/stat.h>
00037 #include <fcntl.h>
00038 #include <termios.h>
00039 #include <unistd.h>
00040 
00041 #include <syslog.h>
00042 
00043 #include <boost/format.hpp>
00044 
00045 #include <uuid/uuid.h>
00046 
00047 using std::cout;
00048 using std::endl;
00049 using boost::format;
00050 using namespace sbuild;
00051 
00052 namespace
00053 {
00054 
00055   typedef std::pair<sbuild::session::error_code,const char *> emap;
00056 
00061   emap init_errors[] =
00062     {
00063       // TRANSLATORS: %1% = directory
00064       emap(session::CHDIR,          N_("Failed to change to directory '%1%'")),
00065       // TRANSLATORS: %4% = directory
00066       emap(session::CHDIR_FB,       N_("Falling back to directory '%4%'")),
00067       emap(session::CHILD_CORE,     N_("Child dumped core")),
00068       emap(session::CHILD_FAIL,     N_("Child exited abnormally (reason unknown; not a signal or core dump)")),
00069       emap(session::CHILD_FORK,     N_("Failed to fork child")),
00070       // TRANSLATORS: %4% = signal name
00071       emap(session::CHILD_SIGNAL,   N_("Child terminated by signal '%4%'")),
00072       emap(session::CHILD_WAIT,     N_("Wait for child failed")),
00073       // TRANSLATORS: %1% = directory
00074       emap(session::CHROOT,         N_("Failed to change root to directory '%1%'")),
00075       // TRANSLATORS: %1% = chroot name
00076       emap(session::CHROOT_ALIAS,   N_("No chroot found matching name or alias '%1%'")),
00077       emap(session::CHROOT_LOCK,    N_("Failed to lock chroot")),
00078       emap(session::CHROOT_SETUP,   N_("Chroot setup failed")),
00079       // TRANSLATORS: %1% = chroot name
00080       emap(session::CHROOT_UNKNOWN, N_("Failed to find chroot '%1%'")),
00081       emap(session::CHROOT_UNLOCK,  N_("Failed to unlock chroot")),
00082       // TRANSLATORS: %1% = command
00083       emap(session::COMMAND_ABS,    N_("Command \"%1%\" must have an absolute path")),
00084       // TRANSLATORS: %1% = command
00085       emap(session::EXEC,           N_("Failed to execute \"%1%\"")),
00086       // TRANSLATORS: A supplementary group is the list of additional
00087       // system groups a user belongs to, in addition to their default
00088       // group.
00089       emap(session::GROUP_GET_SUP,  N_("Failed to get supplementary groups")),
00090       // TRANSLATORS: A supplementary group is the list of additional
00091       // system groups a user belongs to, in addition to their default
00092       // group.
00093       emap(session::GROUP_GET_SUPC, N_("Failed to get supplementary group count")),
00094       // TRANSLATORS: %1% = integer group ID
00095       emap(session::GROUP_SET,      N_("Failed to set group '%1%'")),
00096       emap(session::GROUP_SET_SUP,  N_("Failed to set supplementary groups")),
00097       // TRANSLATORS: %1% = group name
00098       emap(session::GROUP_UNKNOWN,  N_("Group '%1%' not found")),
00099       emap(session::PAM,            N_("PAM error")),
00100       emap(session::ROOT_DROP,      N_("Failed to drop root permissions")),
00101       // TRANSLATORS: %1% = command
00102       emap(session::SHELL,          N_("Shell '%1%' not available")),
00103       // TRANSLATORS: %4% = command
00104       emap(session::SHELL_FB,       N_("Falling back to shell '%4%'")),
00105       // TRANSLATORS: %4% = signal name
00106       emap(session::SIGNAL_CATCH,   N_("Caught signal '%4%'")),
00107       // TRANSLATORS: %4% = signal name
00108       emap(session::SIGNAL_SET,     N_("Failed to set signal handler '%4%'")),
00109       // TRANSLATORS: %1% = integer user ID
00110       emap(session::USER_SET,       N_("Failed to set user '%1%'")),
00111       // TRANSLATORS: %1% = user name
00112       // TRANSLATORS: %2% = user name
00113       // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192
00114       emap(session::USER_SWITCH,    N_("(%1%->%2%): User switching is not permitted")),
00115     };
00116 
00123   std::string
00124   getcwd ()
00125   {
00126     std::string cwd;
00127 
00128     char *raw_cwd = ::getcwd (0, 0);
00129     if (raw_cwd)
00130       cwd = raw_cwd;
00131     else
00132       cwd = "/";
00133     free(raw_cwd);
00134 
00135     return cwd;
00136   }
00137 
00144   bool
00145   is_group_member (std::string const& group)
00146   {
00147     errno = 0;
00148     struct group *groupbuf = getgrnam(group.c_str());
00149     if (groupbuf == 0)
00150       {
00151         if (errno == 0)
00152           {
00153             session::error e(group, session::GROUP_UNKNOWN);
00154             log_exception_warning(e);
00155           }
00156         else
00157           {
00158             session::error e(group, session::GROUP_UNKNOWN, strerror(errno));
00159             log_exception_warning(e);
00160           }
00161         return false;
00162       }
00163 
00164     bool group_member = false;
00165     if (groupbuf->gr_gid == getgid())
00166       {
00167         group_member = true;
00168       }
00169     else
00170       {
00171         int supp_group_count = getgroups(0, 0);
00172         if (supp_group_count < 0)
00173           throw session::error(session::GROUP_GET_SUPC, strerror(errno));
00174         if (supp_group_count > 0)
00175           {
00176             gid_t *supp_groups = new gid_t[supp_group_count];
00177             assert (supp_groups);
00178             if (getgroups(supp_group_count, supp_groups) < 1)
00179               throw session::error(session::GROUP_GET_SUP, strerror(errno));
00180 
00181             for (int i = 0; i < supp_group_count; ++i)
00182               {
00183                 if (groupbuf->gr_gid == supp_groups[i])
00184                   group_member = true;
00185               }
00186             delete[] supp_groups;
00187           }
00188       }
00189 
00190     return group_member;
00191   }
00192 
00193   volatile bool sighup_called = false;
00194   volatile bool sigterm_called = false;
00195 
00201   void
00202   sighup_handler (int ignore)
00203   {
00204     /* This exists so that system calls get interrupted. */
00205     sighup_called = true;
00206   }
00207 
00213   void
00214   sigterm_handler (int ignore)
00215   {
00216     /* This exists so that system calls get interrupted. */
00217     sigterm_called = true;
00218   }
00219 
00220 #ifdef SBUILD_DEBUG
00221   volatile bool child_wait = true;
00222 #endif
00223 
00224 }
00225 
00226 template<>
00227 error<session::error_code>::map_type
00228 error<session::error_code>::error_strings
00229 (init_errors,
00230  init_errors + (sizeof(init_errors) / sizeof(init_errors[0])));
00231 
00232 session::session (std::string const&         service,
00233                   config_ptr&                config,
00234                   operation                  operation,
00235                   sbuild::string_list const& chroots):
00236   auth(service),
00237   config(config),
00238   chroots(chroots),
00239   chroot_status(true),
00240   child_status(0),
00241   session_operation(operation),
00242   session_id(),
00243   force(false),
00244   saved_sighup_signal(),
00245   saved_sigterm_signal(),
00246   saved_termios(),
00247   termios_ok(false),
00248   cwd(getcwd())
00249 {
00250 }
00251 
00252 session::~session ()
00253 {
00254 }
00255 
00256 session::config_ptr const&
00257 session::get_config () const
00258 {
00259   return this->config;
00260 }
00261 
00262 void
00263 session::set_config (config_ptr& config)
00264 {
00265   this->config = config;
00266 }
00267 
00268 string_list const&
00269 session::get_chroots () const
00270 {
00271   return this->chroots;
00272 }
00273 
00274 void
00275 session::set_chroots (string_list const& chroots)
00276 {
00277   this->chroots = chroots;
00278 }
00279 
00280 session::operation
00281 session::get_operation () const
00282 {
00283   return this->session_operation;
00284 }
00285 
00286 void
00287 session::set_operation (operation operation)
00288 {
00289   this->session_operation = operation;
00290 }
00291 
00292 std::string const&
00293 session::get_session_id () const
00294 {
00295   return this->session_id;
00296 }
00297 
00298 void
00299 session::set_session_id (std::string const& session_id)
00300 {
00301   this->session_id = session_id;
00302 }
00303 
00304 bool
00305 session::get_force () const
00306 {
00307   return this->force;
00308 }
00309 
00310 void
00311 session::set_force (bool force)
00312 {
00313   this->force = force;
00314 }
00315 
00316 void
00317 session::save_termios ()
00318 {
00319   int ctty = open("/dev/tty", O_RDONLY|O_NOCTTY);
00320   string_list const& command(auth::get_command());
00321 
00322   this->termios_ok = false;
00323 
00324   // Save if running a login shell and have a controlling terminal.
00325   if (ctty >= 0 &&
00326       (command.empty() || command[0].empty()))
00327     {
00328       if (tcgetattr(ctty, &this->saved_termios) < 0)
00329         {
00330           sbuild::log_warning()
00331             << _("Error saving terminal settings")
00332             << endl;
00333         }
00334       else
00335         this->termios_ok = true;
00336     }
00337 
00338   if (ctty >= 0 && close(ctty))
00339     log_debug(DEBUG_WARNING) << "Failed to close CTTY fd " << ctty << endl;
00340 }
00341 
00342 void
00343 session::restore_termios ()
00344 {
00345   int ctty = open("/dev/tty", O_WRONLY|O_NOCTTY);
00346   string_list const& command(auth::get_command());
00347 
00348   // Restore if running a login shell and have a controlling terminal,
00349   // and have previously saved the terminal state.
00350   if (ctty >= 0 &&
00351       (command.empty() || command[0].empty()) &&
00352       termios_ok)
00353     {
00354       if (tcsetattr(ctty, TCSANOW, &this->saved_termios) < 0)
00355         sbuild::log_warning()
00356           << _("Error restoring terminal settings")
00357           << endl;
00358     }
00359 
00360   if (ctty >= 0 && close(ctty))
00361     log_debug(DEBUG_WARNING) << "Failed to close CTTY fd " << ctty << endl;
00362 }
00363 
00364 int
00365 session::get_child_status () const
00366 {
00367   return this->child_status;
00368 }
00369 
00370 auth::status
00371 session::get_chroot_auth_status (auth::status status,
00372                                  chroot::ptr const& chroot) const
00373 {
00374   string_list const& users = chroot->get_users();
00375   string_list const& root_users = chroot->get_root_users();
00376   string_list const& groups = chroot->get_groups();
00377   string_list const& root_groups = chroot->get_root_groups();
00378 
00379   bool in_users = false;
00380   bool in_root_users = false;
00381   bool in_groups = false;
00382   bool in_root_groups = false;
00383 
00384   sbuild::string_list::const_iterator upos =
00385     find(users.begin(), users.end(), get_ruser());
00386   if (upos != users.end())
00387     in_users = true;
00388 
00389   sbuild::string_list::const_iterator rupos =
00390     find(root_users.begin(), root_users.end(), get_ruser());
00391   if (rupos != root_users.end())
00392     in_root_users = true;
00393 
00394   if (!groups.empty())
00395     {
00396       for (string_list::const_iterator gp = groups.begin();
00397            gp != groups.end();
00398            ++gp)
00399         if (is_group_member(*gp))
00400           in_groups = true;
00401     }
00402 
00403   if (!root_groups.empty())
00404     {
00405       for (string_list::const_iterator gp = root_groups.begin();
00406            gp != root_groups.end();
00407            ++gp)
00408         if (is_group_member(*gp))
00409           in_root_groups = true;
00410     }
00411 
00412   log_debug(DEBUG_INFO)
00413     << "In users: " << in_users << endl
00414     << "In groups: " << in_groups << endl
00415     << "In root-users: " << in_root_users << endl
00416     << "In root-groups: " << in_root_groups << endl;
00417 
00418   /*
00419    * No auth required if in root users or root groups and
00420    * changing to root, or if the uid is not changing.  If not
00421    * in user or group, authentication fails immediately.
00422    */
00423   if ((in_users == true || in_groups == true ||
00424        in_root_users == true || in_root_groups == true) &&
00425       this->get_ruid() == this->get_uid())
00426     {
00427       status = change_auth(status, auth::STATUS_NONE);
00428     }
00429   else if ((in_root_users == true || in_root_groups == true) &&
00430            this->get_uid() == 0)
00431     {
00432       status = change_auth(status, auth::STATUS_NONE);
00433     }
00434   else if (in_users == true || in_groups == true)
00435     // Auth required if not in root group
00436     {
00437       status = change_auth(status, auth::STATUS_USER);
00438     }
00439   else // Not in any groups
00440     {
00441       if (this->get_ruid() == 0)
00442         status = change_auth(status, auth::STATUS_USER);
00443       else
00444         status = change_auth(status, auth::STATUS_FAIL);
00445     }
00446 
00447   return status;
00448 }
00449 
00450 auth::status
00451 session::get_auth_status () const
00452 {
00453   assert(!this->chroots.empty());
00454   if (this->config.get() == 0) return auth::STATUS_FAIL;
00455 
00456   /*
00457    * Note that the root user can't escape authentication.  This is
00458    * because pam_rootok.so should be used in the PAM configuration if
00459    * root should automatically be granted access.  The only exception
00460    * is that the root group doesn't need to be added to the groups or
00461    * root groups lists.
00462    */
00463 
00464   auth::status status = auth::STATUS_NONE;
00465 
00466   /* @todo Use set difference rather than iteration and
00467      is_group_member. */
00468   for (string_list::const_iterator cur = this->chroots.begin();
00469        cur != this->chroots.end();
00470        ++cur)
00471     {
00472       const chroot::ptr chroot = this->config->find_alias(*cur);
00473       if (!chroot) // Should never happen, but cater for it anyway.
00474         {
00475           error e(*cur, CHROOT_ALIAS);
00476           log_exception_warning(e);
00477           status = change_auth(status, auth::STATUS_FAIL);
00478         }
00479 
00480       status = change_auth(status, get_chroot_auth_status(status, chroot));
00481     }
00482 
00483   return status;
00484 }
00485 
00486 void
00487 session::run_impl ()
00488 {
00489   assert(this->config.get() != 0);
00490   assert(!this->chroots.empty());
00491 
00492   try
00493     {
00494       sighup_called = false;
00495       set_sighup_handler();
00496       sigterm_called = false;
00497       set_sigterm_handler();
00498 
00499       for (string_list::const_iterator cur = this->chroots.begin();
00500            cur != this->chroots.end();
00501            ++cur)
00502         {
00503           log_debug(DEBUG_NOTICE)
00504             << format("Running session in %1% chroot:") % *cur
00505             << endl;
00506 
00507           const chroot::ptr ch = this->config->find_alias(*cur);
00508           if (!ch) // Should never happen, but cater for it anyway.
00509             throw error(*cur, CHROOT_UNKNOWN);
00510 
00511           chroot::ptr chroot(ch->clone());
00512 
00513           /* If restoring a session, set the session ID from the
00514              chroot name, or else generate it.  Only chroots which
00515              support session creation append a UUID to the session
00516              ID. */
00517           if (chroot->get_active() ||
00518               !(chroot->get_session_flags() & chroot::SESSION_CREATE))
00519             {
00520               set_session_id(chroot->get_name());
00521             }
00522           else
00523             {
00524               uuid_t uuid;
00525               char uuid_str[37];
00526               uuid_generate(uuid);
00527               uuid_unparse(uuid, uuid_str);
00528               uuid_clear(uuid);
00529               std::string session_id(chroot->get_name() + "-" + uuid_str);
00530               set_session_id(session_id);
00531             }
00532 
00533           log_debug(DEBUG_INFO)
00534             << format("Session ID: %1%") % get_session_id() << endl;
00535 
00536           /* Activate chroot. */
00537           chroot->set_active(true);
00538 
00539           /* If a chroot mount location has not yet been set, and the
00540              chroot is not a plain chroot, set a mount location with the
00541              session id. */
00542           {
00543             chroot_plain *plain = dynamic_cast<chroot_plain *>(chroot.get());
00544             if (chroot->get_mount_location().empty() &&
00545                 (plain == 0 || plain->get_run_setup_scripts() == true))
00546               {
00547                 std::string location(std::string(SCHROOT_MOUNT_DIR) + "/" +
00548                                      this->session_id);
00549                 chroot->set_mount_location(location);
00550               }
00551           }
00552 
00553           log_debug(DEBUG_NOTICE)
00554             << format("Mount Location: %1%") % chroot->get_mount_location()
00555             << endl;
00556 
00557           /* Chroot types which create a session (e.g. LVM devices)
00558              need the chroot name respecifying. */
00559           if (chroot->get_session_flags() & chroot::SESSION_CREATE)
00560             {
00561               chroot->set_name(this->session_id);
00562               chroot->set_aliases(string_list());
00563             }
00564 
00565           /* LVM devices need the snapshot device name specifying. */
00566           chroot_lvm_snapshot *snapshot = 0;
00567           if ((snapshot = dynamic_cast<chroot_lvm_snapshot *>(chroot.get())) != 0)
00568             {
00569               std::string dir(dirname(snapshot->get_device(), '/'));
00570               std::string device(dir + "/" + this->session_id);
00571               snapshot->set_snapshot_device(device);
00572             }
00573 
00574           try
00575             {
00576               /* Run setup-start chroot setup scripts. */
00577               setup_chroot(chroot, chroot::SETUP_START);
00578               if (this->session_operation == OPERATION_BEGIN)
00579                 cout << this->session_id << endl;
00580 
00581               /* Run recover scripts. */
00582               setup_chroot(chroot, chroot::SETUP_RECOVER);
00583 
00584               try
00585                 {
00586                   /* Run exec-start scripts. */
00587                   setup_chroot(chroot, chroot::EXEC_START);
00588 
00589                   /* Run session if setup succeeded. */
00590                   if (this->session_operation == OPERATION_AUTOMATIC ||
00591                       this->session_operation == OPERATION_RUN)
00592                     {
00593                       try
00594                         {
00595                           open_session();
00596                           save_termios();
00597                           run_chroot(chroot);
00598                         }
00599                       catch (std::runtime_error const& e)
00600                         {
00601                           log_debug(DEBUG_WARNING)
00602                             << "Chroot session failed" << endl;
00603                           restore_termios();
00604                           close_session();
00605                           throw;
00606                         }
00607                       restore_termios();
00608                       close_session();
00609                     }
00610 
00611                 }
00612               catch (error const& e)
00613                 {
00614                   log_debug(DEBUG_WARNING)
00615                     << "Chroot exec scripts or session failed" << endl;
00616                   setup_chroot(chroot, chroot::EXEC_STOP);
00617                   throw;
00618                 }
00619 
00620               /* Run exec-stop scripts whether or not there was an
00621                  error. */
00622               setup_chroot(chroot, chroot::EXEC_STOP);
00623             }
00624           catch (error const& e)
00625             {
00626               log_debug(DEBUG_WARNING)
00627                 << "Chroot setup scripts, exec scripts or session failed" << endl;
00628               try
00629                 {
00630                   setup_chroot(chroot, chroot::SETUP_STOP);
00631                 }
00632               catch (error const& discard)
00633                 {
00634                   log_debug(DEBUG_WARNING)
00635                     << "Chroot setup scripts failed during stop" << endl;
00636                 }
00637               chroot->set_active(false);
00638               throw;
00639             }
00640 
00641           /* Run setup-stop chroot setup scripts whether or not there
00642              was an error. */
00643           setup_chroot(chroot, chroot::SETUP_STOP);
00644 
00645           /* Deactivate chroot. */
00646           chroot->set_active(false);
00647         }
00648     }
00649   catch (error const& e)
00650     {
00651       clear_sigterm_handler();
00652       clear_sighup_handler();
00653 
00654       /* If a command was not run, but something failed, the exit
00655          status still needs setting. */
00656       if (this->child_status == 0)
00657         this->child_status = EXIT_FAILURE;
00658       throw;
00659     }
00660 
00661   clear_sigterm_handler();
00662   clear_sighup_handler();
00663 }
00664 
00665 string_list
00666 session::get_login_directories () const
00667 {
00668   string_list ret;
00669 
00670   std::string const& wd(get_wd());
00671   if (!wd.empty())
00672     {
00673       // Set specified working directory.
00674       ret.push_back(wd);
00675     }
00676   else
00677     {
00678       // Set current working directory.
00679       ret.push_back(this->cwd);
00680 
00681       // Set $HOME.
00682       environment env = get_pam_environment();
00683       std::string home;
00684       if (env.get("HOME", home) &&
00685           std::find(ret.begin(), ret.end(), home) == ret.end())
00686         ret.push_back(home);
00687 
00688       // Set passwd home.
00689       if (std::find(ret.begin(), ret.end(), get_home()) == ret.end())
00690         ret.push_back(get_home());
00691 
00692       // Final fallback to root.
00693       if (std::find(ret.begin(), ret.end(), "/") == ret.end())
00694         ret.push_back("/");
00695     }
00696 
00697   return ret;
00698 }
00699 
00700 string_list
00701 session::get_command_directories () const
00702 {
00703   string_list ret;
00704 
00705   std::string const& wd(get_wd());
00706   if (!wd.empty())
00707     // Set specified working directory.
00708     ret.push_back(wd);
00709   else
00710     // Set current working directory.
00711     ret.push_back(this->cwd);
00712 
00713   return ret;
00714 }
00715 
00716 std::string
00717 session::get_shell () const
00718 {
00719   assert (!auth::get_shell().empty());
00720   std::string shell = auth::get_shell();
00721 
00722   struct stat statbuf;
00723   if (stat(shell.c_str(), &statbuf) < 0)
00724     {
00725       if (shell != "/bin/sh")
00726         {
00727           error e1(shell, SHELL, strerror(errno));
00728           log_exception_warning(e1);
00729           shell = "/bin/sh";
00730           error e2(SHELL_FB, shell);
00731           log_exception_warning(e2);
00732         }
00733     }
00734 
00735   return shell;
00736 }
00737 
00738 void
00739 session::get_command (sbuild::chroot::ptr& session_chroot,
00740                       std::string&         file,
00741                       string_list&         command) const
00742 {
00743   /* Run login shell */
00744   if (command.empty() ||
00745       command[0].empty()) // No command
00746     get_login_command(session_chroot, file, command);
00747   else
00748     get_user_command(session_chroot, file, command);
00749 }
00750 
00751 void
00752 session::get_login_command (sbuild::chroot::ptr& session_chroot,
00753                             std::string&         file,
00754                             string_list&         command) const
00755 {
00756   command.clear();
00757 
00758   std::string shell = get_shell();
00759   file = shell;
00760 
00761   if (get_environment().empty() &&
00762       session_chroot->get_command_prefix().empty())
00763     // Not keeping environment and can setup argv correctly; login shell
00764     {
00765       std::string shellbase = basename(shell, '/');
00766       std::string loginshell = "-" + shellbase;
00767       command.push_back(loginshell);
00768 
00769       log_debug(DEBUG_NOTICE)
00770         << format("Running login shell: %1%") % shell << endl;
00771       if (get_uid() == 0 || get_ruid() != get_uid())
00772         syslog(LOG_USER|LOG_NOTICE,
00773                "[%s chroot] (%s->%s) Running login shell: '%s'",
00774                session_chroot->get_name().c_str(),
00775                get_ruser().c_str(), get_user().c_str(),
00776                shell.c_str());
00777     }
00778   else
00779     {
00780       command.push_back(shell);
00781       log_debug(DEBUG_NOTICE)
00782         << format("Running shell: %1%") % shell << endl;
00783       if (get_uid() == 0 || get_ruid() != get_uid())
00784         syslog(LOG_USER|LOG_NOTICE,
00785                "[%s chroot] (%s->%s) Running shell: '%s'",
00786                session_chroot->get_name().c_str(),
00787                get_ruser().c_str(), get_user().c_str(),
00788                shell.c_str());
00789     }
00790 
00791   if (get_verbosity() != auth::VERBOSITY_QUIET)
00792     {
00793       std::string format_string;
00794       if (get_ruid() == get_uid())
00795         {
00796           if (get_environment().empty() &&
00797               session_chroot->get_command_prefix().empty())
00798             // TRANSLATORS: %1% = chroot name
00799             // TRANSLATORS: %4% = command
00800             format_string = _("[%1% chroot] Running login shell: '%4%'");
00801           else
00802             // TRANSLATORS: %1% = chroot name
00803             // TRANSLATORS: %4% = command
00804             format_string = _("[%1% chroot] Running shell: '%4%'");
00805         }
00806       else
00807         {
00808           if (get_environment().empty() &&
00809               session_chroot->get_command_prefix().empty())
00810             // TRANSLATORS: %1% = chroot name
00811             // TRANSLATORS: %2% = user name
00812             // TRANSLATORS: %3% = user name
00813             // TRANSLATORS: %4% = command
00814             // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192
00815             format_string = _("[%1% chroot] (%2%->%3%) Running login shell: '%4%'");
00816           else
00817             // TRANSLATORS: %1% = chroot name
00818             // TRANSLATORS: %2% = user name
00819             // TRANSLATORS: %3% = user name
00820             // TRANSLATORS: %4% = command
00821             // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192
00822             format_string = _("[%1% chroot] (%2%->%3%) Running shell: '%4%'");
00823         }
00824 
00825       format fmt(format_string);
00826       fmt % session_chroot->get_name()
00827         % get_ruser() % get_user()
00828         % shell;
00829       log_info() << fmt << endl;
00830     }
00831 }
00832 
00833 void
00834 session::get_user_command (sbuild::chroot::ptr& session_chroot,
00835                            std::string&         file,
00836                            string_list&         command) const
00837 {
00838   /* Search for program in path. */
00839   environment env = get_pam_environment();
00840   std::string path;
00841   if (!env.get("PATH", path))
00842     path.clear();
00843 
00844   file = find_program_in_path(command[0], path, "");
00845   if (file.empty())
00846     file = command[0];
00847   std::string commandstring = string_list_to_string(command, " ");
00848   log_debug(DEBUG_NOTICE)
00849     << format("Running command: %1%") % commandstring << endl;
00850   if (get_uid() == 0 || get_ruid() != get_uid())
00851     syslog(LOG_USER|LOG_NOTICE, "[%s chroot] (%s->%s) Running command: \"%s\"",
00852            session_chroot->get_name().c_str(), get_ruser().c_str(), get_user().c_str(), commandstring.c_str());
00853 
00854   if (get_verbosity() != auth::VERBOSITY_QUIET)
00855     {
00856       std::string format_string;
00857       if (get_ruid() == get_uid())
00858         // TRANSLATORS: %1% = chroot name
00859         // TRANSLATORS: %4% = command
00860         format_string = _("[%1% chroot] Running command: \"%4%\"");
00861       else
00862         // TRANSLATORS: %1% = chroot name
00863         // TRANSLATORS: %2% = user name
00864         // TRANSLATORS: %3% = user name
00865         // TRANSLATORS: %4% = command
00866         // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192
00867         format_string = (_("[%1% chroot] (%2%->%3%) Running command: \"%4%\""));
00868 
00869       format fmt(format_string);
00870       fmt % session_chroot->get_name()
00871         % get_ruser() % get_user()
00872         % commandstring;
00873       log_info() << fmt << endl;
00874     }
00875 }
00876 
00877 void
00878 session::setup_chroot (sbuild::chroot::ptr&       session_chroot,
00879                        sbuild::chroot::setup_type setup_type)
00880 {
00881   assert(!session_chroot->get_name().empty());
00882 
00883   if (!((this->session_operation == OPERATION_BEGIN &&
00884          setup_type == chroot::SETUP_START) ||
00885         (this->session_operation == OPERATION_RECOVER &&
00886          setup_type == chroot::SETUP_RECOVER) ||
00887         (this->session_operation == OPERATION_END &&
00888          setup_type == chroot::SETUP_STOP) ||
00889         (this->session_operation == OPERATION_RUN &&
00890          (setup_type == chroot::EXEC_START ||
00891           setup_type == chroot::EXEC_STOP)) ||
00892         (this->session_operation == OPERATION_AUTOMATIC &&
00893          (setup_type == chroot::SETUP_START ||
00894           setup_type == chroot::SETUP_STOP  ||
00895           setup_type == chroot::EXEC_START   ||
00896           setup_type == chroot::EXEC_STOP))))
00897     return;
00898 
00899   if (((setup_type == chroot::SETUP_START   ||
00900         setup_type == chroot::SETUP_RECOVER ||
00901         setup_type == chroot::SETUP_STOP) &&
00902        session_chroot->get_run_setup_scripts() == false) ||
00903       ((setup_type == chroot::EXEC_START ||
00904         setup_type == chroot::EXEC_STOP) &&
00905        session_chroot->get_run_exec_scripts() == false))
00906     return;
00907 
00908   if (setup_type == chroot::SETUP_START)
00909     this->chroot_status = true;
00910 
00911   try
00912     {
00913       session_chroot->lock(setup_type);
00914     }
00915   catch (chroot::error const& e)
00916     {
00917       this->chroot_status = false;
00918       try
00919         {
00920           // Release lock, which also removes session metadata.
00921           session_chroot->unlock(setup_type, 0);
00922         }
00923       catch (chroot::error const& ignore)
00924         {
00925         }
00926       throw error(session_chroot->get_name(), CHROOT_LOCK, e);
00927     }
00928 
00929   std::string setup_type_string;
00930   if (setup_type == chroot::SETUP_START)
00931     setup_type_string = "setup-start";
00932   else if (setup_type == chroot::SETUP_RECOVER)
00933     setup_type_string = "setup-recover";
00934   else if (setup_type == chroot::SETUP_STOP)
00935     setup_type_string = "setup-stop";
00936   else if (setup_type == chroot::EXEC_START)
00937     setup_type_string = "exec-start";
00938   else if (setup_type == chroot::EXEC_STOP)
00939     setup_type_string = "exec-stop";
00940 
00941   std::string chroot_status_string;
00942   if (this->chroot_status)
00943     chroot_status_string = "ok";
00944   else
00945     chroot_status_string = "fail";
00946 
00947   string_list arg_list;
00948   arg_list.push_back(setup_type_string);
00949   arg_list.push_back(chroot_status_string);
00950 
00951   /* Get a complete list of environment variables to set.  We need to
00952      query the chroot here, since this can vary depending upon the
00953      chroot type. */
00954   environment env;
00955   session_chroot->setup_env(env);
00956   env.add("AUTH_USER", get_user());
00957   {
00958     const char *verbosity = 0;
00959     switch (get_verbosity())
00960       {
00961       case auth::VERBOSITY_QUIET:
00962         verbosity = "quiet";
00963         break;
00964       case auth::VERBOSITY_NORMAL:
00965         verbosity = "normal";
00966         break;
00967       case auth::VERBOSITY_VERBOSE:
00968         verbosity = "verbose";
00969         break;
00970       default:
00971         log_debug(DEBUG_CRITICAL) << format("Invalid verbosity level: %1%, falling back to 'normal'")
00972           % static_cast<int>(get_verbosity())
00973                      << endl;
00974         verbosity = "normal";
00975         break;
00976       }
00977     env.add("AUTH_VERBOSITY", verbosity);
00978   }
00979 
00980   env.add("MOUNT_DIR", SCHROOT_MOUNT_DIR);
00981   env.add("LIBEXEC_DIR", SCHROOT_LIBEXEC_DIR);
00982   env.add("PID", getpid());
00983   env.add("SESSION_ID", this->session_id);
00984 
00985   run_parts rp((setup_type == chroot::SETUP_START ||
00986                 setup_type == chroot::SETUP_RECOVER ||
00987                 setup_type == chroot::SETUP_STOP)
00988                ? SCHROOT_CONF_SETUP_D // Setup directory
00989                : SCHROOT_CONF_EXEC_D, // Execution directory
00990                true, true, 022);
00991   rp.set_reverse((setup_type == chroot::SETUP_STOP ||
00992                   setup_type == chroot::EXEC_STOP));
00993   rp.set_verbose(get_verbosity() == auth::VERBOSITY_VERBOSE);
00994 
00995   log_debug(DEBUG_INFO) << rp << std::endl;
00996 
00997   int exit_status = 0;
00998   pid_t pid;
00999 
01000   if ((pid = fork()) == -1)
01001     {
01002       this->chroot_status = false;
01003       throw error(session_chroot->get_name(), CHILD_FORK, strerror(errno));
01004     }
01005   else if (pid == 0)
01006     {
01007       try
01008         {
01009           // The setup scripts don't use our syslog fd.
01010           closelog();
01011 
01012           chdir("/");
01013           /* This is required to ensure the scripts run with uid=0 and gid=0,
01014              otherwise setuid programs such as mount(8) will fail.  This
01015              should always succeed, because our euid=0 and egid=0.*/
01016           setuid(0);
01017           setgid(0);
01018           initgroups("root", 0);
01019 
01020           int status = rp.run(arg_list, env);
01021 
01022           _exit (status);
01023         }
01024       catch (std::exception const& e)
01025         {
01026           sbuild::log_exception_error(e);
01027         }
01028       catch (...)
01029         {
01030           sbuild::log_error()
01031             << _("An unknown exception occurred") << std::endl;
01032         }
01033       _exit(EXIT_FAILURE);
01034     }
01035   else
01036     {
01037       wait_for_child(pid, exit_status);
01038     }
01039 
01040   try
01041     {
01042       session_chroot->unlock(setup_type, exit_status);
01043     }
01044   catch (chroot::error const& e)
01045     {
01046       this->chroot_status = false;
01047       throw error(session_chroot->get_name(), CHROOT_UNLOCK, e);
01048     }
01049 
01050   if (exit_status != 0)
01051     {
01052       this->chroot_status = false;
01053 
01054       format fmt(_("stage=%1%"));
01055       fmt % setup_type_string;
01056       throw error(session_chroot->get_name(), CHROOT_SETUP, fmt.str());
01057     }
01058 }
01059 
01060 void
01061 session::run_child (sbuild::chroot::ptr& session_chroot)
01062 {
01063   assert(!session_chroot->get_name().empty());
01064 
01065   assert(!get_user().empty());
01066   assert(!get_shell().empty());
01067   assert(auth::pam != 0); // PAM must be initialised
01068 
01069   // Store before chroot call.
01070   this->cwd = getcwd();
01071   log_debug(DEBUG_INFO) << "CWD=" << this->cwd << std::endl;
01072 
01073   std::string location(session_chroot->get_path());
01074   log_debug(DEBUG_INFO) << "location=" << location << std::endl;
01075 
01076   /* Set group ID and supplementary groups */
01077   if (setgid (get_gid()))
01078     throw error(get_gid(), GROUP_SET, strerror(errno));
01079   log_debug(DEBUG_NOTICE) << "Set GID=" << get_gid() << std::endl;
01080   if (initgroups (get_user().c_str(), get_gid()))
01081     throw error(GROUP_SET_SUP, strerror(errno));
01082   log_debug(DEBUG_NOTICE) << "Set supplementary groups" << std::endl;
01083 
01084   /* Set the process execution domain. */
01085   /* Will throw on failure. */
01086   session_chroot->get_persona().set();
01087   log_debug(DEBUG_NOTICE) << "Set personality="
01088                           << session_chroot->get_persona()<< std::endl;
01089 
01090   /* Enter the chroot */
01091   if (chdir (location.c_str()))
01092     throw error(location, CHDIR, strerror(errno));
01093   log_debug(DEBUG_NOTICE) << "Changed directory to " << location << std::endl;
01094   if (::chroot (location.c_str()))
01095     throw error(location, CHROOT, strerror(errno));
01096   log_debug(DEBUG_NOTICE) << "Changed root to " << location << std::endl;
01097 
01098   /* Set uid and check we are not still root */
01099   if (setuid (get_uid()))
01100     throw error(get_uid(), USER_SET, strerror(errno));
01101   log_debug(DEBUG_NOTICE) << "Set UID=" << get_uid() << std::endl;
01102   if (!setuid (0) && get_uid())
01103     throw error(ROOT_DROP);
01104   if (get_uid())
01105     log_debug(DEBUG_NOTICE) << "Dropped root privileges" << std::endl;
01106 
01107   std::string file;
01108   string_list command(auth::get_command());
01109 
01110   string_list dlist;
01111   if (command.empty() ||
01112       command[0].empty()) // No command
01113     dlist = get_login_directories();
01114   else
01115     dlist = get_command_directories();
01116   log_debug(DEBUG_INFO)
01117     << format("Directory fallbacks: %1%") % string_list_to_string(dlist, ", ") << endl;
01118 
01119   /* Attempt to chdir to current directory. */
01120   for (string_list::const_iterator dpos = dlist.begin();
01121        dpos != dlist.end();
01122        ++dpos)
01123     {
01124       if (chdir ((*dpos).c_str()) < 0)
01125         {
01126           error e(*dpos, CHDIR, strerror(errno));
01127 
01128           if (dpos + 1 == dlist.end())
01129             throw e;
01130           else
01131             log_exception_warning(e);
01132         }
01133       else
01134         {
01135           log_debug(DEBUG_NOTICE) << "Changed directory to "
01136                                   << *dpos << std::endl;
01137           if (dpos != dlist.begin())
01138             {
01139               error e(CHDIR_FB, *dpos);
01140               log_exception_warning(e);
01141             }
01142           break;
01143         }
01144     }
01145 
01146   /* Fix up the command for exec. */
01147   get_command(session_chroot, file, command);
01148   log_debug(DEBUG_NOTICE) << "command="
01149                           << string_list_to_string(command, ", ")
01150                           << std::endl;
01151 
01152   /* Set up environment */
01153   environment env = get_pam_environment();
01154   log_debug(DEBUG_INFO) << "Set environment:\n" << env;
01155 
01156   // The user's command does not use our syslog fd.
01157   closelog();
01158 
01159   // Add command prefix.
01160   string_list full_command(session_chroot->get_command_prefix());
01161   if (full_command.size() > 0)
01162     file = full_command[0];
01163   for (string_list::const_iterator pos = command.begin();
01164        pos != command.end();
01165        ++pos)
01166     full_command.push_back(*pos);
01167 
01168   /* Execute */
01169   if (exec (file, full_command, env))
01170     throw error(file, EXEC, strerror(errno));
01171 
01172   /* This should never be reached */
01173   _exit(EXIT_FAILURE);
01174 }
01175 
01176 void
01177 session::wait_for_child (pid_t pid,
01178                          int&  child_status)
01179 {
01180   child_status = EXIT_FAILURE; // Default exit status
01181 
01182   int status;
01183   bool child_killed = false;
01184 
01185   while (1)
01186     {
01187       if ((sighup_called || sigterm_called) && !child_killed)
01188         {
01189           if (sighup_called)
01190             {
01191               error e(SIGNAL_CATCH, strsignal(SIGHUP),
01192                       _("terminating immediately"));
01193               log_exception_error(e);
01194               kill(pid, SIGHUP);
01195             }
01196           else // SIGTERM
01197             {
01198               error e(SIGNAL_CATCH, strsignal(SIGTERM),
01199                       _("terminating immediately"));
01200               log_exception_error(e);
01201               kill(pid, SIGTERM);
01202             }
01203           this->chroot_status = false;
01204           child_killed = true;
01205         }
01206 
01207       if (wait(&status) != pid)
01208         {
01209           if (errno == EINTR && (sighup_called || sigterm_called))
01210             continue; // Kill child and wait again.
01211           else
01212             throw error(CHILD_WAIT, strerror(errno));
01213         }
01214       else if (sighup_called)
01215         {
01216           sighup_called = false;
01217           throw error(SIGNAL_CATCH, strsignal(SIGHUP));
01218         }
01219       else if (sigterm_called)
01220         {
01221           sigterm_called = false;
01222           throw error(SIGNAL_CATCH, strsignal(SIGTERM));
01223         }
01224       else
01225         break;
01226     }
01227 
01228   if (!WIFEXITED(status))
01229     {
01230       if (WIFSIGNALED(status))
01231         throw error(CHILD_SIGNAL, strsignal(WTERMSIG(status)));
01232       else if (WCOREDUMP(status))
01233         throw error(CHILD_CORE);
01234       else
01235         throw error(CHILD_FAIL);
01236     }
01237 
01238   child_status = WEXITSTATUS(status);
01239 }
01240 
01241 void
01242 session::run_chroot (sbuild::chroot::ptr& session_chroot)
01243 {
01244   assert(!session_chroot->get_name().empty());
01245 
01246   pid_t pid;
01247   if ((pid = fork()) == -1)
01248     {
01249       throw error(CHILD_FORK, strerror(errno));
01250     }
01251   else if (pid == 0)
01252     {
01253 #ifdef SBUILD_DEBUG
01254       while (child_wait)
01255         ;
01256 #endif
01257       try
01258         {
01259           run_child(session_chroot);
01260         }
01261       catch (std::runtime_error const& e)
01262         {
01263           log_exception_error(e);
01264         }
01265       catch (...)
01266         {
01267           sbuild::log_error()
01268             << _("An unknown exception occurred") << std::endl;
01269         }
01270       _exit (EXIT_FAILURE);
01271     }
01272   else
01273     {
01274       wait_for_child(pid, this->child_status);
01275     }
01276 }
01277 
01278 void
01279 session::set_sighup_handler ()
01280 {
01281   set_signal_handler(SIGHUP, &this->saved_sighup_signal, sighup_handler);
01282 }
01283 
01284 void
01285 session::clear_sighup_handler ()
01286 {
01287   clear_signal_handler(SIGHUP, &this->saved_sighup_signal);
01288 }
01289 
01290 void
01291 session::set_sigterm_handler ()
01292 {
01293   set_signal_handler(SIGTERM, &this->saved_sigterm_signal, sigterm_handler);
01294 }
01295 
01296 void
01297 session::clear_sigterm_handler ()
01298 {
01299   clear_signal_handler(SIGTERM, &this->saved_sigterm_signal);
01300 }
01301 
01302 void
01303 session::set_signal_handler (int                signal,
01304                              struct sigaction  *saved_signal,
01305                              void             (*handler)(int))
01306 {
01307   struct sigaction new_sa;
01308   sigemptyset(&new_sa.sa_mask);
01309   new_sa.sa_flags = 0;
01310   new_sa.sa_handler = handler;
01311 
01312   if (sigaction(signal, &new_sa, saved_signal) != 0)
01313     throw error(SIGNAL_SET, strsignal(signal), strerror(errno));
01314 }
01315 
01316 void
01317 session::clear_signal_handler (int               signal,
01318                                struct sigaction *saved_signal)
01319 {
01320   /* Restore original handler */
01321   sigaction (signal, saved_signal, 0);
01322 }

Generated on Sat Jan 27 16:11:03 2007 for schroot by  doxygen 1.5.1