xref: /freebsd/contrib/blocklist/lib/old_bl.c (revision df21a004be237a1dccd03c7b47254625eea62fa9)
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
99 bl_isconnected(bl_t b)
100 {
101 	return b->b_connected == 0;
102 }
103 
104 int
105 bl_getfd(bl_t b)
106 {
107 	return b->b_fd;
108 }
109 
110 static void
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
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
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
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
320 bl_destroy(bl_t b)
321 {
322 	bl_reset(b, false);
323 	free(b);
324 }
325 
326 static int
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
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 *
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