]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
chroot: accept new options --userspec=U:G and --groups=G1,G2,G3
authorGiuseppe Scrivano <gscrivano@gnu.org>
Fri, 1 May 2009 21:50:11 +0000 (23:50 +0200)
committerJim Meyering <meyering@redhat.com>
Tue, 26 May 2009 13:10:22 +0000 (15:10 +0200)
* 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.

NEWS
doc/coreutils.texi
src/chroot.c
tests/Makefile.am
tests/chroot/credentials [new file with mode: 0755]
tests/test-lib.sh

diff --git a/NEWS b/NEWS
index 31f1b1a244d27d4ea0f44c7bd7713b24c84fdef4..3af06e42080f4c0f8d9b25a1269954f0b8fd66c4 100644 (file)
--- 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
index 1a3075ffec8aba8828edf7aa606d933f79712312..97ea830e11c2b7c6bde531557d7b53fe63299407 100644 (file)
@@ -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
index 6d3fddf77232576a22b8fa66fda738207a002203..788a1fc416c0255aa3a72811ab79625a6e2aaaa8 100644 (file)
 #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);
 
index 7fe74c0c39dc6a486cd636c74c5f7886a49fa66a..a0ed986ea7e4943d426ae6b42b543e5a993e5c09 100644 (file)
@@ -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 (executable)
index 0000000..23d66bd
--- /dev/null
@@ -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
index 17a35389b3c179ff182f2252daed2e85fde61d4f..a765bd6269bdd8060dc4e602ab49df38c31ca512 100644 (file)
@@ -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"; }