/* chroot -- run command or shell with special root directory                   This is the chroot utility
   Copyright (C) 1995-2018 Free Software Foundation, Inc.                       
                                                                                
   This program is free software: you can redistribute it and/or modify         
   it under the terms of the GNU General Public License as published by         
   the Free Software Foundation, either version 3 of the License, or            
   (at your option) any later version.                                          
                                                                                
   This program is distributed in the hope that it will be useful,              
   but WITHOUT ANY WARRANTY; without even the implied warranty of               
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                
   GNU General Public License for more details.                                 
                                                                                
   You should have received a copy of the GNU General Public License            
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */   The GNUv3 license
                                                                                
/* Written by Roland McGrath.  */                                               
                                                                                
#include <config.h>                                                             Provides system specific information
#include <getopt.h>                                                             ...!includes auto-comment...
#include <stdio.h>                                                              Provides standard I/O capability
#include <sys/types.h>                                                          Provides system data types
#include <pwd.h>                                                                ...!includes auto-comment...
#include <grp.h>                                                                ...!includes auto-comment...
                                                                                
#include "system.h"                                                             ...!includes auto-comment...
#include "die.h"                                                                ...!includes auto-comment...
#include "error.h"                                                              ...!includes auto-comment...
#include "ignore-value.h"                                                       ...!includes auto-comment...
#include "mgetgroups.h"                                                         ...!includes auto-comment...
#include "quote.h"                                                              ...!includes auto-comment...
#include "root-dev-ino.h"                                                       ...!includes auto-comment......!includes auto-comment...
#include "userspec.h"                                                           ...!includes auto-comment...
#include "xstrtol.h"                                                            ...!includes auto-comment...
                                                                                
/* The official name of this program (e.g., no 'g' prefix).  */                 
#define PROGRAM_NAME "chroot"                                                   Line 37
                                                                                
#define AUTHORS proper_name ("Roland McGrath")                                  Line 39
                                                                                
#ifndef MAXGID                                                                  Line 41
# define MAXGID GID_T_MAX                                                       Line 42
#endif                                                                          Line 43
                                                                                
static inline bool uid_unset (uid_t uid) { return uid == (uid_t) -1; }          Line 45Block 1
static inline bool gid_unset (gid_t gid) { return gid == (gid_t) -1; }          Line 46Block 2
#define uid_set(x) (!uid_unset (x))                                             Line 47
#define gid_set(x) (!gid_unset (x))                                             Line 48
                                                                                
enum                                                                            Line 50
{                                                                               
  GROUPS = UCHAR_MAX + 1,                                                       Line 52
  USERSPEC,                                                                     Line 53
  SKIP_CHDIR                                                                    Line 54
};                                                                              Block 3
                                                                                
static struct option const long_opts[] =                                        Line 57
{                                                                               
  {"groups", required_argument, NULL, GROUPS},                                  Line 59
  {"userspec", required_argument, NULL, USERSPEC},                              Line 60
  {"skip-chdir", no_argument, NULL, SKIP_CHDIR},                                Line 61
  {GETOPT_HELP_OPTION_DECL},                                                    Line 62
  {GETOPT_VERSION_OPTION_DECL},                                                 Line 63
  {NULL, 0, NULL, 0}                                                            Line 64
};                                                                              Block 4
                                                                                
#if ! HAVE_SETGROUPS                                                            Line 67
/* At least Interix lacks supplemental group support.  */                       
static int                                                                      Line 69
setgroups (size_t size, gid_t const *list _GL_UNUSED)                           Line 70
{                                                                               
  if (size == 0)                                                                Line 72
    {                                                                           
      /* Return success when clearing supplemental groups                       
         as ! HAVE_SETGROUPS should only be the case on                         
         platforms that don't support supplemental groups.  */                  
      return 0;                                                                 Line 77
    }                                                                           
  else                                                                          Line 79
    {                                                                           
      errno = ENOTSUP;                                                          Line 81
      return -1;                                                                Line 82
    }                                                                           
}                                                                               Block 5
#endif                                                                          Line 85
                                                                                
/* Determine the group IDs for the specified supplementary GROUPS,              
   which is a comma separated list of supplementary groups (names or numbers).  
   Allocate an array for the parsed IDs and store it in PGIDS,                  
   which may be allocated even on parse failure.                                
   Update the number of parsed groups in PN_GIDS on success.                    
   Upon any failure return nonzero, and issue diagnostic if SHOW_ERRORS is true.
   Otherwise return zero.  */                                                   
                                                                                
