1 /* $OpenBSD: resolvd.c,v 1.11 2021/03/19 08:10:57 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 #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 /* serial the file for debugging */ 586 dprintf(fd, "# resolvd: serial %u\n", arc4random()); 587 588 #ifndef SMALL 589 if (unwind_running) 590 dprintf(fd, "nameserver 127.0.0.1 # resolvd: unwind\n"); 591 592 #endif /* SMALL */ 593 for (i = 0; i < ASR_MAXNS; i++) { 594 if (learned[i].prio != 0) { 595 char ifnambuf[IF_NAMESIZE], *ifnam; 596 597 ifnam = if_indextoname(learned[i].if_index, 598 ifnambuf); 599 dprintf(fd, "%snameserver %s # resolvd: %s\n", 600 #ifndef SMALL 601 unwind_running ? "#" : "", 602 #else 603 "", 604 #endif 605 learned[i].ip, 606 ifnam ? ifnam : ""); 607 } 608 } 609 610 /* Replay user-managed lines from old resolv.conf file */ 611 if (resolvfd == -1) 612 resolvfd = open(_PATH_RESCONF, O_RDWR); 613 if (resolvfd != -1) { 614 char *line = NULL; 615 size_t linesize = 0; 616 ssize_t linelen; 617 FILE *fp; 618 619 lseek(resolvfd, 0, SEEK_SET); 620 fp = fdopen(resolvfd, "r"); 621 if (fp == NULL) 622 goto err; 623 while ((linelen = getline(&line, &linesize, fp)) != -1) { 624 char *end = strchr(line, '\n'); 625 if (end) 626 *end = '\0'; 627 if (strstr(line, "# resolvd: ")) 628 continue; 629 dprintf(fd, "%s\n", line); 630 } 631 free(line); 632 } 633 634 if (rename(_PATH_RESCONF_NEW, _PATH_RESCONF) == -1) 635 goto err; 636 637 if (resolvfd == -1) { 638 close(fd); 639 resolvfd = open(_PATH_RESCONF, O_RDWR | O_CREAT); 640 } else { 641 dup2(fd, resolvfd); 642 close(fd); 643 } 644 645 newkevent = 1; 646 return; 647 648 err: 649 if (fd != -1) 650 close(fd); 651 unlink(_PATH_RESCONF_NEW); 652 } 653 654 int 655 cmp(const void *a, const void *b) 656 { 657 const struct rdns_proposal *rpa, *rpb; 658 659 rpa = a; 660 rpb = b; 661 662 if (rpa->prio == rpb->prio) 663 return strcmp(rpa->ip, rpb->ip); 664 else 665 return rpa->prio < rpb->prio ? -1 : 1; 666 } 667 668 #ifndef SMALL 669 int 670 open_unwind_ctl(void) 671 { 672 static struct sockaddr_un sun; 673 int s; 674 675 if (sun.sun_family == 0) { 676 sun.sun_family = AF_UNIX; 677 strlcpy(sun.sun_path, _PATH_UNWIND_SOCKET, sizeof(sun.sun_path)); 678 } 679 680 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) { 681 if (connect(s, (struct sockaddr *)&sun, sizeof(sun)) == -1) { 682 close(s); 683 s = -1; 684 } 685 } 686 newkevent = 1; 687 return s; 688 } 689 690 void 691 syslog_vstrerror(int e, int priority, const char *fmt, va_list ap) 692 { 693 char *s; 694 695 if (vasprintf(&s, fmt, ap) == -1) { 696 syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror"); 697 exit(1); 698 } 699 syslog(priority, "%s: %s", s, strerror(e)); 700 free(s); 701 } 702 703 __dead void 704 syslog_err(int ecode, const char *fmt, ...) 705 { 706 va_list ap; 707 708 va_start(ap, fmt); 709 syslog_vstrerror(errno, LOG_CRIT, fmt, ap); 710 va_end(ap); 711 exit(ecode); 712 } 713 714 __dead void 715 syslog_errx(int ecode, const char *fmt, ...) 716 { 717 va_list ap; 718 719 va_start(ap, fmt); 720 vsyslog(LOG_CRIT, fmt, ap); 721 va_end(ap); 722 exit(ecode); 723 } 724 725 void 726 syslog_warn(const char *fmt, ...) 727 { 728 va_list ap; 729 730 va_start(ap, fmt); 731 syslog_vstrerror(errno, LOG_ERR, fmt, ap); 732 va_end(ap); 733 } 734 735 void 736 syslog_warnx(const char *fmt, ...) 737 { 738 va_list ap; 739 740 va_start(ap, fmt); 741 vsyslog(LOG_ERR, fmt, ap); 742 va_end(ap); 743 } 744 745 void 746 syslog_info(const char *fmt, ...) 747 { 748 va_list ap; 749 750 va_start(ap, fmt); 751 vsyslog(LOG_INFO, fmt, ap); 752 va_end(ap); 753 } 754 755 void 756 syslog_debug(const char *fmt, ...) 757 { 758 va_list ap; 759 760 va_start(ap, fmt); 761 vsyslog(LOG_DEBUG, fmt, ap); 762 va_end(ap); 763 } 764 765 void 766 warnx_verbose(const char *fmt, ...) 767 { 768 va_list ap; 769 770 va_start(ap, fmt); 771 if (verbose) 772 vwarnx(fmt, ap); 773 va_end(ap); 774 } 775 776 #endif /* SMALL */ 777