]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
Privilege separation for SNMP using a custom transport
authorVincent Bernat <bernat@luffy.cx>
Sun, 16 Nov 2008 18:57:45 +0000 (19:57 +0100)
committerVincent Bernat <bernat@luffy.cx>
Sun, 16 Nov 2008 18:57:45 +0000 (19:57 +0100)
src/Makefile.am
src/agent.c
src/agent_priv.c [new file with mode: 0644]
src/lldpd.c
src/lldpd.h
src/priv.c

index 55aedca6933728f2a256864fc84ae63a5a2d3705..de012328a688570f0034eb7d059fff65b340ef35 100644 (file)
@@ -8,6 +8,6 @@ lldpd_LDADD = @LIBOBJS@
 lldpctl_LDADD = @LIBOBJS@
 
 if USE_SNMP
-lldpd_SOURCES += agent.c
+lldpd_SOURCES += agent.c agent_priv.c
 lldpd_LDADD += @NETSNMP_LIB@
 endif
index c2f1685d85cdb72345c4a69e259145e2089dcf9f..ee4150e000b22274314714c8651b6d6a37285291 100644 (file)
@@ -804,6 +804,9 @@ agent_init(struct lldpd *cfg, int debug)
        /* Do not load any MIB */
        setenv("MIBS", "", 1);
 
+       /* We provide our UNIX domain transport */
+       agent_priv_register_domain();
+
        init_agent("lldpAgent");
        REGISTER_MIB("lldp", lldp_vars, variable8, lldp_oid);
        init_snmp("lldpAgent");
diff --git a/src/agent_priv.c b/src/agent_priv.c
new file mode 100644 (file)
index 0000000..8df2808
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * Copyright (c) 2008 Vincent Bernat <bernat@luffy.cx>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "lldpd.h"
+
+#include <errno.h>
+
+#include <net-snmp/net-snmp-config.h>
+#include <net-snmp/net-snmp-includes.h>
+#include <net-snmp/agent/net-snmp-agent-includes.h>
+#include <net-snmp/agent/snmp_vars.h>
+#include <net-snmp/agent/util_funcs.h>
+#include <net-snmp/library/snmpUnixDomain.h>
+
+oid netsnmp_UnixDomain[] = { TRANSPORT_DOMAIN_LOCAL };
+static netsnmp_tdomain unixDomain;
+
+static char *
+agent_priv_unix_fmtaddr(netsnmp_transport *t, void *data, int len)
+{
+       /* We don't bother to implement the full function */
+       return strdup("Local Unix socket with privilege separation: unknown");
+}
+
+static int
+agent_priv_unix_recv(netsnmp_transport *t, void *buf, int size,
+    void **opaque, int *olength)
+{
+       int rc = -1;
+       socklen_t  tolen = sizeof(struct sockaddr_un);
+       struct sockaddr *to = NULL;
+       
+       if (t == NULL || t->sock < 0)
+               goto recv_error;
+       to = (struct sockaddr *)malloc(sizeof(struct sockaddr_un));
+       if (to == NULL)
+               goto recv_error;
+       memset(to, 0, tolen);
+       if (getsockname(t->sock, to, &tolen) != 0)
+               goto recv_error;
+       while (rc < 0) {
+               rc = recv(t->sock, buf, size, 0);
+               if (rc < 0 && errno != EINTR) {
+                       LLOG_WARN("unable to receive from fd %d",
+                           t->sock);
+                       goto recv_error;
+               }
+       }
+       *opaque = (void*)to;
+       *olength = sizeof(struct sockaddr_un);
+       return rc;
+
+recv_error:
+       free(to);
+       *opaque = NULL;
+       *olength = 0;
+       return -1;
+}
+
+static int
+agent_priv_unix_send(netsnmp_transport *t, void *buf, int size,
+    void **opaque, int *olength)
+{
+       int rc = -1;
+       if (t != NULL && t->sock >= 0) {
+               while (rc < 0) {
+                       rc = send(t->sock, buf, size, 0);
+                       if (rc < 0 && errno != EINTR) {
+                               break;
+                       }
+               }
+       }
+       return rc;
+}
+
+static int
+agent_priv_unix_close(netsnmp_transport *t)
+{
+       int rc = 0;
+
+       if (t->sock >= 0) {
+               rc = close(t->sock);
+               t->sock = -1;
+               return rc;
+       }
+       return -1;
+}
+
+static int
+agent_priv_unix_accept(netsnmp_transport *t)
+{
+       LLOG_WARNX("should not have been called");
+       return -1;
+}
+
+netsnmp_transport *
+agent_priv_unix_transport(const char *string, int len, int local)
+{
+       struct sockaddr_un addr;
+       netsnmp_transport *t = NULL;
+
+       if (local) {
+               LLOG_WARNX("should not have been called for local transport");
+               return NULL;
+       }
+       
+       if (len > 0 && len < (sizeof(addr.sun_path) - 1)) {
+               addr.sun_family = AF_UNIX;
+               memset(addr.sun_path, 0, sizeof(addr.sun_path));
+               strncpy(addr.sun_path, string, len);
+       } else {
+               LLOG_WARNX("path too long for Unix domain transport");
+               return NULL;
+       }
+
+       if ((t = (netsnmp_transport *)
+               malloc(sizeof(netsnmp_transport))) == NULL)
+               return NULL;
+
+       memset(t, 0, sizeof(netsnmp_transport));
+
+       t->domain = netsnmp_UnixDomain;
+       t->domain_length =
+           sizeof(netsnmp_UnixDomain) / sizeof(netsnmp_UnixDomain[0]);
+
+       if ((t->sock = priv_snmp_socket(&addr)) < 0) {
+               netsnmp_transport_free(t);
+               return NULL;
+       }
+
+       t->flags = NETSNMP_TRANSPORT_FLAG_STREAM;
+
+       if ((t->remote = (u_char *)
+               malloc(strlen(addr.sun_path))) == NULL) {
+               agent_priv_unix_close(t);
+               netsnmp_transport_free(t);
+               return NULL;
+        }
+        memcpy(t->remote, addr.sun_path, strlen(addr.sun_path));
+        t->remote_length = strlen(addr.sun_path);
+
+       t->msgMaxSize = 0x7fffffff;
+       t->f_recv     = agent_priv_unix_recv;
+       t->f_send     = agent_priv_unix_send;
+       t->f_close    = agent_priv_unix_close;
+       t->f_accept   = agent_priv_unix_accept;
+       t->f_fmtaddr  = agent_priv_unix_fmtaddr;
+
+       return t;
+}
+
+netsnmp_transport *
+agent_priv_unix_create_tstring(const char *string, int local,
+    const char *default_target)
+{
+       if ((!string || *string == '\0') && default_target &&
+           *default_target != '\0') {
+               string = default_target;
+       }
+
+       return agent_priv_unix_transport(string, strlen(string), local);
+}
+
+netsnmp_transport *
+agent_priv_unix_create_ostring(const u_char * o, size_t o_len, int local)
+{
+       return agent_priv_unix_transport((char *)o, o_len, local);
+}
+
+void
+agent_priv_register_domain()
+{
+       unixDomain.name = netsnmp_UnixDomain;
+       unixDomain.name_length = sizeof(netsnmp_UnixDomain) / sizeof(oid);
+       unixDomain.prefix = (const char**)calloc(2, sizeof(char *));
+       unixDomain.prefix[0] = "unix";
+       
+       unixDomain.f_create_from_tstring_new = agent_priv_unix_create_tstring;
+       unixDomain.f_create_from_ostring = agent_priv_unix_create_ostring;
+       
+       netsnmp_tdomain_register(&unixDomain);
+}
index 4c28aefa3cd101874da6bf4df9338b65f47ed226..98ed8d483cbcf5f6bb08ed92ef6c3ace4ea79da1 100644 (file)
@@ -1349,36 +1349,7 @@ main(int argc, char *argv[])
 
        log_init(debug);
 
