From: Vincent Bernat Date: Thu, 13 Nov 2008 10:30:45 +0000 (+0100) Subject: More privilege separation X-Git-Tag: 0.2~24 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4afe659ec6955868d41a11dd8d9fc779f344122a;p=thirdparty%2Flldpd.git More privilege separation --- diff --git a/src/Makefile.am b/src/Makefile.am index ebae44b1..55aedca6 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ sbin_PROGRAMS = lldpd lldpctl COMMON = log.c ctl.c lldpd.h lldp.h cdp.h compat.h sonmp.h llc.h edp.h -lldpd_SOURCES = lldpd.c lldp.c cdp.c sonmp.c edp.c iov.c features.c client.c priv.c $(COMMON) +lldpd_SOURCES = lldpd.c lldp.c cdp.c sonmp.c edp.c iov.c features.c client.c priv.c privsep_fdpass.c $(COMMON) lldpctl_SOURCES = lldpctl.c $(COMMON) lldpd_LDADD = @LIBOBJS@ diff --git a/src/lldpd.c b/src/lldpd.c index f25ddadc..f995465a 100644 --- a/src/lldpd.c +++ b/src/lldpd.c @@ -37,7 +37,6 @@ #include #include #include -#include #ifdef USE_SNMP #include @@ -523,7 +522,6 @@ lldpd_port_add(struct lldpd *cfg, struct ifaddrs *ifa) struct lldpd_hardware *hardware; struct lldpd_port *port; struct lldpd_vlan *vlan; - struct ifreq ifr; struct vlan_ioctl_args ifv; struct ethtool_cmd ethc; u_int8_t *lladdr; @@ -613,12 +611,7 @@ lldpd_port_add(struct lldpd *cfg, struct ifaddrs *ifa) freeifaddrs(oifap); /* MAC/PHY */ - memset(&ifr, 0, sizeof(ifr)); - memset(ðc, 0, sizeof(ethc)); - strlcpy(ifr.ifr_name, hardware->h_ifname, sizeof(ifr.ifr_name)); - ifr.ifr_data = (caddr_t)ðc; - ethc.cmd = ETHTOOL_GSET; - if (ioctl(cfg->g_sock, SIOCETHTOOL, &ifr) == 0) { + if (priv_ethtool(hardware->h_ifname, ðc) == 0) { int j; int advertised_ethtool_to_rfc3636[][2] = { {ADVERTISED_10baseT_Half, LLDP_DOT3_LINK_AUTONEG_10BASE_T}, @@ -1220,8 +1213,9 @@ lldpd_loop(struct lldpd *cfg) /* Check forwarding */ cfg->g_lchassis.c_cap_enabled = 0; if ((f = priv_open("/proc/sys/net/ipv4/ip_forward")) >= 0) { - if ((read(f, &status, 1) == 1) && (status == '1')) + if ((read(f, &status, 1) == 1) && (status == '1')) { cfg->g_lchassis.c_cap_enabled = LLDP_CAP_ROUTER; + } close(f); } diff --git a/src/lldpd.h b/src/lldpd.h index 866517ee..6156822b 100644 --- a/src/lldpd.h +++ b/src/lldpd.h @@ -33,6 +33,7 @@ #endif #include #include +#include #include "compat.h" #include "lldp.h" @@ -333,5 +334,10 @@ int priv_ctl_create(); void priv_ctl_cleanup(); char *priv_gethostbyname(); int priv_open(char*); +int priv_ethtool(char*, struct ethtool_cmd*); + +/* privsep_fdpass.c */ +int receive_fd(int); +void send_fd(int, int); #endif /* _LLDPD_H */ diff --git a/src/priv.c b/src/priv.c index d97ad161..fd26a8fa 100644 --- a/src/priv.c +++ b/src/priv.c @@ -31,110 +31,25 @@ #include #include #include +#include #include - -int remote; /* Other side */ -int monitored; /* Child */ - -/* Message to be sent between monitor and child. The convention is that both - * ends agree on the content (value) which depends on the message and on the - * direction. */ -struct priv_msg { - enum { - PRIV_FORK, - PRIV_CREATE_CTL_SOCKET, - PRIV_DELETE_CTL_SOCKET, - PRIV_GET_HOSTNAME, - PRIV_OPEN, - } msg; - union { - int integer; - char iface[IFNAMSIZ]; - char buf[1024]; - } value; +#include + +enum { + PRIV_FORK, + PRIV_CREATE_CTL_SOCKET, + PRIV_DELETE_CTL_SOCKET, + PRIV_GET_HOSTNAME, + PRIV_OPEN, + PRIV_ETHTOOL, }; -int -priv_send(struct priv_msg *msg) -{ - if (write(remote, msg, sizeof(struct priv_msg)) != - sizeof(struct priv_msg)) { - LLOG_WARN("unable to send message"); - errno = EPIPE; - return -1; - } - if (read(remote, msg, sizeof(struct priv_msg)) != - sizeof(struct priv_msg)) { - LLOG_WARN("unable to get answer"); - errno = EPIPE; - return -1; - } - return 0; -} - -/* Run as root */ -void -priv_send_back(struct priv_msg *msg) -{ - if (write(remote, msg, sizeof(struct priv_msg)) != - sizeof(struct priv_msg)) { - fatal("unable to send message"); - } -} - -/* Run as root */ -void -priv_send_fd(int fd) -{ - struct msghdr msg = {0}; - struct cmsghdr *cmsg; - char buf[CMSG_SPACE(sizeof(int))]; - - msg.msg_control = buf; - msg.msg_controllen = sizeof(buf); - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); - msg.msg_controllen = cmsg->cmsg_len; - if (sendmsg(remote, &msg, 0) == -1) { - LLOG_WARN("unable to send file descriptor %d", fd); - fatal(NULL); - } -} - -int -priv_get_fd() -{ - struct msghdr msg; - struct cmsghdr *cmsg; - char buf[CMSG_SPACE(sizeof(int))]; - - memset(&msg, 0, sizeof(struct msghdr)); - msg.msg_control = buf; - msg.msg_controllen = sizeof(buf); +static int may_read(int, void *, size_t); +static void must_read(int, void *, size_t); +static void must_write(int, void *, size_t); - if (recvmsg(remote, &msg, 0) == -1) { - LLOG_WARN("unable to receive file descriptor"); - return -1; - } - if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) { - LLOG_WARNX("no file descriptor in received message"); - return -1; - } - if (CMSG_NXTHDR(&msg, cmsg) != NULL) { - LLOG_WARNX("more than one file descriptor received"); - return -1; - } - if ((cmsg->cmsg_level != SOL_SOCKET) || - (cmsg->cmsg_type != SCM_RIGHTS)) { - LLOG_WARNX("unknown control data received (%d, %d)", - cmsg->cmsg_level, cmsg->cmsg_type); - return -1; - } - return (*(int *)CMSG_DATA(cmsg)); -} +int remote; /* Other side */ +int monitored = -1; /* Child */ /* Proxies */ @@ -142,9 +57,10 @@ priv_get_fd() void priv_fork() { - struct priv_msg(msg); - msg.msg = PRIV_FORK; - priv_send(&msg); + int cmd, rc; + cmd = PRIV_FORK; + must_write(remote, &cmd, sizeof(int)); + must_read(remote, &rc, sizeof(int)); } /* Proxy for ctl_create, no argument since this is the monitor that decides the @@ -152,167 +68,239 @@ priv_fork() int priv_ctl_create() { - struct priv_msg msg; - msg.msg = PRIV_CREATE_CTL_SOCKET; - if ((priv_send(&msg) == -1) || - (msg.value.integer == -1)) + int cmd, rc; + cmd = PRIV_CREATE_CTL_SOCKET; + must_write(remote, &cmd, sizeof(int)); + must_read(remote, &rc, sizeof(int)); + if (rc == -1) return -1; - return priv_get_fd(); + return receive_fd(remote); } /* Proxy for ctl_cleanup */ void priv_ctl_cleanup() { - struct priv_msg msg; - msg.msg = PRIV_DELETE_CTL_SOCKET; - priv_send(&msg); + int cmd, rc; + cmd = PRIV_DELETE_CTL_SOCKET; + must_write(remote, &cmd, sizeof(int)); + must_read(remote, &rc, sizeof(int)); } /* Proxy for gethostbyname */ char * priv_gethostbyname() { - static struct priv_msg msg; - msg.msg = PRIV_GET_HOSTNAME; - if (priv_send(&msg) == -1) - fatal("unable to get hostname"); - return msg.value.buf; + int cmd, rc; + static char *buf = NULL; + cmd = PRIV_GET_HOSTNAME; + must_write(remote, &cmd, sizeof(int)); + must_read(remote, &rc, sizeof(int)); + if ((buf = (char*)realloc(buf, rc+1)) == NULL) + fatal(NULL); + must_read(remote, buf, rc+1); + return buf; } /* Proxy for open */ int priv_open(char *file) { - struct priv_msg msg; - msg.msg = PRIV_OPEN; - if (strlen(file) >= sizeof(msg.value.buf)) { - errno = ENAMETOOLONG; - return -1; - } - strlcpy(msg.value.buf, file, sizeof(msg.value.buf)); - if ((priv_send(&msg) == -1) || - (msg.value.integer == -1)) - return -1; - return priv_get_fd(); + int cmd, len, rc; + cmd = PRIV_OPEN; + must_write(remote, &cmd, sizeof(int)); + len = strlen(file); + must_write(remote, &len, sizeof(int)); + must_write(remote, file, len + 1); + must_read(remote, &rc, sizeof(int)); + if (rc == -1) + return rc; + return receive_fd(remote); +} + +/* Proxy for ethtool ioctl */ +int +priv_ethtool(char *ifname, struct ethtool_cmd *ethc) +{ + int cmd, rc, len; + cmd = PRIV_ETHTOOL; + must_write(remote, &cmd, sizeof(int)); + len = strlen(ifname); + must_write(remote, &len, sizeof(int)); + must_write(remote, ifname, len + 1); + must_read(remote, &rc, sizeof(int)); + if (rc != 0) + return rc; + must_read(remote, ethc, sizeof(struct ethtool_cmd)); + return rc; } void -priv_fork_daemon(struct priv_msg *msg) +asroot_fork() { int pid; char *spid; if (daemon(0, 0) != 0) - fatal("failed to detach daemon"); + fatal("[priv]: failed to detach daemon"); if ((pid = open(LLDPD_PID_FILE, O_TRUNC | O_CREAT | O_WRONLY)) == -1) - fatal("unable to open pid file " LLDPD_PID_FILE); + fatal("[priv]: unable to open pid file " LLDPD_PID_FILE); if (asprintf(&spid, "%d\n", getpid()) == -1) - fatal("unable to create pid file " LLDPD_PID_FILE); + fatal("[priv]: unable to create pid file " LLDPD_PID_FILE); if (write(pid, spid, strlen(spid)) == -1) - fatal("unable to write pid file " LLDPD_PID_FILE); + fatal("[priv]: unable to write pid file " LLDPD_PID_FILE); free(spid); close(pid); + + /* Ack */ + must_write(remote, &pid, sizeof(int)); } void -priv_create_ctl_socket(struct priv_msg *msg) +asroot_ctl_create() { - if ((msg->value.integer = - ctl_create(LLDPD_CTL_SOCKET)) == -1) { - LLOG_WARN("unable to create control socket"); - priv_send_back(msg); - } else { - priv_send_back(msg); - priv_send_fd(msg->value.integer); - close(msg->value.integer); + int rc; + if ((rc = ctl_create(LLDPD_CTL_SOCKET)) == -1) { + LLOG_WARN("[priv]: unable to create control socket"); + must_write(remote, &rc, sizeof(int)); + return; } + must_write(remote, &rc, sizeof(int)); + send_fd(remote, rc); + close(rc); } void -priv_delete_ctl_socket(struct priv_msg *msg) +asroot_ctl_cleanup() { + int rc = 0; ctl_cleanup(LLDPD_CTL_SOCKET); - priv_send_back(msg); + + /* Ack */ + must_write(remote, &rc, sizeof(int)); } void -priv_get_hostname(struct priv_msg *msg) +asroot_gethostbyname() { struct utsname un; struct hostent *hp; + int len; if (uname(&un) != 0) - fatal("failed to get system information"); + fatal("[priv]: failed to get system information"); if ((hp = gethostbyname(un.nodename)) == NULL) - fatal("failed to get system name"); - strlcpy(msg->value.buf, hp->h_name, sizeof(msg->value.buf)); - priv_send_back(msg); + fatal("[priv]: failed to get system name"); + len = strlen(hp->h_name); + must_write(remote, &len, sizeof(int)); + must_write(remote, hp->h_name, strlen(hp->h_name) + 1); } void -priv_open_readonly(struct priv_msg *msg) +asroot_open() { - char* authorized[] = { + const char* authorized[] = { "/proc/sys/net/ipv4/ip_forward", NULL }; char **f; - int fd; + char *file; + int fd, len, rc; + + must_read(remote, &len, sizeof(len)); + if ((file = (char *)malloc(len + 1)) == NULL) + fatal(NULL); + must_read(remote, file, len + 1); for (f=authorized; *f != NULL; f++) { - if (strncmp(msg->value.buf, *f, - sizeof(msg->value.buf)) == 0) - continue; + if (strncmp(file, *f, len) == 0) + break; } - msg->value.buf[sizeof(msg->value.buf) - 1] = '\0'; - if (f == NULL) { - LLOG_WARNX("not authorized to open %s", msg->value.buf); - msg->value.integer = -1; - priv_send_back(msg); + if (*f == NULL) { + LLOG_WARNX("[priv]: not authorized to open %s", file); + rc = -1; + must_write(remote, &rc, sizeof(int)); + free(file); return; } if ((fd = open(*f, 0)) == -1) { - msg->value.integer = -1; - priv_send_back(msg); + LLOG_WARN("[priv]: unable to open %s", *f); + rc = -1; + must_write(remote, &rc, sizeof(int)); + free(file); return; } - msg->value.integer = fd; - priv_send_back(msg); - priv_send_fd(fd); + free(file); + must_write(remote, &fd, sizeof(int)); + send_fd(remote, fd); close(fd); } +void +asroot_ethtool() +{ + struct ifreq ifr; + struct ethtool_cmd ethc; + int len, rc, sock; + char *ifname; + + memset(&ifr, 0, sizeof(ifr)); + memset(ðc, 0, sizeof(ethc)); + must_read(remote, &len, sizeof(int)); + if ((ifname = (char*)malloc(len + 1)) == NULL) + fatal(NULL); + must_read(remote, ifname, len + 1); + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_data = (caddr_t)ðc; + ethc.cmd = ETHTOOL_GSET; + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + LLOG_WARN("[priv]: unable to get a socket"); + must_write(remote, &sock, sizeof(int)); + free(ifname); + return; + } + if ((rc = ioctl(sock, SIOCETHTOOL, &ifr)) != 0) { + LLOG_WARN("[priv]: unable to ioctl ETHTOOL for %s", ifname); + must_write(remote, &rc, sizeof(int)); + free(ifname); + close(sock); + return; + } + close(sock); + must_write(remote, &rc, sizeof(int)); + must_write(remote, ðc, sizeof(struct ethtool_cmd)); +} + struct dispatch_actions { int msg; - void(*function)(struct priv_msg *); + void(*function)(void); }; struct dispatch_actions actions[] = { - {PRIV_FORK, priv_fork_daemon}, - {PRIV_CREATE_CTL_SOCKET, priv_create_ctl_socket}, - {PRIV_DELETE_CTL_SOCKET, priv_delete_ctl_socket}, - {PRIV_GET_HOSTNAME, priv_get_hostname}, - {PRIV_OPEN, priv_open_readonly}, - {0, NULL} + {PRIV_FORK, asroot_fork}, + {PRIV_CREATE_CTL_SOCKET, asroot_ctl_create}, + {PRIV_DELETE_CTL_SOCKET, asroot_ctl_cleanup}, + {PRIV_GET_HOSTNAME, asroot_gethostbyname}, + {PRIV_OPEN, asroot_open}, + {PRIV_ETHTOOL, asroot_ethtool}, + {-1, NULL} }; /* Main loop, run as root */ void priv_loop() { - struct priv_msg msg; + int cmd; struct dispatch_actions *a; - while (read(remote, &msg, sizeof(struct priv_msg)) == - sizeof(struct priv_msg)) { + while (!may_read(remote, &cmd, sizeof(int))) { for (a = actions; a->function != NULL; a++) { - if (msg.msg == a->msg) { - a->function(&msg); + if (cmd == a->msg) { + a->function(); break; } } if (a->function == NULL) - fatal("bogus message received"); + fatal("[priv]: bogus message received"); } /* Should never be there */ } @@ -323,18 +311,29 @@ priv_exit() int status; int rc; if ((rc = waitpid(monitored, &status, WNOHANG)) == 0) { - LLOG_DEBUG("killing child"); + LLOG_DEBUG("[priv]: killing child"); kill(monitored, SIGTERM); } if ((rc = waitpid(monitored, &status, WNOHANG)) == -1) _exit(0); - LLOG_DEBUG("waiting for child %d to terminate", monitored); + LLOG_DEBUG("[priv]: waiting for child %d to terminate", monitored); } -void -priv_shutdown(int sig) +/* If priv parent gets a TERM or HUP, pass it through to child instead */ +static void +sig_pass_to_chld(int sig) { - LLOG_DEBUG("received signal %d, exiting", sig); + int oerrno = errno; + if (monitored != -1) + kill(monitored, sig); + errno = oerrno; +} + +/* if parent gets a SIGCHLD, it will exit */ +static void +sig_chld(int sig) +{ + LLOG_DEBUG("[priv]: received signal %d, exiting", sig); priv_exit(); } @@ -351,29 +350,37 @@ priv_init() uid_t uid; struct group *group; gid_t gid; + gid_t gidset[1]; /* Create socket pair */ - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - fatal("unable to create socket pair for privilege separation"); + if (socketpair(AF_LOCAL, SOCK_DGRAM, PF_UNSPEC, pair) < 0) + fatal("[priv]: unable to create socket pair for privilege separation"); /* Get users */ if ((user = getpwnam(PRIVSEP_USER)) == NULL) - fatal("no " PRIVSEP_USER " user for privilege separation"); + fatal("[priv]: no " PRIVSEP_USER " user for privilege separation"); uid = user->pw_uid; if ((group = getgrnam(PRIVSEP_GROUP)) == NULL) - fatal("no " PRIVSEP_GROUP " group for privilege separation"); + fatal("[priv]: no " PRIVSEP_GROUP " group for privilege separation"); gid = group->gr_gid; /* Spawn off monitor */ if ((monitored = fork()) < 0) - fatal("unable to fork monitor"); + fatal("[priv]: unable to fork monitor"); switch (monitored) { case 0: /* We are in the children, drop privileges */ if (chroot(PRIVSEP_CHROOT) == -1) - fatal("unable to chroot"); - if ((setgid(gid) == -1) || (setuid(uid) == -1)) - fatal("unable to drop privileges"); + fatal("[priv]: unable to chroot"); + if (chdir("/") != 0) + fatal("[priv]: unable to chdir"); + gidset[0] = gid; + if (setresgid(gid, gid, gid) == -1) + fatal("[priv]: setresgid() failed"); + if (setgroups(1, gidset) == -1) + fatal("[priv]: setgroups() failed"); + if (setresuid(uid, uid, uid) == -1) + fatal("[priv]: setresuid() failed"); remote = pair[0]; close(pair[1]); break; @@ -382,13 +389,100 @@ priv_init() remote = pair[1]; close(pair[0]); if (atexit(priv_exit) != 0) - fatal("unable to set exit function"); - signal(SIGHUP, priv_shutdown); - signal(SIGTERM, priv_shutdown); - signal(SIGINT, priv_shutdown); - signal(SIGCHLD, priv_shutdown); + fatal("[priv]: unable to set exit function"); + + signal(SIGALRM, sig_pass_to_chld); + signal(SIGTERM, sig_pass_to_chld); + signal(SIGHUP, sig_pass_to_chld); + signal(SIGINT, sig_pass_to_chld); + signal(SIGQUIT, sig_pass_to_chld); + signal(SIGCHLD, sig_chld); priv_loop(); exit(0); } } + +/* Stolen from sbin/pflogd/privsep.c from OpenBSD */ +/* + * Copyright (c) 2003 Can Erkin Acar + * Copyright (c) 2003 Anil Madhavapeddy + * + * 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. + */ + +/* Read all data or return 1 for error. */ +static int +may_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while (n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + return (1); + default: + pos += res; + } + } + return (0); +} + +/* Read data with the assertion that it all must come through, or + * else abort the process. Based on atomicio() from openssh. */ +static void +must_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while (n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} + +/* Write data with the assertion that it all has to be written, or + * else abort the process. Based on atomicio() from openssh. */ +static void +must_write(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while (n > pos) { + res = write(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} diff --git a/src/privsep_fdpass.c b/src/privsep_fdpass.c new file mode 100644 index 00000000..7235dfb8 --- /dev/null +++ b/src/privsep_fdpass.c @@ -0,0 +1,130 @@ +/* + * Copyright 2001 Niels Provos + * All rights reserved. + * + * Copyright (c) 2002 Matthieu Herrb + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "lldpd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void +send_fd(int sock, int fd) +{ + struct msghdr msg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + struct cmsghdr *cmsg; + struct iovec vec; + int result = 0; + ssize_t n; + + memset(&msg, 0, sizeof(msg)); + + if (fd >= 0) { + msg.msg_control = (caddr_t)&cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = fd; + } else { + result = errno; + } + + vec.iov_base = &result; + vec.iov_len = sizeof(int); + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + + if ((n = sendmsg(sock, &msg, 0)) == -1) + LLOG_WARN("sendmsg(%d)", sock); + if (n != sizeof(int)) + LLOG_WARNX("sendmsg: expected sent 1 got %ld", + (long)n); +} + +int +receive_fd(int sock) +{ + struct msghdr msg; + union { + struct cmsghdr hdr; + char buf[CMSG_SPACE(sizeof(int))]; + } cmsgbuf; + struct cmsghdr *cmsg; + struct iovec vec; + ssize_t n; + int result; + int fd; + + memset(&msg, 0, sizeof(msg)); + vec.iov_base = &result; + vec.iov_len = sizeof(int); + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_control = &cmsgbuf.buf; + msg.msg_controllen = sizeof(cmsgbuf.buf); + + if ((n = recvmsg(sock, &msg, 0)) == -1) + LLOG_WARN("recvmsg"); + if (n != sizeof(int)) + LLOG_WARNX("recvmsg: expected received 1 got %ld", + (long)n); + if (result == 0) { + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg == NULL) { + LLOG_WARNX("no message header"); + return -1; + } + if (cmsg->cmsg_type != SCM_RIGHTS) + LLOG_WARNX("expected type %d got %d", + SCM_RIGHTS, cmsg->cmsg_type); + fd = (*(int *)CMSG_DATA(cmsg)); + return fd; + } else { + errno = result; + return -1; + } +}