1/* chroot -- run command or shell with special root directory
2   Copyright (C) 1995-1997, 1999-2004, 2007-2010 Free Software Foundation, Inc.
3
4   This program is free software: you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation, either version 3 of the License, or
7   (at your option) any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17/* Written by Roland McGrath.  */
18
19#include <config.h>
20#include <getopt.h>
21#include <stdio.h>
22#include <sys/types.h>
23#include <grp.h>
24
25#include "system.h"
26#include "error.h"
27#include "long-options.h"
28#include "quote.h"
29#include "userspec.h"
30#include "xstrtol.h"
31
32/* The official name of this program (e.g., no `g' prefix).  */
33#define PROGRAM_NAME "chroot"
34
35#define AUTHORS proper_name ("Roland McGrath")
36
37#ifndef MAXGID
38# define MAXGID GID_T_MAX
39#endif
40
41enum
42{
43  GROUPS = UCHAR_MAX + 1,
44  USERSPEC
45};
46
47static struct option const long_opts[] =
48{
49  {"groups", required_argument, NULL, GROUPS},
50  {"userspec", required_argument, NULL, USERSPEC},
51  {GETOPT_HELP_OPTION_DECL},
52  {GETOPT_VERSION_OPTION_DECL},
53  {NULL, 0, NULL, 0}
54};
55
56/* Call setgroups to set the supplementary groups to those listed in GROUPS.
57   GROUPS is a comma separated list of supplementary groups (names or numbers).
58   Parse that list, converting any names to numbers, and call setgroups on the
59   resulting numbers.  Upon any failure give a diagnostic and return nonzero.
60   Otherwise return zero.  */
61static int
62set_additional_groups (char const *groups)
63{
64  GETGROUPS_T *gids = NULL;
65  size_t n_gids_allocated = 0;
66  size_t n_gids = 0;
67  char *buffer = xstrdup (groups);
68  char const *tmp;
69  int ret = 0;
70
71  for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))
72    {
73      struct group *g;
74      unsigned long int value;
75
76      if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)
77        g = getgrgid (value);
78      else
79        {
80          g = getgrnam (tmp);
81          if (g != NULL)
82            value = g->gr_gid;
83        }
84
85      if (g == NULL)
86        {
87          error (0, errno, _("invalid group %s"), quote (tmp));
88          ret = -1;
89          continue;
90        }
91
92      if (n_gids == n_gids_allocated)
93        gids = X2NREALLOC (gids, &n_gids_allocated);
94      gids[n_gids++] = value;
95    }
96
97  if (ret == 0 && n_gids == 0)
98    {
99      error (0, 0, _("invalid group list %s"), quote (groups));
100      ret = -1;
101    }
102
103  if (ret == 0)
104    {
105      ret = setgroups (n_gids, gids);
106      if (ret)
107        error (0, errno, _("failed to set additional groups"));
108    }
109
110  free (buffer);
111  free (gids);
112  return ret;
113}
114
115void
116usage (int status)
117{
118  if (status != EXIT_SUCCESS)
119    fprintf (stderr, _("Try `%s --help' for more information.\n"),
120             program_name);
121  else
122    {
123      printf (_("\
124Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
125  or:  %s OPTION\n\
126"), program_name, program_name);
127
128      fputs (_("\
129Run COMMAND with root directory set to NEWROOT.\n\
130\n\
131"), stdout);
132
133      fputs (_("\
134  --userspec=USER:GROUP  specify user and group (ID or name) to use\n\
135  --groups=G_LIST        specify supplementary groups as g1,g2,..,gN\n\
136"), stdout);
137
138      fputs (HELP_OPTION_DESCRIPTION, stdout);
139      fputs (VERSION_OPTION_DESCRIPTION, stdout);
140      fputs (_("\
141\n\
142If no command is given, run ``${SHELL} -i'' (default: /bin/sh).\n\
143"), stdout);
144      emit_ancillary_info ();
145    }
146  exit (status);
147}
148
149int
150main (int argc, char **argv)
151{
152  int c;
153  char const *userspec = NULL;
154  char const *groups = NULL;
155
156  initialize_main (&argc, &argv);
157  set_program_name (argv[0]);
158  setlocale (LC_ALL, "");
159  bindtextdomain (PACKAGE, LOCALEDIR);
160  textdomain (PACKAGE);
161
162  initialize_exit_failure (EXIT_CANCELED);
163  atexit (close_stdout);
164
165  parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version,
166                      usage, AUTHORS, (char const *) NULL);
167
168  while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
169    {
170      switch (c)
171        {
172        case USERSPEC:
173          userspec = optarg;
174          break;
175        case GROUPS:
176          groups = optarg;
177          break;
178        default:
179          usage (EXIT_CANCELED);
180        }
181    }
182
183  if (argc <= optind)
184    {
185      error (0, 0, _("missing operand"));
186      usage (EXIT_CANCELED);
187    }
188
189  if (chroot (argv[optind]) != 0)
190    error (EXIT_CANCELED, errno, _("cannot change root directory to %s"),
191           argv[optind]);
192
193  if (chdir ("/"))
194    error (EXIT_CANCELED, errno, _("cannot chdir to root directory"));
195
196  if (argc == optind + 1)
197    {
198      /* No command.  Run an interactive shell.  */
199      char *shell = getenv ("SHELL");
200      if (shell == NULL)
201        shell = bad_cast ("/bin/sh");
202      argv[0] = shell;
203      argv[1] = bad_cast ("-i");
204      argv[2] = NULL;
205    }
206  else
207    {
208      /* The following arguments give the command.  */
209      argv += optind + 1;
210    }
211
212  {
213  bool fail = false;
214
215  /* Attempt to set all three: supplementary groups, group ID, user ID.
216     Diagnose any failures.  If any have failed, exit before execvp.  */
217  if (userspec)
218    {
219      uid_t uid = -1;
220      gid_t gid = -1;
221      char *user;
222      char *group;
223      char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group);
224
225      if (err)
226        error (EXIT_CANCELED, errno, "%s", err);
227
228      free (user);
229      free (group);
230
231      if (groups && set_additional_groups (groups))
232        fail = true;
233
234      if (gid != (gid_t) -1 && setgid (gid))
235        {
236          error (0, errno, _("failed to set group-ID"));
237          fail = true;
238        }
239
240      if (uid != (uid_t) -1 && setuid (uid))
241        {
242          error (0, errno, _("failed to set user-ID"));
243          fail = true;
244        }
245    }
246  else
247    {
248      /* Yes, this call is identical to the one above.
249         However, when --userspec and --groups groups are used together,
250         we don't want to call this function until after parsing USER:GROUP,
251         and it must be called before setuid.  */
252      if (groups && set_additional_groups (groups))
253        fail = true;
254    }
255
256  if (fail)
257    exit (EXIT_CANCELED);
258  }
259
260  /* Execute the given command.  */
261  execvp (argv[0], argv);
262
263  {
264    int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
265    error (0, errno, _("failed to run command %s"), quote (argv[0]));
266    exit (exit_status);
267  }
268}
269