summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiuseppe Scrivano <gscrivano@gnu.org>2009-05-01 23:50:11 +0200
committerJim Meyering <meyering@redhat.com>2009-05-26 15:10:22 +0200
commitc45c51fe97193898f3909dcf5e4c0e117ab239a2 (patch)
tree9a146adbde39cce97dbd4e1ee5aa3eb296fe02a6
parent39285f6008a26ff1d5facbffcbf12f57d60564d1 (diff)
downloadcoreutils-c45c51fe97193898f3909dcf5e4c0e117ab239a2.tar.xz
chroot: accept new options --userspec=U:G and --groups=G1,G2,G3
* NEWS: Note chroot's new options. * doc/coreutils.texi: Document them. * src/chroot.c (main): Add support for --userspec and --groups. * tests/Makefile.am (root-tests): Add chroot/credentials. * tests/chroot/credentials: New file. * tests/test-lib.sh: Define NON_ROOT_GROUP to a default value.
-rw-r--r--NEWS5
-rw-r--r--doc/coreutils.texi23
-rw-r--r--src/chroot.c125
-rw-r--r--tests/Makefile.am1
-rwxr-xr-xtests/chroot/credentials43
-rw-r--r--tests/test-lib.sh1
6 files changed, 192 insertions, 6 deletions
diff --git a/NEWS b/NEWS
index 31f1b1a24..3af06e420 100644
--- a/NEWS
+++ b/NEWS
@@ -7,6 +7,11 @@ GNU coreutils NEWS -*- outline -*-
truncate -s failed to skip all whitespace in the option argument in
some locales.
+** New features
+
+ chroot now accepts the options --userspec and --groups.
+
+
* Noteworthy changes in release 7.4 (2009-05-07) [stable]
** Bug fixes
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 1a3075ffe..97ea830e1 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -14144,7 +14144,7 @@ underlying function is non-privileged due to lack of support in MS-Windows.}
Synopses:
@example
-chroot @var{newroot} [@var{command} [@var{args}]@dots{}]
+chroot @var{option} @var{newroot} [@var{command} [@var{args}]@dots{}]
chroot @var{option}
@end example
@@ -14157,8 +14157,25 @@ variable or @command{/bin/sh} if not set, invoked with the @option{-i} option.
@var{command} must not be a special built-in utility
(@pxref{Special built-in utilities}).
-The only options are @option{--help} and @option{--version}. @xref{Common
-options}. Options must precede operands.
+The program accepts the following options. Also see @ref{Common options}.
+Options must precede operands.
+
+@table @samp
+
+@itemx --userspec=@var{user}[:@var{group}]
+@opindex --userspec
+By default, @var{command} is run with the same credentials
+as the invoking process.
+Use this option to run it as a different @var{user} and/or with a
+different primary @var{group}.
+
+@itemx --groups=@var{groups}
+@opindex --groups
+Use this option to specify the supplementary @var{groups} to be
+used by the new process.
+The items in the list (names or numeric IDs) must be separated by commas.
+
+@end table
Here are a few tips to help avoid common problems in using chroot.
To start with a simple example, make @var{command} refer to a statically
diff --git a/src/chroot.c b/src/chroot.c
index 6d3fddf77..788a1fc41 100644
--- a/src/chroot.c
+++ b/src/chroot.c
@@ -21,17 +21,88 @@
#include <getopt.h>
#include <stdio.h>
#include <sys/types.h>
+#include <grp.h>
#include "system.h"
#include "error.h"
#include "long-options.h"
#include "quote.h"
+#include "userspec.h"
+#include "xstrtol.h"
/* The official name of this program (e.g., no `g' prefix). */
#define PROGRAM_NAME "chroot"
#define AUTHORS proper_name ("Roland McGrath")
+#ifndef MAXGID
+# define MAXGID GID_T_MAX
+#endif
+
+enum
+{
+ GROUPS = UCHAR_MAX + 1,
+ USERSPEC
+};
+
+static struct option const long_opts[] =
+{
+ {"groups", required_argument, NULL, GROUPS},
+ {"userspec", required_argument, NULL, USERSPEC},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ {NULL, 0, NULL, 0}
+};
+
+/* Groups is a comma separated list of additional groups. */
+static int
+set_additional_groups (char const *groups)
+{
+ GETGROUPS_T *gids = NULL;
+ size_t n_gids_allocated = 0;
+ size_t n_gids = 0;
+ char *buffer = xstrdup (groups);
+ char const *tmp;
+ int ret;
+
+ for (tmp = strtok (buffer, ","); tmp; tmp = strtok (NULL, ","))
+ {
+ struct group *g;
+ unsigned long int value;
+
+ if (xstrtoul (tmp, NULL, 10, &value, "") == LONGINT_OK && value <= MAXGID)
+ {
+ g = getgrgid (value);
+ }
+ else
+ {
+ g = getgrnam (tmp);
+ if (g != NULL)
+ value = g->gr_gid;
+ }
+
+ if (g == NULL)
+ {
+ error (0, errno, _("cannot find group %s"), tmp);
+ free (buffer);
+ return 1;
+ }
+
+ if (n_gids == n_gids_allocated)
+ gids = x2nrealloc (gids, &n_gids_allocated, sizeof *gids);
+ gids[n_gids++] = value;
+ }
+
+ free (buffer);
+
+ ret = setgroups (n_gids, gids);
+
+ free (gids);
+
+ return ret;
+}
+
+
void
usage (int status)
{
@@ -41,13 +112,20 @@ usage (int status)
else
{
printf (_("\
-Usage: %s NEWROOT [COMMAND [ARG]...]\n\
+Usage: %s [OPTION] NEWROOT [COMMAND [ARG]...]\n\
or: %s OPTION\n\
"), program_name, program_name);
+
fputs (_("\
Run COMMAND with root directory set to NEWROOT.\n\
\n\
"), stdout);
+
+ fputs (_("\
+ --userspec=USER:GROUP specify user and group (ID or name) to use\n\
+ --groups=G_LIST specify supplementary groups as g1,g2,..,gN\n\
+"), stdout);
+
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
fputs (_("\
@@ -62,6 +140,10 @@ If no command is given, run ``${SHELL} -i'' (default: /bin/sh).\n\
int
main (int argc, char **argv)
{
+ int c;
+ char const *userspec = NULL;
+ char const *groups = NULL;
+
initialize_main (&argc, &argv);
set_program_name (argv[0]);
setlocale (LC_ALL, "");
@@ -73,8 +155,21 @@ main (int argc, char **argv)
parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version,
usage, AUTHORS, (char const *) NULL);
- if (getopt_long (argc, argv, "+", NULL, NULL) != -1)
- usage (EXIT_FAILURE);
+
+ while ((c = getopt_long (argc, argv, "+", long_opts, NULL)) != -1)
+ {
+ switch (c)
+ {
+ case USERSPEC:
+ userspec = optarg;
+ break;
+ case GROUPS:
+ groups = optarg;
+ break;
+ default:
+ usage (EXIT_FAILURE);
+ }
+ }
if (argc <= optind)
{
@@ -105,6 +200,30 @@ main (int argc, char **argv)
argv += optind + 1;
}
+ if (userspec)
+ {
+ uid_t uid;
+ gid_t gid;
+ char *user;
+ char *group;
+ char const *err = parse_user_spec (userspec, &uid, &gid, &user, &group);
+
+ if (err)
+ error (EXIT_FAILURE, errno, "%s", err);
+
+ free (user);
+ free (group);
+
+ if (groups && set_additional_groups (groups))
+ error (0, errno, _("failed to set additional groups"));
+
+ if (gid && setgid (gid))
+ error (0, errno, _("failed to set group-ID"));
+
+ if (uid && setuid (uid))
+ error (0, errno, _("failed to set user-ID"));
+ }
+
/* Execute the given command. */
execvp (argv[0], argv);
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7fe74c0c3..a0ed986ea 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -24,6 +24,7 @@ root_tests = \
cp/preserve-gid \
cp/special-bits \
cp/cp-mv-enotsup-xattr \
+ chroot/credentials \
dd/skip-seek-past-dev \
install/install-C-root \
ls/capability \
diff --git a/tests/chroot/credentials b/tests/chroot/credentials
new file mode 100755
index 000000000..23d66bd62
--- /dev/null
+++ b/tests/chroot/credentials
@@ -0,0 +1,43 @@
+#!/bin/sh
+# Verify that the credentials are changed correctly.
+
+# Copyright (C) 2009 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if test "$VERBOSE" = yes; then
+ set -x
+ chroot --version
+fi
+
+. $srcdir/test-lib.sh
+
+require_root_
+
+fail=0
+
+# Verify that root credentials are kept.
+test $(chroot / whoami) = root || fail=1
+test "$(groups)" = "$(chroot / groups)" || fail=1
+
+# Verify that credentials are changed correctly.
+test "$(chroot --userspec=$NON_ROOT_USERNAME:$NON_ROOT_GROUP / whoami)" != root \
+ || fail=1
+
+# Verify that there are no additional groups.
+test "$(chroot --userspec=$NON_ROOT_USERNAME:$NON_ROOT_GROUP --groups= / id -nG)"\
+ = $NON_ROOT_GROUP || fail=1
+
+Exit $fail
diff --git a/tests/test-lib.sh b/tests/test-lib.sh
index 17a35389b..a765bd626 100644
--- a/tests/test-lib.sh
+++ b/tests/test-lib.sh
@@ -204,6 +204,7 @@ require_root_()
{
uid_is_privileged_ || skip_test_ "must be run as root"
NON_ROOT_USERNAME=${NON_ROOT_USERNAME=nobody}
+ NON_ROOT_GROUP=${NON_ROOT_GROUP=nogroup}
}
skip_if_root_() { uid_is_privileged_ && skip_test_ "must be run as non-root"; }