Browse Source

Merge branch 'feature8195_small_squashed'

Nick Mathewson 8 years ago
parent
commit
aba39ea390
11 changed files with 414 additions and 14 deletions
  1. 2 0
      .gitignore
  2. 6 0
      changes/feature8195
  3. 15 1
      configure.ac
  4. 8 0
      doc/tor.1.txt
  5. 107 1
      src/common/compat.c
  6. 12 1
      src/common/compat.h
  7. 41 8
      src/or/config.c
  8. 3 0
      src/or/or.h
  9. 14 3
      src/test/include.am
  10. 181 0
      src/test/test_switch_id.c
  11. 25 0
      src/test/test_switch_id.sh

+ 2 - 0
.gitignore

@@ -172,6 +172,7 @@ cscope.*
 /src/test/test-child
 /src/test/test-memwipe
 /src/test/test-ntor-cl
+/src/test/test-switch-id
 /src/test/test_workqueue
 /src/test/test.exe
 /src/test/test-slow.exe
@@ -179,6 +180,7 @@ cscope.*
 /src/test/test-child.exe
 /src/test/test-ntor-cl.exe
 /src/test/test-memwipe.exe
+/src/test/test-switch-id.exe
 /src/test/test_workqueue.exe
 /src/test/test_zero_length_keys.sh
 /src/test/test_ntor.sh

+ 6 - 0
changes/feature8195

@@ -0,0 +1,6 @@
+  o Major features:
+    - When Tor is started as root on Linux and told to switch user ID, it
+      can now retain the capabilitity to bind to low ports.  By default,
+      Tor will do this only when it's switching user ID and some low
+      ports have been configured.  You can change this behavior with
+      the new option KeepBindCapabilities.  Closes ticket 8195.

+ 15 - 1
configure.ac

@@ -712,6 +712,19 @@ else
 fi
 AC_SUBST(TOR_ZLIB_LIBS)
 
+dnl ----------------------------------------------------------------------
+dnl Check if libcap is available for capabilities.
+
+tor_cap_pkg_debian="libcap2"
+tor_cap_pkg_redhat="libcap"
+tor_cap_devpkg_debian="libcap-dev"
+tor_cap_devpkg_redhat="libcap-devel"
+
+AC_CHECK_LIB([cap], [cap_init], [],
+  AC_MSG_NOTICE([Libcap was not found. Capabilities will not be usable.])
+)
+AC_CHECK_FUNCS(cap_set_proc)
+
 dnl ---------------------------------------------------------------------
 dnl Now that we know about our major libraries, we can check for compiler
 dnl and linker hardening options.  We need to do this with the libraries known,
@@ -719,7 +732,7 @@ dnl since sometimes the linker will like an option but not be willing to
 dnl use it with a build of a library.
 
 all_ldflags_for_check="$TOR_LDFLAGS_zlib $TOR_LDFLAGS_openssl $TOR_LDFLAGS_libevent"
