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