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