1 /* $NetBSD: bl.c,v 1.3 2024/08/02 17:11:55 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2014 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Christos Zoulas. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 #ifdef HAVE_CONFIG_H 32 #include "config.h" 33 #endif 34 35 #include <sys/cdefs.h> 36 __RCSID("$NetBSD: bl.c,v 1.3 2024/08/02 17:11:55 christos Exp $"); 37 38 #include <sys/param.h> 39 #include <sys/types.h> 40 #include <sys/socket.h> 41 #include <sys/stat.h> 42 #include <sys/un.h> 43 44 #include <stdio.h> 45 #include <string.h> 46 #include <syslog.h> 47 #include <signal.h> 48 #include <fcntl.h> 49 #include <stdlib.h> 50 #include <unistd.h> 51 #include <stdint.h> 52 #include <stdbool.h> 53 #include <errno.h> 54 #include <stdarg.h> 55 #include <netinet/in.h> 56 #ifdef _REENTRANT 57 #include <pthread.h> 58 #endif 59 60 #include "bl.h" 61 62 #ifndef SYSLOG_DATA_INIT 63 struct syslog_data { 64 int dummy; 65 }; 66 #define SYSLOG_DATA_INIT { 0 } 67 68 static void 69 vsyslog_r(int priority, struct syslog_data *sd, const char *fmt, va_list ap) 70 { 71 vsyslog(priority, fmt, ap); 72 } 73 #endif 74 75 typedef struct { 76 uint32_t bl_len; 77 uint32_t bl_version; 78 uint32_t bl_type; 79 uint32_t bl_salen; 80 struct sockaddr_storage bl_ss; 81 char bl_data[]; 82 } bl_message_t; 83 84 struct blocklist { 85 #ifdef _REENTRANT 86 pthread_mutex_t b_mutex; 87 # define BL_INIT(b) pthread_mutex_init(&b->b_mutex, NULL) 88 # define BL_LOCK(b) pthread_mutex_lock(&b->b_mutex) 89 # define BL_UNLOCK(b) pthread_mutex_unlock(&b->b_mutex) 90 #else 91 # define BL_INIT(b) do {} while(/*CONSTCOND*/0) 92 # define BL_LOCK(b) BL_INIT(b) 93 # define BL_UNLOCK(b) BL_INIT(b) 94 #endif 95 int b_fd; 96 int b_connected; 97 struct sockaddr_un b_sun; 98 struct syslog_data b_syslog_data; 99 void (*b_fun)(int, struct syslog_data *, const char *, va_list); 100 bl_info_t b_info; 101 }; 102 103 #define BL_VERSION 1 104 105 bool 106 bl_isconnected(bl_t b) 107 { 108 return b->b_connected == 0; 109 } 110 111 int 112 bl_getfd(bl_t b) 113 { 114 return b->b_fd; 115 } 116 117 static void 118 bl_reset(bl_t b, bool locked) 119 { 120 int serrno = errno; 121 if (!locked) 122 BL_LOCK(b); 123 close(b->b_fd); 124 errno = serrno; 125 b->b_fd = -1; 126 b->b_connected = -1; 127 if (!locked) 128 BL_UNLOCK(b); 129 } 130 131 static void 132 bl_log(bl_t b, int level, const char *fmt, ...) 133 { 134 va_list ap; 135 int serrno = errno; 136 137 if (b->b_fun == NULL) 138 return; 139 140 va_start(ap, fmt); 141 (*b->b_fun)(level, &b->b_syslog_data, fmt, ap); 142 va_end(ap); 143 errno = serrno; 144 } 145 146 static int 147 bl_init(bl_t b, bool srv) 148 { 149 static int one = 1; 150 /* AF_UNIX address of local logger */ 151 mode_t om; 152 int rv, serrno; 153 struct sockaddr_un *sun = &b->b_sun; 154 155 #ifndef SOCK_NONBLOCK 156 #define SOCK_NONBLOCK 0 157 #endif 158 #ifndef SOCK_CLOEXEC 159 #define SOCK_CLOEXEC 0 160 #endif 161 #ifndef SOCK_NOSIGPIPE 162 #define SOCK_NOSIGPIPE 0 163 #endif 164 165 BL_LOCK(b); 166 167 if (b->b_fd == -1) { 168 b->b_fd = socket(PF_LOCAL, 169 SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK|SOCK_NOSIGPIPE, 0); 170 if (b->b_fd == -1) { 171 bl_log(b, LOG_ERR, "%s: socket failed (%s)", 172 __func__, strerror(errno)); 173 BL_UNLOCK(b); 174 return -1; 175 } 176 #if SOCK_CLOEXEC == 0 177 fcntl(b->b_fd, F_SETFD, FD_CLOEXEC); 178 #endif 179 #if SOCK_NONBLOCK == 0 180 fcntl(b->b_fd, F_SETFL, fcntl(b->b_fd, F_GETFL) | O_NONBLOCK); 181 #endif 182 #if SOCK_NOSIGPIPE == 0 183 #ifdef SO_NOSIGPIPE 184 int o = 1; 185 setsockopt(b->b_fd, SOL_SOCKET, SO_NOSIGPIPE, &o, sizeof(o)); 186 #else 187 signal(SIGPIPE, SIG_IGN); 188 #endif 189 #endif 190 } 191 192 if (bl_isconnected(b)) { 193 BL_UNLOCK(b); 194 return 0; 195 } 196 197 /* 198 * We try to connect anyway even when we are a server to verify 199 * that no other server is listening to the socket. If we succeed 200 * to connect and we are a server, someone else owns it. 201 */ 202 rv = connect(b->b_fd, (const void *)sun, (socklen_t)sizeof(*sun)); 203 if (rv == 0) { 204 if (srv) { 205 bl_log(b, LOG_ERR, 206 "%s: another daemon is handling `%s'", 207 __func__, sun->sun_path); 208 goto out; 209 } 210 } else { 211 if (!srv) { 212 /* 213 * If the daemon is not running, we just try a 214 * connect, so leave the socket alone until it does 215 * and only log once. 216 */ 217 if (b->b_connected != 1) { 218 bl_log(b, LOG_DEBUG, 219 "%s: connect failed for `%s' (%s)", 220 __func__, sun->sun_path, strerror(errno)); 221 b->b_connected = 1; 222 } 223 BL_UNLOCK(b); 224 return -1; 225 } 226 bl_log(b, LOG_DEBUG, "Connected to blocklist server", __func__); 227 } 228 229 if (srv) { 230 (void)unlink(sun->sun_path); 231 om = umask(0); 232 rv = bind(b->b_fd, (const void *)sun, (socklen_t)sizeof(*sun)); 233 serrno = errno; 234 (void)umask(om); 235 errno = serrno; 236 if (rv == -1) { 237 bl_log(b, LOG_ERR, "%s: bind failed for `%s' (%s)", 238 __func__, sun->sun_path, strerror(errno)); 239 goto out; 240 } 241 } 242 243 b->b_connected = 0; 244 #define GOT_FD 1 245 #if defined(LOCAL_CREDS) 246 #define CRED_LEVEL 0 247 #define CRED_NAME LOCAL_CREDS 248 #define CRED_SC_UID sc_euid 249 #define CRED_SC_GID sc_egid 250 #define CRED_MESSAGE SCM_CREDS 251 #define CRED_SIZE SOCKCREDSIZE(NGROUPS_MAX) 252 #define CRED_TYPE struct sockcred 253 #define GOT_CRED 2 254 #elif defined(SO_PASSCRED) 255 #define CRED_LEVEL SOL_SOCKET 256 #define CRED_NAME SO_PASSCRED 257 #define CRED_SC_UID uid 258 #define CRED_SC_GID gid 259 #define CRED_MESSAGE SCM_CREDENTIALS 260 #define CRED_SIZE sizeof(struct ucred) 261 #define CRED_TYPE struct ucred 262 #define GOT_CRED 2 263 #else 264 #define GOT_CRED 0 265 /* 266 * getpeereid() and LOCAL_PEERCRED don't help here 267 * because we are not a stream socket! 268 */ 269 #define CRED_SIZE 0 270 #define CRED_TYPE void * __unused 271 #endif 272 273 #ifdef CRED_LEVEL 274 if (setsockopt(b->b_fd, CRED_LEVEL, CRED_NAME, 275 &one, (socklen_t)sizeof(one)) == -1) { 276 bl_log(b, LOG_ERR, "%s: setsockopt %s " 277 "failed (%s)", __func__, __STRING(CRED_NAME), 278 strerror(errno)); 279 goto out; 280 } 281 #endif 282 283 BL_UNLOCK(b); 284 return 0; 285 out: 286 bl_reset(b, true); 287 BL_UNLOCK(b); 288 return -1; 289 } 290 291 bl_t 292 bl_create(bool srv, const char *path, 293 void (*fun)(int, struct syslog_data *, const char *, va_list)) 294 { 295 static struct syslog_data sd = SYSLOG_DATA_INIT; 296 bl_t b = calloc(1, sizeof(*b)); 297 if (b == NULL) 298 return NULL; 299 b->b_fun = fun; 300 b->b_syslog_data = sd; 301 b->b_fd = -1; 302 b->b_connected = -1; 303 BL_INIT(b); 304 305 memset(&b->b_sun, 0, sizeof(b->b_sun)); 306 b->b_sun.sun_family = AF_LOCAL; 307 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN 308 b->b_sun.sun_len = sizeof(b->b_sun); 309 #endif 310 strlcpy(b->b_sun.sun_path, 311 path ? path : _PATH_BLSOCK, sizeof(b->b_sun.sun_path)); 312 313 bl_init(b, srv); 314 return b; 315 } 316 317 void 318 bl_destroy(bl_t b) 319 { 320 bl_reset(b, false); 321 free(b); 322 } 323 324 static int 325 bl_getsock(bl_t b, struct sockaddr_storage *ss, const struct sockaddr *sa, 326 socklen_t slen, const char *ctx) 327 { 328 uint8_t family; 329 330 memset(ss, 0, sizeof(*ss)); 331 332 switch (slen) { 333 case 0: 334 return 0; 335 case sizeof(struct sockaddr_in): 336 family = AF_INET; 337 break; 338 case sizeof(struct sockaddr_in6): 339 family = AF_INET6; 340 break; 341 default: 342 bl_log(b, LOG_ERR, "%s: invalid socket len %u (%s)", 343 __func__, (unsigned)slen, ctx); 344 errno = EINVAL; 345 return -1; 346 } 347 348 memcpy(ss, sa, slen); 349 350 if (ss->ss_family != family) { 351 bl_log(b, LOG_INFO, 352 "%s: correcting socket family %d to %d (%s)", 353 __func__, ss->ss_family, family, ctx); 354 ss->ss_family = family; 355 } 356 357 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN 358 if (ss->ss_len != slen) { 359 bl_log(b, LOG_INFO, 360 "%s: correcting socket len %u to %u (%s)", 361 __func__, ss->ss_len, (unsigned)slen, ctx); 362 ss->ss_len = (uint8_t)slen; 363 } 364 #endif 365 return 0; 366 } 367 368 int 369 bl_send(bl_t b, bl_type_t e, int pfd, const struct sockaddr *sa, 370 socklen_t slen, const char *ctx) 371 { 372 struct msghdr msg; 373 struct iovec iov; 374 union { 375 char ctrl[CMSG_SPACE(sizeof(int))]; 376 uint32_t fd; 377 } ua; 378 struct cmsghdr *cmsg; 379 union { 380 bl_message_t bl; 381 char buf[512]; 382 } ub; 383 size_t ctxlen, tried; 384 #define NTRIES 5 385 386 ctxlen = strlen(ctx); 387 if (ctxlen > 128) 388 ctxlen = 128; 389 390 iov.iov_base = ub.buf; 391 iov.iov_len = sizeof(bl_message_t) + ctxlen; 392 ub.bl.bl_len = (uint32_t)iov.iov_len; 393 ub.bl.bl_version = BL_VERSION; 394 ub.bl.bl_type = (uint32_t)e; 395 396 if (bl_getsock(b, &ub.bl.bl_ss, sa, slen, ctx) == -1) 397 return -1; 398 399 400 ub.bl.bl_salen = slen; 401 memcpy(ub.bl.bl_data, ctx, ctxlen); 402 403 msg.msg_name = NULL; 404 msg.msg_namelen = 0; 405 msg.msg_iov = &iov; 406 msg.msg_iovlen = 1; 407 msg.msg_flags = 0; 408 409 msg.msg_control = ua.ctrl; 410 msg.msg_controllen = sizeof(ua.ctrl); 411 412 cmsg = CMSG_FIRSTHDR(&msg); 413 cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 414 cmsg->cmsg_level = SOL_SOCKET; 415 cmsg->cmsg_type = SCM_RIGHTS; 416 417 memcpy(CMSG_DATA(cmsg), &pfd, sizeof(pfd)); 418 419 tried = 0; 420 again: 421 if (bl_init(b, false) == -1) 422 return -1; 423 424 if ((sendmsg(b->b_fd, &msg, 0) == -1) && tried++ < NTRIES) { 425 bl_reset(b, false); 426 goto again; 427 } 428 return tried >= NTRIES ? -1 : 0; 429 } 430 431 bl_info_t * 432 bl_recv(bl_t b) 433 { 434 struct msghdr msg; 435 struct iovec iov; 436 union { 437 char ctrl[CMSG_SPACE(sizeof(int)) + CMSG_SPACE(CRED_SIZE)]; 438 uint32_t fd; 439 CRED_TYPE sc; 440 } ua; 441 struct cmsghdr *cmsg; 442 CRED_TYPE *sc; 443 union { 444 bl_message_t bl; 445 char buf[512]; 446 } ub; 447 int got; 448 ssize_t rlen; 449 size_t rem; 450 bl_info_t *bi = &b->b_info; 451 452 got = 0; 453 memset(bi, 0, sizeof(*bi)); 454 455 iov.iov_base = ub.buf; 456 iov.iov_len = sizeof(ub); 457 458 msg.msg_name = NULL; 459 msg.msg_namelen = 0; 460 msg.msg_iov = &iov; 461 msg.msg_iovlen = 1; 462 msg.msg_flags = 0; 463 464 msg.msg_control = ua.ctrl; 465 msg.msg_controllen = sizeof(ua.ctrl) + 100; 466 467 rlen = recvmsg(b->b_fd, &msg, 0); 468 if (rlen == -1) { 469 bl_log(b, LOG_ERR, "%s: recvmsg failed (%s)", __func__, 470 strerror(errno)); 471 return NULL; 472 } 473 474 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { 475 if (cmsg->cmsg_level != SOL_SOCKET) { 476 bl_log(b, LOG_ERR, 477 "%s: unexpected cmsg_level %d", 478 __func__, cmsg->cmsg_level); 479 continue; 480 } 481 switch (cmsg->cmsg_type) { 482 case SCM_RIGHTS: 483 if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) { 484 bl_log(b, LOG_ERR, 485 "%s: unexpected cmsg_len %d != %zu", 486 __func__, cmsg->cmsg_len, 487 CMSG_LEN(2 * sizeof(int))); 488 continue; 489 } 490 memcpy(&bi->bi_fd, CMSG_DATA(cmsg), sizeof(bi->bi_fd)); 491 got |= GOT_FD; 492 break; 493 #ifdef CRED_MESSAGE 494 case CRED_MESSAGE: 495 sc = (void *)CMSG_DATA(cmsg); 496 bi->bi_uid = sc->CRED_SC_UID; 497 bi->bi_gid = sc->CRED_SC_GID; 498 got |= GOT_CRED; 499 break; 500 #endif 501 default: 502 bl_log(b, LOG_ERR, 503 "%s: unexpected cmsg_type %d", 504 __func__, cmsg->cmsg_type); 505 continue; 506 } 507 508 } 509 510 if (got != (GOT_CRED|GOT_FD)) { 511 bl_log(b, LOG_ERR, "message missing %s %s", 512 #if GOT_CRED != 0 513 (got & GOT_CRED) == 0 ? "cred" : 514 #endif 515 "", (got & GOT_FD) == 0 ? "fd" : ""); 516 return NULL; 517 } 518 519 rem = (size_t)rlen; 520 if (rem < sizeof(ub.bl)) { 521 bl_log(b, LOG_ERR, "message too short %zd", rlen); 522 return NULL; 523 } 524 rem -= sizeof(ub.bl); 525 526 if (ub.bl.bl_version != BL_VERSION) { 527 bl_log(b, LOG_ERR, "bad version %d", ub.bl.bl_version); 528 return NULL; 529 } 530 531 bi->bi_type = ub.bl.bl_type; 532 bi->bi_slen = ub.bl.bl_salen; 533 bi->bi_ss = ub.bl.bl_ss; 534 #ifndef CRED_MESSAGE 535 bi->bi_uid = -1; 536 bi->bi_gid = -1; 537 #endif 538 rem = MIN(sizeof(bi->bi_msg), rem); 539 if (rem == 0) 540 bi->bi_msg[0] = '\0'; 541 else 542 strlcpy(bi->bi_msg, ub.bl.bl_data, rem); 543 return bi; 544 } 545