From: Vincent Bernat Date: Sun, 16 Nov 2008 18:57:45 +0000 (+0100) Subject: Privilege separation for SNMP using a custom transport X-Git-Tag: 0.2~17 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d72a05d4d2c4e669f44cab6087104792e3bef923;p=thirdparty%2Flldpd.git Privilege separation for SNMP using a custom transport --- diff --git a/src/Makefile.am b/src/Makefile.am index 55aedca6..de012328 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/agent.c b/src/agent.c index c2f1685d..ee4150e0 100644 --- a/src/agent.c +++ b/src/agent.c @@ -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 index 00000000..8df2808b --- /dev/null +++ b/src/agent_priv.c @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * 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 + +#include +#include +#include +#include +#include +#include + +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); +} diff --git a/src/lldpd.c b/src/lldpd.c index 4c28aefa..98ed8d48 100644 --- a/src/lldpd.c +++ b/src/lldpd.c @@ -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; diff --git a/src/lldpd.h b/src/lldpd.h index 725cebe7..f9ee0a6e 100644 --- a/src/lldpd.h +++ b/src/lldpd.h @@ -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); diff --git a/src/priv.c b/src/priv.c index 0bf1e3ed..0fc1c446 100644 --- a/src/priv.c +++ b/src/priv.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include #include @@ -39,6 +41,7 @@ #include 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 */