1 /* $OpenBSD: resolvd.c,v 1.16 2021/07/21 03:53:50 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 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 if (addr4.s_addr == INADDR_LOOPBACK) 480 continue; 481 new = findslot(learning); 482 if (inet_ntop(af, &addr4, learning[new].ip, 483 INET6_ADDRSTRLEN) != NULL) { 484 learning[new].prio = rtm->rtm_priority; 485 learning[new].if_index = rtm->rtm_index; 486 learning[new].af = af; 487 } else 488 lwarn("inet_ntop"); 489 break; 490 case AF_INET6: 491 memcpy(&addr6, src, sizeof(struct in6_addr)); 492 src += sizeof(struct in6_addr); 493 if (IN6_IS_ADDR_LOOPBACK(&addr6)) 494 continue; 495 new = findslot(learning); 496 if (inet_ntop(af, &addr6, learning[new].ip, 497 INET6_ADDRSTRLEN) != NULL) { 498 learning[new].prio = rtm->rtm_priority; 499 learning[new].if_index = rtm->rtm_index; 500 learning[new].af = af; 501 } else 502 lwarn("inet_ntop"); 503 break; 504 } 505 } 506 break; 507 default: 508 return; 509 } 510 511 /* Sort proposals, based upon priority and IP */ 512 qsort(learning, ASR_MAXNS, sizeof(learning[0]), cmp); 513 514 /* Eliminate duplicates */ 515 for (i = 0; i < ASR_MAXNS - 1; i++) { 516 if (learning[i].prio == 0) 517 continue; 518 if (learning[i].if_index == learning[i+1].if_index && 519 strcmp(learning[i].ip, learning[i+1].ip) == 0) { 520 zeroslot(&learning[i + 1]); 521 i--; /* backup and re-check */ 522 } 523 } 524 525 /* If proposal result is different, rebuild the file */ 526 if (memcmp(learned, learning, sizeof(learned)) != 0) { 527 memcpy(learned, learning, sizeof(learned)); 528 regen_resolvconf("route proposals"); 529 } 530 } 531 532 #define ROUNDUP(a) \ 533 ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) 534 535 void 536 get_rtaddrs(int addrs, struct sockaddr *sa, struct sockaddr **rti_info) 537 { 538 int i; 539 540 for (i = 0; i < RTAX_MAX; i++) { 541 if (addrs & (1 << i)) { 542 rti_info[i] = sa; 543 sa = (struct sockaddr *)((char *)(sa) + 544 ROUNDUP(sa->sa_len)); 545 } else 546 rti_info[i] = NULL; 547 } 548 } 549 550 void 551 solicit_dns_proposals(int routesock) 552 { 553 struct rt_msghdr rtm; 554 struct iovec iov[1]; 555 int iovcnt = 0; 556 557 memset(&rtm, 0, sizeof(rtm)); 558 559 rtm.rtm_version = RTM_VERSION; 560 rtm.rtm_type = RTM_PROPOSAL; 561 rtm.rtm_msglen = sizeof(rtm); 562 rtm.rtm_tableid = 0; 563 rtm.rtm_index = 0; 564 rtm.rtm_seq = arc4random(); 565 rtm.rtm_priority = RTP_PROPOSAL_SOLICIT; 566 567 iov[iovcnt].iov_base = &rtm; 568 iov[iovcnt++].iov_len = sizeof(rtm); 569 570 if (writev(routesock, iov, iovcnt) == -1) 571 lwarn("failed to send solicitation"); 572 } 573 574 void 575 regen_resolvconf(char *why) 576 { 577 int i, fd; 578 579 linfo("rebuilding: %s", why); 580 581 if ((fd = open(_PATH_RESCONF_NEW, O_CREAT|O_TRUNC|O_RDWR, 0644)) == -1) { 582 lwarn(_PATH_RESCONF_NEW); 583 return; 584 } 585 586 #ifndef SMALL 587 if (unwind_running) 588 dprintf(fd, "nameserver 127.0.0.1 # resolvd: unwind\n"); 589 590 #endif /* SMALL */ 591 for (i = 0; i < ASR_MAXNS; i++) { 592 if (learned[i].prio != 0) { 593 char ifnambuf[IF_NAMESIZE], *ifnam; 594 595 ifnam = if_indextoname(learned[i].if_index, 596 ifnambuf); 597 dprintf(fd, "%snameserver %s # resolvd: %s\n", 598 #ifndef SMALL 599 unwind_running ? "#" : "", 600 #else 601 "", 602 #endif 603 learned[i].ip, 604 ifnam ? ifnam : ""); 605 } 606 } 607 608 /* Replay user-managed lines from old resolv.conf file */ 609 if (resolvfd == -1) 610 resolvfd = open(_PATH_RESCONF, O_RDWR); 611 if (resolvfd != -1) { 612 char *line = NULL; 613 size_t linesize = 0; 614 ssize_t linelen; 615 FILE *fp; 616 617 lseek(resolvfd, 0, SEEK_SET); 618 fp = fdopen(resolvfd, "r"); 619 if (fp == NULL) 620 goto err; 621 while ((linelen = getline(&line, &linesize, fp)) != -1) { 622 char *end = strchr(line, '\n'); 623 if (end) 624 *end = '\0'; 625 if (strstr(line, "# resolvd: ")) 626 continue; 627 dprintf(fd, "%s\n", line); 628 } 629 free(line); 630 } 631 632 if (rename(_PATH_RESCONF_NEW, _PATH_RESCONF) == -1) 633 goto err; 634 635 if (resolvfd == -1) { 636 close(fd); 637 resolvfd = open(_PATH_RESCONF, O_RDWR | O_CREAT); 638 } else { 639 dup2(fd, resolvfd); 640 close(fd); 641 } 642 643 newkevent = 1; 644 return; 645 646 err: 647 if (fd != -1) 648 close(fd); 649 unlink(_PATH_RESCONF_NEW); 650 } 651 652 int 653 cmp(const void *a, const void *b) 654 { 655 const struct rdns_proposal *rpa, *rpb; 656 657 rpa = a; 658 rpb = b; 659 660 if (rpa->prio == rpb->prio) 661 return strcmp(rpa->ip, rpb->ip); 662 else 663 return rpa->prio < rpb->prio ? -1 : 1; 664 } 665 666 #ifndef SMALL 667 int 668 open_unwind_ctl(void) 669 { 670 static struct sockaddr_un sun; 671 int s; 672 673 if (sun.sun_family == 0) { 674 sun.sun_family = AF_UNIX; 675 strlcpy(sun.sun_path, _PATH_UNWIND_SOCKET, sizeof(sun.sun_path)); 676 } 677 678 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) { 679 if (connect(s, (struct sockaddr *)&sun, sizeof(sun)) == -1) { 680 close(s); 681 s = -1; 682 } 683 } 684 newkevent = 1; 685 return s; 686 } 687 688 void 689 syslog_vstrerror(int e, int priority, const char *fmt, va_list ap) 690 { 691 char *s; 692 693 if (vasprintf(&s, fmt, ap) == -1) { 694 syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror"); 695 exit(1); 696 } 697 syslog(priority, "%s: %s", s, strerror(e)); 698 free(s); 699 } 700 701 __dead void 702 syslog_err(int ecode, const char *fmt, ...) 703 { 704 va_list ap; 705 706 va_start(ap, fmt); 707 syslog_vstrerror(errno, LOG_CRIT, fmt, ap); 708 va_end(ap); 709 exit(ecode); 710 } 711 712 __dead void 713 syslog_errx(int ecode, const char *fmt, ...) 714 { 715 va_list ap; 716 717 va_start(ap, fmt); 718 vsyslog(LOG_CRIT, fmt, ap); 719 va_end(ap); 720 exit(ecode); 721 } 722 723 void 724 syslog_warn(const char *fmt, ...) 725 { 726 va_list ap; 727 728 va_start(ap, fmt); 729 syslog_vstrerror(errno, LOG_ERR, fmt, ap); 730 va_end(ap); 731 } 732 733 void 734 syslog_warnx(const char *fmt, ...) 735 { 736 va_list ap; 737 738 va_start(ap, fmt); 739 vsyslog(LOG_ERR, fmt, ap); 740 va_end(ap); 741 } 742 743 void 744 syslog_info(const char *fmt, ...) 745 { 746 va_list ap; 747 748 va_start(ap, fmt); 749 vsyslog(LOG_INFO, fmt, ap); 750 va_end(ap); 751 } 752 753 void 754 syslog_debug(const char *fmt, ...) 755 { 756 va_list ap; 757 758 va_start(ap, fmt); 759 vsyslog(LOG_DEBUG, fmt, ap); 760 va_end(ap); 761 } 762 763 void 764 warnx_verbose(const char *fmt, ...) 765 { 766 va_list ap; 767 768 va_start(ap, fmt); 769 if (verbose) 770 vwarnx(fmt, ap); 771 va_end(ap); 772 } 773 774 #endif /* SMALL */ 775