|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#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; |
|
|
} |
|
|
|