/* chroot -- run command or shell with special root directory Copyright (C) 1995-1997, 1999-2004, 2007-2012 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 . */ /* Written by Roland McGrath. */ #include #include #include #include #include #include "system.h" #include "error.h" #include "quote.h" #include "userspec.h" #include "xstrtol.h" /* The official name of this program (e.g., no `g' prefix). */ #define PROGRAM_NAME "chroot" #define AUTHORS proper_name ("Roland McGrath") #ifndef MAXGID # define MAXGID GID_T_MAX #endif enum { GROUPS = UCHAR_MAX + 1, USERSPEC }; static struct option const long_opts[] = { {"groups", required_argument, NULL, GROUPS}, {"userspec", required_argument, NULL, USERSPEC}, {GETOPT_HELP_OPTION_DECL}, {GETOPT_VERSION_OPTION_DECL}, {NULL, 0, NULL, 0} }; #if ! HAVE_SETGROUPS /* At least Interix lacks supplemental group support. Define an always-successful replacement to avoid checking for setgroups availability everywhere, just to support broken platforms. */ static int setgroups (size_t size ATTRIBUTE_UNUSED, gid_t const *list ATTRIBUTE_UNUSED) { return 0; } #endif /* Call setgroups to set the supplementary groups to those listed in GROUPS. GROUPS is a comma separated list of supplementary groups (names or numbers). Parse that list, converting any names to numbers, and call setgroups on the resulting numbers. Upon any failure give a diagnostic and return nonzero. Otherwise return zero. */ static int set_additional_groups (char const *groups) { GETGROUPS_T *gids = NULL; size_t n_gids_allocated = 0; size_t n_gids = 0; char *buffer = xstrdup (groups); char const *tmp; int ret = 0; for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ",")) { struct group *g; unsigned long int value; if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID) g = getgrgid (value); else { g = getgrnam (tmp); if (g != NULL) value = g->gr_gid; } if (g == NULL) { error (0, errno, _("invalid group %s"), quote (tmp)); ret = -1; continue; } if (n_gids == n_gids_allocated) gids = X2NREALLOC (gids, &n_gids_allocated); gids[n_gids++] = value; } if (ret == 0 && n_gids == 0) { error (0, 0, _("invalid group list %s"), quote (groups)); ret = -1; } if (ret == 0) { ret = setgroups (n_gids, gids); if (ret) error (0, errno, _("failed to set additional groups")); } free (buffer); free (gids); return ret; } void usage (int status) { if (status != EXIT_SUCCESS) emit_try_help (); else { printf (_("\ Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\ or: %s OPTION\n\ "), program_name, program_name); fputs (_("\ Run COMMAND with root directory set to NEWROOT.\n\ \n\ "), stdout); fputs (_("\ --userspec=USER:GROUP specify user and group (ID or name) to use\n\ --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\ "), stdout); fputs (HELP_OPTION_DESCRIPTION, stdout); fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ If no command is given, run '${SHELL} -i' (default: '/bin/sh -i').\n\ "), stdout); emit_ancillary_info (); } exit (status); } int main (int argc, char **argv) { int c; char const *userspec = NULL; char const *groups = NULL; initialize_main (&argc, &argv); set_program_name (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); initialize_exit_failure (EXIT_CANCELED); atexit (close_stdout); while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1) { switch (c) { case USERSPEC: userspec = optarg; break; case GROUPS: groups = optarg; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (EXIT_CANCELED); } } if (argc <= optind) { error (0, 0, _("missing operand")); usage (EXIT_CANCELED); } if (chroot (argv[optind]) != 0) error (EXIT_CANCELED, errno, _("cannot change root directory to %s"), argv[optind]); if (chdir ("/")) error (EXIT_CANCELED, errno, _("cannot chdir to root directory")); if (argc == optind + 1) { /* No command. Run an interactive shell. */ char *shell = getenv ("SHELL"); if (shell == NULL) shell = bad_cast ("/bin/sh"); argv[0] = shell; argv[1] = bad_cast ("-i"); argv[2] = NULL; } else { /* The following arguments give the command. */ argv += optind + 1; } bool fail = false; /* Attempt to set all three: supplementary groups, group ID, user ID. Diagnose any failures. If any have failed, exit before execvp. */ if (userspec) { uid_t uid = -1; gid_t gid = -1; char *user; char *group; char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group); if (err) error (EXIT_CANCELED, errno, "%s", err); free (user); free (group); if (groups && set_additional_groups (groups)) fail = true; if (gid != (gid_t) -1 && setgid (gid)) { error (0, errno, _("failed to set group-ID")); fail = true; } if (uid != (uid_t) -1 && setuid (uid)) { error (0, errno, _("failed to set user-ID")); fail = true; } } else { /* Yes, this call is identical to the one above. However, when --userspec and --groups groups are used together, we don't want to call this function until after parsing USER:GROUP, and it must be called before setuid. */ if (groups && set_additional_groups (groups)) fail = true; } if (fail) exit (EXIT_CANCELED); /* Execute the given command. */ execvp (argv[0], argv); { int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE); error (0, errno, _("failed to run command %s"), quote (argv[0])); exit (exit_status); } }