static int                                                                      Line 95
parse_additional_groups (char const *groups, GETGROUPS_T **pgids,               Line 96
                         size_t *pn_gids, bool show_errors)                     Line 97
{                                                                               
  GETGROUPS_T *gids = NULL;                                                     Line 99
  size_t n_gids_allocated = 0;                                                  Line 100
  size_t n_gids = 0;                                                            Line 101
  char *buffer = xstrdup (groups);                                              Line 102
  char const *tmp;                                                              Line 103
  int ret = 0;                                                                  Line 104
                                                                                
  for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))               Line 106
    {                                                                           
      struct group *g;                                                          Line 108
      unsigned long int value;                                                  Line 109
                                                                                
      if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)Line 111
        {                                                                       
          while (isspace (to_uchar (*tmp)))                                     Line 113
            tmp++;                                                              Line 114
          if (*tmp != '+')                                                      Line 115
            {                                                                   
              /* Handle the case where the name is numeric.  */                 
              g = getgrnam (tmp);                                               Line 118
              if (g != NULL)                                                    Line 119
                value = g->gr_gid;                                              Line 120
            }                                                                   
          /* Flag that we've got a group from the number.  */                   
          g = (struct group *) (intptr_t) ! NULL;                               Line 123
        }                                                                       
      else                                                                      Line 125
        {                                                                       
          g = getgrnam (tmp);                                                   Line 127
          if (g != NULL)                                                        Line 128
            value = g->gr_gid;                                                  Line 129
        }                                                                       
                                                                                
      if (g == NULL)                                                            Line 132
        {                                                                       
          ret = -1;                                                             Line 134
                                                                                
          if (show_errors)                                                      Line 136
            {                                                                   
              error (0, errno, _("invalid group %s"), quote (tmp));             Line 138
              continue;                                                         Line 139
            }                                                                   
                                                                                
          break;                                                                Line 142
        }                                                                       
                                                                                
      if (n_gids == n_gids_allocated)                                           Line 145
        gids = X2NREALLOC (gids, &n_gids_allocated);                            Line 146
      gids[n_gids++] = value;                                                   Line 147
    }                                                                           
                                                                                
  if (ret == 0 && n_gids == 0)                                                  Line 150
    {                                                                           
      if (show_errors)                                                          Line 152
        error (0, 0, _("invalid group list %s"), quote (groups));               Line 153
      ret = -1;                                                                 Line 154
    }                                                                           
                                                                                
  *pgids = gids;                                                                Line 157
                                                                                
  if (ret == 0)                                                                 Line 159
    *pn_gids = n_gids;                                                          Line 160
                                                                                
  free (buffer);                                                                Line 162
  return ret;                                                                   Line 163
}                                                                               Block 6
                                                                                
/* Return whether the passed path is equivalent to "/".                         
   Note we don't compare against get_root_dev_ino() as "/"                      
   could be bind mounted to a separate location.  */                            
                                                                                
static bool                                                                     Line 170
is_root (const char* dir)                                                       Line 171
{                                                                               
  char *resolved = canonicalize_file_name (dir);                                Line 173
  bool is_res_root = resolved && STREQ ("/", resolved);                         Line 174
  free (resolved);                                                              Line 175
  return is_res_root;                                                           Line 176
}                                                                               Block 7
                                                                                
