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