-all_libs_for_check="$TOR_ZLIB_LIBS $TOR_LIB_MATH $TOR_LIBEVENT_LIBS $TOR_OPENSSL_LIBS $TOR_SYSTEMD_LIBS $TOR_LIB_WS32 $TOR_LIB_GDI"
+all_libs_for_check="$TOR_ZLIB_LIBS $TOR_LIB_MATH $TOR_LIBEVENT_LIBS $TOR_OPENSSL_LIBS $TOR_SYSTEMD_LIBS $TOR_LIB_WS32 $TOR_LIB_GDI $TOR_CAP_LIBS"
 
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [
 #if !defined(__clang__)
@@ -912,6 +925,7 @@ AC_CHECK_HEADERS(
         fcntl.h \
         signal.h \
         string.h \
+	sys/capability.h \
         sys/fcntl.h \
         sys/stat.h \
         sys/time.h \

+ 8 - 0
doc/tor.1.txt

@@ -619,6 +619,14 @@ GENERAL OPTIONS
 [[User]] **User** __UID__::
     On startup, setuid to this user and setgid to their primary group.
 
+[[KeepBindCapabilities]] **KeepBindCapabilities** **0**|**1**|**auto**::
+    On Linux, when we are started as root and we switch our identity using
+    the **User** option, the **KeepBindCapabilities** option tells us whether to
+    try to retain our ability to bind to low ports.  If this value is 1, we
+    try to keep the capability; if it is 0 we do not; and if it is **auto**,
+    we keep the capability only if we are configured to listen on a low port.
+    (Default: auto.)
+
 [[HardwareAccel]] **HardwareAccel** **0**|**1**::
     If non-zero, try to use built-in (static) crypto hardware acceleration when
     available. (Default: 0)

+ 107 - 1
src/common/compat.c

@@ -71,6 +71,9 @@
 #ifdef HAVE_SYS_STATVFS_H
 #include <sys/statvfs.h>
 #endif
+#ifdef HAVE_SYS_CAPABILITY_H
+#include <sys/capability.h>
+#endif
 
 #ifdef _WIN32
 #include <conio.h>
@@ -1966,17 +1969,99 @@ tor_getpwuid(uid_t uid)
 }
 #endif
 
+/** Return true iff we were compiled with capability support, and capabilities
+ * seem to work. **/
+int
+have_capability_support(void)
+{
+#ifdef HAVE_LINUX_CAPABILITIES
+  cap_t caps = cap_get_proc();
+  if (caps == NULL)
+    return 0;
+  cap_free(caps);
+  return 1;
+#else
+  return 0;
+#endif
+}
+
+#ifdef HAVE_LINUX_CAPABILITIES
+/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as
+ * appropriate.
+ *
+ * If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and
+ * CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across
+ * setuid().
+ *
+ * If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable
+ * PR_KEEPCAPS.
+ *
+ * Return 0 on success, and -1 on failure.
+ */
+static int
+drop_capabilities(int pre_setuid)
+{
+  /* We keep these three capabilities, and these only, as we setuid.
+   * After we setuid, we drop all but the first. */
+  const cap_value_t caplist[] = {
+    CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
+  };
+  const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
+  const int n_effective = pre_setuid ? 3 : 1;
+  const int n_permitted = pre_setuid ? 3 : 1;
+  const int n_inheritable = 1;
+  const int keepcaps = pre_setuid ? 1 : 0;
+
+  /* Sets whether we keep capabilities across a setuid. */
+  if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
+    log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
+             where, strerror(errno));
+    return -1;
+  }
+
+  cap_t caps = cap_get_proc();
+  if (!caps) {
+    log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
+             where, strerror(errno));
+    return -1;
+  }
+  cap_clear(caps);
+
+  cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
+  cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
+  cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
+
+  int r = cap_set_proc(caps);
+  cap_free(caps);
+  if (r < 0) {
+    log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
+             where, strerror(errno));
+    return -1;
+  }
+
+  return 0;
+}
+#endif
+
 /** Call setuid and setgid to run as <b>user</b> and switch to their
  * primary group.  Return 0 on success.  On failure, log and return -1.
+ *
+ * If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability
+ * system to retain the abilitity to bind low ports.
+ *
+ * If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have
+ * don't have capability support.
  */
 int
-switch_id(const char *user)
+switch_id(const char *user, const unsigned flags)
 {
 #ifndef _WIN32
   const struct passwd *pw = NULL;
   uid_t old_uid;
   gid_t old_gid;
   static int have_already_switched_id = 0;
+  const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
+  const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
 
   tor_assert(user);
 
@@ -2000,6 +2085,20 @@ switch_id(const char *user)
     return -1;
   }
 
