xref: /openbsd-src/sbin/resolvd/resolvd.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: resolvd.c,v 1.12 2021/05/10 15:06:34 deraadt Exp $	*/
2 /*
3  * Copyright (c) 2021 Florian Obser <florian@openbsd.org>
4  * Copyright (c) 2021 Theo de Raadt <deraadt@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/event.h>
21 #include <sys/socket.h>
22 #include <sys/stat.h>
23 #include <sys/syslog.h>
24 #include <sys/time.h>
25 #include <sys/un.h>
26 
27 #include <arpa/inet.h>
28 #include <netinet/in.h>
29 #include <net/if.h>
30 #include <net/route.h>
31 
32 #include <err.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <event.h>
36 #include <signal.h>
37 #include <stddef.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #define	ROUTE_SOCKET_BUF_SIZE	16384
44 #define	ASR_MAXNS		10
45 #ifndef SMALL
46 #define	_PATH_UNWIND_SOCKET	"/dev/unwind.sock"
47 #endif
48 #define	_PATH_RESCONF		"/etc/resolv.conf"
49 #define	_PATH_RESCONF_NEW	"/etc/resolv.conf.new"
50 #define _PATH_LOCKFILE		"/var/run/resolvd.lock"
51 
52 #ifndef nitems
53 #define	nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
54 #endif
55 
56 __dead void	usage(void);
57 
58 struct rdns_proposal {
59 	uint32_t	 if_index;
60 	int		 af;
61 	int		 prio;
62 	char		 ip[INET6_ADDRSTRLEN];
63 };
64 
65 void		route_receive(int);
66 void		handle_route_message(struct rt_msghdr *, struct sockaddr **);
67 void		get_rtaddrs(int, struct sockaddr *, struct sockaddr **);
68 void		solicit_dns_proposals(int);
69 void		regen_resolvconf(char *reason);
70 int		cmp(const void *, const void *);
71 int		findslot(struct rdns_proposal *);
72 void		zeroslot(struct rdns_proposal *);
73 
74 struct rdns_proposal	 learned[ASR_MAXNS];
75 int			 resolvfd = -1;
76 int			 newkevent = 1;
77 
78 #ifndef SMALL
79 int			 open_unwind_ctl(void);
80 int			 check_unwind = 1, unwind_running = 0;
81 
82 struct loggers {
83 	__dead void (*err)(int, const char *, ...)
84 	    __attribute__((__format__ (printf, 2, 3)));
85 	__dead void (*errx)(int, const char *, ...)
86 	    __attribute__((__format__ (printf, 2, 3)));
87 	void (*warn)(const char *, ...)
88 	    __attribute__((__format__ (printf, 1, 2)));
89 	void (*warnx)(const char *, ...)
90 	    __attribute__((__format__ (printf, 1, 2)));
91 	void (*info)(const char *, ...)
92 	    __attribute__((__format__ (printf, 1, 2)));
93 	void (*debug)(const char *, ...)
94 	    __attribute__((__format__ (printf, 1, 2)));
95 };
96 
97 void		warnx_verbose(const char *, ...)
98 		    __attribute__((__format__ (printf, 1, 2)));
99 
100 const struct loggers conslogger = {
101 	err,
102 	errx,
103 	warn,
104 	warnx,
105 	warnx_verbose, /* info */
106 	warnx_verbose /* debug */
107 };
108 
109 __dead void	syslog_err(int, const char *, ...)
110 		    __attribute__((__format__ (printf, 2, 3)));
111 __dead void	syslog_errx(int, const char *, ...)
112 		    __attribute__((__format__ (printf, 2, 3)));
113 void		syslog_warn(const char *, ...)
114 		    __attribute__((__format__ (printf, 1, 2)));
115 void		syslog_warnx(const char *, ...)
116 		    __attribute__((__format__ (printf, 1, 2)));
117 void		syslog_info(const char *, ...)
118 		    __attribute__((__format__ (printf, 1, 2)));
119 void		syslog_debug(const char *, ...)
120 		    __attribute__((__format__ (printf, 1, 2)));
121 void		syslog_vstrerror(int, int, const char *, va_list)
122 		    __attribute__((__format__ (printf, 3, 0)));
123 
124 int verbose = 0;
125 
126 const struct loggers syslogger = {
127 	syslog_err,
128 	syslog_errx,
129 	syslog_warn,
130 	syslog_warnx,
131 	syslog_info,
132 	syslog_debug
133 };
134 
135 const struct loggers *logger = &conslogger;
136 
137 #define lerr(_e, _f...) logger->err((_e), _f)
138 #define lerrx(_e, _f...) logger->errx((_e), _f)
139 #define lwarn(_f...) logger->warn(_f)
140 #define lwarnx(_f...) logger->warnx(_f)
141 #define linfo(_f...) logger->info(_f)
142 #define ldebug(_f...) logger->debug(_f)
143 #else
144 #define lerr(x...) do {} while(0)
145 #define lerrx(x...) do {} while(0)
146 #define lwarn(x...) do {} while(0)
147 #define lwarnx(x...) do {} while(0)
148 #define linfo(x...) do {} while(0)
149 #define ldebug(x...) do {} while(0)
150 #endif /* SMALL */
151 
152 enum {
153 	KQ_ROUTE,
154 	KQ_RESOLVE_CONF,
155 	KQ_UNWIND,
156 };
157 
158 int
159 main(int argc, char *argv[])
160 {
161 	struct timespec		 one = {1, 0};
162 	int			 kq, ch, debug = 0, routesock;
163 	int			 rtfilter, nready, lockfd;
164 	struct kevent		 kev[3];
165 #ifndef SMALL
166 	int			 unwindsock = -1;
167 #endif
168 
169 	while ((ch = getopt(argc, argv, "dv")) != -1) {
170 		switch (ch) {
171 		case 'd':
172 			debug = 1;
173 			break;
174 		case 'v':
175 #ifndef SMALL
176 			verbose++;
177 #endif
178 			break;
179 		default:
180 			usage();
181 		}
182 	}
183 
184 	argc -= optind;
185 	argv += optind;
186 	if (argc > 0)
187 		usage();
188 
189 	/* Check for root privileges. */
190 	if (geteuid())
191 		errx(1, "need root privileges");
192 
193 	lockfd = open(_PATH_LOCKFILE, O_CREAT|O_RDWR|O_EXLOCK|O_NONBLOCK, 0600);
194 	if (lockfd == -1)
195 		errx(1, "already running, " _PATH_LOCKFILE);
196 
197 	if (!debug)
198 		daemon(0, 0);
199 
200 #ifndef SMALL
201 	if (!debug) {
202 		openlog("resolvd", LOG_PID|LOG_NDELAY, LOG_DAEMON);
203 		logger = &syslogger;
204 	}
205 #endif
206 
207 	signal(SIGHUP, SIG_IGN);
208 
209 	if ((routesock = socket(AF_ROUTE, SOCK_RAW, 0)) == -1)
210 		lerr(1, "route socket");
211 
212 	rtfilter = ROUTE_FILTER(RTM_PROPOSAL) | ROUTE_FILTER(RTM_IFANNOUNCE);
213 	if (setsockopt(routesock, AF_ROUTE, ROUTE_MSGFILTER, &rtfilter,
214 	    sizeof(rtfilter)) == -1)
215 		lerr(1, "setsockopt(ROUTE_MSGFILTER)");
216 
217 	solicit_dns_proposals(routesock);
218 
219 	if (unveil(_PATH_RESCONF, "rwc") == -1)
220 		lerr(1, "unveil " _PATH_RESCONF);
221 	if (unveil(_PATH_RESCONF_NEW, "rwc") == -1)
222 		lerr(1, "unveil " _PATH_RESCONF_NEW);
223 #ifndef SMALL
224 	if (unveil(_PATH_UNWIND_SOCKET, "r") == -1)
225 		lerr(1, "unveil " _PATH_UNWIND_SOCKET);
226 #endif
227 
228 	if (pledge("stdio unix rpath wpath cpath", NULL) == -1)
229 		lerr(1, "pledge");
230 
231 	if ((kq = kqueue()) == -1)
232 		lerr(1, "kqueue");
233 
234 	for(;;) {
235 		int	i;
236 
237 #ifndef SMALL
238 		if (!unwind_running && check_unwind) {
239 			check_unwind = 0;
240 			unwindsock = open_unwind_ctl();
241 			unwind_running = unwindsock != -1;
242 			if (unwind_running)
243 				regen_resolvconf("new unwind");
244 		}
245 #endif
246 
247 		if (newkevent) {
248 			int kevi = 0;
249 
250 			if (routesock != -1)
251 				EV_SET(&kev[kevi++], routesock, EVFILT_READ,
252 				    EV_ADD, 0, 0,
253 				    (void *)KQ_ROUTE);
254 			if (resolvfd != -1)
255 				EV_SET(&kev[kevi++], resolvfd, EVFILT_VNODE,
256 				    EV_ADD | EV_CLEAR,
257 				    NOTE_DELETE | NOTE_RENAME | NOTE_TRUNCATE | NOTE_WRITE, 0,
258 				    (void *)KQ_RESOLVE_CONF);
259 
260 #ifndef SMALL
261 			if (unwind_running) {
262 				EV_SET(&kev[kevi++], unwindsock, EVFILT_READ,
263 				    EV_ADD, 0, 0,
264 				    (void *)KQ_UNWIND);
265 			}
266 #endif /* SMALL */
267 
268 			if (kevent(kq, kev, kevi, NULL, 0, NULL) == -1)
269 				lerr(1, "kevent");
270 			newkevent = 0;
271 		}
272 
273 		nready = kevent(kq, NULL, 0, kev, nitems(kev), NULL);
274 		if (nready == -1) {
275 			if (errno == EINTR)
276 				continue;
277 			lerr(1, "kevent");
278 		}
279 
280 		if (nready == 0)
281 			continue;
282 
283 		for (i = 0; i < nready; i++) {
284 			unsigned short fflags = kev[i].fflags;
285 
286 			switch ((int)(long)kev[i].udata) {
287 			case KQ_ROUTE:
288 				route_receive(routesock);
289 				break;
290 
291 			case KQ_RESOLVE_CONF:
292 				if (fflags & (NOTE_DELETE | NOTE_RENAME)) {
293 					close(resolvfd);
294 					resolvfd = -1;
295 					regen_resolvconf("file delete/rename");
296 				}
297 				if (fflags & (NOTE_TRUNCATE | NOTE_WRITE)) {
298 					/* some editors truncate and write */
299 					if (fflags & NOTE_TRUNCATE)
300 						nanosleep(&one, NULL);
301 					regen_resolvconf("file trunc/write");
302 				}
303 				break;
304 
305 #ifndef SMALL
306 			case KQ_UNWIND: {
307 				uint8_t buf[1024];
308 				ssize_t	n;
309 
310 				n = read(unwindsock, buf, sizeof(buf));
311 				if (n == -1) {
312 					if (errno == EAGAIN || errno == EINTR)
313 						continue;
314 				}
315 				if (n == 0 || n == -1) {
316 					if (n == -1)
317 						check_unwind = 1;
318 					newkevent = 1;
319 					close(unwindsock);
320 					unwindsock = -1;
321 					unwind_running = 0;
322 					regen_resolvconf("unwind closed");
323 				} else
324 					lwarnx("read %ld from unwind ctl", n);
325 				break;
326 			}
327 #endif
328 
329 			default:
330 				lwarnx("unknown kqueue event on %lu",
331 				    kev[i].ident);
332 			}
333 		}
334 	}
335 	return 0;
336 }
337 
338 __dead void
339 usage(void)
340 {
341 	fprintf(stderr, "usage: resolvd [-dv]\n");
342 	exit(1);
343 }
344 
345 void
346 route_receive(int fd)
347 {
348 	uint8_t			 rsock_buf[ROUTE_SOCKET_BUF_SIZE];
349 	struct sockaddr		*sa, *rti_info[RTAX_MAX];
350 	struct rt_msghdr	*rtm;
351 	ssize_t			 n;
352 
353 	rtm = (struct rt_msghdr *) rsock_buf;
354 	if ((n = read(fd, rsock_buf, sizeof(rsock_buf))) == -1) {
355 		if (errno == EAGAIN || errno == EINTR)
356 			return;
357 		lwarn("%s: read error", __func__);
358 		return;
359 	}
360 
361 	if (n == 0)
362 		lerr(1, "routing socket closed");
363 
364 	if (n < (ssize_t)sizeof(rtm->rtm_msglen) || n < rtm->rtm_msglen) {
365 		lwarnx("partial rtm of %zd in buffer", n);
366 		return;
367 	}
368 
369 	if (rtm->rtm_version != RTM_VERSION)
370 		return;
371 
372 	if (rtm->rtm_pid == getpid())
373 		return;
374 
375 	sa = (struct sockaddr *)(rsock_buf + rtm->rtm_hdrlen);
376 	get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
377 	handle_route_message(rtm, rti_info);
378 }
379 
380 void
381 zeroslot(struct rdns_proposal *tab)
382 {
383 	tab->prio = 0;
384 	tab->af = 0;
385 	tab->if_index = 0;
386 	tab->ip[0] = '\0';
387 }
388 
389 int
390 findslot(struct rdns_proposal *tab)
391 {
392 	int i;
393 
394 	for (i = 0; i < ASR_MAXNS; i++)
395 		if (tab[i].prio == 0)
396 			return i;
397 
398 	/* New proposals might be important, so replace the last slot */
399 	i = ASR_MAXNS - 1;
400 	zeroslot(&tab[i]);
401 	return i;
402 }
403 
404 void
405 handle_route_message(struct rt_msghdr *rtm, struct sockaddr **rti_info)
406 {
407 	struct rdns_proposal		 learning[nitems(learned)];
408 	struct sockaddr_rtdns		*rtdns;
409 	struct if_announcemsghdr	*ifan;
410 	int				 rdns_count, af, i;
411 	char				*src;
412 
413 	memcpy(learning, learned, sizeof learned);
414 
415 	switch (rtm->rtm_type) {
416 	case RTM_IFANNOUNCE:
417 		ifan = (struct if_announcemsghdr *)rtm;
418 		if (ifan->ifan_what == IFAN_ARRIVAL)
419 			return;
420 		/* Delete proposals learned from departing interfaces */
421 		for (i = 0; i < ASR_MAXNS; i++)
422 			if (learning[i].if_index == ifan->ifan_index)
423 				zeroslot(&learning[i]);
424 		break;
425 	case RTM_PROPOSAL:
426 		if (rtm->rtm_priority == RTP_PROPOSAL_SOLICIT) {
427 #ifndef SMALL
428 			check_unwind = 1;
429 #endif /* SMALL */
430 			return;
431 		}
432 
433 		if (!(rtm->rtm_addrs & RTA_DNS))
434 			return;
435 
436 		rtdns = (struct sockaddr_rtdns*)rti_info[RTAX_DNS];
437 		src = rtdns->sr_dns;
438 		af = rtdns->sr_family;
439 
440 		switch (af) {
441 		case AF_INET:
442 			if ((rtdns->sr_len - 2) % sizeof(struct in_addr) != 0) {
443 				lwarnx("ignoring invalid RTM_PROPOSAL");
444 				return;
445 			}
446 			rdns_count = (rtdns->sr_len - offsetof(struct
447 			    sockaddr_rtdns, sr_dns)) / sizeof(struct in_addr);
448 			break;
449 		case AF_INET6:
450 			if ((rtdns->sr_len - 2) % sizeof(struct in6_addr) != 0) {
451 				lwarnx("ignoring invalid RTM_PROPOSAL");
452 				return;
453 			}
454 			rdns_count = (rtdns->sr_len - offsetof(struct
455 			    sockaddr_rtdns, sr_dns)) / sizeof(struct in6_addr);
456 			break;
457 		default:
458 			lwarnx("ignoring invalid RTM_PROPOSAL");
459 			return;
460 		}
461 
462 		/* New proposal from interface means previous proposals expire */
463 		for (i = 0; i < ASR_MAXNS; i++)
464 			if (learning[i].af == af &&
465 			    learning[i].if_index == rtm->rtm_index)
466 				zeroslot(&learning[i]);
467 
468 		/* Add the new proposals */
469 		for (i = 0; i < rdns_count; i++) {
470 			struct in_addr addr4;
471 			struct in6_addr addr6;
472 			int new;
473 
474 			switch (af) {
475 			case AF_INET:
476 				memcpy(&addr4, src, sizeof(struct in_addr));
477 				src += sizeof(struct in_addr);
478 				if (addr4.s_addr == INADDR_LOOPBACK)
479 					continue;
480 				new = findslot(learning);
481 				if (inet_ntop(af, &addr4, learning[new].ip,
482 				    INET6_ADDRSTRLEN) != NULL) {
483 					learning[new].prio = rtm->rtm_priority;
484 					learning[new].if_index = rtm->rtm_index;
485 					learning[new].af = af;
486 				} else
487 					lwarn("inet_ntop");
488 				break;
489 			case AF_INET6:
490 				memcpy(&addr6, src, sizeof(struct in6_addr));
491 				src += sizeof(struct in6_addr);
492 				if (IN6_IS_ADDR_LOOPBACK(&addr6))
493 					continue;
494 				new = findslot(learning);
495 				if (inet_ntop(af, &addr6, learning[new].ip,
496 				    INET6_ADDRSTRLEN) != NULL) {
497 					learning[new].prio = rtm->rtm_priority;
498 					learning[new].if_index = rtm->rtm_index;
499 					learning[new].af = af;
500 				} else
501 					lwarn("inet_ntop");
502 				break;
503 			}
504 		}
505 		break;
506 	default:
507 		return;
508 	}
509 
510 	/* Sort proposals, based upon priority and IP */
511 	qsort(learning, ASR_MAXNS, sizeof(learning[0]), cmp);
512 
513 	/* Eliminate duplicates */
514 	for (i = 0; i < ASR_MAXNS - 1; i++) {
515 		if (learning[i].prio == 0)
516 			continue;
517 		if (learning[i].if_index == learning[i+1].if_index &&
518 		    strcmp(learning[i].ip, learning[i+1].ip) == 0) {
519 			zeroslot(&learning[i + 1]);
520 			i--;	/* backup and re-check */
521 		}
522 	}
523 
524 	/* If proposal result is different, rebuild the file */
525 	if (memcmp(learned, learning, sizeof(learned)) != 0) {
526 		memcpy(learned, learning, sizeof(learned));
527 		regen_resolvconf("route proposals");
528 	}
529 }
530 
531 #define ROUNDUP(a) \
532 	((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
533 
534 void
535 get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info)
536 {
537 	int	i;
538 
539 	for (i = 0; i < RTAX_MAX; i++) {
540 		if (addrs & (1 << i)) {
541 			rti_info[i] = sa;
542 			sa = (struct sockaddr *)((char *)(sa) +
543 			    ROUNDUP(sa->sa_len));
544 		} else
545 			rti_info[i] = NULL;
546 	}
547 }
548 
549 void
550 solicit_dns_proposals(int routesock)
551 {
552 	struct rt_msghdr	 rtm;
553 	struct iovec		 iov[1];
554 	int			 iovcnt = 0;
555 
556 	memset(&rtm, 0, sizeof(rtm));
557 
558 	rtm.rtm_version = RTM_VERSION;
559 	rtm.rtm_type = RTM_PROPOSAL;
560 	rtm.rtm_msglen = sizeof(rtm);
561 	rtm.rtm_tableid = 0;
562 	rtm.rtm_index = 0;
563 	rtm.rtm_seq = arc4random();
564 	rtm.rtm_priority = RTP_PROPOSAL_SOLICIT;
565 
566 	iov[iovcnt].iov_base = &rtm;
567 	iov[iovcnt++].iov_len = sizeof(rtm);
568 
569 	if (writev(routesock, iov, iovcnt) == -1)
570 		lwarn("failed to send solicitation");
571 }
572 
573 void
574 regen_resolvconf(char *why)
575 {
576 	int	 i, fd;
577 
578 	linfo("rebuilding: %s", why);
579 
580 	if ((fd = open(_PATH_RESCONF_NEW, O_CREAT|O_TRUNC|O_RDWR, 0644)) == -1) {
581 		lwarn(_PATH_RESCONF_NEW);
582 		return;
583 	}
584 
585 #ifndef SMALL
586 	if (unwind_running)
587 		dprintf(fd, "nameserver 127.0.0.1 # resolvd: unwind\n");
588 
589 #endif /* SMALL */
590 	for (i = 0; i < ASR_MAXNS; i++) {
591 		if (learned[i].prio != 0) {
592 			char ifnambuf[IF_NAMESIZE], *ifnam;
593 
594 			ifnam = if_indextoname(learned[i].if_index,
595 			    ifnambuf);
596 			dprintf(fd, "%snameserver %s # resolvd: %s\n",
597 #ifndef SMALL
598 			    unwind_running ? "#" : "",
599 #else
600 			    "",
601 #endif
602 			    learned[i].ip,
603 			    ifnam ? ifnam : "");
604 		}
605 	}
606 
607 	/* Replay user-managed lines from old resolv.conf file */
608 	if (resolvfd == -1)
609 		resolvfd = open(_PATH_RESCONF, O_RDWR);
610 	if (resolvfd != -1) {
611 		char *line = NULL;
612 		size_t linesize = 0;
613 		ssize_t linelen;
614 		FILE *fp;
615 
616 		lseek(resolvfd, 0, SEEK_SET);
617 		fp = fdopen(resolvfd, "r");
618 		if (fp == NULL)
619 			goto err;
620 		while ((linelen = getline(&line, &linesize, fp)) != -1) {
621 			char *end = strchr(line, '\n');
622 			if (end)
623 				*end = '\0';
624 			if (strstr(line, "# resolvd: "))
625 				continue;
626 			dprintf(fd, "%s\n", line);
627 		}
628 		free(line);
629 	}
630 
631 	if (rename(_PATH_RESCONF_NEW, _PATH_RESCONF) == -1)
632 		goto err;
633 
634 	if (resolvfd == -1) {
635 		close(fd);
636 		resolvfd = open(_PATH_RESCONF, O_RDWR | O_CREAT);
637 	} else {
638 		dup2(fd, resolvfd);
639 		close(fd);
640 	}
641 
642 	newkevent = 1;
643 	return;
644 
645  err:
646 	if (fd != -1)
647 		close(fd);
648 	unlink(_PATH_RESCONF_NEW);
649 }
650 
651 int
652 cmp(const void *a, const void *b)
653 {
654 	const struct rdns_proposal	*rpa, *rpb;
655 
656 	rpa = a;
657 	rpb = b;
658 
659 	if (rpa->prio == rpb->prio)
660 		return strcmp(rpa->ip, rpb->ip);
661 	else
662 		return rpa->prio < rpb->prio ? -1 : 1;
663 }
664 
665 #ifndef SMALL
666 int
667 open_unwind_ctl(void)
668 {
669 	static struct sockaddr_un	 sun;
670 	int				 s;
671 
672 	if (sun.sun_family == 0) {
673 		sun.sun_family = AF_UNIX;
674 		strlcpy(sun.sun_path, _PATH_UNWIND_SOCKET, sizeof(sun.sun_path));
675 	}
676 
677 	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {
678 		if (connect(s, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
679 			close(s);
680 			s = -1;
681 		}
682 	}
683 	newkevent = 1;
684 	return s;
685 }
686 
687 void
688 syslog_vstrerror(int e, int priority, const char *fmt, va_list ap)
689 {
690 	char *s;
691 
692 	if (vasprintf(&s, fmt, ap) == -1) {
693 		syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror");
694 		exit(1);
695 	}
696 	syslog(priority, "%s: %s", s, strerror(e));
697 	free(s);
698 }
699 
700 __dead void
701 syslog_err(int ecode, const char *fmt, ...)
702 {
703 	va_list ap;
704 
705 	va_start(ap, fmt);
706 	syslog_vstrerror(errno, LOG_CRIT, fmt, ap);
707 	va_end(ap);
708 	exit(ecode);
709 }
710 
711 __dead void
712 syslog_errx(int ecode, const char *fmt, ...)
713 {
714 	va_list ap;
715 
716 	va_start(ap, fmt);
717 	vsyslog(LOG_CRIT, fmt, ap);
718 	va_end(ap);
719 	exit(ecode);
720 }
721 
722 void
723 syslog_warn(const char *fmt, ...)
724 {
725 	va_list ap;
726 
727 	va_start(ap, fmt);
728 	syslog_vstrerror(errno, LOG_ERR, fmt, ap);
729 	va_end(ap);
730 }
731 
732 void
733 syslog_warnx(const char *fmt, ...)
734 {
735 	va_list ap;
736 
737 	va_start(ap, fmt);
738 	vsyslog(LOG_ERR, fmt, ap);
739 	va_end(ap);
740 }
741 
742 void
743 syslog_info(const char *fmt, ...)
744 {
745 	va_list ap;
746 
747 	va_start(ap, fmt);
748 	vsyslog(LOG_INFO, fmt, ap);
749 	va_end(ap);
750 }
751 
752 void
753 syslog_debug(const char *fmt, ...)
754 {
755 	va_list ap;
756 
757 	va_start(ap, fmt);
758 	vsyslog(LOG_DEBUG, fmt, ap);
759 	va_end(ap);
760 }
761 
762 void
763 warnx_verbose(const char *fmt, ...)
764 {
765 	va_list ap;
766 
767 	va_start(ap, fmt);
768 	if (verbose)
769 		vwarnx(fmt, ap);
770 	va_end(ap);
771 }
772 
773 #endif /* SMALL */
774