1/* $OpenBSD: util.c,v 1.44 2024/02/03 00:38:08 jsg Exp $ */ 2 3/* 4 * Copyright (c) 2010-2013 Reyk Floeter <reyk@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/queue.h> 21#include <sys/socket.h> 22#include <sys/uio.h> 23 24#include <netdb.h> 25#include <stdio.h> 26#include <stdlib.h> 27#include <unistd.h> 28#include <string.h> 29#include <errno.h> 30#include <limits.h> 31#include <fcntl.h> 32#include <ctype.h> 33#include <event.h> 34 35#include "iked.h" 36#include "ikev2.h" 37 38int 39socket_af(struct sockaddr *sa, in_port_t port) 40{ 41 errno = 0; 42 switch (sa->sa_family) { 43 case AF_INET: 44 ((struct sockaddr_in *)sa)->sin_port = port; 45 ((struct sockaddr_in *)sa)->sin_len = 46 sizeof(struct sockaddr_in); 47 break; 48 case AF_INET6: 49 ((struct sockaddr_in6 *)sa)->sin6_port = port; 50 ((struct sockaddr_in6 *)sa)->sin6_len = 51 sizeof(struct sockaddr_in6); 52 break; 53 default: 54 errno = EPFNOSUPPORT; 55 return (-1); 56 } 57 58 return (0); 59} 60 61in_port_t 62socket_getport(struct sockaddr *sa) 63{ 64 switch (sa->sa_family) { 65 case AF_INET: 66 return (ntohs(((struct sockaddr_in *)sa)->sin_port)); 67 case AF_INET6: 68 return (ntohs(((struct sockaddr_in6 *)sa)->sin6_port)); 69 default: 70 return (0); 71 } 72 73 /* NOTREACHED */ 74 return (0); 75} 76 77int 78socket_setport(struct sockaddr *sa, in_port_t port) 79{ 80 switch (sa->sa_family) { 81 case AF_INET: 82 ((struct sockaddr_in *)sa)->sin_port = htons(port); 83 break; 84 case AF_INET6: 85 ((struct sockaddr_in6 *)sa)->sin6_port = htons(port); 86 break; 87 default: 88 return (-1); 89 } 90 return (0); 91} 92 93int 94socket_getaddr(int s, struct sockaddr_storage *ss) 95{ 96 socklen_t sslen = sizeof(*ss); 97 98 return (getsockname(s, (struct sockaddr *)ss, &sslen)); 99} 100 101int 102socket_bypass(int s, struct sockaddr *sa) 103{ 104 int v, *a; 105 int a4[] = { 106 IPPROTO_IP, 107 IP_AUTH_LEVEL, 108 IP_ESP_TRANS_LEVEL, 109 IP_ESP_NETWORK_LEVEL, 110#ifdef IPV6_IPCOMP_LEVEL 111 IP_IPCOMP_LEVEL 112#endif 113 }; 114 int a6[] = { 115 IPPROTO_IPV6, 116 IPV6_AUTH_LEVEL, 117 IPV6_ESP_TRANS_LEVEL, 118 IPV6_ESP_NETWORK_LEVEL, 119#ifdef IPV6_IPCOMP_LEVEL 120 IPV6_IPCOMP_LEVEL 121#endif 122 }; 123 124 switch (sa->sa_family) { 125 case AF_INET: 126 a = a4; 127 break; 128 case AF_INET6: 129 a = a6; 130 break; 131 default: 132 log_warn("%s: invalid address family", __func__); 133 return (-1); 134 } 135 136 v = IPSEC_LEVEL_BYPASS; 137 if (setsockopt(s, a[0], a[1], &v, sizeof(v)) == -1) { 138 log_warn("%s: AUTH_LEVEL", __func__); 139 return (-1); 140 } 141 if (setsockopt(s, a[0], a[2], &v, sizeof(v)) == -1) { 142 log_warn("%s: ESP_TRANS_LEVEL", __func__); 143 return (-1); 144 } 145 if (setsockopt(s, a[0], a[3], &v, sizeof(v)) == -1) { 146 log_warn("%s: ESP_NETWORK_LEVEL", __func__); 147 return (-1); 148 } 149#ifdef IP_IPCOMP_LEVEL 150 if (setsockopt(s, a[0], a[4], &v, sizeof(v)) == -1) { 151 log_warn("%s: IPCOMP_LEVEL", __func__); 152 return (-1); 153 } 154#endif 155 156 return (0); 157} 158 159int 160udp_bind(struct sockaddr *sa, in_port_t port) 161{ 162 int s, val; 163 164 if (socket_af(sa, port) == -1) { 165 log_warn("%s: failed to set UDP port", __func__); 166 return (-1); 167 } 168 169 if ((s = socket(sa->sa_family, 170 SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP)) == -1) { 171 log_warn("%s: failed to get UDP socket", __func__); 172 return (-1); 173 } 174 175 /* Skip IPsec processing (don't encrypt) for IKE messages */ 176 if (socket_bypass(s, sa) == -1) { 177 log_warn("%s: failed to bypass IPsec on IKE socket", 178 __func__); 179 goto bad; 180 } 181 182 val = 1; 183 if (setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(int)) == -1) { 184 log_warn("%s: failed to set reuseport", __func__); 185 goto bad; 186 } 187 val = 1; 188 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)) == -1) { 189 log_warn("%s: failed to set reuseaddr", __func__); 190 goto bad; 191 } 192 193 if (sa->sa_family == AF_INET) { 194 val = 1; 195 if (setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, 196 &val, sizeof(int)) == -1) { 197 log_warn("%s: failed to set IPv4 packet info", 198 __func__); 199 goto bad; 200 } 201 } else { 202 val = 1; 203 if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, 204 &val, sizeof(int)) == -1) { 205 log_warn("%s: failed to set IPv6 packet info", 206 __func__); 207 goto bad; 208 } 209 } 210 211 if (bind(s, sa, sa->sa_len) == -1) { 212 log_warn("%s: failed to bind UDP socket", __func__); 213 goto bad; 214 } 215 216 return (s); 217 bad: 218 close(s); 219 return (-1); 220} 221 222int 223sockaddr_cmp(struct sockaddr *a, struct sockaddr *b, int prefixlen) 224{ 225 struct sockaddr_in *a4, *b4; 226 struct sockaddr_in6 *a6, *b6; 227 uint32_t av[4], bv[4], mv[4]; 228 229 if (a->sa_family == AF_UNSPEC || b->sa_family == AF_UNSPEC) 230 return (0); 231 else if (a->sa_family > b->sa_family) 232 return (1); 233 else if (a->sa_family < b->sa_family) 234 return (-1); 235 236 if (prefixlen == -1) 237 memset(&mv, 0xff, sizeof(mv)); 238 239 switch (a->sa_family) { 240 case AF_INET: 241 a4 = (struct sockaddr_in *)a; 242 b4 = (struct sockaddr_in *)b; 243 244 av[0] = a4->sin_addr.s_addr; 245 bv[0] = b4->sin_addr.s_addr; 246 if (prefixlen != -1) 247 mv[0] = prefixlen2mask(prefixlen); 248 249 if ((av[0] & mv[0]) > (bv[0] & mv[0])) 250 return (1); 251 if ((av[0] & mv[0]) < (bv[0] & mv[0])) 252 return (-1); 253 break; 254 case AF_INET6: 255 a6 = (struct sockaddr_in6 *)a; 256 b6 = (struct sockaddr_in6 *)b; 257 258 memcpy(&av, &a6->sin6_addr.s6_addr, 16); 259 memcpy(&bv, &b6->sin6_addr.s6_addr, 16); 260 if (prefixlen != -1) 261 prefixlen2mask6(prefixlen, mv); 262 263 if ((av[3] & mv[3]) > (bv[3] & mv[3])) 264 return (1); 265 if ((av[3] & mv[3]) < (bv[3] & mv[3])) 266 return (-1); 267 if ((av[2] & mv[2]) > (bv[2] & mv[2])) 268 return (1); 269 if ((av[2] & mv[2]) < (bv[2] & mv[2])) 270 return (-1); 271 if ((av[1] & mv[1]) > (bv[1] & mv[1])) 272 return (1); 273 if ((av[1] & mv[1]) < (bv[1] & mv[1])) 274 return (-1); 275 if ((av[0] & mv[0]) > (bv[0] & mv[0])) 276 return (1); 277 if ((av[0] & mv[0]) < (bv[0] & mv[0])) 278 return (-1); 279 break; 280 } 281 282 return (0); 283} 284 285ssize_t 286sendtofrom(int s, void *buf, size_t len, int flags, struct sockaddr *to, 287 socklen_t tolen, struct sockaddr *from, socklen_t fromlen) 288{ 289 struct iovec iov; 290 struct msghdr msg; 291 struct cmsghdr *cmsg; 292 struct in6_pktinfo *pkt6; 293 struct sockaddr_in *in; 294 struct sockaddr_in6 *in6; 295 union { 296 struct cmsghdr hdr; 297 char inbuf[CMSG_SPACE(sizeof(struct in_addr))]; 298 char in6buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; 299 } cmsgbuf; 300 301 bzero(&msg, sizeof(msg)); 302 bzero(&cmsgbuf, sizeof(cmsgbuf)); 303 304 iov.iov_base = buf; 305 iov.iov_len = len; 306 msg.msg_iov = &iov; 307 msg.msg_iovlen = 1; 308 msg.msg_name = to; 309 msg.msg_namelen = tolen; 310 msg.msg_control = &cmsgbuf; 311 msg.msg_controllen = sizeof(cmsgbuf); 312 313 cmsg = CMSG_FIRSTHDR(&msg); 314 switch (to->sa_family) { 315 case AF_INET: 316 msg.msg_controllen = sizeof(cmsgbuf.inbuf); 317 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); 318 cmsg->cmsg_level = IPPROTO_IP; 319 cmsg->cmsg_type = IP_SENDSRCADDR; 320 in = (struct sockaddr_in *)from; 321 memcpy(CMSG_DATA(cmsg), &in->sin_addr, sizeof(struct in_addr)); 322 break; 323 case AF_INET6: 324 msg.msg_controllen = sizeof(cmsgbuf.in6buf); 325 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); 326 cmsg->cmsg_level = IPPROTO_IPV6; 327 cmsg->cmsg_type = IPV6_PKTINFO; 328 in6 = (struct sockaddr_in6 *)from; 329 pkt6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); 330 pkt6->ipi6_addr = in6->sin6_addr; 331 break; 332 } 333 334 return sendmsg(s, &msg, flags); 335} 336 337ssize_t 338recvfromto(int s, void *buf, size_t len, int flags, struct sockaddr *from, 339 socklen_t *fromlen, struct sockaddr *to, socklen_t *tolen) 340{ 341 struct iovec iov; 342 struct msghdr msg; 343 struct cmsghdr *cmsg; 344 struct in6_pktinfo *pkt6; 345 struct sockaddr_in *in; 346 struct sockaddr_in6 *in6; 347 ssize_t ret; 348 union { 349 struct cmsghdr hdr; 350 char buf[CMSG_SPACE(sizeof(struct sockaddr_storage))]; 351 } cmsgbuf; 352 353 bzero(&msg, sizeof(msg)); 354 bzero(&cmsgbuf.buf, sizeof(cmsgbuf.buf)); 355 356 iov.iov_base = buf; 357 iov.iov_len = len; 358 msg.msg_iov = &iov; 359 msg.msg_iovlen = 1; 360 msg.msg_name = from; 361 msg.msg_namelen = *fromlen; 362 msg.msg_control = &cmsgbuf.buf; 363 msg.msg_controllen = sizeof(cmsgbuf.buf); 364 365 if ((ret = recvmsg(s, &msg, flags)) == -1) 366 return (-1); 367 368 *fromlen = from->sa_len; 369 370 if (getsockname(s, to, tolen) != 0) 371 *tolen = 0; 372 373 for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; 374 cmsg = CMSG_NXTHDR(&msg, cmsg)) { 375 switch (from->sa_family) { 376 case AF_INET: 377 if (cmsg->cmsg_level == IPPROTO_IP && 378 cmsg->cmsg_type == IP_RECVDSTADDR) { 379 in = (struct sockaddr_in *)to; 380 in->sin_family = AF_INET; 381 in->sin_len = *tolen = sizeof(*in); 382 memcpy(&in->sin_addr, CMSG_DATA(cmsg), 383 sizeof(struct in_addr)); 384 } 385 break; 386 case AF_INET6: 387 if (cmsg->cmsg_level == IPPROTO_IPV6 && 388 cmsg->cmsg_type == IPV6_PKTINFO) { 389 in6 = (struct sockaddr_in6 *)to; 390 in6->sin6_family = AF_INET6; 391 in6->sin6_len = *tolen = sizeof(*in6); 392 pkt6 = (struct in6_pktinfo *)CMSG_DATA(cmsg); 393 memcpy(&in6->sin6_addr, &pkt6->ipi6_addr, 394 sizeof(struct in6_addr)); 395 if (IN6_IS_ADDR_LINKLOCAL(&in6->sin6_addr)) 396 in6->sin6_scope_id = 397 pkt6->ipi6_ifindex; 398 } 399 break; 400 } 401 } 402 403 return (ret); 404} 405 406const char * 407print_spi(uint64_t spi, int size) 408{ 409 static char buf[IKED_CYCLE_BUFFERS][32]; 410 static int i = 0; 411 char *ptr; 412 413 ptr = buf[i]; 414 415 switch (size) { 416 case 2: 417 snprintf(ptr, 32, "0x%04x", (uint16_t)spi); 418 break; 419 case 4: 420 snprintf(ptr, 32, "0x%08x", (uint32_t)spi); 421 break; 422 case 8: 423 snprintf(ptr, 32, "0x%016llx", spi); 424 break; 425 default: 426 snprintf(ptr, 32, "%llu", spi); 427 break; 428 } 429 430 if (++i >= IKED_CYCLE_BUFFERS) 431 i = 0; 432 433 return (ptr); 434} 435 436const char * 437print_map(unsigned int type, struct iked_constmap *map) 438{ 439 unsigned int i; 440 static char buf[IKED_CYCLE_BUFFERS][32]; 441 static int idx = 0; 442 const char *name = NULL; 443 444 if (idx >= IKED_CYCLE_BUFFERS) 445 idx = 0; 446 bzero(buf[idx], sizeof(buf[idx])); 447 448 for (i = 0; map[i].cm_name != NULL; i++) { 449 if (map[i].cm_type == type) 450 name = map[i].cm_name; 451 } 452 453 if (name == NULL) 454 snprintf(buf[idx], sizeof(buf[idx]), "<UNKNOWN:%u>", type); 455 else 456 strlcpy(buf[idx], name, sizeof(buf[idx])); 457 458 return (buf[idx++]); 459} 460 461void 462lc_idtype(char *str) 463{ 464 for (; *str != '\0' && *str != '/'; str++) 465 *str = tolower((unsigned char)*str); 466} 467 468void 469print_hex(const uint8_t *buf, off_t offset, size_t length) 470{ 471 unsigned int i; 472 473 if (log_getverbose() < 3 || !length) 474 return; 475 476 for (i = 0; i < length; i++) { 477 if (i && (i % 4) == 0) { 478 if ((i % 32) == 0) 479 print_debug("\n"); 480 else 481 print_debug(" "); 482 } 483 print_debug("%02x", buf[offset + i]); 484 } 485 print_debug("\n"); 486} 487 488void 489print_hexval(const uint8_t *buf, off_t offset, size_t length) 490{ 491 unsigned int i; 492 493 if (log_getverbose() < 2 || !length) 494 return; 495 496 print_debug("0x"); 497 for (i = 0; i < length; i++) 498 print_debug("%02x", buf[offset + i]); 499 print_debug("\n"); 500} 501 502void 503print_hexbuf(struct ibuf *ibuf) 504{ 505 print_hex(ibuf_data(ibuf), 0, ibuf_size(ibuf)); 506} 507 508const char * 509print_bits(unsigned short v, unsigned char *bits) 510{ 511 static char buf[IKED_CYCLE_BUFFERS][BUFSIZ]; 512 static int idx = 0; 513 unsigned int i, any = 0, j = 0; 514 unsigned char c; 515 516 if (!bits) 517 return (""); 518 519 if (++idx >= IKED_CYCLE_BUFFERS) 520 idx = 0; 521 522 bzero(buf[idx], sizeof(buf[idx])); 523 524 bits++; 525 while ((i = *bits++)) { 526 if (v & (1 << (i-1))) { 527 if (any) { 528 buf[idx][j++] = ','; 529 if (j >= sizeof(buf[idx])) 530 return (buf[idx]); 531 } 532 any = 1; 533 for (; (c = *bits) > 32; bits++) { 534 buf[idx][j++] = tolower((unsigned char)c); 535 if (j >= sizeof(buf[idx])) 536 return (buf[idx]); 537 } 538 } else 539 for (; *bits > 32; bits++) 540 ; 541 } 542 543 return (buf[idx]); 544} 545 546uint8_t 547mask2prefixlen(struct sockaddr *sa) 548{ 549 struct sockaddr_in *sa_in = (struct sockaddr_in *)sa; 550 in_addr_t ina = sa_in->sin_addr.s_addr; 551 552 if (ina == 0) 553 return (0); 554 else 555 return (33 - ffs(ntohl(ina))); 556} 557 558uint8_t 559mask2prefixlen6(struct sockaddr *sa) 560{ 561 struct sockaddr_in6 *sa_in6 = (struct sockaddr_in6 *)sa; 562 uint8_t *ap, *ep; 563 unsigned int l = 0; 564 565 /* 566 * sin6_len is the size of the sockaddr so substract the offset of 567 * the possibly truncated sin6_addr struct. 568 */ 569 ap = (uint8_t *)&sa_in6->sin6_addr; 570 ep = (uint8_t *)sa_in6 + sa_in6->sin6_len; 571 for (; ap < ep; ap++) { 572 /* this "beauty" is adopted from sbin/route/show.c ... */ 573 switch (*ap) { 574 case 0xff: 575 l += 8; 576 break; 577 case 0xfe: 578 l += 7; 579 goto done; 580 case 0xfc: 581 l += 6; 582 goto done; 583 case 0xf8: 584 l += 5; 585 goto done; 586 case 0xf0: 587 l += 4; 588 goto done; 589 case 0xe0: 590 l += 3; 591 goto done; 592 case 0xc0: 593 l += 2; 594 goto done; 595 case 0x80: 596 l += 1; 597 goto done; 598 case 0x00: 599 goto done; 600 default: 601 fatalx("non contiguous inet6 netmask"); 602 } 603 } 604 605done: 606 if (l > sizeof(struct in6_addr) * 8) 607 fatalx("%s: prefixlen %d out of bound", __func__, l); 608 return (l); 609} 610 611uint32_t 612prefixlen2mask(uint8_t prefixlen) 613{ 614 if (prefixlen == 0) 615 return (0); 616 617 if (prefixlen > 32) 618 prefixlen = 32; 619 620 return (htonl(0xffffffff << (32 - prefixlen))); 621} 622 623struct in6_addr * 624prefixlen2mask6(uint8_t prefixlen, uint32_t *mask) 625{ 626 static struct in6_addr s6; 627 int i; 628 629 if (prefixlen > 128) 630 prefixlen = 128; 631 632 bzero(&s6, sizeof(s6)); 633 for (i = 0; i < prefixlen / 8; i++) 634 s6.s6_addr[i] = 0xff; 635 i = prefixlen % 8; 636 if (i) 637 s6.s6_addr[prefixlen / 8] = 0xff00 >> i; 638 639 memcpy(mask, &s6, sizeof(s6)); 640 641 return (&s6); 642} 643 644const char * 645print_addr(void *addr) 646{ 647 static char sbuf[IKED_CYCLE_BUFFERS][NI_MAXHOST + 7]; 648 static int idx; 649 struct sockaddr *sa = addr; 650 char *buf; 651 size_t len; 652 char pbuf[7]; 653 in_port_t port; 654 655 buf = sbuf[idx]; 656 len = sizeof(sbuf[idx]); 657 if (++idx >= IKED_CYCLE_BUFFERS) 658 idx = 0; 659 660 if (sa->sa_family == AF_UNSPEC) { 661 strlcpy(buf, "any", len); 662 return (buf); 663 } 664 665 if (getnameinfo(sa, sa->sa_len, 666 buf, len, NULL, 0, NI_NUMERICHOST) != 0) { 667 strlcpy(buf, "unknown", len); 668 return (buf); 669 } 670 671 if ((port = socket_getport(sa)) != 0) { 672 snprintf(pbuf, sizeof(pbuf), ":%d", port); 673 (void)strlcat(buf, pbuf, len); 674 } 675 676 return (buf); 677} 678 679char * 680get_string(uint8_t *ptr, size_t len) 681{ 682 size_t i; 683 684 for (i = 0; i < len; i++) 685 if (!isprint(ptr[i])) 686 break; 687 688 return strndup(ptr, i); 689} 690 691const char * 692print_proto(uint8_t proto) 693{ 694 struct protoent *p; 695 static char buf[IKED_CYCLE_BUFFERS][BUFSIZ]; 696 static int idx = 0; 697 698 if (idx >= IKED_CYCLE_BUFFERS) 699 idx = 0; 700 701 if ((p = getprotobynumber(proto)) != NULL) 702 strlcpy(buf[idx], p->p_name, sizeof(buf[idx])); 703 else 704 snprintf(buf[idx], sizeof(buf[idx]), "%u", proto); 705 706 return (buf[idx++]); 707} 708 709int 710expand_string(char *label, size_t len, const char *srch, const char *repl) 711{ 712 char *tmp; 713 char *p, *q; 714 715 if ((tmp = calloc(1, len)) == NULL) { 716 log_debug("%s: calloc", __func__); 717 return (-1); 718 } 719 p = label; 720 while ((q = strstr(p, srch)) != NULL) { 721 *q = '\0'; 722 if ((strlcat(tmp, p, len) >= len) || 723 (strlcat(tmp, repl, len) >= len)) { 724 log_debug("%s: string too long", __func__); 725 free(tmp); 726 return (-1); 727 } 728 q += strlen(srch); 729 p = q; 730 } 731 if (strlcat(tmp, p, len) >= len) { 732 log_debug("%s: string too long", __func__); 733 free(tmp); 734 return (-1); 735 } 736 strlcpy(label, tmp, len); /* always fits */ 737 free(tmp); 738 739 return (0); 740} 741 742uint8_t * 743string2unicode(const char *ascii, size_t *outlen) 744{ 745 uint8_t *uc = NULL; 746 size_t i, len = strlen(ascii); 747 748 if ((uc = calloc(1, (len * 2) + 2)) == NULL) 749 return (NULL); 750 751 for (i = 0; i < len; i++) { 752 /* XXX what about the byte order? */ 753 uc[i * 2] = ascii[i]; 754 } 755 *outlen = len * 2; 756 757 return (uc); 758} 759 760void 761print_debug(const char *emsg, ...) 762{ 763 va_list ap; 764 765 if (log_getverbose() > 2) { 766 va_start(ap, emsg); 767 vfprintf(stderr, emsg, ap); 768 va_end(ap); 769 } 770} 771 772void 773print_verbose(const char *emsg, ...) 774{ 775 va_list ap; 776 777 if (log_getverbose()) { 778 va_start(ap, emsg); 779 vfprintf(stderr, emsg, ap); 780 va_end(ap); 781 } 782} 783