+#ifdef HAVE_LINUX_CAPABILITIES
+  (void) warn_if_no_caps;
+  if (keep_bindlow) {
+    if (drop_capabilities(1))
+      return -1;
+  }
+#else
+  (void) keep_bindlow;
+  if (warn_if_no_caps) {
+    log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
+             "on this system.");
+  }
+#endif
+
   /* Properly switch egid,gid,euid,uid here or bail out */
   if (setgroups(1, &pw->pw_gid)) {
     log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
@@ -2053,6 +2152,12 @@ switch_id(const char *user)
 
   /* We've properly switched egid, gid, euid, uid, and supplementary groups if
    * we're here. */
+#ifdef HAVE_LINUX_CAPABILITIES
+  if (keep_bindlow) {
+    if (drop_capabilities(0))
+      return -1;
+  }
+#endif
 
 #if !defined(CYGWIN) && !defined(__CYGWIN__)
   /* If we tried to drop privilege to a group/user other than root, attempt to
@@ -2100,6 +2205,7 @@ switch_id(const char *user)
 
 #else
   (void)user;
+  (void)flags;
 
   log_warn(LD_CONFIG,
            "User specified but switching users is unsupported on your OS.");

+ 12 - 1
src/common/compat.h

@@ -625,7 +625,18 @@ typedef unsigned long rlim_t;
 int get_max_sockets(void);
 int set_max_file_descriptors(rlim_t limit, int *max);
 int tor_disable_debugger_attach(void);
-int switch_id(const char *user);
+
+#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
+#define HAVE_LINUX_CAPABILITIES
+#endif
+
+int have_capability_support(void);
+
+/** Flag for switch_id; see switch_id() for documentation */
+#define SWITCH_ID_KEEP_BINDLOW    (1<<0)
+/** Flag for switch_id; see switch_id() for documentation */
+#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1)
+int switch_id(const char *user, unsigned flags);
 #ifdef HAVE_PWD_H
 char *get_user_homedir(const char *username);
 #endif

+ 41 - 8
src/or/config.c

@@ -310,6 +310,7 @@ static config_var_t option_vars_[] = {
   V(Socks5ProxyUsername,         STRING,   NULL),
   V(Socks5ProxyPassword,         STRING,   NULL),
   V(KeepalivePeriod,             INTERVAL, "5 minutes"),
+  V(KeepBindCapabilities,            AUTOBOOL, "auto"),
   VAR("Log",                     LINELIST, Logs,             NULL),
   V(LogMessageDomains,           BOOL,     "0"),
   V(LogTimeGranularity,          MSEC_INTERVAL, "1 second"),
@@ -606,7 +607,8 @@ static int parse_ports(or_options_t *options, int validate_only,
                               char **msg_out, int *n_ports_out,
                               int *world_writable_control_socket);
 static int check_server_ports(const smartlist_t *ports,
-                              const or_options_t *options);
+                              const or_options_t *options,
+                              int *num_low_ports_out);
 
 static int validate_data_directory(or_options_t *options);
 static int write_configuration_file(const char *fname,
@@ -1085,6 +1087,9 @@ consider_adding_dir_servers(const or_options_t *options,
   return 0;
 }
 
+/* Helps determine flags to pass to switch_id. */
+static int have_low_ports = -1;
+
 /** Fetch the active option list, and take actions based on it. All of the
  * things we do should survive being done repeatedly.  If present,
  * <b>old_options</b> contains the previous value of the options.
@@ -1219,7 +1224,16 @@ options_act_reversible(const or_options_t *old_options, char **msg)
 
   /* Setuid/setgid as appropriate */
   if (options->User) {
-    if (switch_id(options->User) != 0) {
+    tor_assert(have_low_ports != -1);
+    unsigned switch_id_flags = 0;
+    if (options->KeepBindCapabilities == 1) {
+      switch_id_flags |= SWITCH_ID_KEEP_BINDLOW;
+      switch_id_flags |= SWITCH_ID_WARN_IF_NO_CAPS;
+    }
+    if (options->KeepBindCapabilities == -1 && have_low_ports) {
+      switch_id_flags |= SWITCH_ID_KEEP_BINDLOW;
+    }
+    if (switch_id(options->User, switch_id_flags) != 0) {
       /* No need to roll back, since you can't change the value. */
       *msg = tor_strdup("Problem with User value. See logs for details.");
       goto done;
@@ -4094,6 +4108,12 @@ options_transition_allowed(const or_options_t *old,
     return -1;
   }
 
+  if (old->KeepBindCapabilities != new_val->KeepBindCapabilities) {
+    *msg = tor_strdup("While Tor is running, changing KeepBindCapabilities is "
+                      "not allowed.");
+    return -1;
+  }
+
   if (!opt_streq(old->SyslogIdentityTag, new_val->SyslogIdentityTag)) {
     *msg = tor_strdup("While Tor is running, changing "
                       "SyslogIdentityTag is not allowed.");
@@ -6632,10 +6652,13 @@ parse_ports(or_options_t *options, int validate_only,
     }
   }
 
-  if (check_server_ports(ports, options) < 0) {
+  int n_low_ports = 0;
+  if (check_server_ports(ports, options, &n_low_ports) < 0) {
     *msg = tor_strdup("Misconfigured server ports");
     goto err;
   }
+  if (have_low_ports < 0)
+    have_low_ports = (n_low_ports > 0);
 
   *n_ports_out = smartlist_len(ports);
 
@@ -6689,10 +6712,12 @@ parse_ports(or_options_t *options, int validate_only,
 }
 
 /** Given a list of <b>port_cfg_t</b> in <b>ports</b>, check them for internal
- * consistency and warn as appropriate. */
+ * consistency and warn as appropriate.  Set *<b>n_low_ports_out</b> to the
+ * number of sub-1024 ports we will be binding. */
 static int
 check_server_ports(const smartlist_t *ports,
-                   const or_options_t *options)
+                   const or_options_t *options,
+                   int *n_low_ports_out)
 {
   int n_orport_advertised = 0;
   int n_orport_advertised_ipv4 = 0;
@@ -6755,16 +6780,24 @@ check_server_ports(const smartlist_t *ports,
     r = -1;
   }
 
-  if (n_low_port && options->AccountingMax) {
+  if (n_low_port && options->AccountingMax &&
+      (!have_capability_support() || options->KeepBindCapabilities == 0)) {
+    const char *extra = "";
+    if (options->KeepBindCapabilities == 0 && have_capability_support())
+      extra = ", and you have disabled KeepBindCapabilities.";
     log_warn(LD_CONFIG,
           "You have set AccountingMax to use hibernation. You have also "
-          "chosen a low DirPort or OrPort. This combination can make Tor stop "
+          "chosen a low DirPort or OrPort%s."
+          "This combination can make Tor stop "
           "working when it tries to re-attach the port after a period of "
           "hibernation. Please choose a different port or turn off "
           "hibernation unless you know this combination will work on your "
-          "platform.");
+          "platform.", extra);
   }
 
+  if (n_low_ports_out)
+    *n_low_ports_out = n_low_port;
+
   return r;
 }
 

+ 3 - 0
src/or/or.h

@@ -4432,6 +4432,9 @@ typedef struct {
   int keygen_passphrase_fd;
   int change_key_passphrase;
   char *master_key_fname;
+
+  /** Autobool: Do we try to retain capabilities if we can? */
+  int KeepBindCapabilities;
 } or_options_t;
 
 /** Persistent state for an onion router, as saved to disk. */

+ 14 - 3
src/test/include.am

@@ -8,14 +8,16 @@ TESTS_ENVIRONMENT = \
 	export builddir="$(builddir)"; \
 	export TESTING_TOR_BINARY="$(TESTING_TOR_BINARY)";
 
-TESTSCRIPTS = src/test/test_zero_length_keys.sh
+TESTSCRIPTS = src/test/test_zero_length_keys.sh \
+	src/test/test_switch_id.sh
 
 if USEPYTHON
 TESTSCRIPTS += src/test/test_ntor.sh src/test/test_bt.sh
 endif
 
 TESTS += src/test/test src/test/test-slow src/test/test-memwipe \
-	src/test/test_workqueue src/test/test_keygen.sh $(TESTSCRIPTS)
+	src/test/test_workqueue src/test/test_keygen.sh \
+	$(TESTSCRIPTS)
 
 # These flavors are run using automake's test-driver and test-network.sh
 TEST_CHUTNEY_FLAVORS = basic-min bridges-min hs-min bridges+hs
@@ -37,7 +39,8 @@ noinst_PROGRAMS+= \
 	src/test/test-slow \
 	src/test/test-memwipe \
 	src/test/test-child \
-	src/test/test_workqueue
+	src/test/test_workqueue \
+	src/test/test-switch-id
 endif
 
 src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \
@@ -136,6 +139,14 @@ src_test_test_workqueue_SOURCES = \
 src_test_test_workqueue_CPPFLAGS= $(src_test_AM_CPPFLAGS)
 src_test_test_workqueue_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
 
+src_test_test_switch_id_SOURCES = \
+	src/test/test_switch_id.c
+src_test_test_switch_id_CPPFLAGS= $(src_test_AM_CPPFLAGS)
+src_test_test_switch_id_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
+src_test_test_switch_id_LDADD = \
+	src/common/libor-testing.a \
+	@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@
+
 src_test_test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \
         @TOR_LDFLAGS_libevent@
 src_test_test_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \

+ 181 - 0
src/test/test_switch_id.c

@@ -0,0 +1,181 @@
+/* Copyright (c) 2015, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "or.h"
+
+#ifdef HAVE_SYS_CAPABILITY_H
+#include <sys/capability.h>
+#endif
+
+#define TEST_BUILT_WITH_CAPS         0
+#define TEST_HAVE_CAPS               1
+#define TEST_ROOT_CAN_BIND_LOW       2
+#define TEST_SETUID                  3
+#define TEST_SETUID_KEEPCAPS         4
+#define TEST_SETUID_STRICT           5
+
+static const char *username;
+
+static const struct {
+  const char *name;
+  int test_id;
+} which_test[] = {
+  { "built-with-caps",    TEST_BUILT_WITH_CAPS },
+  { "have-caps",          TEST_HAVE_CAPS },
+  { "root-bind-low",      TEST_ROOT_CAN_BIND_LOW },
+  { "setuid",             TEST_SETUID },
+  { "setuid-keepcaps",    TEST_SETUID_KEEPCAPS },
+  { "setuid-strict",      TEST_SETUID_STRICT },
+  { NULL, 0 }
+};
+
+/* 0 on no, 1 on yes, -1 on failure. */
+static int
+check_can_bind_low_ports(void)
+{
+  int port;
+  struct sockaddr_in sin;
+  memset(&sin, 0, sizeof(sin));
+  sin.sin_family = AF_INET;
+
+  for (port = 600; port < 1024; ++port) {
+    sin.sin_port = htons(port);
+    tor_socket_t fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+    if (! SOCKET_OK(fd)) {
+      perror("socket");
+      return -1;
+    }
+
+    int one = 1;
+    if (setsockopt(fd, SOL_SOCKET,SO_REUSEADDR, &one, sizeof(one))) {
+      perror("setsockopt");
+      tor_close_socket_simple(fd);
+      return -1;
+    }
+
+    int res = bind(fd, (struct sockaddr *)&sin, sizeof(sin));
+    tor_close_socket_simple(fd);
+
+    if (res == 0) {
+      /* bind was successful */
+      return 1;
+    } else if (errno == EACCES || errno == EPERM) {
+      /* Got a permission-denied error. */
+      return 0;
+    } else if (errno == EADDRINUSE) {
+      /* Huh; somebody is using that port. */
+    } else {
+      perror("bind");
+    }
+  }
+
+  return -1;
+}
+
+int
+main(int argc, char **argv)
+{
+  const char *testname;
+  if (argc != 3) {
+    fprintf(stderr, "I want 2 arguments: a username and a command.\n");
+    return 1;
+  }
+  if (getuid() != 0) {
+    fprintf(stderr, "This test only works when it's run as root.\n");
+    return 1;
+  }
+  username = argv[1];
+  testname = argv[2];
+  int test_id = -1;
+  int i;
+  for (i = 0; which_test[i].name; ++i) {
+    if (!strcmp(which_test[i].name, testname)) {
+      test_id = which_test[i].test_id;
+      break;
+    }
+  }
+  if (test_id == -1) {
+    fprintf(stderr, "Unrecognized test '%s'\n", testname);
+    return 1;
+  }
+
+#ifdef HAVE_LINUX_CAPABILITIES
+  const int have_cap_support = 1;
+#else
+  const int have_cap_support = 0;
+#endif
+
+  int okay;
+
+  init_logging(1);
+  log_severity_list_t sev;
+  memset(&sev, 0, sizeof(sev));
+  set_log_severity_config(LOG_WARN, LOG_ERR, &sev);
+  add_stream_log(&sev, "", fileno(stderr));
+
+  switch (test_id)
+    {
+    case TEST_BUILT_WITH_CAPS:
+      /* Succeed if we were built with capability support. */
+      okay = have_cap_support;
+      break;
+    case TEST_HAVE_CAPS:
+      /* Succeed if "capabilities work" == "we were built with capability
+       * support." */
+      okay = have_cap_support == have_capability_support();
+      break;
+    case TEST_ROOT_CAN_BIND_LOW:
+      /* Succeed if root can bind low ports. */
+      okay = check_can_bind_low_ports() == 1;
+      break;
+    case TEST_SETUID:
+      /* Succeed if we can do a setuid with no capability retention, and doing
+       * so makes us lose the ability to bind low ports */
+    case TEST_SETUID_KEEPCAPS:
+      /* Succeed if we can do a setuid with capability retention, and doing so
+       * does not make us lose the ability to bind low ports */
+    {
+      int keepcaps = (test_id == TEST_SETUID_KEEPCAPS);
+      okay = switch_id(username, keepcaps ? SWITCH_ID_KEEP_BINDLOW : 0) == 0;
+      if (okay) {
+        okay = check_can_bind_low_ports() == keepcaps;
+      }
+      break;
+    }
+    case TEST_SETUID_STRICT:
+      /* Succeed if, after a setuid, we cannot setuid back, and we cannot
+       * re-grab any capabilities. */
+      okay = switch_id(username, SWITCH_ID_KEEP_BINDLOW) == 0;
+      if (okay) {
+        /* We'd better not be able to setuid back! */
+        if (setuid(0) == 0 || errno != EPERM) {
+          okay = 0;
+        }
+      }
+#ifdef HAVE_LINUX_CAPABILITIES
+      if (okay) {
+        cap_t caps = cap_get_proc();
+        const cap_value_t caplist[] = {
+          CAP_SETUID,
+        };
+        cap_set_flag(caps, CAP_PERMITTED, 1, caplist, CAP_SET);
+        if (cap_set_proc(caps) == 0 || errno != EPERM) {
+          okay = 0;
+        }
+        cap_free(caps);
+      }
+#endif
+      break;
+    default:
+      fprintf(stderr, "Unsupported test '%s'\n", testname);
+      okay = 0;
+      break;
+    }
+
+  if (!okay) {
+    fprintf(stderr, "Test %s failed!\n", testname);
+  }
+
+  return (okay ? 0 : 1);
+}
+

+ 25 - 0
src/test/test_switch_id.sh

@@ -0,0 +1,25 @@
+#!/bin/sh
+
+if test "`id -u`" != '0'; then
+    echo "This test only works when run as root. Skipping." >&2
+    exit 77
+fi
+
+if test "`id -u nobody`" = ""; then
+    echo "This test requires that your system have a 'nobody' user. Sorry." >&2
+    exit 1
+fi
+
+"${builddir:-.}/src/test/test-switch-id" nobody setuid          || exit 1
+"${builddir:-.}/src/test/test-switch-id" nobody root-bind-low   || exit 1
+"${builddir:-.}/src/test/test-switch-id" nobody setuid-strict   || exit 1
+"${builddir:-.}/src/test/test-switch-id" nobody built-with-caps || exit 0
+# ... Go beyond this point only if we were built with capability support.
+
+"${builddir:-.}/src/test/test-switch-id" nobody have-caps       || exit 1
+"${builddir:-.}/src/test/test-switch-id" nobody setuid-keepcaps || exit 1
+
+
+echo "All okay"
+
+exit 0