1 /* chroot -- run command or shell with special root directory
2 Copyright (C) 1995-2013 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* Written by Roland McGrath. */
22 #include <sys/types.h>
31 /* The official name of this program (e.g., no 'g' prefix). */
32 #define PROGRAM_NAME "chroot"
34 #define AUTHORS proper_name ("Roland McGrath")
37 # define MAXGID GID_T_MAX
42 GROUPS = UCHAR_MAX + 1,
46 static struct option const long_opts[] =
48 {"groups", required_argument, NULL, GROUPS},
49 {"userspec", required_argument, NULL, USERSPEC},
50 {GETOPT_HELP_OPTION_DECL},
51 {GETOPT_VERSION_OPTION_DECL},
56 /* At least Interix lacks supplemental group support. Define an
57 always-successful replacement to avoid checking for setgroups
58 availability everywhere, just to support broken platforms. */
60 setgroups (size_t size ATTRIBUTE_UNUSED, gid_t const *list ATTRIBUTE_UNUSED)
66 /* Call setgroups to set the supplementary groups to those listed in GROUPS.
67 GROUPS is a comma separated list of supplementary groups (names or numbers).
68 Parse that list, converting any names to numbers, and call setgroups on the
69 resulting numbers. Upon any failure give a diagnostic and return nonzero.
70 Otherwise return zero. */
72 set_additional_groups (char const *groups)
74 GETGROUPS_T *gids = NULL;
75 size_t n_gids_allocated = 0;
77 char *buffer = xstrdup (groups);
81 for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))
84 unsigned long int value;
86 if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)
97 error (0, errno, _("invalid group %s"), quote (tmp));
102 if (n_gids == n_gids_allocated)
103 gids = X2NREALLOC (gids, &n_gids_allocated);
104 gids[n_gids++] = value;
107 if (ret == 0 && n_gids == 0)
109 error (0, 0, _("invalid group list %s"), quote (groups));
115 ret = setgroups (n_gids, gids);
117 error (0, errno, _("failed to set additional groups"));
128 if (status != EXIT_SUCCESS)
133 Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
135 "), program_name, program_name);
138 Run COMMAND with root directory set to NEWROOT.\n\
143 --userspec=USER:GROUP specify user and group (ID or name) to use\n\
144 --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\
147 fputs (HELP_OPTION_DESCRIPTION, stdout);
148 fputs (VERSION_OPTION_DESCRIPTION, stdout);
151 If no command is given, run '${SHELL} -i' (default: '/bin/sh -i').\n\
153 emit_ancillary_info ();
159 main (int argc, char **argv)
162 char const *userspec = NULL;
163 char const *groups = NULL;
165 initialize_main (&argc, &argv);
166 set_program_name (argv[0]);
167 setlocale (LC_ALL, "");
168 bindtextdomain (PACKAGE, LOCALEDIR);
169 textdomain (PACKAGE);
171 initialize_exit_failure (EXIT_CANCELED);
172 atexit (close_stdout);
174 while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
186 case_GETOPT_HELP_CHAR;
188 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
191 usage (EXIT_CANCELED);
197 error (0, 0, _("missing operand"));
198 usage (EXIT_CANCELED);
201 if (chroot (argv[optind]) != 0)
202 error (EXIT_CANCELED, errno, _("cannot change root directory to %s"),
206 error (EXIT_CANCELED, errno, _("cannot chdir to root directory"));
208 if (argc == optind + 1)
210 /* No command. Run an interactive shell. */
211 char *shell = getenv ("SHELL");
213 shell = bad_cast ("/bin/sh");
215 argv[1] = bad_cast ("-i");
220 /* The following arguments give the command. */
226 /* Attempt to set all three: supplementary groups, group ID, user ID.
227 Diagnose any failures. If any have failed, exit before execvp. */
234 char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group);
237 error (EXIT_CANCELED, errno, "%s", err);
242 if (groups && set_additional_groups (groups))
245 if (gid != (gid_t) -1 && setgid (gid))
247 error (0, errno, _("failed to set group-ID"));
251 if (uid != (uid_t) -1 && setuid (uid))
253 error (0, errno, _("failed to set user-ID"));
259 /* Yes, this call is identical to the one above.
260 However, when --userspec and --groups groups are used together,
261 we don't want to call this function until after parsing USER:GROUP,
262 and it must be called before setuid. */
263 if (groups && set_additional_groups (groups))
268 exit (EXIT_CANCELED);
270 /* Execute the given command. */
271 execvp (argv[0], argv);
274 int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
275 error (0, errno, _("failed to run command %s"), quote (argv[0]));