1 /* chroot -- run command or shell with special root directory
2 Copyright (C) 1995-1997, 1999-2004, 2007-2011 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
)
129 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
134 Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
136 "), program_name
, program_name
);
139 Run COMMAND with root directory set to NEWROOT.\n\
144 --userspec=USER:GROUP specify user and group (ID or name) to use\n\
145 --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\
148 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
149 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
152 If no command is given, run ``${SHELL} -i'' (default: /bin/sh).\n\
154 emit_ancillary_info ();
160 main (int argc
, char **argv
)
163 char const *userspec
= NULL
;
164 char const *groups
= NULL
;
166 initialize_main (&argc
, &argv
);
167 set_program_name (argv
[0]);
168 setlocale (LC_ALL
, "");
169 bindtextdomain (PACKAGE
, LOCALEDIR
);
170 textdomain (PACKAGE
);
172 initialize_exit_failure (EXIT_CANCELED
);
173 atexit (close_stdout
);
175 while ((c
= getopt_long (argc
, argv
, "+", long_opts
, NULL
)) != -1)
187 case_GETOPT_HELP_CHAR
;
189 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
192 usage (EXIT_CANCELED
);
198 error (0, 0, _("missing operand"));
199 usage (EXIT_CANCELED
);
202 if (chroot (argv
[optind
]) != 0)
203 error (EXIT_CANCELED
, errno
, _("cannot change root directory to %s"),
207 error (EXIT_CANCELED
, errno
, _("cannot chdir to root directory"));
209 if (argc
== optind
+ 1)
211 /* No command. Run an interactive shell. */
212 char *shell
= getenv ("SHELL");
214 shell
= bad_cast ("/bin/sh");
216 argv
[1] = bad_cast ("-i");
221 /* The following arguments give the command. */
227 /* Attempt to set all three: supplementary groups, group ID, user ID.
228 Diagnose any failures. If any have failed, exit before execvp. */
235 char const *err
= parse_user_spec (userspec
, &uid
, &gid
, &user
, &group
);
238 error (EXIT_CANCELED
, errno
, "%s", err
);
243 if (groups
&& set_additional_groups (groups
))
246 if (gid
!= (gid_t
) -1 && setgid (gid
))
248 error (0, errno
, _("failed to set group-ID"));
252 if (uid
!= (uid_t
) -1 && setuid (uid
))
254 error (0, errno
, _("failed to set user-ID"));
260 /* Yes, this call is identical to the one above.
261 However, when --userspec and --groups groups are used together,
262 we don't want to call this function until after parsing USER:GROUP,
263 and it must be called before setuid. */
264 if (groups
&& set_additional_groups (groups
))
269 exit (EXIT_CANCELED
);
271 /* Execute the given command. */
272 execvp (argv
[0], argv
);
275 int exit_status
= (errno
== ENOENT
? EXIT_ENOENT
: EXIT_CANNOT_INVOKE
);
276 error (0, errno
, _("failed to run command %s"), quote (argv
[0]));