/* $NetBSD: bl.c,v 1.28 2016/07/29 17:13:09 christos Exp $ */ /*- * Copyright (c) 2014 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Christos Zoulas. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include __RCSID("$NetBSD: bl.c,v 1.28 2016/07/29 17:13:09 christos Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _REENTRANT #include #endif #include "bl.h" typedef struct { uint32_t bl_len; uint32_t bl_version; uint32_t bl_type; uint32_t bl_salen; struct sockaddr_storage bl_ss; char bl_data[]; } bl_message_t; struct blacklist { #ifdef _REENTRANT pthread_mutex_t b_mutex; # define BL_INIT(b) pthread_mutex_init(&b->b_mutex, NULL) # define BL_LOCK(b) pthread_mutex_lock(&b->b_mutex) # define BL_UNLOCK(b) pthread_mutex_unlock(&b->b_mutex) #else # define BL_INIT(b) do {} while(/*CONSTCOND*/0) # define BL_LOCK(b) BL_INIT(b) # define BL_UNLOCK(b) BL_INIT(b) #endif int b_fd; int b_connected; struct sockaddr_un b_sun; void (*b_fun)(int, const char *, va_list); bl_info_t b_info; }; #define BL_VERSION 1 bool bl_isconnected(bl_t b) { return b->b_connected == 0; } int bl_getfd(bl_t b) { return b->b_fd; } static void bl_reset(bl_t b, bool locked) { int serrno = errno; if (!locked) BL_LOCK(b); close(b->b_fd); errno = serrno; b->b_fd = -1; b->b_connected = -1; if (!locked) BL_UNLOCK(b); } static void bl_log(void (*fun)(int, const char *, va_list), int level, const char *fmt, ...) { va_list ap; int serrno = errno; va_start(ap, fmt); (*fun)(level, fmt, ap); va_end(ap); errno = serrno; } static int bl_init(bl_t b, bool srv) { static int one = 1; /* AF_UNIX address of local logger */ mode_t om; int rv, serrno; struct sockaddr_un *sun = &b->b_sun; #ifndef SOCK_NONBLOCK #define SOCK_NONBLOCK 0 #endif #ifndef SOCK_CLOEXEC #define SOCK_CLOEXEC 0 #endif #ifndef SOCK_NOSIGPIPE #define SOCK_NOSIGPIPE 0 #endif BL_LOCK(b); if (b->b_fd == -1) { b->b_fd = socket(PF_LOCAL, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK|SOCK_NOSIGPIPE, 0); if (b->b_fd == -1) { bl_log(b->b_fun, LOG_ERR, "%s: socket failed (%s)", __func__, strerror(errno)); BL_UNLOCK(b); return -1; } #if SOCK_CLOEXEC == 0 fcntl(b->b_fd, F_SETFD, FD_CLOEXEC); #endif #if SOCK_NONBLOCK == 0 fcntl(b->b_fd, F_SETFL, fcntl(b->b_fd, F_GETFL) | O_NONBLOCK); #endif #if SOCK_NOSIGPIPE == 0 #ifdef SO_NOSIGPIPE int o = 1; setsockopt(b->b_fd, SOL_SOCKET, SO_NOSIGPIPE, &o, sizeof(o)); #else signal(SIGPIPE, SIG_IGN); #endif #endif } if (bl_isconnected(b)) { BL_UNLOCK(b); return 0; } /* * We try to connect anyway even when we are a server to verify * that no other server is listening to the socket. If we succeed * to connect and we are a server, someone else owns it. */ rv = connect(b->b_fd, (const void *)sun, (socklen_t)sizeof(*sun)); if (rv == 0) { if (srv) { bl_log(b->b_fun, LOG_ERR, "%s: another daemon is handling `%s'", __func__, sun->sun_path); goto out; } } else { if (!srv) { /* * If the daemon is not running, we just try a * connect, so leave the socket alone until it does * and only log once. */ if (b->b_connected != 1) { bl_log(b->b_fun, LOG_DEBUG, "%s: connect failed for `%s' (%s)", __func__, sun->sun_path, strerror(errno)); b->b_connected = 1; } BL_UNLOCK(b); return -1; } bl_log(b->b_fun, LOG_DEBUG, "Connected to blacklist server", __func__); } if (srv) { (void)unlink(sun->sun_path); om = umask(0); rv = bind(b->b_fd, (const void *)sun, (socklen_t)sizeof(*sun)); serrno = errno; (void)umask(om); errno = serrno; if (rv == -1) { bl_log(b->b_fun, LOG_ERR, "%s: bind failed for `%s' (%s)", __func__, sun->sun_path, strerror(errno)); goto out; } } b->b_connected = 0; #define GOT_FD 1 #if defined(LOCAL_CREDS) #define CRED_LEVEL 0 #define CRED_NAME LOCAL_CREDS #define CRED_SC_UID sc_euid #define CRED_SC_GID sc_egid #define CRED_MESSAGE SCM_CREDS #define CRED_SIZE SOCKCREDSIZE(NGROUPS_MAX) #define CRED_TYPE struct sockcred #define GOT_CRED 2 #elif defined(SO_PASSCRED) #define CRED_LEVEL SOL_SOCKET #define CRED_NAME SO_PASSCRED #define CRED_SC_UID uid #define CRED_SC_GID gid #define CRED_MESSAGE SCM_CREDENTIALS #define CRED_SIZE sizeof(struct ucred) #define CRED_TYPE struct ucred #define GOT_CRED 2 #else #define GOT_CRED 0 /* * getpeereid() and LOCAL_PEERCRED don't help here * because we are not a stream socket! */ #define CRED_SIZE 0 #define CRED_TYPE void * __unused #endif #ifdef CRED_LEVEL if (setsockopt(b->b_fd, CRED_LEVEL, CRED_NAME, &one, (socklen_t)sizeof(one)) == -1) { bl_log(b->b_fun, LOG_ERR, "%s: setsockopt %s " "failed (%s)", __func__, __STRING(CRED_NAME), strerror(errno)); goto out; } #endif BL_UNLOCK(b); return 0; out: bl_reset(b, true); BL_UNLOCK(b); return -1; } bl_t bl_create(bool srv, const char *path, void (*fun)(int, const char *, va_list)) { bl_t b = calloc(1, sizeof(*b)); if (b == NULL) goto out; b->b_fun = fun == NULL ? vsyslog : fun; b->b_fd = -1; b->b_connected = -1; BL_INIT(b); memset(&b->b_sun, 0, sizeof(b->b_sun)); b->b_sun.sun_family = AF_LOCAL; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN b->b_sun.sun_len = sizeof(b->b_sun); #endif strlcpy(b->b_sun.sun_path, path ? path : _PATH_BLSOCK, sizeof(b->b_sun.sun_path)); bl_init(b, srv); return b; out: free(b); bl_log(fun, LOG_ERR, "%s: malloc failed (%s)", __func__, strerror(errno)); return NULL; } void bl_destroy(bl_t b) { bl_reset(b, false); free(b); } static int bl_getsock(bl_t b, struct sockaddr_storage *ss, const struct sockaddr *sa, socklen_t slen, const char *ctx) { uint8_t family; memset(ss, 0, sizeof(*ss)); switch (slen) { case 0: return 0; case sizeof(struct sockaddr_in): family = AF_INET; break; case sizeof(struct sockaddr_in6): family = AF_INET6; break; default: bl_log(b->b_fun, LOG_ERR, "%s: invalid socket len %u (%s)", __func__, (unsigned)slen, ctx); errno = EINVAL; return -1; } memcpy(ss, sa, slen); if (ss->ss_family != family) { bl_log(b->b_fun, LOG_INFO, "%s: correcting socket family %d to %d (%s)", __func__, ss->ss_family, family, ctx); ss->ss_family = family; } #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN if (ss->ss_len != slen) { bl_log(b->b_fun, LOG_INFO, "%s: correcting socket len %u to %u (%s)", __func__, ss->ss_len, (unsigned)slen, ctx); ss->ss_len = (uint8_t)slen; } #endif return 0; } int bl_send(bl_t b, bl_type_t e, int pfd, const struct sockaddr *sa, socklen_t slen, const char *ctx) { struct msghdr msg; struct iovec iov; union { char ctrl[CMSG_SPACE(sizeof(int))]; uint32_t fd; } ua; struct cmsghdr *cmsg; union { bl_message_t bl; char buf[512]; } ub; size_t ctxlen, tried; #define NTRIES 5 ctxlen = strlen(ctx); if (ctxlen > 128) ctxlen = 128; iov.iov_base = ub.buf; iov.iov_len = sizeof(bl_message_t) + ctxlen; ub.bl.bl_len = (uint32_t)iov.iov_len; ub.bl.bl_version = BL_VERSION; ub.bl.bl_type = (uint32_t)e; if (bl_getsock(b, &ub.bl.bl_ss, sa, slen, ctx) == -1) return -1; ub.bl.bl_salen = slen; memcpy(ub.bl.bl_data, ctx, ctxlen); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = ua.ctrl; msg.msg_controllen = sizeof(ua.ctrl); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; memcpy(CMSG_DATA(cmsg), &pfd, sizeof(pfd)); tried = 0; again: if (bl_init(b, false) == -1) return -1; if ((sendmsg(b->b_fd, &msg, 0) == -1) && tried++ < NTRIES) { bl_reset(b, false); goto again; } return tried >= NTRIES ? -1 : 0; } bl_info_t * bl_recv(bl_t b) { struct msghdr msg; struct iovec iov; union { char ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(CRED_SIZE)]; uint32_t fd; CRED_TYPE sc; } ua; struct cmsghdr *cmsg; CRED_TYPE *sc; union { bl_message_t bl; char buf[512]; } ub; int got; ssize_t rlen; bl_info_t *bi = &b->b_info; got = 0; memset(bi, 0, sizeof(*bi)); iov.iov_base = ub.buf; iov.iov_len = sizeof(ub); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_flags = 0; msg.msg_control = ua.ctrl; msg.msg_controllen = sizeof(ua.ctrl) + 100; rlen = recvmsg(b->b_fd, &msg, 0); if (rlen == -1) { bl_log(b->b_fun, LOG_ERR, "%s: recvmsg failed (%s)", __func__, strerror(errno)); return NULL; } for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET) { bl_log(b->b_fun, LOG_ERR, "%s: unexpected cmsg_level %d", __func__, cmsg->cmsg_level); continue; } switch (cmsg->cmsg_type) { case SCM_RIGHTS: if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) { bl_log(b->b_fun, LOG_ERR, "%s: unexpected cmsg_len %d != %zu", __func__, cmsg->cmsg_len, CMSG_LEN(2 * sizeof(int))); continue; } memcpy(&bi->bi_fd, CMSG_DATA(cmsg), sizeof(bi->bi_fd)); got |= GOT_FD; break; #ifdef CRED_MESSAGE case CRED_MESSAGE: sc = (void *)CMSG_DATA(cmsg); bi->bi_uid = sc->CRED_SC_UID; bi->bi_gid = sc->CRED_SC_GID; got |= GOT_CRED; break; #endif default: bl_log(b->b_fun, LOG_ERR, "%s: unexpected cmsg_type %d", __func__, cmsg->cmsg_type); continue; } } if (got != (GOT_CRED|GOT_FD)) { bl_log(b->b_fun, LOG_ERR, "message missing %s %s", #if GOT_CRED != 0 (got & GOT_CRED) == 0 ? "cred" : #endif "", (got & GOT_FD) == 0 ? "fd" : ""); return NULL; } if ((size_t)rlen <= sizeof(ub.bl)) { bl_log(b->b_fun, LOG_ERR, "message too short %zd", rlen); return NULL; } if (ub.bl.bl_version != BL_VERSION) { bl_log(b->b_fun, LOG_ERR, "bad version %d", ub.bl.bl_version); return NULL; } bi->bi_type = ub.bl.bl_type; bi->bi_slen = ub.bl.bl_salen; bi->bi_ss = ub.bl.bl_ss; #ifndef CRED_MESSAGE bi->bi_uid = -1; bi->bi_gid = -1; #endif strlcpy(bi->bi_msg, ub.bl.bl_data, MIN(sizeof(bi->bi_msg), ((size_t)rlen - sizeof(ub.bl) + 1))); return bi; }