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