]> git.sur5r.net Git - bacula/bacula/commitdiff
Implement support of keeping readall capabilities after UID/GID switch
authorDmitry V. Levin <ldv@altlinux.org>
Fri, 18 Sep 2009 16:32:38 +0000 (16:32 +0000)
committerKern Sibbald <kern@sibbald.com>
Sat, 19 Sep 2009 17:15:37 +0000 (19:15 +0200)
Extend drop() function interface to accept 3rd parameter specifying
whether process should keep capabilities required to read and search
files and directories regardless of their access permissions.

Introduce new bacula-fd option (-k) specifying that readall capabilities
should be kept after UID/GID switch.

The change moves drop() definition from bsys.c to new file priv.c,
which is necessary to avoid linking every bacula executable with -lcap.
If drop() would remain in bsys.c, then every executable which directly
or indirectly uses other functions defined in bsys.c would have to be
linked with -lcap, unless libbac is built as a shared library.

While the change itself is portable, the implementation is Linux
specific, it uses libcap to keep CAP_DAC_READ_SEARCH capability.
If libcap is not available, or OS does not have sys/prctl.h,
sys/capability.h, prctl(2), setreuid(2) and PR_SET_KEEPCAPS, then
this change is almost noop.

bacula/autoconf/configure.in
bacula/src/dird/Makefile.in
bacula/src/dird/dird.c
bacula/src/filed/Makefile.in
bacula/src/filed/filed.c
bacula/src/lib/Makefile.in
bacula/src/lib/bsys.c
bacula/src/lib/priv.c [new file with mode: 0644]
bacula/src/lib/protos.h
bacula/src/stored/Makefile.in
bacula/src/stored/stored.c

index 0c449aa716a10767e24e491d555fb8873da491c0..0b8149d7d73e59855d1c4bb44f03b696fb0767bd 100644 (file)
@@ -2488,6 +2488,18 @@ AC_CHECK_LIB(pthread, pthread_create, PTHREAD_LIB="-lpthread",
    ]
 )
 
+dnl
+dnl Check for headers, functions and libraries required to support
+dnl keeping readall capabilities
+dnl
+AC_CHECK_HEADERS(sys/prctl.h sys/capability.h)
+AC_CHECK_FUNCS(prctl setreuid)
+AC_CHECK_LIB([cap], [cap_set_proc], [CAP_LIBS="-lcap"], [CAP_LIBS=])
+if test x$CAP_LIBS = x-lcap; then
+   AC_DEFINE(HAVE_LIBCAP, 1, [Define if you have libcap])
+fi
+AC_SUBST(CAP_LIBS)
+
 AC_SUBST(FDLIBS)
 AC_DEFINE(FDLIBS)
 
index 076dea1ac86ab8aa49c132b203a07a8947be6866..0abab0e2a2bedcb4d91d279a080e62e6f9540e36 100644 (file)
@@ -19,6 +19,7 @@ thisdir = src/dird
 DEBUG=@DEBUG@
 
 GETTEXT_LIBS = @LIBINTL@
+CAP_LIBS = @CAP_LIBS@
 
 PYTHON_LIBS = @PYTHON_LIBS@
 PYTHON_INC = @PYTHON_INCDIR@
@@ -64,12 +65,12 @@ bacula-dir: Makefile $(SVROBJS) ../lib/libbac$(DEFAULT_ARCHIVE_TYPE) ../lib/libb
        @echo "Linking $@ ..."
        $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -L../lib -L../cats -L../findlib -o $@ $(SVROBJS) \
              -lbacfind -lbacsql -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(DLIB) $(DB_LIBS) $(LIBS) \
-             $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS)
+             $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS)
 
 static-bacula-dir:  Makefile $(SVROBJS) ../lib/libbac$(DEFAULT_ARCHIVE_TYPE) ../lib/libbaccfg$(DEFAULT_ARCHIVE_TYPE) ../lib/libbacpy$(DEFAULT_ARCHIVE_TYPE) ../cats/libbacsql$(DEFAULT_ARCHIVE_TYPE) ../findlib/libbacfind$(DEFAULT_ARCHIVE_TYPE)
        $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -static -L../lib -L../cats -L../findlib -o $@ $(SVROBJS) \
              -lbacfind -lbacsql -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(DLIB) $(DB_LIBS) $(LIBS) \
-             $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS)
+             $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS)
        strip $@
 
 Makefile: $(srcdir)/Makefile.in $(topdir)/config.status