void                                                                            Line 179
usage (int status)                                                              Line 180
{                                                                               
  if (status != EXIT_SUCCESS)                                                   Line 182
    emit_try_help ();                                                           ...!common auto-comment...
  else                                                                          Line 184
    {                                                                           
      printf (_("\                                                              Line 186
Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\                                Line 187
  or:  %s OPTION\n\                                                             Line 188
"), program_name, program_name);                                                Line 189
                                                                                
      fputs (_("\                                                               Line 191
Run COMMAND with root directory set to NEWROOT.\n\                              Line 192
\n\                                                                             
"), stdout);                                                                    Line 194
                                                                                
      fputs (_("\                                                               Line 196
  --groups=G_LIST        specify supplementary groups as g1,g2,..,gN\n\         Line 197
"), stdout);                                                                    Line 198
      fputs (_("\                                                               Line 199
  --userspec=USER:GROUP  specify user and group (ID or name) to use\n\          Line 200
"), stdout);                                                                    Line 201
      printf (_("\                                                              Line 202
  --skip-chdir           do not change working directory to %s\n\               Line 203
"), quoteaf ("/"));                                                             Line 204
                                                                                
      fputs (HELP_OPTION_DESCRIPTION, stdout);                                  Line 206
      fputs (VERSION_OPTION_DESCRIPTION, stdout);                               Line 207
      fputs (_("\                                                               Line 208
\n\                                                                             
If no command is given, run '\"$SHELL\" -i' (default: '/bin/sh -i').\n\         Line 210
"), stdout);                                                                    Line 211
      emit_ancillary_info (PROGRAM_NAME);                                       Line 212
    }                                                                           
  exit (status);                                                                Line 214
}                                                                               Block 8
                                                                                
int                                                                             
main (int argc, char **argv)                                                    Line 218
{                                                                               
  int c;                                                                        Line 220
                                                                                
  /* Input user and groups spec.  */                                            
  char *userspec = NULL;                                                        Line 223
  char const *username = NULL;                                                  Line 224
  char const *groups = NULL;                                                    Line 225
  bool skip_chdir = false;                                                      Line 226
                                                                                
  /* Parsed user and group IDs.  */                                             
  uid_t uid = -1;                                                               Line 229
  gid_t gid = -1;                                                               Line 230
  GETGROUPS_T *out_gids = NULL;                                                 Line 231
  size_t n_gids = 0;                                                            Line 232
                                                                                
  initialize_main (&argc, &argv);                                               VMS-specific entry point handling wildcard expansion
  set_program_name (argv[0]);                                                   Retains program name and discards path
  setlocale (LC_ALL, "");                                                       Sets up internationalization (i18n)
  bindtextdomain (PACKAGE, LOCALEDIR);                                          Assigns i18n directorySets text domain for _() [gettext()] function
  textdomain (PACKAGE);                                                         Sets text domain for _() [gettext()] function
                                                                                
  initialize_exit_failure (EXIT_CANCELED);                                      Line 240
  atexit (close_stdout);                                                        Close stdout on exit (see gnulib)
                                                                                
  while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)            Line 243
    {                                                                           
      switch (c)                                                                Line 245
        {                                                                       
        case USERSPEC:                                                          Line 247
          {                                                                     
            userspec = optarg;                                                  Line 249
            /* Treat 'user:' just like 'user'                                   
               as we lookup the primary group by default                        
               (and support doing so for UIDs as well as names.  */             
            size_t userlen = strlen (userspec);                                 Line 253
            if (userlen && userspec[userlen - 1] == ':')                        Line 254
              userspec[userlen - 1] = '\0';                                     Line 255
            break;                                                              Line 256
          }                                                                     
                                                                                
        case GROUPS:                                                            Line 259
          groups = optarg;                                                      Line 260
          break;                                                                Line 261
                                                                                
        case SKIP_CHDIR:                                                        Line 263
          skip_chdir = true;                                                    Line 264
          break;                                                                Line 265
                                                                                
        case_GETOPT_HELP_CHAR;                                                  Line 267
                                                                                
        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);                       Line 269
                                                                                
        default:                                                                Line 271
          usage (EXIT_CANCELED);                                                Line 272
        }                                                                       
    }                                                                           
                                                                                
  if (argc <= optind)                                                           Line 276
    {                                                                           
      error (0, 0, _("missing operand"));                                       Line 278
      usage (EXIT_CANCELED);                                                    Line 279
    }                                                                           
                                                                                
  char const *newroot = argv[optind];                                           Line 282
  bool is_oldroot = is_root (newroot);                                          Line 283
                                                                                
  if (! is_oldroot && skip_chdir)                                               Line 285
    {                                                                           
      error (0, 0, _("option --skip-chdir only permitted if NEWROOT is old %s"),Line 287
             quoteaf ("/"));                                                    Line 288
      usage (EXIT_CANCELED);                                                    Line 289
    }                                                                           
                                                                                
  if (! is_oldroot)                                                             Line 292
    {                                                                           
      /* We have to look up users and groups twice.                             
        - First, outside the chroot to load potentially necessary passwd/group  
          parsing plugins (e.g. NSS);                                           
        - Second, inside chroot to redo parsing in case IDs are different.      
          Within chroot lookup is the main justification for having             
          the --user option supported by the chroot command itself.  */         
      if (userspec)                                                             Line 300
        ignore_value (parse_user_spec (userspec, &uid, &gid, NULL, NULL));      Line 301
                                                                                
      /* If no gid is supplied or looked up, do so now.                         
        Also lookup the username for use with getgroups.  */                    
      if (uid_set (uid) && (! groups || gid_unset (gid)))                       Line 305
        {                                                                       
          const struct passwd *pwd;                                             Line 307
          if ((pwd = getpwuid (uid)))                                           Line 308
            {                                                                   
              if (gid_unset (gid))                                              Line 310
                gid = pwd->pw_gid;                                              Line 311
              username = pwd->pw_name;                                          Line 312
            }                                                                   
        }                                                                       
                                                                                
      if (groups && *groups)                                                    Line 316
        ignore_value (parse_additional_groups (groups, &out_gids, &n_gids,      Line 317
                                               false));                         Line 318
#if HAVE_SETGROUPS                                                              Line 319
      else if (! groups && gid_set (gid) && username)                           Line 320
        {                                                                       
          int ngroups = xgetgroups (username, gid, &out_gids);                  Line 322...!syscalls auto-comment...
          if (0 < ngroups)                                                      Line 323
            n_gids = ngroups;                                                   Line 324
        }                                                                       
#endif                                                                          Line 326
    }                                                                           
                                                                                
  if (chroot (newroot) != 0)                                                    Line 329
    die (EXIT_CANCELED, errno, _("cannot change root directory to %s"),         Line 330
         quoteaf (newroot));                                                    Line 331
                                                                                
  if (! skip_chdir && chdir ("/"))                                              Line 333
    die (EXIT_CANCELED, errno, _("cannot chdir to root directory"));            Line 334
                                                                                
  if (argc == optind + 1)                                                       Line 336
    {                                                                           
      /* No command.  Run an interactive shell.  */                             
      char *shell = getenv ("SHELL");                                           Line 339
      if (shell == NULL)                                                        Line 340
        shell = bad_cast ("/bin/sh");                                           Line 341
      argv[0] = shell;                                                          Line 342
      argv[1] = bad_cast ("-i");                                                Line 343
      argv[2] = NULL;                                                           Line 344
    }                                                                           
  else                                                                          Line 346
    {                                                                           
      /* The following arguments give the command.  */                          
      argv += optind + 1;                                                       Line 349
    }                                                                           
                                                                                
  /* Attempt to set all three: supplementary groups, group ID, user ID.         
     Diagnose any failures.  If any have failed, exit before execvp.  */        
  if (userspec)                                                                 Line 354
    {                                                                           
      char const *err = parse_user_spec (userspec, &uid, &gid, NULL, NULL);     Line 356
                                                                                
      if (err && uid_unset (uid) && gid_unset (gid))                            Line 358
        die (EXIT_CANCELED, errno, "%s", (err));                                Line 359
    }                                                                           
                                                                                
  /* If no gid is supplied or looked up, do so now.                             
     Also lookup the username for use with getgroups.  */                       
  if (uid_set (uid) && (! groups || gid_unset (gid)))                           Line 364
    {                                                                           
      const struct passwd *pwd;                                                 Line 366
      if ((pwd = getpwuid (uid)))                                               Line 367
        {                                                                       
          if (gid_unset (gid))                                                  Line 369
            gid = pwd->pw_gid;                                                  Line 370
          username = pwd->pw_name;                                              Line 371
        }                                                                       
      else if (gid_unset (gid))                                                 Line 373
        {                                                                       
          die (EXIT_CANCELED, errno,                                            Line 375
               _("no group specified for unknown uid: %d"), (int) uid);         Line 376
        }                                                                       
    }                                                                           
                                                                                
  GETGROUPS_T *gids = out_gids;                                                 Line 380
  GETGROUPS_T *in_gids = NULL;                                                  Line 381
  if (groups && *groups)                                                        Line 382
    {                                                                           
      if (parse_additional_groups (groups, &in_gids, &n_gids, !n_gids) != 0)    Line 384
        {                                                                       
          if (! n_gids)                                                         Line 386
            return EXIT_CANCELED;                                               Line 387
          /* else look-up outside the chroot worked, then go with those.  */    
        }                                                                       
      else                                                                      Line 390
        gids = in_gids;                                                         Line 391
    }                                                                           
#if HAVE_SETGROUPS                                                              Line 393
  else if (! groups && gid_set (gid) && username)                               Line 394
    {                                                                           
      int ngroups = xgetgroups (username, gid, &in_gids);                       Line 396...!syscalls auto-comment...
      if (ngroups <= 0)                                                         Line 397
        {                                                                       
          if (! n_gids)                                                         Line 399
            die (EXIT_CANCELED, errno,                                          Line 400
                 _("failed to get supplemental groups"));                       Line 401
          /* else look-up outside the chroot worked, then go with those.  */    
        }                                                                       
      else                                                                      Line 404
        {                                                                       
          n_gids = ngroups;                                                     Line 406
          gids = in_gids;                                                       Line 407
        }                                                                       
    }                                                                           
#endif                                                                          Line 410
                                                                                
  if ((uid_set (uid) || groups) && setgroups (n_gids, gids) != 0)               Line 412
    die (EXIT_CANCELED, errno, _("failed to set supplemental groups"));         Line 413
                                                                                
  free (in_gids);                                                               Line 415
  free (out_gids);                                                              Line 416
                                                                                
  if (gid_set (gid) && setgid (gid))                                            Line 418
    die (EXIT_CANCELED, errno, _("failed to set group-ID"));                    Line 419
                                                                                
  if (uid_set (uid) && setuid (uid))                                            Line 421
    die (EXIT_CANCELED, errno, _("failed to set user-ID"));                     Line 422
                                                                                
  /* Execute the given command.  */                                             
  execvp (argv[0], argv);                                                       Line 425
                                                                                
  int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;         Line 427
  error (0, errno, _("failed to run command %s"), quote (argv[0]));             Line 428
  return exit_status;                                                           Line 429
}                                                                               Block 9