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