index 441a0eb29819613b8ed5d91ee623a2d88e0911b1..f6f4225f079f16072d2c0ec7dae866ea9a57f222 100644 (file)
@@ -270,7 +270,7 @@ int main (int argc, char *argv[])
 
    load_dir_plugins(director->plugin_directory);
 
-   drop(uid, gid);                    /* reduce privileges if requested */
+   drop(uid, gid, false);                    /* reduce privileges if requested */
 
    /* If we are in testing mode, we don't try to fix the catalog */
    cat_op mode=(test_config)?CHECK_CONNECTION:UPDATE_AND_FIX;
index e19a727702cb813053e86aedc0d667748775d942..907e537bfc7df215b81d9712734804122fb748a8 100644 (file)
@@ -37,6 +37,7 @@ SVROBJS = $(SVRSRCS:.c=.o)
 # these are the objects that are changed by the .configure process
 EXTRAOBJS = @OBJLIST@
 
+CAP_LIBS = @CAP_LIBS@
 FDLIBS = @FDLIBS@                # extra libs for File daemon
 
 # extra items for linking on Win32
@@ -87,12 +88,12 @@ bacula-fd:  Makefile $(SVROBJS) ../findlib/libbacfind$(DEFAULT_ARCHIVE_TYPE) ../
        @echo "Linking $@ ..."
        $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -L../lib -L../findlib -o $@ $(SVROBJS) \
          $(WIN32LIBS) $(FDLIBS) -lbacfind -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(LIBS) \
-         $(DLIB) $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS)
+         $(DLIB) $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS)
 
 static-bacula-fd: Makefile $(SVROBJS) ../findlib/libbacfind.a ../lib/libbacpy$(DEFAULT_ARCHIVE_TYPE) ../lib/libbaccfg$(DEFAULT_ARCHIVE_TYPE) ../lib/libbac$(DEFAULT_ARCHIVE_TYPE) @WIN32@
        $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -static -L../lib -L../findlib -o $@ $(SVROBJS) \
           $(WIN32LIBS) $(FDLIBS) -lbacfind -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(LIBS) \
-          $(DLIB) $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS)
+          $(DLIB) $(WRAPLIBS) $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS)
        strip $@
 
 Makefile: $(srcdir)/Makefile.in $(topdir)/config.status
index 21b7c68fc374e71d40f0cc1a61904290438e2df2..d5e5ada948a716963e2c5b124745f3b34076cf8e 100644 (file)
@@ -82,6 +82,7 @@ PROG_COPYRIGHT
 "        -dt         print timestamp in debug output\n"
 "        -f          run in foreground (for debugging)\n"
 "        -g          groupid\n"
+"        -k          keep readall capabilities\n"
 "        -s          no signals (for debugging)\n"
 "        -t          test configuration file and exit\n"
 "        -u          userid\n"
