Changeset View
Changeset View
Standalone View
Standalone View
contrib/blacklist/lib/bl.c
- This file was added.
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/* $NetBSD: bl.c,v 1.27 2015/12/30 16:42:48 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 <sys/cdefs.h> | |||||
__RCSID("$NetBSD: bl.c,v 1.27 2015/12/30 16:42:48 christos Exp $"); | |||||
#include <sys/param.h> | |||||
#include <sys/types.h> | |||||
#include <sys/socket.h> | |||||
#include <sys/stat.h> | |||||
#include <sys/un.h> | |||||
#include <stdio.h> | |||||
#include <string.h> | |||||
#include <syslog.h> | |||||
#include <signal.h> | |||||
#include <fcntl.h> | |||||
#include <stdlib.h> | |||||
#include <unistd.h> | |||||
#include <stdint.h> | |||||
#include <stdbool.h> | |||||
#include <errno.h> | |||||
#include <stdarg.h> | |||||
#include <netinet/in.h> | |||||
#ifdef _REENTRANT | |||||
#include <pthread.h> | |||||
#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 (%m)", | |||||
__func__); | |||||
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' (%m)", | |||||
__func__, sun->sun_path); | |||||
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' (%m)", | |||||
__func__, sun->sun_path); | |||||
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 (%m)", __func__, __STRING(CRED_NAME)); | |||||
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 (%m)", __func__); | |||||
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 (%m)", __func__); | |||||
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; | |||||
} |