This increases the privileges of non-root haproxy processes
so that they may bind to privileged ports and change ulimits.
The purpose of this is to allow non-root haproxy processes
to be restarted without the need for help from root.

Once capabilities are no longer needed they are dropped.
This will become conditional once the ability to restart is added.
---
 Makefile      |    8 +++++
 src/haproxy.c |   98 ++++++++++++++++++++++++++++++++++++++++++++++++++-------
 2 files changed, 94 insertions(+), 12 deletions(-)

diff --git a/Makefile b/Makefile
index 6d3f156..c532f3f 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,7 @@
 #   USE_LIBCRYPT         : enable crypted passwords using -lcrypt
 #   USE_CRYPT_H          : set it if your system requires including crypt.h
 #   USE_VSYSCALL         : enable vsyscall on Linux x86, bypassing libc
+#   USE_LIBCAP          : enable non-root processes to bind to privileged ports
 #
 # Options can be forced by specifying "USE_xxx=1" or can be disabled by using
 # "USE_xxx=" (empty string).
@@ -212,6 +213,7 @@ ifeq ($(TARGET),linux26)
   USE_SEPOLL      = implicit
   USE_TPROXY      = implicit
   USE_LIBCRYPT    = implicit
+  USE_LIBCAP      = implicit
 else
 ifeq ($(TARGET),solaris)
   # This is for Solaris 8
@@ -389,6 +391,12 @@ OPTIONS_CFLAGS += -DCONFIG_HAP_LINUX_VSYSCALL
 BUILD_OPTIONS  += $(call ignore_implicit,USE_VSYSCALL)
 endif
 
+ifneq ($(USE_LIBCAP),)
+OPTIONS_CFLAGS  += -DCONFIG_HAP_CAP
+BUILD_OPTIONS   += $(call ignore_implicit,USE_LIBCAP)
+OPTIONS_LDFLAGS += -lcap
+endif
+
 ifneq ($(USE_NETFILTER),)
 OPTIONS_CFLAGS += -DNETFILTER
 BUILD_OPTIONS  += $(call ignore_implicit,USE_NETFILTER)
diff --git a/src/haproxy.c b/src/haproxy.c
index d1f7195..577de61 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -44,6 +44,10 @@
 #include <sys/resource.h>
 #include <time.h>
 #include <syslog.h>
+#ifdef CONFIG_HAP_CAP
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#endif
 
 #ifdef DEBUG_FULL
 #include <assert.h>
@@ -951,6 +955,83 @@ void run_poll_loop()
 }
 
 
+/* setgid / setuid */
+static void setid(const char *name)
+{
+       if (getuid())
+               return;
+
+       if (global.gid && setgid(global.gid) == -1) {
+               if (setgid(global.gid) == -1) {
+                       Alert("[%s.setid()] Cannot set gid %d.\n",
+                             name, global.gid);
+                       goto err;
+               }
+       }
+
+       if (global.uid) {
+#ifdef CONFIG_HAP_CAP
+               cap_t caps;
+
+               if (prctl(PR_SET_KEEPCAPS, 1)) {
+                       Alert("[%s.setid()] Cannot set prctl 
PR_SET_KEEPCAPS.\n",
+                             name);
+                       goto err;
+               }
+#endif
+               if (setuid(global.uid) == -1) {
+                       Alert("[%s.setid()] Cannot set uid %d.\n",
+                             name, global.uid);
+                       goto err;
+               }
+#ifdef CONFIG_HAP_CAP
+               caps = cap_from_text("cap_net_bind_service=+eip "
+                                    "cap_sys_resource=+eip");
+               if (!caps || cap_set_proc(caps)) {
+                       Alert("[%s.setid()] Cannot set CAP_NET_BIND_SERVICE "
+                             "and CAP_SYS_RESOURCE capabilities.\n", name);
+                       protocol_unbind_all();
+                       exit(1);
+               }
+#endif
+       }
+
+       return;
+err:
+       protocol_unbind_all();
+       exit(1);
+}
+
+static void drop_capabilities(void)
+{
+#ifdef CONFIG_HAP_CAP
+       cap_t cap;
+       /* Drop all capabilities */
+       cap = cap_from_text("");
+       if (!cap || cap_set_proc(cap)) {
+               send_log(NULL, LOG_ERR, "Cannot drop capabilities.\n");
+               protocol_unbind_all();
+               exit(1);
+       }
+#endif
+}
+
+static void setid_early(const char *name)
+{
+#ifdef CONFIG_HAP_CAP
+       Warning("[%s.setsid_early()] enter.\n", name);
+       setid(name);
+#endif
+}
+
+static void setid_late(const char *name)
+{
+#ifndef CONFIG_HAP_CAP
+       Warning("[%s.setsid_late()] enter.\n", name);
+       setid(name);
+#endif
+}
+
 int main(int argc, char **argv)
 {
        int err, retry;
@@ -969,6 +1050,8 @@ int main(int argc, char **argv)
         */
        signal_register_fct(SIGPIPE, NULL, 0);
 
+       setid_early(argv[0]);
+
        /* ulimits */
        if (!global.rlimit_nofile)
                global.rlimit_nofile = global.maxsock;
@@ -1134,18 +1217,7 @@ int main(int argc, char **argv)
         * be able to restart the old pids.
         */
 
-       /* setgid / setuid */
-       if (global.gid && setgid(global.gid) == -1) {
-               Alert("[%s.main()] Cannot set gid %d.\n", argv[0], global.gid);
-               protocol_unbind_all();
-               exit(1);
-       }
-
-       if (global.uid && setuid(global.uid) == -1) {
-               Alert("[%s.main()] Cannot set uid %d.\n", argv[0], global.uid);
-               protocol_unbind_all();
-               exit(1);
-       }
+       setid_late(argv[0]);
 
        /* check ulimits */
        limit.rlim_cur = limit.rlim_max = 0;
@@ -1214,6 +1286,8 @@ int main(int argc, char **argv)
                fork_poller();
        }
 
+       drop_capabilities();
+
        protocol_enable_all();
        /*
         * That's it : the central polling loop. Run until we stop.
-- 
1.7.2.3


Reply via email to