@@ -105,6 +106,7 @@ int main (int argc, char *argv[])
 {
    int ch;
    bool test_config = false;
+   bool keep_readall_caps = false;
    char *uid = NULL;
    char *gid = NULL;
 #ifdef HAVE_PYTHON
@@ -121,7 +123,7 @@ int main (int argc, char *argv[])
    init_msg(NULL, NULL);
    daemon_start_time = time(NULL);
 
-   while ((ch = getopt(argc, argv, "c:d:fg:stu:v?")) != -1) {
+   while ((ch = getopt(argc, argv, "c:d:fg:kstu:v?")) != -1) {
       switch (ch) {
       case 'c':                    /* configuration file */
          if (configfile != NULL) {
@@ -149,6 +151,10 @@ int main (int argc, char *argv[])
          gid = optarg;
          break;
 
+      case 'k':
+         keep_readall_caps = true;
+         break;
+
       case 's':
          no_signals = true;
          break;
@@ -185,6 +191,11 @@ int main (int argc, char *argv[])
       usage();
    }
 
+   if (!uid && keep_readall_caps) {
+      Emsg0(M_ERROR, 0, _("-k option has no meaning without -u option.\n"));
+      exit(1);
+   }
+
    server_tid = pthread_self();
    if (!no_signals) {
       init_signals(terminate_filed);
@@ -227,7 +238,7 @@ int main (int argc, char *argv[])
 
    load_fd_plugins(me->plugin_directory);
 
-   drop(uid, gid);
+   drop(uid, gid, keep_readall_caps);
 
 #ifdef BOMB
    me += 1000000;
index 9189d3eb4fc7c7baa00e02efa194d8ea873107c7..9d3b46a77450961b4e5d59400d2c84f7e9a8dcb3 100644 (file)
@@ -15,6 +15,7 @@ topdir = ../..
 thisdir = src/lib
 
 DEBUG=@DEBUG@
+CAP_LIBS = @CAP_LIBS@
 
 first_rule: all
 dummy:
@@ -45,7 +46,7 @@ LIBBAC_SRCS = attr.c base64.c berrno.c bsys.c bget_msg.c \
              cram-md5.c crc32.c crypto.c daemon.c edit.c fnmatch.c \
              guid_to_name.c hmac.c jcr.c lex.c alist.c dlist.c \
              md5.c message.c mem_pool.c openssl.c \
-             plugins.c queue.c bregex.c \
+             plugins.c priv.c queue.c bregex.c \
              rwlock.c scan.c serial.c sha1.c \
              signal.c smartall.c rblist.c tls.c tree.c \
              util.c var.c watchdog.c workq.c btimers.c \
@@ -116,7 +117,7 @@ libbac.a: $(LIBBAC_OBJS)
 
 libbac.la: Makefile $(LIBBAC_OBJS)
        @echo "Making $@ ..."
-       $(LIBTOOL_LINK) $(CXX) $(DEFS) $(DEBUG) $(LDFLAGS) -o $@ $(LIBBAC_OBJS) -export-dynamic -rpath $(libdir) -version-info $(LIBBAC_LT_CURRENT):$(LIBBAC_LT_REVISION):$(LIBBAC_LT_AGE) $(WRAPLIBS)
+       $(LIBTOOL_LINK) $(CXX) $(DEFS) $(DEBUG) $(LDFLAGS) -o $@ $(LIBBAC_OBJS) -export-dynamic -rpath $(libdir) -version-info $(LIBBAC_LT_CURRENT):$(LIBBAC_LT_REVISION):$(LIBBAC_LT_AGE) $(WRAPLIBS) $(CAP_LIBS)
 
 libbaccfg.a: $(LIBBACCFG_OBJS) 
        @echo "Making $@ ..."
index 6626f645aff2d97b8f1b701a0485487afce9c401..a23d041fa9c3c01cf049393fb0b246f3490f4422 100644 (file)
 
 #include "bacula.h"
 
-#ifdef HAVE_AIX_OS
-extern "C" int initgroups(const char *,int);
-#endif
-
 
 static pthread_mutex_t timer_mutex = PTHREAD_MUTEX_INITIALIZER;
 static pthread_cond_t timer = PTHREAD_COND_INITIALIZER;
@@ -578,75 +574,6 @@ bail_out:
 }
 
 
-/*
- * Drop to privilege new userid and new gid if non-NULL
- */
-void drop(char *uname, char *gname)
-{
-#if   defined(HAVE_PWD_H) && defined(HAVE_GRP_H)
-   struct passwd *passw = NULL;
-   struct group *group = NULL;
-   gid_t gid;
-   uid_t uid;
-   char username[1000];         
-
-   Dmsg2(900, "uname=%s gname=%s\n", uname?uname:"NONE", gname?gname:"NONE");
-   if (!uname && !gname) {
-      return;                            /* Nothing to do */
-   }
-
-   if (uname) {
-      if ((passw = getpwnam(uname)) == NULL) {
-         berrno be;
-         Emsg2(M_ERROR_TERM, 0, _("Could not find userid=%s: ERR=%s\n"), uname,
-            be.bstrerror());
-      }
-   } else {
-      if ((passw = getpwuid(getuid())) == NULL) {
-         berrno be;
-         Emsg1(M_ERROR_TERM, 0, _("Could not find password entry. ERR=%s\n"),
-            be.bstrerror());
-      } else {
-         uname = passw->pw_name;
-      }
-   }
-   /* Any OS uname pointer may get overwritten, so save name, uid, and gid */
-   bstrncpy(username, uname, sizeof(username));
-   uid = passw->pw_uid;
-   gid = passw->pw_gid;
-   if (gname) {
-      if ((group = getgrnam(gname)) == NULL) {
-         berrno be;
-         Emsg2(M_ERROR_TERM, 0, _("Could not find group=%s: ERR=%s\n"), gname,
-            be.bstrerror());
-      }
-      gid = group->gr_gid;
-   }
-   if (initgroups(username, gid)) {
-      berrno be;
-      if (gname) {
-         Emsg3(M_ERROR_TERM, 0, _("Could not initgroups for group=%s, userid=%s: ERR=%s\n"),         
-            gname, username, be.bstrerror());
-      } else {
-         Emsg2(M_ERROR_TERM, 0, _("Could not initgroups for userid=%s: ERR=%s\n"),         
-            username, be.bstrerror());
-      }
-   }
-   if (gname) {
-      if (setgid(gid)) {
-         berrno be;
-         Emsg2(M_ERROR_TERM, 0, _("Could not set group=%s: ERR=%s\n"), gname,
-            be.bstrerror());
-      }
-   }
-   if (setuid(uid)) {
-      berrno be;
-      Emsg1(M_ERROR_TERM, 0, _("Could not set specified userid: %s\n"), username);
-   }
-#endif
-}
-
-
 /* BSDI does not have this.  This is a *poor* simulation */
 #ifndef HAVE_STRTOLL
 long long int
diff --git a/bacula/src/lib/priv.c b/bacula/src/lib/priv.c
new file mode 100644 (file)
index 0000000..82f4165
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+   Bacula® - The Network Backup Solution
+
+   Copyright (C) 2000-2009 Free Software Foundation Europe e.V.
+
+   The main author of Bacula is Kern Sibbald, with contributions from
+   many others, a complete list can be found in the file AUTHORS.
+   This program is Free Software; you can redistribute it and/or
+   modify it under the terms of version two of the GNU General Public
+   License as published by the Free Software Foundation and included
+   in the file LICENSE.
+
+   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, write to the Free Software
+   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+   02110-1301, USA.
+
+   Bacula® is a registered trademark of Kern Sibbald.
+   The licensor of Bacula is the Free Software Foundation Europe
+   (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
+   Switzerland, email:ftf@fsfeurope.org.
+*/
+
+#include "bacula.h"
+
+#undef ENABLE_KEEP_READALL_CAPS_SUPPORT
+#if defined(HAVE_SYS_PRCTL_H) && defined(HAVE_SYS_CAPABILITY_H) && \
+   defined(HAVE_PRCTL) && defined(HAVE_SETREUID) && defined(HAVE_LIBCAP)
+# include <sys/prctl.h>
+# include <sys/capability.h>
+# if defined(PR_SET_KEEPCAPS)
+#  define ENABLE_KEEP_READALL_CAPS_SUPPORT
+# endif
+#endif
+
+#ifdef HAVE_AIX_OS
+extern "C" int initgroups(const char *,int);
+#endif
+
+/*
+ * Lower privileges by switching to new UID and GID if non-NULL.
+ * If requested, keep readall capabilities after switch.
+ */
+void drop(char *uname, char *gname, bool keep_readall_caps)
+{
+   struct passwd *passw = NULL;
+   struct group *group = NULL;
+   gid_t gid;
+   uid_t uid;
+   char username[1000];
+
+   Dmsg2(900, "uname=%s gname=%s\n", uname?uname:"NONE", gname?gname:"NONE");
+   if (!uname && !gname) {
+      return;                            /* Nothing to do */
+   }
+
+   if (uname) {
+      if ((passw = getpwnam(uname)) == NULL) {
+         berrno be;
+         Emsg2(M_ERROR_TERM, 0, _("Could not find userid=%s: ERR=%s\n"), uname,
+            be.bstrerror());
+      }
+   } else {
+      if ((passw = getpwuid(getuid())) == NULL) {
+         berrno be;
+         Emsg1(M_ERROR_TERM, 0, _("Could not find password entry. ERR=%s\n"),
+            be.bstrerror());
+      } else {
+         uname = passw->pw_name;
+      }
+   }
+   /* Any OS uname pointer may get overwritten, so save name, uid, and gid */
+   bstrncpy(username, uname, sizeof(username));
+   uid = passw->pw_uid;
+   gid = passw->pw_gid;
+   if (gname) {
+      if ((group = getgrnam(gname)) == NULL) {
+         berrno be;
+         Emsg2(M_ERROR_TERM, 0, _("Could not find group=%s: ERR=%s\n"), gname,
+            be.bstrerror());
+      }
+      gid = group->gr_gid;
+   }
+   if (initgroups(username, gid)) {
+      berrno be;
+      if (gname) {
+         Emsg3(M_ERROR_TERM, 0, _("Could not initgroups for group=%s, userid=%s: ERR=%s\n"),
+            gname, username, be.bstrerror());
+      } else {
+         Emsg2(M_ERROR_TERM, 0, _("Could not initgroups for userid=%s: ERR=%s\n"),
+            username, be.bstrerror());
+      }
+   }
+   if (gname) {
+      if (setgid(gid)) {
+         berrno be;
+         Emsg2(M_ERROR_TERM, 0, _("Could not set group=%s: ERR=%s\n"), gname,
+            be.bstrerror());
+      }
+   }
+   if (keep_readall_caps) {
+#ifdef ENABLE_KEEP_READALL_CAPS_SUPPORT
+      cap_t caps;
+
+      if (prctl(PR_SET_KEEPCAPS, 1)) {
+         berrno be;
+         Emsg1(M_ERROR_TERM, 0, _("prctl failed: ERR=%s\n"), be.bstrerror());
+      }
+      if (setreuid(uid, uid)) {
+         berrno be;
+         Emsg1(M_ERROR_TERM, 0, _("setreuid failed: ERR=%s\n"), be.bstrerror());
+      }
+      if (!(caps = cap_from_text("cap_dac_read_search=ep"))) {
+         berrno be;
+         Emsg1(M_ERROR_TERM, 0, _("cap_from_text failed: ERR=%s\n"), be.bstrerror());
+      }
+      if (cap_set_proc(caps) < 0) {
+         berrno be;
+         Emsg1(M_ERROR_TERM, 0, _("cap_set_proc failed: ERR=%s\n"), be.bstrerror());
+      }
+      cap_free(caps);
+#else
+      Emsg0(M_ERROR_TERM, 0, _("Keep readall capabilities is not implemented on this platform yet\n"));
+#endif
+   } else if (setuid(uid)) {
+      berrno be;
+      Emsg1(M_ERROR_TERM, 0, _("Could not set specified userid: %s\n"), username);
+   }
+}
index 5379ec5e05732f1fe72f360f9a594237e0b770ea..c5b7ce1fb2487e2b481168d74fc4b1f3a97a8925 100644 (file)
@@ -69,7 +69,7 @@ int       bvsnprintf             (char *str, int32_t size, const char *format, v
 int       pool_sprintf           (char *pool_buf, const char *fmt, ...);
 void      create_pid_file        (char *dir, const char *progname, int port);
 int       delete_pid_file        (char *dir, const char *progname, int port);
-void      drop                   (char *uid, char *gid);
+void      drop                   (char *uid, char *gid, bool keep_readall_caps);
 int       bmicrosleep            (int32_t sec, int32_t usec);
 char     *bfgets                 (char *s, int size, FILE *fd);
 void      make_unique_filename   (POOLMEM **name, int Id, char *what);
index 80ccf46145c66f473557ffdb20eb5422ee01fac8..bd54b897bd7f63ba05110f04168f327ab141b94e 100644 (file)
@@ -70,6 +70,7 @@ COPYOBJS = bcopy.o block.o device.o dev.o label.o vtape.o \
 # these are the objects that are changed by the .configure process
 EXTRAOBJS = @OBJLIST@
 
+CAP_LIBS = @CAP_LIBS@
 FDLIBS=@FDLIBS@
 
 
@@ -91,12 +92,12 @@ bacula-sd: Makefile $(SDOBJS) ../lib/libbacpy$(DEFAULT_ARCHIVE_TYPE) ../lib/libb
        @echo "Linking $@ ..."
        $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -L../lib -o $@ $(SDOBJS) $(FDLIBS) \
           -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(DLIB) $(LIBS) $(WRAPLIBS) \
-       $(GETTEXT_LIBS) $(OPENSSL_LIBS)
+       $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS)
 
 static-bacula-sd: Makefile $(SDOBJS) ../lib/libbacpy$(DEFAULT_ARCHIVE_TYPE) ../lib/libbaccfg$(DEFAULT_ARCHIVE_TYPE) ../lib/libbac$(DEFAULT_ARCHIVE_TYPE)
        $(LIBTOOL_LINK) $(CXX) $(WLDFLAGS) $(LDFLAGS) -static -L../lib -o $@ $(SDOBJS) $(FDLIBS) \
           -lbacpy -lbaccfg -lbac -lm $(PYTHON_LIBS) $(DLIB) $(LIBS) $(WRAPLIBS) \
-       $(GETTEXT_LIBS) $(OPENSSL_LIBS)
+       $(GETTEXT_LIBS) $(OPENSSL_LIBS) $(CAP_LIBS)
        strip $@
 
 btape.o: btape.c
index 76f7b2e2cd39c82aee5d8233ed9843db3f7ac309..3ee885bad8a3e78e9eea4990154ad2fc11fb2119 100644 (file)
@@ -261,7 +261,7 @@ int main (int argc, char *argv[])
 
    load_sd_plugins(me->plugin_directory);
 
-   drop(uid, gid);
+   drop(uid, gid, false);
 
    cleanup_old_files();