source: trunk/debathena/third/schroot/sbuild/sbuild-session.cc @ 24167

Revision 24167, 36.8 KB checked in by broder, 15 years ago (diff)
Import schroot upstream into subversion.
Line 
1/* Copyright © 2005-2009  Roger Leigh <rleigh@debian.org>
2 *
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 3 of the License, or
6 * (at your option) any later version.
7 *
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.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program.  If not, see
15 * <http://www.gnu.org/licenses/>.
16 *
17 *********************************************************************/
18
19#include <config.h>
20
21#include "sbuild-chroot-config.h"
22#include "sbuild-chroot-facet-personality.h"
23#include "sbuild-chroot-facet-session.h"
24#include "sbuild-chroot-facet-session-clonable.h"
25#include "sbuild-auth-null.h"
26#include "sbuild-ctty.h"
27#include "sbuild-run-parts.h"
28#include "sbuild-session.h"
29#include "sbuild-util.h"
30
31#include <cassert>
32#include <cerrno>
33#include <cstdlib>
34#include <cstring>
35#include <iostream>
36#include <memory>
37
38#include <sys/types.h>
39#include <sys/stat.h>
40#include <fcntl.h>
41#include <termios.h>
42#include <unistd.h>
43
44#include <syslog.h>
45
46#include <boost/format.hpp>
47
48using std::cout;
49using std::endl;
50using boost::format;
51using namespace sbuild;
52
53namespace
54{
55
56  typedef std::pair<sbuild::session::error_code,const char *> emap;
57
58  /**
59   * This is a list of the supported error codes.  It's used to
60   * construct the real error codes map.
61   */
62  emap init_errors[] =
63    {
64      // TRANSLATORS: %1% = directory
65      emap(session::CHDIR,          N_("Failed to change to directory '%1%'")),
66      // TRANSLATORS: %4% = directory
67      emap(session::CHDIR_FB,       N_("Falling back to directory '%4%'")),
68      emap(session::CHILD_CORE,     N_("Child dumped core")),
69      emap(session::CHILD_FAIL,     N_("Child exited abnormally (reason unknown; not a signal or core dump)")),
70      emap(session::CHILD_FORK,     N_("Failed to fork child")),
71      // TRANSLATORS: %4% = signal name
72      emap(session::CHILD_SIGNAL,   N_("Child terminated by signal '%4%'")),
73      emap(session::CHILD_WAIT,     N_("Wait for child failed")),
74      // TRANSLATORS: %1% = directory
75      emap(session::CHROOT,         N_("Failed to change root to directory '%1%'")),
76      // TRANSLATORS: %1% = chroot name
77      emap(session::CHROOT_ALIAS,   N_("No chroot found matching name or alias '%1%'")),
78      emap(session::CHROOT_LOCK,    N_("Failed to lock chroot")),
79      emap(session::CHROOT_SETUP,   N_("Chroot setup failed")),
80      // TRANSLATORS: %1% = chroot name
81      emap(session::CHROOT_UNKNOWN, N_("Failed to find chroot '%1%'")),
82      emap(session::CHROOT_UNLOCK,  N_("Failed to unlock chroot")),
83      // TRANSLATORS: %1% = command
84      emap(session::COMMAND_ABS,    N_("Command \"%1%\" must have an absolute path")),
85      // TRANSLATORS: %1% = command
86      emap(session::EXEC,           N_("Failed to execute \"%1%\"")),
87      // TRANSLATORS: A supplementary group is the list of additional
88      // system groups a user belongs to, in addition to their default
89      // group.
90      emap(session::GROUP_GET_SUP,  N_("Failed to get supplementary groups")),
91      // TRANSLATORS: A supplementary group is the list of additional
92      // system groups a user belongs to, in addition to their default
93      // group.
94      emap(session::GROUP_GET_SUPC, N_("Failed to get supplementary group count")),
95      // TRANSLATORS: %1% = integer group ID
96      emap(session::GROUP_SET,      N_("Failed to set group '%1%'")),
97      emap(session::GROUP_SET_SUP,  N_("Failed to set supplementary groups")),
98      // TRANSLATORS: %1% = group name
99      emap(session::GROUP_UNKNOWN,  N_("Group '%1%' not found")),
100      emap(session::PAM,            N_("PAM error")),
101      emap(session::ROOT_DROP,      N_("Failed to drop root permissions")),
102      // TRANSLATORS: %1% = chroot name
103      // TRANSLATORS: %4% = session identifier
104      emap(session::SET_SESSION_ID, N_("%1%: Chroot does not support setting a session ID; ignoring session ID '%4%'")),
105      // TRANSLATORS: %1% = command
106      emap(session::SHELL,          N_("Shell '%1%' not available")),
107      // TRANSLATORS: %4% = command
108      emap(session::SHELL_FB,       N_("Falling back to shell '%4%'")),
109      // TRANSLATORS: %4% = signal name
110      emap(session::SIGNAL_CATCH,   N_("Caught signal '%4%'")),
111      // TRANSLATORS: %4% = signal name
112      emap(session::SIGNAL_SET,     N_("Failed to set signal handler '%4%'")),
113      // TRANSLATORS: %1% = integer user ID
114      emap(session::USER_SET,       N_("Failed to set user '%1%'")),
115      // TRANSLATORS: %1% = user name
116      // TRANSLATORS: %2% = user name
117      // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192
118      emap(session::USER_SWITCH,    N_("(%1%->%2%): User switching is not permitted")),
119    };
120
121  /**
122   * Check group membership.
123   *
124   * @param group the group to check for.
125   * @returns true if the user is a member of group, otherwise false.
126   */
127  bool
128  is_group_member (std::string const& groupname)
129  {
130    errno = 0;
131    sbuild::group grp(groupname);
132    if (!grp)
133      {
134        if (errno == 0)
135          {
136            session::error e(groupname, session::GROUP_UNKNOWN);
137            log_exception_warning(e);
138          }
139        else
140          {
141            session::error e(groupname, session::GROUP_UNKNOWN, strerror(errno));
142            log_exception_warning(e);
143          }
144        return false;
145      }
146
147    bool group_member = false;
148    if (grp.gr_gid == getgid())
149      {
150        group_member = true;
151      }
152    else
153      {
154        int supp_group_count = getgroups(0, 0);
155        if (supp_group_count < 0)
156          throw session::error(session::GROUP_GET_SUPC, strerror(errno));
157        if (supp_group_count > 0)
158          {
159            gid_t *supp_groups = new gid_t[supp_group_count];
160            assert (supp_groups);
161            if (getgroups(supp_group_count, supp_groups) < 1)
162              {
163                // Free supp_groups before throwing to avoid leak.
164                delete[] supp_groups;
165                throw session::error(session::GROUP_GET_SUP, strerror(errno));
166              }
167
168            for (int i = 0; i < supp_group_count; ++i)
169              {
170                if (grp.gr_gid == supp_groups[i])
171                  group_member = true;
172              }
173            delete[] supp_groups;
174          }
175      }
176
177    return group_member;
178  }
179
180  volatile bool sighup_called = false;
181  volatile bool sigterm_called = false;
182
183  /**
184   * Handle the SIGHUP signal.
185   *
186   * @param ignore the signal number.
187   */
188  void
189  sighup_handler (int ignore)
190  {
191    /* This exists so that system calls get interrupted. */
192    sighup_called = true;
193  }
194
195  /**
196   * Handle the SIGTERM signal.
197   *
198   * @param ignore the signal number.
199   */
200  void
201  sigterm_handler (int ignore)
202  {
203    /* This exists so that system calls get interrupted. */
204    sigterm_called = true;
205  }
206
207#ifdef SBUILD_DEBUG
208  volatile bool child_wait = true;
209#endif
210
211}
212
213template<>
214error<session::error_code>::map_type
215error<session::error_code>::error_strings
216(init_errors,
217 init_errors + (sizeof(init_errors) / sizeof(init_errors[0])));
218
219session::session (std::string const&         service,
220                  config_ptr&                config,
221                  operation                  operation,
222                  sbuild::string_list const& chroots):
223  authstat(auth_null::create(service)),
224  config(config),
225  chroots(chroots),
226  chroot_status(true),
227  lock_status(true),
228  child_status(EXIT_FAILURE),
229  session_operation(operation),
230  session_id(),
231  force(false),
232  saved_sighup_signal(),
233  saved_sigterm_signal(),
234  saved_termios(),
235  termios_ok(false),
236  cwd(sbuild::getcwd())
237{
238}
239
240session::~session ()
241{
242}
243
244auth::ptr const&
245session::get_auth () const
246{
247  return this->authstat;
248}
249
250void
251session::set_auth (auth::ptr& auth)
252{
253  this->authstat = auth;
254}
255
256session::config_ptr const&
257session::get_config () const
258{
259  return this->config;
260}
261
262void
263session::set_config (config_ptr& config)
264{
265  this->config = config;
266}
267
268string_list const&
269session::get_chroots () const
270{
271  return this->chroots;
272}
273
274void
275session::set_chroots (string_list const& chroots)
276{
277  this->chroots = chroots;
278}
279
280session::operation
281session::get_operation () const
282{
283  return this->session_operation;
284}
285
286void
287session::set_operation (operation operation)
288{
289  this->session_operation = operation;
290}
291
292std::string const&
293session::get_session_id () const
294{
295  return this->session_id;
296}
297
298void
299session::set_session_id (std::string const& session_id)
300{
301  this->session_id = session_id;
302}
303
304bool
305session::get_force () const
306{
307  return this->force;
308}
309
310void
311session::set_force (bool force)
312{
313  this->force = force;
314}
315
316void
317session::save_termios ()
318{
319  string_list const& command(this->authstat->get_command());
320
321  this->termios_ok = false;
322
323  // Save if running a login shell and have a controlling terminal.
324  if (CTTY_FILENO >= 0 &&
325      (command.empty() || command[0].empty()))
326    {
327      if (tcgetattr(CTTY_FILENO, &this->saved_termios) < 0)
328        {
329          sbuild::log_warning()
330            << _("Error saving terminal settings")
331            << endl;
332        }
333      else
334        this->termios_ok = true;
335    }
336}
337
338void
339session::restore_termios ()
340{
341  string_list const& command(this->authstat->get_command());
342
343  // Restore if running a login shell and have a controlling terminal,
344  // and have previously saved the terminal state.
345  if (CTTY_FILENO >= 0 &&
346      (command.empty() || command[0].empty()) &&
347      termios_ok)
348    {
349      if (tcsetattr(CTTY_FILENO, TCSANOW, &this->saved_termios) < 0)
350        sbuild::log_warning()
351          << _("Error restoring terminal settings")
352          << endl;
353    }
354}
355
356int
357session::get_child_status () const
358{
359  return this->child_status;
360}
361
362void
363session::get_chroot_membership (chroot::ptr const& chroot,
364                                bool&              in_users,
365                                bool&              in_root_users,
366                                bool&              in_groups,
367                                bool&              in_root_groups) const
368{
369  string_list const& users = chroot->get_users();
370  string_list const& root_users = chroot->get_root_users();
371  string_list const& groups = chroot->get_groups();
372  string_list const& root_groups = chroot->get_root_groups();
373
374  in_users = false;
375  in_root_users = false;
376  in_groups = false;
377  in_root_groups = false;
378
379  sbuild::string_list::const_iterator upos =
380    find(users.begin(), users.end(), this->authstat->get_ruser());
381  if (upos != users.end())
382    in_users = true;
383
384  sbuild::string_list::const_iterator rupos =
385    find(root_users.begin(), root_users.end(), this->authstat->get_ruser());
386  if (rupos != root_users.end())
387    in_root_users = true;
388
389  if (!groups.empty())
390    {
391      for (string_list::const_iterator gp = groups.begin();
392           gp != groups.end();
393           ++gp)
394        if (is_group_member(*gp))
395          in_groups = true;
396    }
397
398  if (!root_groups.empty())
399    {
400      for (string_list::const_iterator gp = root_groups.begin();
401           gp != root_groups.end();
402           ++gp)
403        if (is_group_member(*gp))
404          in_root_groups = true;
405    }
406
407  log_debug(DEBUG_INFO)
408    << "In users: " << in_users << endl
409    << "In groups: " << in_groups << endl
410    << "In root-users: " << in_root_users << endl
411    << "In root-groups: " << in_root_groups << endl;
412
413}
414
415auth::status
416session::get_chroot_auth_status (auth::status status,
417                                 chroot::ptr const& chroot) const
418{
419  bool in_users = false;
420  bool in_root_users = false;
421  bool in_groups = false;
422  bool in_root_groups = false;
423
424  get_chroot_membership(chroot,
425                        in_users, in_root_users,
426                        in_groups, in_root_groups);
427
428  /*
429   * No auth required if in root users or root groups and
430   * changing to root, or if the uid is not changing.  If not
431   * in user or group, authentication fails immediately.
432   */
433  if ((in_users == true || in_groups == true ||
434       in_root_users == true || in_root_groups == true) &&
435      this->authstat->get_ruid() == this->authstat->get_uid())
436    {
437      status = auth::change_auth(status, auth::STATUS_NONE);
438    }
439  else if ((in_root_users == true || in_root_groups == true) &&
440           this->authstat->get_uid() == 0)
441    {
442      status = auth::change_auth(status, auth::STATUS_NONE);
443    }
444  else if (in_users == true || in_groups == true)
445    // Auth required if not in root group
446    {
447      status = auth::change_auth(status, auth::STATUS_USER);
448    }
449  else // Not in any groups
450    {
451      if (this->authstat->get_ruid() == 0)
452        status = auth::change_auth(status, auth::STATUS_USER);
453      else
454        status = auth::change_auth(status, auth::STATUS_FAIL);
455    }
456
457  return status;
458}
459
460auth::status
461session::get_auth_status () const
462{
463  assert(!this->chroots.empty());
464  if (this->config.get() == 0) return auth::STATUS_FAIL;
465
466  /*
467   * Note that the root user can't escape authentication.  This is
468   * because pam_rootok.so should be used in the PAM configuration if
469   * root should automatically be granted access.  The only exception
470   * is that the root group doesn't need to be added to the groups or
471   * root groups lists.
472   */
473
474  auth::status status = auth::STATUS_NONE;
475
476  /** @todo Use set difference rather than iteration and
477   * is_group_member.
478   */
479  for (string_list::const_iterator cur = this->chroots.begin();
480       cur != this->chroots.end();
481       ++cur)
482    {
483      const chroot::ptr chroot = this->config->find_alias(*cur);
484      if (!chroot) // Should never happen, but cater for it anyway.
485        {
486          error e(*cur, CHROOT_ALIAS);
487          log_exception_warning(e);
488          status = auth::change_auth(status, auth::STATUS_FAIL);
489        }
490
491      status = auth::change_auth(status, get_chroot_auth_status(status, chroot));
492    }
493
494  return status;
495}
496
497void
498session::run ()
499{
500  try
501    {
502      this->authstat->start();
503      this->authstat->authenticate(get_auth_status());
504      this->authstat->setupenv();
505      this->authstat->account();
506      try
507        {
508          this->authstat->cred_establish();
509
510          run_impl();
511
512          /* The session is now finished, either
513             successfully or not.  All PAM operations are
514             now for cleanup and shutdown, and we must
515             clean up whether or not errors were raised at
516             any previous point.  This means only the
517             first error is reported back to the user. */
518
519          /* Don't cope with failure, since we are now
520             already bailing out, and an error may already
521             have been raised */
522        }
523      catch (auth::error const& e)
524        {
525          try
526            {
527              this->authstat->cred_delete();
528            }
529          catch (auth::error const& discard)
530            {
531            }
532          throw;
533        }
534      this->authstat->cred_delete();
535    }
536  catch (auth::error const& e)
537    {
538      try
539        {
540          /* Don't cope with failure, since we are now already bailing out,
541             and an error may already have been raised */
542          this->authstat->stop();
543        }
544      catch (auth::error const& discard)
545        {
546        }
547      throw;
548    }
549  this->authstat->stop();
550}
551
552void
553session::run_impl ()
554{
555  assert(this->config.get() != 0);
556  assert(!this->chroots.empty());
557
558  try
559    {
560      sighup_called = false;
561      set_sighup_handler();
562      sigterm_called = false;
563      set_sigterm_handler();
564
565      for (string_list::const_iterator cur = this->chroots.begin();
566           cur != this->chroots.end();
567           ++cur)
568        {
569          log_debug(DEBUG_NOTICE)
570            << format("Running session in %1% chroot:") % *cur
571            << endl;
572
573          const chroot::ptr ch = this->config->find_alias(*cur);
574          if (!ch) // Should never happen, but cater for it anyway.
575            throw error(*cur, CHROOT_UNKNOWN);
576
577          // For now, use a copy of the chroot; if we create a session
578          // later, we will replace it.
579          chroot::ptr chroot(ch->clone());
580          assert(chroot);
581
582          /* Create a session using randomly-generated session ID. */
583          if (ch->get_session_flags() & chroot::SESSION_CREATE)
584            {
585              chroot_facet_session_clonable::ptr session_clonable
586                (ch->get_facet<chroot_facet_session_clonable>());
587              assert(session_clonable);
588
589              std::string new_session_id;
590
591              if (!get_session_id().empty())
592                {
593                  new_session_id = get_session_id();
594                }
595              else
596                {
597                  new_session_id =
598                    ch->get_name() + '-' + unique_identifier();
599                }
600
601              // Replace clone of chroot with cloned session.
602
603              bool in_users = false;
604              bool in_root_users = false;
605              bool in_groups = false;
606              bool in_root_groups = false;
607
608              get_chroot_membership(chroot,
609                                    in_users, in_root_users,
610                                    in_groups, in_root_groups);
611
612              chroot = ch->clone_session(new_session_id,
613                                         this->authstat->get_ruser(),
614                                         (in_root_users || in_root_groups));
615              chroot_facet_session::ptr session
616                (chroot->get_facet<chroot_facet_session>());
617              assert(session);
618              assert(chroot->get_active());
619            }
620          assert(chroot);
621
622          // Following authentication success, default child status to
623          // success so that operations such as beginning, ending and
624          // recovering sessions will return success unless an
625          // exception is thrown.
626          this->child_status = EXIT_SUCCESS;
627
628          try
629            {
630              /* Run setup-start chroot setup scripts. */
631              setup_chroot(chroot, chroot::SETUP_START);
632              if (this->session_operation == OPERATION_BEGIN)
633                {
634                  cout << chroot->get_session_id() << endl;
635                }
636
637              /* Run recover scripts. */
638              setup_chroot(chroot, chroot::SETUP_RECOVER);
639
640              /* Run session if setup succeeded. */
641              if (this->session_operation == OPERATION_AUTOMATIC ||
642                  this->session_operation == OPERATION_RUN)
643                {
644                  try
645                    {
646                      this->authstat->open_session();
647                      save_termios();
648                      run_chroot(chroot);
649                    }
650                  catch (std::runtime_error const& e)
651                    {
652                      log_debug(DEBUG_WARNING)
653                        << "Chroot session failed" << endl;
654                      restore_termios();
655                      this->authstat->close_session();
656                      throw;
657                    }
658                  restore_termios();
659                  this->authstat->close_session();
660                }
661            }
662          catch (error const& e)
663            {
664              log_debug(DEBUG_WARNING)
665                << "Chroot setup scripts, exec scripts or session failed" << endl;
666              try
667                {
668                  setup_chroot(chroot, chroot::SETUP_STOP);
669                }
670              catch (error const& discard)
671                {
672                  log_debug(DEBUG_WARNING)
673                    << "Chroot setup scripts failed during stop" << endl;
674                }
675              throw;
676            }
677
678          /* Run setup-stop chroot setup scripts whether or not there
679             was an error. */
680          setup_chroot(chroot, chroot::SETUP_STOP);
681        }
682    }
683  catch (error const& e)
684    {
685      clear_sigterm_handler();
686      clear_sighup_handler();
687
688      /* If a command was not run, but something failed, the exit
689         status still needs setting. */
690      if (this->child_status == 0)
691        this->child_status = EXIT_FAILURE;
692      throw;
693    }
694
695  clear_sigterm_handler();
696  clear_sighup_handler();
697}
698
699string_list
700session::get_login_directories () const
701{
702  string_list ret;
703
704  std::string const& wd(this->authstat->get_wd());
705  if (!wd.empty())
706    {
707      // Set specified working directory.
708      ret.push_back(wd);
709    }
710  else
711    {
712      // Set current working directory.
713      ret.push_back(this->cwd);
714
715      // Set $HOME.
716      environment env = this->authstat->get_auth_environment();
717      std::string home;
718      if (env.get("HOME", home) &&
719          std::find(ret.begin(), ret.end(), home) == ret.end())
720        ret.push_back(home);
721
722      // Set passwd home.
723      if (std::find(ret.begin(), ret.end(), this->authstat->get_home()) == ret.end())
724        ret.push_back(this->authstat->get_home());
725
726      // Final fallback to root.
727      if (std::find(ret.begin(), ret.end(), "/") == ret.end())
728        ret.push_back("/");
729    }
730
731  return ret;
732}
733
734string_list
735session::get_command_directories () const
736{
737  string_list ret;
738
739  std::string const& wd(this->authstat->get_wd());
740  if (!wd.empty())
741    // Set specified working directory.
742    ret.push_back(wd);
743  else
744    // Set current working directory.
745    ret.push_back(this->cwd);
746
747  return ret;
748}
749
750std::string
751session::get_shell () const
752{
753  assert (!this->authstat->get_shell().empty());
754  std::string shell = this->authstat->get_shell();
755
756  try
757    {
758      stat(shell).check();
759    }
760  catch (std::runtime_error const& e)
761    {
762      error e1(shell, SHELL, e.what());
763      log_exception_warning(e1);
764
765      if (shell != "/bin/sh")
766        {
767          shell = "/bin/sh";
768          error e2(SHELL_FB, shell);
769          log_exception_warning(e2);
770        }
771    }
772
773  return shell;
774}
775
776void
777session::get_command (sbuild::chroot::ptr& session_chroot,
778                      std::string&         file,
779                      string_list&         command) const
780{
781  /* Run login shell */
782  if (command.empty() ||
783      command[0].empty()) // No command
784    get_login_command(session_chroot, file, command);
785  else
786    get_user_command(session_chroot, file, command);
787}
788
789void
790session::get_login_command (sbuild::chroot::ptr& session_chroot,
791                            std::string&         file,
792                            string_list&         command) const
793{
794  command.clear();
795
796  std::string shell = get_shell();
797  file = shell;
798
799  if (this->authstat->get_environment().empty() &&
800      session_chroot->get_command_prefix().empty())
801    // Not keeping environment and can setup argv correctly; login shell
802    {
803      std::string shellbase = basename(shell, '/');
804      std::string loginshell = "-" + shellbase;
805      command.push_back(loginshell);
806
807      log_debug(DEBUG_NOTICE)
808        << format("Running login shell: %1%") % shell << endl;
809      if (this->authstat->get_uid() == 0 || this->authstat->get_ruid() != this->authstat->get_uid())
810        syslog(LOG_USER|LOG_NOTICE,
811               "[%s chroot] (%s->%s) Running login shell: '%s'",
812               session_chroot->get_name().c_str(),
813               this->authstat->get_ruser().c_str(), this->authstat->get_user().c_str(),
814               shell.c_str());
815    }
816  else
817    {
818      command.push_back(shell);
819      log_debug(DEBUG_NOTICE)
820        << format("Running shell: %1%") % shell << endl;
821      if (this->authstat->get_uid() == 0 || this->authstat->get_ruid() != this->authstat->get_uid())
822        syslog(LOG_USER|LOG_NOTICE,
823               "[%s chroot] (%s->%s) Running shell: '%s'",
824               session_chroot->get_name().c_str(),
825               this->authstat->get_ruser().c_str(), this->authstat->get_user().c_str(),
826               shell.c_str());
827    }
828
829  if (this->authstat->get_verbosity() == auth::VERBOSITY_VERBOSE)
830    {
831      std::string format_string;
832      if (this->authstat->get_ruid() == this->authstat->get_uid())
833        {
834          if (this->authstat->get_environment().empty() &&
835              session_chroot->get_command_prefix().empty())
836            // TRANSLATORS: %1% = chroot name
837            // TRANSLATORS: %4% = command
838            format_string = _("[%1% chroot] Running login shell: '%4%'");
839          else
840            // TRANSLATORS: %1% = chroot name
841            // TRANSLATORS: %4% = command
842            format_string = _("[%1% chroot] Running shell: '%4%'");
843        }
844      else
845        {
846          if (this->authstat->get_environment().empty() &&
847              session_chroot->get_command_prefix().empty())
848            // TRANSLATORS: %1% = chroot name
849            // TRANSLATORS: %2% = user name
850            // TRANSLATORS: %3% = user name
851            // TRANSLATORS: %4% = command
852            // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192
853            format_string = _("[%1% chroot] (%2%->%3%) Running login shell: '%4%'");
854          else
855            // TRANSLATORS: %1% = chroot name
856            // TRANSLATORS: %2% = user name
857            // TRANSLATORS: %3% = user name
858            // TRANSLATORS: %4% = command
859            // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192
860            format_string = _("[%1% chroot] (%2%->%3%) Running shell: '%4%'");
861        }
862
863      format fmt(format_string);
864      fmt % session_chroot->get_name()
865        % this->authstat->get_ruser() % this->authstat->get_user()
866        % shell;
867      log_info() << fmt << endl;
868    }
869}
870
871void
872session::get_user_command (sbuild::chroot::ptr& session_chroot,
873                           std::string&         file,
874                           string_list&         command) const
875{
876  /* Search for program in path. */
877  environment env = this->authstat->get_auth_environment();
878  std::string path;
879  if (!env.get("PATH", path))
880    path.clear();
881
882  file = find_program_in_path(command[0], path, "");
883  if (file.empty())
884    file = command[0];
885  std::string commandstring = string_list_to_string(command, " ");
886  log_debug(DEBUG_NOTICE)
887    << format("Running command: %1%") % commandstring << endl;
888  if (this->authstat->get_uid() == 0 || this->authstat->get_ruid() != this->authstat->get_uid())
889    syslog(LOG_USER|LOG_NOTICE, "[%s chroot] (%s->%s) Running command: \"%s\"",
890           session_chroot->get_name().c_str(), this->authstat->get_ruser().c_str(), this->authstat->get_user().c_str(), commandstring.c_str());
891
892  if (this->authstat->get_verbosity() == auth::VERBOSITY_VERBOSE)
893    {
894      std::string format_string;
895      if (this->authstat->get_ruid() == this->authstat->get_uid())
896        // TRANSLATORS: %1% = chroot name
897        // TRANSLATORS: %4% = command
898        format_string = _("[%1% chroot] Running command: \"%4%\"");
899      else
900        // TRANSLATORS: %1% = chroot name
901        // TRANSLATORS: %2% = user name
902        // TRANSLATORS: %3% = user name
903        // TRANSLATORS: %4% = command
904        // TRANSLATORS: Please translate "->" as a right arrow, e.g. U+2192
905        format_string = (_("[%1% chroot] (%2%->%3%) Running command: \"%4%\""));
906
907      format fmt(format_string);
908      fmt % session_chroot->get_name()
909        % this->authstat->get_ruser() % this->authstat->get_user()
910        % commandstring;
911      log_info() << fmt << endl;
912    }
913}
914
915void
916session::setup_chroot (sbuild::chroot::ptr&       session_chroot,
917                       sbuild::chroot::setup_type setup_type)
918{
919  assert(!session_chroot->get_name().empty());
920
921  log_debug(DEBUG_INFO) << format("setup_chroot: chroot=%1%, setup_type=%2%, chroot_status=%3%, lock_status=%4%")
922    % session_chroot->get_name() % setup_type % chroot_status % lock_status
923                        << std::endl;
924
925  if (!((this->session_operation == OPERATION_BEGIN &&
926         setup_type == chroot::SETUP_START) ||
927        (this->session_operation == OPERATION_RECOVER &&
928         setup_type == chroot::SETUP_RECOVER) ||
929        (this->session_operation == OPERATION_END &&
930         setup_type == chroot::SETUP_STOP) ||
931        (this->session_operation == OPERATION_AUTOMATIC &&
932         (setup_type == chroot::SETUP_START ||
933          setup_type == chroot::SETUP_STOP))))
934    return;
935
936  // Don't clean up chroot on a lock failure--it's actually in use.
937  if (this->lock_status == false)
938    return;
939
940  if ((setup_type == chroot::SETUP_START   ||
941       setup_type == chroot::SETUP_RECOVER ||
942       setup_type == chroot::SETUP_STOP) &&
943      session_chroot->get_run_setup_scripts() == false)
944    return;
945
946  if (setup_type == chroot::SETUP_START)
947    this->chroot_status = true;
948
949  try
950    {
951      session_chroot->lock(setup_type);
952    }
953  catch (chroot::error const& e)
954    {
955      this->chroot_status = false;
956      this->lock_status = false;
957      try
958        {
959          // Release lock, which also removes session metadata.
960          session_chroot->unlock(setup_type, 0);
961        }
962      catch (chroot::error const& ignore)
963        {
964        }
965      throw error(session_chroot->get_name(), CHROOT_LOCK, e);
966    }
967
968  std::string setup_type_string;
969  if (setup_type == chroot::SETUP_START)
970    setup_type_string = "setup-start";
971  else if (setup_type == chroot::SETUP_RECOVER)
972    setup_type_string = "setup-recover";
973  else if (setup_type == chroot::SETUP_STOP)
974    setup_type_string = "setup-stop";
975
976  std::string chroot_status_string;
977  if (this->chroot_status)
978    chroot_status_string = "ok";
979  else
980    chroot_status_string = "fail";
981
982  string_list arg_list;
983  arg_list.push_back(setup_type_string);
984  arg_list.push_back(chroot_status_string);
985
986  /* Get a complete list of environment variables to set.  We need to
987     query the chroot here, since this can vary depending upon the
988     chroot type. */
989  environment env;
990  session_chroot->setup_env(env);
991  env.add("AUTH_USER", this->authstat->get_user());
992  env.add("AUTH_RUSER", this->authstat->get_ruser());
993  env.add("AUTH_RGROUP", this->authstat->get_rgroup());
994  env.add("AUTH_UID", this->authstat->get_uid());
995  env.add("AUTH_GID", this->authstat->get_gid());
996  env.add("AUTH_RUID", this->authstat->get_ruid());
997  env.add("AUTH_RGID", this->authstat->get_rgid());
998  env.add("AUTH_HOME", this->authstat->get_home());
999  env.add("AUTH_SHELL", this->authstat->get_shell());
1000  {
1001    const char *verbosity = 0;
1002    switch (this->authstat->get_verbosity())
1003      {
1004      case auth::VERBOSITY_QUIET:
1005        verbosity = "quiet";
1006        break;
1007      case auth::VERBOSITY_NORMAL:
1008        verbosity = "normal";
1009        break;
1010      case auth::VERBOSITY_VERBOSE:
1011        verbosity = "verbose";
1012        break;
1013      default:
1014        log_debug(DEBUG_CRITICAL) << format("Invalid verbosity level: %1%, falling back to 'normal'")
1015          % static_cast<int>(this->authstat->get_verbosity())
1016                     << endl;
1017        verbosity = "normal";
1018        break;
1019      }
1020    env.add("AUTH_VERBOSITY", verbosity);
1021  }
1022
1023  env.add("MOUNT_DIR", SCHROOT_MOUNT_DIR);
1024  env.add("LIBEXEC_DIR", SCHROOT_LIBEXEC_DIR);
1025  env.add("PID", getpid());
1026  env.add("SESSION_ID", session_chroot->get_session_id());
1027
1028  run_parts rp(SCHROOT_CONF_SETUP_D,
1029               true, true, 022);
1030  rp.set_reverse(setup_type == chroot::SETUP_STOP);
1031  rp.set_verbose(this->authstat->get_verbosity() == auth::VERBOSITY_VERBOSE);
1032
1033  log_debug(DEBUG_INFO) << rp << std::endl;
1034
1035  int exit_status = 0;
1036  pid_t pid;
1037
1038  if ((pid = fork()) == -1)
1039    {
1040      this->chroot_status = false;
1041      throw error(session_chroot->get_name(), CHILD_FORK, strerror(errno));
1042    }
1043  else if (pid == 0)
1044    {
1045      try
1046        {
1047          // The setup scripts don't use our syslog fd.
1048          closelog();
1049
1050          chdir("/");
1051          /* This is required to ensure the scripts run with uid=0 and gid=0,
1052             otherwise setuid programs such as mount(8) will fail.  This
1053             should always succeed, because our euid=0 and egid=0.*/
1054          setuid(0);
1055          setgid(0);
1056          initgroups("root", 0);
1057
1058          int status = rp.run(arg_list, env);
1059
1060          _exit (status);
1061        }
1062      catch (std::exception const& e)
1063        {
1064          sbuild::log_exception_error(e);
1065        }
1066      catch (...)
1067        {
1068          sbuild::log_error()
1069            << _("An unknown exception occurred") << std::endl;
1070        }
1071      _exit(EXIT_FAILURE);
1072    }
1073  else
1074    {
1075      wait_for_child(pid, exit_status);
1076    }
1077
1078  try
1079    {
1080      session_chroot->unlock(setup_type, exit_status);
1081    }
1082  catch (chroot::error const& e)
1083    {
1084      this->chroot_status = false;
1085      this->lock_status = false;
1086      throw error(session_chroot->get_name(), CHROOT_UNLOCK, e);
1087    }
1088
1089  if (exit_status != 0)
1090    {
1091      this->chroot_status = false;
1092
1093      format fmt(_("stage=%1%"));
1094      fmt % setup_type_string;
1095      throw error(session_chroot->get_name(), CHROOT_SETUP, fmt.str());
1096    }
1097}
1098
1099void
1100session::run_child (sbuild::chroot::ptr& session_chroot)
1101{
1102  assert(!session_chroot->get_name().empty());
1103
1104  assert(!this->authstat->get_user().empty());
1105  assert(!get_shell().empty());
1106  assert(this->authstat->is_initialised()); // PAM must be initialised
1107
1108  // Store before chroot call.
1109  this->cwd = sbuild::getcwd();
1110  log_debug(DEBUG_INFO) << "CWD=" << this->cwd << std::endl;
1111
1112  std::string location(session_chroot->get_path());
1113  log_debug(DEBUG_INFO) << "location=" << location << std::endl;
1114
1115  /* Set group ID and supplementary groups */
1116  if (setgid (this->authstat->get_gid()))
1117    throw error(this->authstat->get_gid(), GROUP_SET, strerror(errno));
1118  log_debug(DEBUG_NOTICE) << "Set GID=" << this->authstat->get_gid() << std::endl;
1119  if (initgroups (this->authstat->get_user().c_str(), this->authstat->get_gid()))
1120    throw error(GROUP_SET_SUP, strerror(errno));
1121  log_debug(DEBUG_NOTICE) << "Set supplementary groups" << std::endl;
1122
1123  /* Set the process execution domain. */
1124  /* Will throw on failure. */
1125  chroot_facet_personality::const_ptr pfac =
1126    session_chroot->get_facet<chroot_facet_personality>();
1127  if (pfac)
1128    {
1129      pfac->get_persona().set();
1130      log_debug(DEBUG_NOTICE) << "Set personality="
1131                              << pfac->get_persona() << std::endl;
1132    }
1133  else
1134    {
1135      log_debug(DEBUG_NOTICE) << "Personality support unavailable" << std::endl;
1136    }
1137
1138  /* Enter the chroot */
1139  if (chdir (location.c_str()))
1140    throw error(location, CHDIR, strerror(errno));
1141  log_debug(DEBUG_NOTICE) << "Changed directory to " << location << std::endl;
1142  if (::chroot (location.c_str()))
1143    throw error(location, CHROOT, strerror(errno));
1144  log_debug(DEBUG_NOTICE) << "Changed root to " << location << std::endl;
1145
1146  /* Set uid and check we are not still root */
1147  if (setuid (this->authstat->get_uid()))
1148    throw error(this->authstat->get_uid(), USER_SET, strerror(errno));
1149  log_debug(DEBUG_NOTICE) << "Set UID=" << this->authstat->get_uid() << std::endl;
1150  if (!setuid (0) && this->authstat->get_uid())
1151    throw error(ROOT_DROP);
1152  if (this->authstat->get_uid())
1153    log_debug(DEBUG_NOTICE) << "Dropped root privileges" << std::endl;
1154
1155  std::string file;
1156  string_list command(this->authstat->get_command());
1157
1158  string_list dlist;
1159  if (command.empty() ||
1160      command[0].empty()) // No command
1161    dlist = get_login_directories();
1162  else
1163    dlist = get_command_directories();
1164  log_debug(DEBUG_INFO)
1165    << format("Directory fallbacks: %1%") % string_list_to_string(dlist, ", ") << endl;
1166
1167  /* Attempt to chdir to current directory. */
1168  for (string_list::const_iterator dpos = dlist.begin();
1169       dpos != dlist.end();
1170       ++dpos)
1171    {
1172      if (chdir ((*dpos).c_str()) < 0)
1173        {
1174          error e(*dpos, CHDIR, strerror(errno));
1175
1176          if (dpos + 1 == dlist.end())
1177            throw e;
1178          else
1179            log_exception_warning(e);
1180        }
1181      else
1182        {
1183          log_debug(DEBUG_NOTICE) << "Changed directory to "
1184                                  << *dpos << std::endl;
1185          if (dpos != dlist.begin())
1186            {
1187              error e(CHDIR_FB, *dpos);
1188              log_exception_warning(e);
1189            }
1190          break;
1191        }
1192    }
1193
1194  /* Fix up the command for exec. */
1195  get_command(session_chroot, file, command);
1196  log_debug(DEBUG_NOTICE) << "command="
1197                          << string_list_to_string(command, ", ")
1198                          << std::endl;
1199
1200  /* Set up environment */
1201  environment env;
1202  env.set_filter(session_chroot->get_environment_filter());
1203  env += this->authstat->get_auth_environment();
1204
1205  // Add equivalents to sudo's SUDO_USER, SUDO_UID, SUDO_GID, and
1206  // SUDO_COMMAND.
1207  env.add(std::make_pair("SCHROOT_COMMAND",
1208                         string_list_to_string(command, " ")));
1209  env.add(std::make_pair("SCHROOT_USER", this->authstat->get_ruser()));
1210  env.add(std::make_pair("SCHROOT_GROUP", this->authstat->get_rgroup()));
1211  env.add("SCHROOT_UID", this->authstat->get_ruid());
1212  env.add("SCHROOT_GID", this->authstat->get_rgid());
1213  // Add session ID.
1214  env.add("SCHROOT_SESSION_ID", session_chroot->get_session_id());
1215
1216  log_debug(DEBUG_INFO) << "Set environment:\n" << env;
1217
1218  // The user's command does not use our syslog fd.
1219  closelog();
1220
1221  // Add command prefix.
1222  string_list full_command(session_chroot->get_command_prefix());
1223  if (full_command.size() > 0)
1224    {
1225      std::string path;
1226      if (!env.get("PATH", path))
1227        path.clear();
1228      file = find_program_in_path(full_command[0], path, "");
1229    }
1230  for (string_list::const_iterator pos = command.begin();
1231       pos != command.end();
1232       ++pos)
1233    full_command.push_back(*pos);
1234
1235  /* Execute */
1236  if (exec (file, full_command, env))
1237    throw error(file, EXEC, strerror(errno));
1238
1239  /* This should never be reached */
1240  _exit(EXIT_FAILURE);
1241}
1242
1243void
1244session::wait_for_child (pid_t pid,
1245                         int&  child_status)
1246{
1247  child_status = EXIT_FAILURE; // Default exit status
1248
1249  int status;
1250  bool child_killed = false;
1251
1252  while (1)
1253    {
1254      if ((sighup_called || sigterm_called) && !child_killed)
1255        {
1256          if (sighup_called)
1257            {
1258              error e(SIGNAL_CATCH, strsignal(SIGHUP),
1259                      _("terminating immediately"));
1260              log_exception_error(e);
1261              kill(pid, SIGHUP);
1262            }
1263          else // SIGTERM
1264            {
1265              error e(SIGNAL_CATCH, strsignal(SIGTERM),
1266                      _("terminating immediately"));
1267              log_exception_error(e);
1268              kill(pid, SIGTERM);
1269            }
1270          this->chroot_status = false;
1271          child_killed = true;
1272        }
1273
1274      if (waitpid(pid, &status, 0) == -1)
1275        {
1276          if (errno == EINTR && (sighup_called || sigterm_called))
1277            continue; // Kill child and wait again.
1278          else
1279            throw error(CHILD_WAIT, strerror(errno));
1280        }
1281      else if (sighup_called)
1282        {
1283          sighup_called = false;
1284          throw error(SIGNAL_CATCH, strsignal(SIGHUP));
1285        }
1286      else if (sigterm_called)
1287        {
1288          sigterm_called = false;
1289          throw error(SIGNAL_CATCH, strsignal(SIGTERM));
1290        }
1291      else
1292        break;
1293    }
1294
1295  if (!WIFEXITED(status))
1296    {
1297      if (WIFSIGNALED(status))
1298        throw error(CHILD_SIGNAL, strsignal(WTERMSIG(status)));
1299      else if (WCOREDUMP(status))
1300        throw error(CHILD_CORE);
1301      else
1302        throw error(CHILD_FAIL);
1303    }
1304
1305  child_status = WEXITSTATUS(status);
1306}
1307
1308void
1309session::run_chroot (sbuild::chroot::ptr& session_chroot)
1310{
1311  assert(!session_chroot->get_name().empty());
1312
1313  pid_t pid;
1314  if ((pid = fork()) == -1)
1315    {
1316      throw error(CHILD_FORK, strerror(errno));
1317    }
1318  else if (pid == 0)
1319    {
1320#ifdef SBUILD_DEBUG
1321      while (child_wait)
1322        ;
1323#endif
1324      try
1325        {
1326          run_child(session_chroot);
1327        }
1328      catch (std::runtime_error const& e)
1329        {
1330          log_exception_error(e);
1331        }
1332      catch (...)
1333        {
1334          sbuild::log_error()
1335            << _("An unknown exception occurred") << std::endl;
1336        }
1337      _exit (EXIT_FAILURE);
1338    }
1339  else
1340    {
1341      wait_for_child(pid, this->child_status);
1342    }
1343}
1344
1345void
1346session::set_sighup_handler ()
1347{
1348  set_signal_handler(SIGHUP, &this->saved_sighup_signal, sighup_handler);
1349}
1350
1351void
1352session::clear_sighup_handler ()
1353{
1354  clear_signal_handler(SIGHUP, &this->saved_sighup_signal);
1355}
1356
1357void
1358session::set_sigterm_handler ()
1359{
1360  set_signal_handler(SIGTERM, &this->saved_sigterm_signal, sigterm_handler);
1361}
1362
1363void
1364session::clear_sigterm_handler ()
1365{
1366  clear_signal_handler(SIGTERM, &this->saved_sigterm_signal);
1367}
1368
1369void
1370session::set_signal_handler (int                signal,
1371                             struct sigaction  *saved_signal,
1372                             void             (*handler)(int))
1373{
1374  struct sigaction new_sa;
1375  sigemptyset(&new_sa.sa_mask);
1376  new_sa.sa_flags = 0;
1377  new_sa.sa_handler = handler;
1378
1379  if (sigaction(signal, &new_sa, saved_signal) != 0)
1380    throw error(SIGNAL_SET, strsignal(signal), strerror(errno));
1381}
1382
1383void
1384session::clear_signal_handler (int               signal,
1385                               struct sigaction *saved_signal)
1386{
1387  /* Restore original handler */
1388  sigaction (signal, saved_signal, 0);
1389}
Note: See TracBrowser for help on using the repository browser.