tests: remove a non portable localtime test
[coreutils.git] / src / chroot.c
blobd804cc6292e2c7f8a2ba39acd325d699b65ffe1f
1 /* chroot -- run command or shell with special root directory
2 Copyright (C) 1995-2016 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. */
19 #include <config.h>
20 #include <getopt.h>
21 #include <stdio.h>
22 #include <sys/types.h>
23 #include <pwd.h>
24 #include <grp.h>
26 #include "system.h"
27 #include "error.h"
28 #include "ignore-value.h"
29 #include "mgetgroups.h"
30 #include "quote.h"
31 #include "root-dev-ino.h"
32 #include "userspec.h"
33 #include "xstrtol.h"
35 /* The official name of this program (e.g., no 'g' prefix). */
36 #define PROGRAM_NAME "chroot"
38 #define AUTHORS proper_name ("Roland McGrath")
40 #ifndef MAXGID
41 # define MAXGID GID_T_MAX
42 #endif
44 static inline bool uid_unset (uid_t uid) { return uid == (uid_t) -1; }
45 static inline bool gid_unset (gid_t gid) { return gid == (gid_t) -1; }
46 #define uid_set(x) (!uid_unset (x))
47 #define gid_set(x) (!gid_unset (x))
49 enum
51 GROUPS = UCHAR_MAX + 1,
52 USERSPEC,
53 SKIP_CHDIR
56 static struct option const long_opts[] =
58 {"groups", required_argument, NULL, GROUPS},
59 {"userspec", required_argument, NULL, USERSPEC},
60 {"skip-chdir", no_argument, NULL, SKIP_CHDIR},
61 {GETOPT_HELP_OPTION_DECL},
62 {GETOPT_VERSION_OPTION_DECL},
63 {NULL, 0, NULL, 0}
66 #if ! HAVE_SETGROUPS
67 /* At least Interix lacks supplemental group support. */
68 static int
69 setgroups (size_t size, gid_t const *list _GL_UNUSED)
71 if (size == 0)
73 /* Return success when clearing supplemental groups
74 as ! HAVE_SETGROUPS should only be the case on
75 platforms that don't support supplemental groups. */
76 return 0;
78 else
80 errno = ENOTSUP;
81 return -1;
84 #endif
86 /* Determine the group IDs for the specified supplementary GROUPS,
87 which is a comma separated list of supplementary groups (names or numbers).
88 Allocate an array for the parsed IDs and store it in PGIDS,
89 which may be allocated even on parse failure.
90 Update the number of parsed groups in PN_GIDS on success.
91 Upon any failure return nonzero, and issue diagnostic if SHOW_ERRORS is true.
92 Otherwise return zero. */
94 static int
95 parse_additional_groups (char const *groups, GETGROUPS_T **pgids,
96 size_t *pn_gids, bool show_errors)
98 GETGROUPS_T *gids = NULL;
99 size_t n_gids_allocated = 0;
100 size_t n_gids = 0;
101 char *buffer = xstrdup (groups);
102 char const *tmp;
103 int ret = 0;
105 for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))
107 struct group *g;
108 unsigned long int value;
110 if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)
112 while (isspace (to_uchar (*tmp)))
113 tmp++;
114 if (*tmp != '+')
116 /* Handle the case where the name is numeric. */
117 g = getgrnam (tmp);
118 if (g != NULL)
119 value = g->gr_gid;
121 /* Flag that we've got a group from the number. */
122 g = (struct group *) (intptr_t) ! NULL;
124 else
126 g = getgrnam (tmp);
127 if (g != NULL)
128 value = g->gr_gid;
131 if (g == NULL)
133 ret = -1;
135 if (show_errors)
137 error (0, errno, _("invalid group %s"), quote (tmp));
138 continue;
141 break;
144 if (n_gids == n_gids_allocated)
145 gids = X2NREALLOC (gids, &n_gids_allocated);
146 gids[n_gids++] = value;
149 if (ret == 0 && n_gids == 0)
151 if (show_errors)
152 error (0, 0, _("invalid group list %s"), quote (groups));
153 ret = -1;
156 *pgids = gids;
158 if (ret == 0)
159 *pn_gids = n_gids;
161 free (buffer);
162 return ret;
165 /* Return whether the passed path is equivalent to "/".
166 Note we don't compare against get_root_dev_ino() as "/"
167 could be bind mounted to a separate location. */
169 static bool
170 is_root (const char* dir)
172 char *resolved = canonicalize_file_name (dir);
173 bool is_res_root = resolved && STREQ ("/", resolved);
174 free (resolved);
175 return is_res_root;
178 void
179 usage (int status)
181 if (status != EXIT_SUCCESS)
182 emit_try_help ();
183 else
185 printf (_("\
186 Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
187 or: %s OPTION\n\
188 "), program_name, program_name);
190 fputs (_("\
191 Run COMMAND with root directory set to NEWROOT.\n\
193 "), stdout);
195 fputs (_("\
196 --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\
197 "), stdout);
198 fputs (_("\
199 --userspec=USER:GROUP specify user and group (ID or name) to use\n\
200 "), stdout);
201 printf (_("\
202 --skip-chdir do not change working directory to %s\n\
203 "), quoteaf ("/"));
205 fputs (HELP_OPTION_DESCRIPTION, stdout);
206 fputs (VERSION_OPTION_DESCRIPTION, stdout);
207 fputs (_("\
209 If no command is given, run '${SHELL} -i' (default: '/bin/sh -i').\n\
210 "), stdout);
211 emit_ancillary_info (PROGRAM_NAME);
213 exit (status);
217 main (int argc, char **argv)
219 int c;
221 /* Input user and groups spec. */
222 char *userspec = NULL;
223 char const *username = NULL;
224 char const *groups = NULL;
225 bool skip_chdir = false;
227 /* Parsed user and group IDs. */
228 uid_t uid = -1;
229 gid_t gid = -1;
230 GETGROUPS_T *out_gids = NULL;
231 size_t n_gids = 0;
233 initialize_main (&argc, &argv);
234 set_program_name (argv[0]);
235 setlocale (LC_ALL, "");
236 bindtextdomain (PACKAGE, LOCALEDIR);
237 textdomain (PACKAGE);
239 initialize_exit_failure (EXIT_CANCELED);
240 atexit (close_stdout);
242 while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
244 switch (c)
246 case USERSPEC:
248 userspec = optarg;
249 /* Treat 'user:' just like 'user'
250 as we lookup the primary group by default
251 (and support doing so for UIDs as well as names. */
252 size_t userlen = strlen (userspec);
253 if (userlen && userspec[userlen - 1] == ':')
254 userspec[userlen - 1] = '\0';
255 break;
258 case GROUPS:
259 groups = optarg;
260 break;
262 case SKIP_CHDIR:
263 skip_chdir = true;
264 break;
266 case_GETOPT_HELP_CHAR;
268 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
270 default:
271 usage (EXIT_CANCELED);
275 if (argc <= optind)
277 error (0, 0, _("missing operand"));
278 usage (EXIT_CANCELED);
281 char const *newroot = argv[optind];
282 bool is_oldroot = is_root (newroot);
284 if (! is_oldroot && skip_chdir)
286 error (0, 0, _("option --skip-chdir only permitted if NEWROOT is old %s"),
287 quoteaf ("/"));
288 usage (EXIT_CANCELED);
291 if (! is_oldroot)
293 /* We have to look up users and groups twice.
294 - First, outside the chroot to load potentially necessary passwd/group
295 parsing plugins (e.g. NSS);
296 - Second, inside chroot to redo parsing in case IDs are different.
297 Within chroot lookup is the main justification for having
298 the --user option supported by the chroot command itself. */
299 if (userspec)
300 ignore_value (parse_user_spec (userspec, &uid, &gid, NULL, NULL));
302 /* If no gid is supplied or looked up, do so now.
303 Also lookup the username for use with getgroups. */
304 if (uid_set (uid) && (! groups || gid_unset (gid)))
306 const struct passwd *pwd;
307 if ((pwd = getpwuid (uid)))
309 if (gid_unset (gid))
310 gid = pwd->pw_gid;
311 username = pwd->pw_name;
315 if (groups && *groups)
316 ignore_value (parse_additional_groups (groups, &out_gids, &n_gids,
317 false));
318 #if HAVE_SETGROUPS
319 else if (! groups && gid_set (gid) && username)
321 int ngroups = xgetgroups (username, gid, &out_gids);
322 if (0 < ngroups)
323 n_gids = ngroups;
325 #endif
328 if (chroot (newroot) != 0)
329 error (EXIT_CANCELED, errno, _("cannot change root directory to %s"),
330 quoteaf (newroot));
332 if (! skip_chdir && chdir ("/"))
333 error (EXIT_CANCELED, errno, _("cannot chdir to root directory"));
335 if (argc == optind + 1)
337 /* No command. Run an interactive shell. */
338 char *shell = getenv ("SHELL");
339 if (shell == NULL)
340 shell = bad_cast ("/bin/sh");
341 argv[0] = shell;
342 argv[1] = bad_cast ("-i");
343 argv[2] = NULL;
345 else
347 /* The following arguments give the command. */
348 argv += optind + 1;
351 /* Attempt to set all three: supplementary groups, group ID, user ID.
352 Diagnose any failures. If any have failed, exit before execvp. */
353 if (userspec)
355 char const *err = parse_user_spec (userspec, &uid, &gid, NULL, NULL);
357 if (err && uid_unset (uid) && gid_unset (gid))
358 error (EXIT_CANCELED, errno, "%s", (err));
361 /* If no gid is supplied or looked up, do so now.
362 Also lookup the username for use with getgroups. */
363 if (uid_set (uid) && (! groups || gid_unset (gid)))
365 const struct passwd *pwd;
366 if ((pwd = getpwuid (uid)))
368 if (gid_unset (gid))
369 gid = pwd->pw_gid;
370 username = pwd->pw_name;
372 else if (gid_unset (gid))
374 error (EXIT_CANCELED, errno,
375 _("no group specified for unknown uid: %d"), (int) uid);
379 GETGROUPS_T *gids = out_gids;
380 GETGROUPS_T *in_gids = NULL;
381 if (groups && *groups)
383 if (parse_additional_groups (groups, &in_gids, &n_gids, !n_gids) != 0)
385 if (! n_gids)
386 return EXIT_CANCELED;
387 /* else look-up outside the chroot worked, then go with those. */
389 else
390 gids = in_gids;
392 #if HAVE_SETGROUPS
393 else if (! groups && gid_set (gid) && username)
395 int ngroups = xgetgroups (username, gid, &in_gids);
396 if (ngroups <= 0)
398 if (! n_gids)
399 error (EXIT_CANCELED, errno,
400 _("failed to get supplemental groups"));
401 /* else look-up outside the chroot worked, then go with those. */
403 else
405 n_gids = ngroups;
406 gids = in_gids;
409 #endif
411 if ((uid_set (uid) || groups) && setgroups (n_gids, gids) != 0)
412 error (EXIT_CANCELED, errno, _("failed to set supplemental groups"));
414 free (in_gids);
415 free (out_gids);
417 if (gid_set (gid) && setgid (gid))
418 error (EXIT_CANCELED, errno, _("failed to set group-ID"));
420 if (uid_set (uid) && setuid (uid))
421 error (EXIT_CANCELED, errno, _("failed to set user-ID"));
423 /* Execute the given command. */
424 execvp (argv[0], argv);
426 int exit_status = errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE;
427 error (0, errno, _("failed to run command %s"), quote (argv[0]));
428 return exit_status;