-#ifdef USE_SNMP
-       if (NETSNMP_AGENTX_SOCKET[0] == '/') {
-               /* AgentX socket is a file, we need to mangle it to be able to chroot */
-               char *caxsocket;
-               char *chrootdir;
-               char *axsocket;
-
-               /* We chroot into the directory containing the socket. At this
-                * point of the program, no config file has been read. If the
-                * socket is not in the default directory, this won't work. */
-               caxsocket = strdup(NETSNMP_AGENTX_SOCKET);
-               chrootdir = strdup(dirname(caxsocket));
-               free(caxsocket);
-               priv_init(chrootdir);
-               free(chrootdir);
-
-               /* We mangle the name of the socket since it is in the current directory */
-               caxsocket = strdup(NETSNMP_AGENTX_SOCKET);
-               axsocket = strdup(basename(caxsocket));
-               free(caxsocket);
-               netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID,
-                   NETSNMP_DS_AGENT_X_SOCKET,
-                   axsocket);
-               free(axsocket);
-       } else
-               /* Let's suppose that we can chroot normally */
-               priv_init(PRIVSEP_CHROOT);
-#else
        priv_init(PRIVSEP_CHROOT);
-#endif 
 
        if (probe == 0) probe = LLDPD_TTL;
 
index 725cebe7ed332b2473a36267edeacaf500ddf95f..f9ee0a6ec4c73d9194504bf633a025380ac06777 100644 (file)
@@ -303,6 +303,9 @@ void             fatalx(const char *);
 void            agent_shutdown();
 void            agent_init(struct lldpd *, int);
 
+/* agent_priv.c */
+void            agent_priv_register_domain();
+
 /* strlcpy.c */
 size_t strlcpy(char *, const char *, size_t);
 
@@ -337,6 +340,7 @@ int          priv_open(char*);
 int             priv_ethtool(char*, struct ethtool_cmd*);
 int             priv_iface_init(struct lldpd_hardware *, int);
 int     priv_iface_multicast(char *, u_int8_t *, int);
