1/*++ 2/* NAME 3/* dns_lookup 3 4/* SUMMARY 5/* domain name service lookup 6/* SYNOPSIS 7/* #include <dns.h> 8/* 9/* int dns_lookup(name, type, rflags, list, fqdn, why) 10/* const char *name; 11/* unsigned type; 12/* unsigned rflags; 13/* DNS_RR **list; 14/* VSTRING *fqdn; 15/* VSTRING *why; 16/* 17/* int dns_lookup_l(name, rflags, list, fqdn, why, lflags, ltype, ...) 18/* const char *name; 19/* unsigned rflags; 20/* DNS_RR **list; 21/* VSTRING *fqdn; 22/* VSTRING *why; 23/* int lflags; 24/* unsigned ltype; 25/* 26/* int dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype) 27/* const char *name; 28/* unsigned rflags; 29/* DNS_RR **list; 30/* VSTRING *fqdn; 31/* VSTRING *why; 32/* int lflags; 33/* unsigned *ltype; 34/* DESCRIPTION 35/* dns_lookup() looks up DNS resource records. When requested to 36/* look up data other than type CNAME, it will follow a limited 37/* number of CNAME indirections. All result names (including 38/* null terminator) will fit a buffer of size DNS_NAME_LEN. 39/* All name results are validated by \fIvalid_hostname\fR(); 40/* an invalid name is reported as a DNS_INVAL result, while 41/* malformed replies are reported as transient errors. 42/* 43/* dns_lookup_l() and dns_lookup_v() allow the user to specify 44/* a list of resource types. 45/* INPUTS 46/* .ad 47/* .fi 48/* .IP name 49/* The name to be looked up in the domain name system. 50/* This name must pass the valid_hostname() test; it 51/* must not be an IP address. 52/* .IP type 53/* The resource record type to be looked up (T_A, T_MX etc.). 54/* .IP rflags 55/* Resolver flags. These are a bitwise OR of: 56/* .RS 57/* .IP RES_DEBUG 58/* Print debugging information. 59/* .IP RES_DNSRCH 60/* Search local domain and parent domains. 61/* .IP RES_DEFNAMES 62/* Append local domain to unqualified names. 63/* .RE 64/* .IP lflags 65/* Multi-type request control for dns_lookup_l() and dns_lookup_v(). 66/* For convenience, DNS_REQ_FLAG_NONE requests no special 67/* processing. Invoke dns_lookup() for all specified resource 68/* record types in the specified order, and merge their results. 69/* Otherwise, specify one or more of the following: 70/* .RS 71/* .IP DNS_REQ_FLAG_STOP_INVAL 72/* Invoke dns_lookup() for the resource types in the order as 73/* specified, and return when dns_lookup() returns DNS_INVAL. 74/* .IP DNS_REQ_FLAG_STOP_OK 75/* Invoke dns_lookup() for the resource types in the order as 76/* specified, and return when dns_lookup() returns DNS_OK. 77/* .RE 78/* .IP ltype 79/* The resource record types to be looked up. In the case of 80/* dns_lookup_l(), this is a null-terminated argument list. 81/* In the case of dns_lookup_v(), this is a null-terminated 82/* integer array. 83/* OUTPUTS 84/* .ad 85/* .fi 86/* .IP list 87/* A null pointer, or a pointer to a variable that receives a 88/* list of requested resource records. 89/* .IP fqdn 90/* A null pointer, or storage for the fully-qualified domain 91/* name found for \fIname\fR. 92/* .IP why 93/* A null pointer, or storage for the reason for failure. 94/* DIAGNOSTICS 95/* dns_lookup() returns one of the following codes and sets the 96/* \fIwhy\fR argument accordingly: 97/* .IP DNS_OK 98/* The DNS query succeeded. 99/* .IP DNS_NOTFOUND 100/* The DNS query succeeded; the requested information was not found. 101/* .IP DNS_INVAL 102/* The DNS query succeeded; the result failed the valid_hostname() test. 103/* 104/* NOTE: the valid_hostname() test is skipped for results that 105/* the caller suppresses explicitly. For example, when the 106/* caller requests MX record lookup but specifies a null 107/* resource record list argument, no syntax check will be done 108/* for MX server names. 109/* .IP DNS_RETRY 110/* The query failed, or the reply was malformed. 111/* The problem is considered transient. 112/* .IP DNS_FAIL 113/* The query failed. 114/* BUGS 115/* dns_lookup() implements a subset of all possible resource types: 116/* CNAME, MX, A, and some records with similar formatting requirements. 117/* It is unwise to specify the T_ANY wildcard resource type. 118/* 119/* It takes a surprising amount of code to accomplish what appears 120/* to be a simple task. Later versions of the mail system may implement 121/* their own DNS client software. 122/* SEE ALSO 123/* dns_rr(3) resource record memory and list management 124/* LICENSE 125/* .ad 126/* .fi 127/* The Secure Mailer license must be distributed with this software. 128/* AUTHOR(S) 129/* Wietse Venema 130/* IBM T.J. Watson Research 131/* P.O. Box 704 132/* Yorktown Heights, NY 10598, USA 133/*--*/ 134 135/* System library. */ 136 137#include <sys_defs.h> 138#include <netdb.h> 139#include <string.h> 140#include <ctype.h> 141 142/* Utility library. */ 143 144#include <mymalloc.h> 145#include <vstring.h> 146#include <msg.h> 147#include <valid_hostname.h> 148#include <stringops.h> 149 150/* DNS library. */ 151 152#include "dns.h" 153 154/* Local stuff. */ 155 156 /* 157 * Structure to keep track of things while decoding a name server reply. 158 */ 159#define DEF_DNS_REPLY_SIZE 4096 /* in case we're using TCP */ 160#define MAX_DNS_REPLY_SIZE 32768 /* in case we're using TCP */ 161 162typedef struct DNS_REPLY { 163 unsigned char *buf; /* raw reply data */ 164 size_t buf_len; /* reply buffer length */ 165 int query_count; /* number of queries */ 166 int answer_count; /* number of answers */ 167 unsigned char *query_start; /* start of query data */ 168 unsigned char *answer_start; /* start of answer data */ 169 unsigned char *end; /* first byte past reply */ 170} DNS_REPLY; 171 172#define INET_ADDR_LEN 4 /* XXX */ 173#define INET6_ADDR_LEN 16 /* XXX */ 174 175/* dns_query - query name server and pre-parse the reply */ 176 177static int dns_query(const char *name, int type, int flags, 178 DNS_REPLY *reply, VSTRING *why) 179{ 180 HEADER *reply_header; 181 int len; 182 unsigned long saved_options; 183 184 /* 185 * Initialize the reply buffer. 186 */ 187 if (reply->buf == 0) { 188 reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE); 189 reply->buf_len = DEF_DNS_REPLY_SIZE; 190 } 191 192 /* 193 * Initialize the name service. 194 */ 195 if ((_res.options & RES_INIT) == 0 && res_init() < 0) { 196 if (why) 197 vstring_strcpy(why, "Name service initialization failure"); 198 return (DNS_FAIL); 199 } 200 201 /* 202 * Set search options: debugging, parent domain search, append local 203 * domain. Do not allow the user to control other features. 204 */ 205#define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES) 206 207 if ((flags & USER_FLAGS) != flags) 208 msg_panic("dns_query: bad flags: %d", flags); 209 saved_options = (_res.options & USER_FLAGS); 210 211 /* 212 * Perform the lookup. Claim that the information cannot be found if and 213 * only if the name server told us so. 214 */ 215 for (;;) { 216 _res.options &= ~saved_options; 217 _res.options |= flags; 218 len = res_search((char *) name, C_IN, type, reply->buf, reply->buf_len); 219 _res.options &= ~flags; 220 _res.options |= saved_options; 221 if (len < 0) { 222 if (why) 223 vstring_sprintf(why, "Host or domain name not found. " 224 "Name service error for name=%s type=%s: %s", 225 name, dns_strtype(type), dns_strerror(h_errno)); 226 if (msg_verbose) 227 msg_info("dns_query: %s (%s): %s", 228 name, dns_strtype(type), dns_strerror(h_errno)); 229 switch (h_errno) { 230 case NO_RECOVERY: 231 return (DNS_FAIL); 232 case HOST_NOT_FOUND: 233 case NO_DATA: 234 return (DNS_NOTFOUND); 235 default: 236 return (DNS_RETRY); 237 } 238 } 239 if (msg_verbose) 240 msg_info("dns_query: %s (%s): OK", name, dns_strtype(type)); 241 242 reply_header = (HEADER *) reply->buf; 243 if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE) 244 break; 245 reply->buf = (unsigned char *) 246 myrealloc((char *) reply->buf, 2 * reply->buf_len); 247 reply->buf_len *= 2; 248 } 249 250 /* 251 * Paranoia. 252 */ 253 if (len > reply->buf_len) { 254 msg_warn("reply length %d > buffer length %d for name=%s type=%s", 255 len, (int) reply->buf_len, name, dns_strtype(type)); 256 len = reply->buf_len; 257 } 258 259 /* 260 * Initialize the reply structure. Some structure members are filled on 261 * the fly while the reply is being parsed. 262 */ 263 reply->end = reply->buf + len; 264 reply->query_start = reply->buf + sizeof(HEADER); 265 reply->answer_start = 0; 266 reply->query_count = ntohs(reply_header->qdcount); 267 reply->answer_count = ntohs(reply_header->ancount); 268 return (DNS_OK); 269} 270 271/* dns_skip_query - skip query data in name server reply */ 272 273static int dns_skip_query(DNS_REPLY *reply) 274{ 275 int query_count = reply->query_count; 276 unsigned char *pos = reply->query_start; 277 char temp[DNS_NAME_LEN]; 278 int len; 279 280 /* 281 * For each query, skip over the domain name and over the fixed query 282 * data. 283 */ 284 while (query_count-- > 0) { 285 if (pos >= reply->end) 286 return DNS_RETRY; 287 len = dn_expand(reply->buf, reply->end, pos, temp, DNS_NAME_LEN); 288 if (len < 0) 289 return (DNS_RETRY); 290 pos += len + QFIXEDSZ; 291 } 292 reply->answer_start = pos; 293 return (DNS_OK); 294} 295 296/* dns_get_fixed - extract fixed data from resource record */ 297 298static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed) 299{ 300 GETSHORT(fixed->type, pos); 301 GETSHORT(fixed->class, pos); 302 GETLONG(fixed->ttl, pos); 303 GETSHORT(fixed->length, pos); 304 305 if (fixed->class != C_IN) { 306 msg_warn("dns_get_fixed: bad class: %u", fixed->class); 307 return (DNS_RETRY); 308 } 309 return (DNS_OK); 310} 311 312/* valid_rr_name - validate hostname in resource record */ 313 314static int valid_rr_name(const char *name, const char *location, 315 unsigned type, DNS_REPLY *reply) 316{ 317 char temp[DNS_NAME_LEN]; 318 char *query_name; 319 int len; 320 char *gripe; 321 int result; 322 323 /* 324 * People aren't supposed to specify numeric names where domain names are 325 * required, but it "works" with some mailers anyway, so people complain 326 * when software doesn't bend over backwards. 327 */ 328#define PASS_NAME 1 329#define REJECT_NAME 0 330 331 if (valid_hostaddr(name, DONT_GRIPE)) { 332 result = PASS_NAME; 333 gripe = "numeric domain name"; 334 } else if (!valid_hostname(name, DO_GRIPE)) { 335 result = REJECT_NAME; 336 gripe = "malformed domain name"; 337 } else { 338 result = PASS_NAME; 339 gripe = 0; 340 } 341 342 /* 343 * If we have a gripe, show some context, including the name used in the 344 * query and the type of reply that we're looking at. 345 */ 346 if (gripe) { 347 len = dn_expand(reply->buf, reply->end, reply->query_start, 348 temp, DNS_NAME_LEN); 349 query_name = (len < 0 ? "*unparsable*" : temp); 350 msg_warn("%s in %s of %s record for %s: %.100s", 351 gripe, location, dns_strtype(type), query_name, name); 352 } 353 return (result); 354} 355 356/* dns_get_rr - extract resource record from name server reply */ 357 358static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply, 359 unsigned char *pos, char *rr_name, 360 DNS_FIXED *fixed) 361{ 362 char temp[DNS_NAME_LEN]; 363 ssize_t data_len; 364 unsigned pref = 0; 365 unsigned char *src; 366 unsigned char *dst; 367 int ch; 368 369#define MIN2(a, b) ((unsigned)(a) < (unsigned)(b) ? (a) : (b)) 370 371 *list = 0; 372 373 switch (fixed->type) { 374 default: 375 msg_panic("dns_get_rr: don't know how to extract resource type %s", 376 dns_strtype(fixed->type)); 377 case T_CNAME: 378 case T_MB: 379 case T_MG: 380 case T_MR: 381 case T_NS: 382 case T_PTR: 383 if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0) 384 return (DNS_RETRY); 385 if (!valid_rr_name(temp, "resource data", fixed->type, reply)) 386 return (DNS_INVAL); 387 data_len = strlen(temp) + 1; 388 break; 389 case T_MX: 390 GETSHORT(pref, pos); 391 if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0) 392 return (DNS_RETRY); 393 if (!valid_rr_name(temp, "resource data", fixed->type, reply)) 394 return (DNS_INVAL); 395 data_len = strlen(temp) + 1; 396 break; 397 case T_A: 398 if (fixed->length != INET_ADDR_LEN) { 399 msg_warn("extract_answer: bad address length: %d", fixed->length); 400 return (DNS_RETRY); 401 } 402 if (fixed->length > sizeof(temp)) 403 msg_panic("dns_get_rr: length %d > DNS_NAME_LEN", 404 fixed->length); 405 memcpy(temp, pos, fixed->length); 406 data_len = fixed->length; 407 break; 408#ifdef T_AAAA 409 case T_AAAA: 410 if (fixed->length != INET6_ADDR_LEN) { 411 msg_warn("extract_answer: bad address length: %d", fixed->length); 412 return (DNS_RETRY); 413 } 414 if (fixed->length > sizeof(temp)) 415 msg_panic("dns_get_rr: length %d > DNS_NAME_LEN", 416 fixed->length); 417 memcpy(temp, pos, fixed->length); 418 data_len = fixed->length; 419 break; 420#endif 421 case T_TXT: 422 data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp))); 423 for (src = pos + 1, dst = (unsigned char *) (temp); 424 dst < (unsigned char *) (temp) + data_len - 1; /* */ ) { 425 ch = *src++; 426 *dst++ = (ISPRINT(ch) ? ch : ' '); 427 } 428 *dst = 0; 429 break; 430 } 431 *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class, 432 fixed->ttl, pref, temp, data_len); 433 return (DNS_OK); 434} 435 436/* dns_get_alias - extract CNAME from name server reply */ 437 438static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos, 439 DNS_FIXED *fixed, char *cname, int c_len) 440{ 441 if (fixed->type != T_CNAME) 442 msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type)); 443 if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0) 444 return (DNS_RETRY); 445 if (!valid_rr_name(cname, "resource data", fixed->type, reply)) 446 return (DNS_INVAL); 447 return (DNS_OK); 448} 449 450/* dns_get_answer - extract answers from name server reply */ 451 452static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type, 453 DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len) 454{ 455 char rr_name[DNS_NAME_LEN]; 456 unsigned char *pos; 457 int answer_count = reply->answer_count; 458 int len; 459 DNS_FIXED fixed; 460 DNS_RR *rr; 461 int resource_found = 0; 462 int cname_found = 0; 463 int not_found_status = DNS_NOTFOUND; /* can't happen */ 464 int status; 465 466 /* 467 * Initialize. Skip over the name server query if we haven't yet. 468 */ 469 if (reply->answer_start == 0) 470 if ((status = dns_skip_query(reply)) < 0) 471 return (status); 472 pos = reply->answer_start; 473 if (rrlist) 474 *rrlist = 0; 475 476 /* 477 * Either this, or use a GOTO for emergency exits. The purpose is to 478 * prevent incomplete answers from being passed back to the caller. 479 */ 480#define CORRUPT(status) { \ 481 if (rrlist && *rrlist) { \ 482 dns_rr_free(*rrlist); \ 483 *rrlist = 0; \ 484 } \ 485 return (status); \ 486 } 487 488 /* 489 * Iterate over all answers. 490 */ 491 while (answer_count-- > 0) { 492 493 /* 494 * Optionally extract the fully-qualified domain name. 495 */ 496 if (pos >= reply->end) 497 CORRUPT(DNS_RETRY); 498 len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN); 499 if (len < 0) 500 CORRUPT(DNS_RETRY); 501 pos += len; 502 503 /* 504 * Extract the fixed reply data: type, class, ttl, length. 505 */ 506 if (pos + RRFIXEDSZ > reply->end) 507 CORRUPT(DNS_RETRY); 508 if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK) 509 CORRUPT(status); 510 if (!valid_rr_name(rr_name, "resource name", fixed.type, reply)) 511 CORRUPT(DNS_INVAL); 512 if (fqdn) 513 vstring_strcpy(fqdn, rr_name); 514 if (msg_verbose) 515 msg_info("dns_get_answer: type %s for %s", 516 dns_strtype(fixed.type), rr_name); 517 pos += RRFIXEDSZ; 518 519 /* 520 * Optionally extract the requested resource or CNAME data. 521 */ 522 if (pos + fixed.length > reply->end) 523 CORRUPT(DNS_RETRY); 524 if (type == fixed.type || type == T_ANY) { /* requested type */ 525 if (rrlist) { 526 if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name, 527 &fixed)) == DNS_OK) { 528 resource_found++; 529 *rrlist = dns_rr_append(*rrlist, rr); 530 } else if (not_found_status != DNS_RETRY) 531 not_found_status = status; 532 } else 533 resource_found++; 534 } else if (fixed.type == T_CNAME) { /* cname resource */ 535 cname_found++; 536 if (cname && c_len > 0) 537 if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK) 538 CORRUPT(status); 539 } 540 pos += fixed.length; 541 } 542 543 /* 544 * See what answer we came up with. Report success when the requested 545 * information was found. Otherwise, when a CNAME was found, report that 546 * more recursion is needed. Otherwise report failure. 547 */ 548 if (resource_found) 549 return (DNS_OK); 550 if (cname_found) 551 return (DNS_RECURSE); 552 return (not_found_status); 553} 554 555/* dns_lookup - DNS lookup user interface */ 556 557int dns_lookup(const char *name, unsigned type, unsigned flags, 558 DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why) 559{ 560 char cname[DNS_NAME_LEN]; 561 int c_len = sizeof(cname); 562 static DNS_REPLY reply; 563 int count; 564 int status; 565 const char *orig_name = name; 566 567 /* 568 * DJBDNS produces a bogus A record when given a numerical hostname. 569 */ 570 if (valid_hostaddr(name, DONT_GRIPE)) { 571 if (why) 572 vstring_sprintf(why, 573 "Name service error for %s: invalid host or domain name", 574 name); 575 SET_H_ERRNO(HOST_NOT_FOUND); 576 return (DNS_NOTFOUND); 577 } 578 579 /* 580 * The Linux resolver misbehaves when given an invalid domain name. 581 */ 582 if (!valid_hostname(name, DONT_GRIPE)) { 583 if (why) 584 vstring_sprintf(why, 585 "Name service error for %s: invalid host or domain name", 586 name); 587 SET_H_ERRNO(HOST_NOT_FOUND); 588 return (DNS_NOTFOUND); 589 } 590 591 /* 592 * Perform the lookup. Follow CNAME chains, but only up to a 593 * pre-determined maximum. 594 */ 595 for (count = 0; count < 10; count++) { 596 597 /* 598 * Perform the DNS lookup, and pre-parse the name server reply. 599 */ 600 if ((status = dns_query(name, type, flags, &reply, why)) != DNS_OK) 601 return (status); 602 603 /* 604 * Extract resource records of the requested type. Pick up CNAME 605 * information just in case the requested data is not found. 606 */ 607 status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn, 608 cname, c_len); 609 switch (status) { 610 default: 611 if (why) 612 vstring_sprintf(why, "Name service error for name=%s type=%s: " 613 "Malformed or unexpected name server reply", 614 name, dns_strtype(type)); 615 /* FALLTHROUGH */ 616 case DNS_OK: 617 return (status); 618 case DNS_RECURSE: 619 if (msg_verbose) 620 msg_info("dns_lookup: %s aliased to %s", name, cname); 621 name = cname; 622 } 623 } 624 if (why) 625 vstring_sprintf(why, "Name server loop for %s", name); 626 msg_warn("dns_lookup: Name server loop for %s", name); 627 return (DNS_NOTFOUND); 628} 629 630/* dns_lookup_l - DNS lookup interface with types list */ 631 632int dns_lookup_l(const char *name, unsigned flags, DNS_RR **rrlist, 633 VSTRING *fqdn, VSTRING *why, int lflags,...) 634{ 635 va_list ap; 636 unsigned type; 637 int status = DNS_NOTFOUND; 638 DNS_RR *rr; 639 int non_err = 0; 640 int soft_err = 0; 641 642 if (rrlist) 643 *rrlist = 0; 644 va_start(ap, lflags); 645 while ((type = va_arg(ap, unsigned)) != 0) { 646 if (msg_verbose) 647 msg_info("lookup %s type %s flags %d", 648 name, dns_strtype(type), flags); 649 status = dns_lookup(name, type, flags, rrlist ? &rr : (DNS_RR **) 0, 650 fqdn, why); 651 if (status == DNS_OK) { 652 non_err = 1; 653 if (rrlist) 654 *rrlist = dns_rr_append(*rrlist, rr); 655 if (lflags & DNS_REQ_FLAG_STOP_OK) 656 break; 657 } else if (status == DNS_INVAL) { 658 if (lflags & DNS_REQ_FLAG_STOP_INVAL) 659 break; 660 } else if (status == DNS_RETRY) { 661 soft_err = 1; 662 } 663 /* XXX Stop after NXDOMAIN error. */ 664 } 665 va_end(ap); 666 return (non_err ? DNS_OK : soft_err ? DNS_RETRY : status); 667} 668 669/* dns_lookup_v - DNS lookup interface with types vector */ 670 671int dns_lookup_v(const char *name, unsigned flags, DNS_RR **rrlist, 672 VSTRING *fqdn, VSTRING *why, int lflags, 673 unsigned *types) 674{ 675 unsigned type; 676 int status = DNS_NOTFOUND; 677 DNS_RR *rr; 678 int non_err = 0; 679 int soft_err = 0; 680 681 if (rrlist) 682 *rrlist = 0; 683 while ((type = *types++) != 0) { 684 if (msg_verbose) 685 msg_info("lookup %s type %s flags %d", 686 name, dns_strtype(type), flags); 687 status = dns_lookup(name, type, flags, rrlist ? &rr : (DNS_RR **) 0, 688 fqdn, why); 689 if (status == DNS_OK) { 690 non_err = 1; 691 if (rrlist) 692 *rrlist = dns_rr_append(*rrlist, rr); 693 if (lflags & DNS_REQ_FLAG_STOP_OK) 694 break; 695 } else if (status == DNS_INVAL) { 696 if (lflags & DNS_REQ_FLAG_STOP_INVAL) 697 break; 698 } else if (status == DNS_RETRY) { 699 soft_err = 1; 700 } 701 /* XXX Stop after NXDOMAIN error. */ 702 } 703 return (non_err ? DNS_OK : soft_err ? DNS_RETRY : status); 704} 705