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