+int     priv_snmp_socket(struct sockaddr_un *);
 
 /* privsep_fdpass.c */
 int     receive_fd(int);
index 0bf1e3ed43f808146c8b5419a9c433474ba2dbb9..0fc1c446ef6acfd00dd1410ae0b4f56a40eb1abb 100644 (file)
@@ -28,6 +28,8 @@
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
 #include <regex.h>
 #include <fcntl.h>
 #include <pwd.h>
@@ -39,6 +41,7 @@
 #include <netpacket/packet.h>
 
 enum {
+       PRIV_PING,
        PRIV_FORK,
        PRIV_CREATE_CTL_SOCKET,
        PRIV_DELETE_CTL_SOCKET,
@@ -47,6 +50,7 @@ enum {
        PRIV_ETHTOOL,
        PRIV_IFACE_INIT,
        PRIV_IFACE_MULTICAST,
+       PRIV_SNMP_SOCKET,
 };
 
 static int may_read(int, void *, size_t);
@@ -59,6 +63,16 @@ int sock = -1;
 
 /* Proxies */
 
+void
+priv_ping()
+{
+       int cmd, rc;
+       cmd = PRIV_PING;
+       must_write(remote, &cmd, sizeof(int));
+       must_read(remote, &rc, sizeof(int));
+       LLOG_DEBUG("monitor ready");
+}
+
 /* Proxy for fork */
 void
 priv_fork()
@@ -161,7 +175,7 @@ priv_iface_multicast(char *name, u_int8_t *mac, int add)
 {
        int cmd, rc;
        cmd = PRIV_IFACE_MULTICAST;
-       must_write(remote, &cmd, sizeof(cmd));
+       must_write(remote, &cmd, sizeof(int));
        must_write(remote, name, IFNAMSIZ);
        must_write(remote, mac, ETH_ALEN);
        must_write(remote, &add, sizeof(int));
@@ -169,6 +183,26 @@ priv_iface_multicast(char *name, u_int8_t *mac, int add)
        return rc;
 }
 
+int
+priv_snmp_socket(struct sockaddr_un *addr)
+{
+       int cmd, rc;
+       cmd = PRIV_SNMP_SOCKET;
+       must_write(remote, &cmd, sizeof(int));
+       must_write(remote, addr, sizeof(struct sockaddr_un));
+       must_read(remote, &rc, sizeof(int));
+       if (rc < 0)
+               return rc;
+       return receive_fd(remote);
+}
+
+void
+asroot_ping()
+{
+       int rc = 1;
+       must_write(remote, &rc, sizeof(int));
+}
+
 void
 asroot_fork()
 {
@@ -375,12 +409,48 @@ asroot_iface_multicast()
        must_write(remote, &rc, sizeof(rc));
 }
 
+static void
+asroot_snmp_socket()
+{
+       int sock, rc;
+       static struct sockaddr_un *addr = NULL;
+       struct sockaddr_un bogus;
+
+       if (!addr) {
+               addr = (struct sockaddr_un *)malloc(sizeof(struct sockaddr_un));
+               must_read(remote, addr, sizeof(struct sockaddr_un));
+       } else
+               /* We have already been asked to connect to a socket. We will
+                * connect to the same socket. */
+               must_read(remote, &bogus, sizeof(struct sockaddr_un));
+       if (addr->sun_family != AF_UNIX)
+               fatal("someone is trying to trick me");
+       addr->sun_path[sizeof(addr->sun_path)-1] = '\0';
+
+       if ((sock = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
+               LLOG_WARN("cannot open socket");
+               must_write(remote, &sock, sizeof(int));
+               return;
+       }
+        if ((rc = connect(sock, (struct sockaddr *) addr,
+                   sizeof(struct sockaddr_un))) != 0) {
+               LLOG_WARN("cannot connect to %s", addr->sun_path);
+               close(sock);
+               rc = -1;
+               must_write(remote, &rc, sizeof(int));
+               return;
+        }
+       must_write(remote, &rc, sizeof(int));
+       send_fd(remote, sock);
+}
+
 struct dispatch_actions {
        int                             msg;
        void(*function)(void);
 };
 
 struct dispatch_actions actions[] = {
+       {PRIV_PING, asroot_ping},
        {PRIV_FORK, asroot_fork},
        {PRIV_CREATE_CTL_SOCKET, asroot_ctl_create},
        {PRIV_DELETE_CTL_SOCKET, asroot_ctl_cleanup},
@@ -389,6 +459,7 @@ struct dispatch_actions actions[] = {
        {PRIV_ETHTOOL, asroot_ethtool},
        {PRIV_IFACE_INIT, asroot_iface_init},
        {PRIV_IFACE_MULTICAST, asroot_iface_multicast},
+       {PRIV_SNMP_SOCKET, asroot_snmp_socket},
        {-1, NULL}
 };
 
@@ -486,6 +557,7 @@ priv_init(char *chrootdir)
                        fatal("[priv]: setresuid() failed");
                remote = pair[0];
                close(pair[1]);
+               priv_ping();
                break;
        default:
                /* We are in the monitor */