| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| #include <config.h> |
| #include <ctype.h> |
| #include <getopt.h> |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <pwd.h> |
| #include <grp.h> |
|
|
| #include "system.h" |
| #include "ignore-value.h" |
| #include "mgetgroups.h" |
| #include "quote.h" |
| #include "root-dev-ino.h" |
| #include "userspec.h" |
| #include "xstrtol.h" |
|
|
| |
| #define PROGRAM_NAME "chroot" |
|
|
| #define AUTHORS proper_name ("Roland McGrath") |
|
|
| #ifndef MAXGID |
| # define MAXGID GID_T_MAX |
| #endif |
|
|
| static inline bool uid_unset (uid_t uid) { return uid == (uid_t) -1; } |
| static inline bool gid_unset (gid_t gid) { return gid == (gid_t) -1; } |
| #define uid_set(x) (!uid_unset (x)) |
| #define gid_set(x) (!gid_unset (x)) |
|
|
| enum |
| { |
| GROUPS = UCHAR_MAX + 1, |
| USERSPEC, |
| SKIP_CHDIR |
| }; |
|
|
| static struct option const long_opts[] = |
| { |
| {"groups", required_argument, nullptr, GROUPS}, |
| {"userspec", required_argument, nullptr, USERSPEC}, |
| {"skip-chdir", no_argument, nullptr, SKIP_CHDIR}, |
| {GETOPT_HELP_OPTION_DECL}, |
| {GETOPT_VERSION_OPTION_DECL}, |
| {nullptr, 0, nullptr, 0} |
| }; |
|
|
| #if ! HAVE_SETGROUPS |
| |
| static int |
| setgroups (size_t size, MAYBE_UNUSED gid_t const *list) |
| { |
| if (size == 0) |
| { |
| |
| |
| |
| return 0; |
| } |
| else |
| { |
| errno = ENOTSUP; |
| return -1; |
| } |
| } |
| #endif |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| static int |
| parse_additional_groups (char const *groups, GETGROUPS_T **pgids, |
| idx_t *pn_gids, bool show_errors) |
| { |
| GETGROUPS_T *gids = nullptr; |
| idx_t n_gids_allocated = 0; |
| idx_t n_gids = 0; |
| char *buffer = xstrdup (groups); |
| char const *tmp; |
| int ret = 0; |
|
|
| for (tmp = strtok (buffer, ","); tmp; tmp = strtok (nullptr, ",")) |
| { |
| struct group *g; |
| uintmax_t value; |
|
|
| if (xstrtoumax (tmp, nullptr, 10, &value, "") == LONGINT_OK |
| && value <= MAXGID) |
| { |
| while (isspace (to_uchar (*tmp))) |
| tmp++; |
| if (*tmp != '+') |
| { |
| |
| g = getgrnam (tmp); |
| if (g != nullptr) |
| value = g->gr_gid; |
| } |
| |
| g = (struct group *) (intptr_t) ! nullptr; |
| } |
| else |
| { |
| g = getgrnam (tmp); |
| if (g != nullptr) |
| value = g->gr_gid; |
| } |
|
|
| if (g == nullptr) |
| { |
| ret = -1; |
|
|
| if (show_errors) |
| { |
| error (0, errno, _("invalid group %s"), quote (tmp)); |
| continue; |
| } |
|
|
| break; |
| } |
|
|
| if (n_gids == n_gids_allocated) |
| gids = xpalloc (gids, &n_gids_allocated, 1, -1, sizeof *gids); |
| gids[n_gids++] = value; |
| } |
|
|
| if (ret == 0 && n_gids == 0) |
| { |
| if (show_errors) |
| error (0, 0, _("invalid group list %s"), quote (groups)); |
| ret = -1; |
| } |
|
|
| *pgids = gids; |
|
|
| if (ret == 0) |
| *pn_gids = n_gids; |
|
|
| free (buffer); |
| return ret; |
| } |
|
|
| |
| |
| |
|
|
| static bool |
| is_root (char const *dir) |
| { |
| char *resolved = canonicalize_file_name (dir); |
| bool is_res_root = resolved && streq ("/", resolved); |
| free (resolved); |
| return is_res_root; |
| } |
|
|
| void |
| usage (int status) |
| { |
| if (status != EXIT_SUCCESS) |
| emit_try_help (); |
| else |
| { |
| printf (_("\ |
| Usage: %s [OPTION]... NEWROOT [COMMAND [ARG]...]\n"), program_name); |
|
|
| fputs (_("\ |
| Run COMMAND with root directory set to NEWROOT.\n\ |
| \n\ |
| "), stdout); |
|
|
| fputs (_("\ |
| --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\ |
| "), stdout); |
| fputs (_("\ |
| --userspec=USER:GROUP specify user and group (ID or name) to use\n\ |
| "), stdout); |
| printf (_("\ |
| --skip-chdir do not change working directory to %s\n\ |
| "), quoteaf ("/")); |
|
|
| 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_exec_status (PROGRAM_NAME); |
| emit_ancillary_info (PROGRAM_NAME); |
| } |
| exit (status); |
| } |
|
|
| int |
| main (int argc, char **argv) |
| { |
| int c; |
|
|
| |
| char *userspec = nullptr; |
| char const *username = nullptr; |
| char const *groups = nullptr; |
| bool skip_chdir = false; |
|
|
| |
| uid_t uid = -1; |
| gid_t gid = -1; |
| GETGROUPS_T *out_gids = nullptr; |
| idx_t n_gids = 0; |
|
|
| 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, nullptr)) != -1) |
| { |
| switch (c) |
| { |
| case USERSPEC: |
| { |
| userspec = optarg; |
| |
| |
| |
| idx_t userlen = strlen (userspec); |
| if (userlen && userspec[userlen - 1] == ':') |
| userspec[userlen - 1] = '\0'; |
| break; |
| } |
|
|
| case GROUPS: |
| groups = optarg; |
| break; |
|
|
| case SKIP_CHDIR: |
| skip_chdir = true; |
| 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); |
| } |
|
|
| char const *newroot = argv[optind]; |
| bool is_oldroot = is_root (newroot); |
|
|
| if (! is_oldroot && skip_chdir) |
| { |
| error (0, 0, _("option --skip-chdir only permitted if NEWROOT is old %s"), |
| quoteaf ("/")); |
| usage (EXIT_CANCELED); |
| } |
|
|
| if (! is_oldroot) |
| { |
| |
| |
| |
| |
| |
| |
| if (userspec) |
| ignore_value (parse_user_spec (userspec, &uid, &gid, nullptr, nullptr)); |
|
|
| |
| |
| if (uid_set (uid) && (! groups || gid_unset (gid))) |
| { |
| const struct passwd *pwd; |
| if ((pwd = getpwuid (uid))) |
| { |
| if (gid_unset (gid)) |
| gid = pwd->pw_gid; |
| username = pwd->pw_name; |
| } |
| } |
|
|
| if (groups && *groups) |
| ignore_value (parse_additional_groups (groups, &out_gids, &n_gids, |
| false)); |
| #if HAVE_SETGROUPS |
| else if (! groups && gid_set (gid) && username) |
| { |
| int ngroups = xgetgroups (username, gid, &out_gids); |
| if (0 < ngroups) |
| n_gids = ngroups; |
| } |
| #endif |
| } |
|
|
| if (chroot (newroot) != 0) |
| error (EXIT_CANCELED, errno, _("cannot change root directory to %s"), |
| quoteaf (newroot)); |
|
|
| if (! skip_chdir && chdir ("/")) |
| error (EXIT_CANCELED, errno, _("cannot chdir to root directory")); |
|
|
| if (argc == optind + 1) |
| { |
| |
| char *shell = getenv ("SHELL"); |
| if (shell == nullptr) |
| shell = bad_cast ("/bin/sh"); |
| argv[0] = shell; |
| argv[1] = bad_cast ("-i"); |
| argv[2] = nullptr; |
| } |
| else |
| { |
| |
| argv += optind + 1; |
| } |
|
|
| |
| |
| if (userspec) |
| { |
| bool warn; |
| char const *err = parse_user_spec_warn (userspec, &uid, &gid, |
| nullptr, nullptr, &warn); |
| if (err) |
| error (warn ? 0 : EXIT_CANCELED, 0, "%s", (err)); |
| } |
|
|
| |
| |
| if (uid_set (uid) && (! groups || gid_unset (gid))) |
| { |
| const struct passwd *pwd; |
| if ((pwd = getpwuid (uid))) |
| { |
| if (gid_unset (gid)) |
| gid = pwd->pw_gid; |
| username = pwd->pw_name; |
| } |
| else if (gid_unset (gid)) |
| { |
| error (EXIT_CANCELED, errno, |
| _("no group specified for unknown uid: %ju"), |
| (uintmax_t) uid); |
| } |
| } |
|
|
| GETGROUPS_T *gids = out_gids; |
| GETGROUPS_T *in_gids = nullptr; |
| if (groups && *groups) |
| { |
| if (parse_additional_groups (groups, &in_gids, &n_gids, !n_gids) != 0) |
| { |
| if (! n_gids) |
| return EXIT_CANCELED; |
| |
| } |
| else |
| gids = in_gids; |
| } |
| #if HAVE_SETGROUPS |
| else if (! groups && gid_set (gid) && username) |
| { |
| int ngroups = xgetgroups (username, gid, &in_gids); |
| if (ngroups <= 0) |
| { |
| if (! n_gids) |
| error (EXIT_CANCELED, errno, |
| _("failed to get supplemental groups")); |
| |
| } |
| else |
| { |
| n_gids = ngroups; |
| gids = in_gids; |
| } |
| } |
| #endif |
|
|
| if ((uid_set (uid) || groups) && setgroups (n_gids, gids) != 0) |
| error (EXIT_CANCELED, errno, _("failed to set supplemental groups")); |
|
|
| free (in_gids); |
| free (out_gids); |
|
|
| if (gid_set (gid) && setgid (gid)) |
| error (EXIT_CANCELED, errno, _("failed to set group-ID")); |
|
|
| if (uid_set (uid) && setuid (uid)) |
| error (EXIT_CANCELED, errno, _("failed to set user-ID")); |
|
|
| |
| 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])); |
| return exit_status; |
| } |
|
|