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