1 /* 2 * nsec3.c -- nsec3 handling. 3 * 4 * Copyright (c) 2001-2011, NLnet Labs. All rights reserved. 5 * 6 * See LICENSE for the license. 7 * 8 */ 9 #include <config.h> 10 #ifdef NSEC3 11 #include <stdio.h> 12 #include <stdlib.h> 13 14 #include "nsec3.h" 15 #include "iterated_hash.h" 16 #include "namedb.h" 17 #include "nsd.h" 18 #include "answer.h" 19 20 #define NSEC3_SHA1_HASH 1 /* same type code as DS hash */ 21 22 /* detect is the latter rrset has the same hashalgo, iterations and salt 23 as the base. Does not compare optout bit, or other rdata. 24 base=NULL uses the zone soa_rr. */ 25 static int nsec3_rrset_params_ok(rr_type* base, rrset_type* rrset); 26 27 static void 28 detect_nsec3_params(rr_type* nsec3_apex, 29 const unsigned char** salt, int* salt_len, int* iter) 30 { 31 /* always uses first NSEC3 record with SOA bit set */ 32 assert(salt && salt_len && iter); 33 assert(nsec3_apex); 34 *salt_len = rdata_atom_data(nsec3_apex->rdatas[3])[0]; 35 *salt = (unsigned char*)(rdata_atom_data(nsec3_apex->rdatas[3])+1); 36 *iter = read_uint16(rdata_atom_data(nsec3_apex->rdatas[2])); 37 } 38 39 static const dname_type * 40 nsec3_hash_dname_param(region_type *region, zone_type *zone, 41 const dname_type *dname, rr_type* param_rr) 42 { 43 unsigned char hash[SHA_DIGEST_LENGTH]; 44 char b32[SHA_DIGEST_LENGTH*2+1]; 45 const unsigned char* nsec3_salt = NULL; 46 int nsec3_saltlength = 0; 47 int nsec3_iterations = 0; 48 49 detect_nsec3_params(param_rr, &nsec3_salt, 50 &nsec3_saltlength, &nsec3_iterations); 51 iterated_hash(hash, nsec3_salt, nsec3_saltlength, dname_name(dname), 52 dname->name_size, nsec3_iterations); 53 b32_ntop(hash, sizeof(hash), b32, sizeof(b32)); 54 dname=dname_parse(region, b32); 55 dname=dname_concatenate(region, dname, domain_dname(zone->apex)); 56 return dname; 57 } 58 59 const dname_type * 60 nsec3_hash_dname(region_type *region, zone_type *zone, 61 const dname_type *dname) 62 { 63 return nsec3_hash_dname_param(region, zone, 64 dname, zone->nsec3_soa_rr); 65 } 66 67 static int 68 nsec3_has_soa(rr_type* rr) 69 { 70 if(rdata_atom_size(rr->rdatas[5]) >= 3 && /* has types in bitmap */ 71 rdata_atom_data(rr->rdatas[5])[0] == 0 && /* first window = 0, */ 72 /* [1]: windowlen must be >= 1 */ 73 rdata_atom_data(rr->rdatas[5])[2]&0x02) /* SOA bit set */ 74 return 1; 75 return 0; 76 } 77 78 static rr_type* 79 find_zone_nsec3(namedb_type* namedb, zone_type *zone) 80 { 81 size_t i; 82 domain_type* domain; 83 region_type* tmpregion; 84 /* Check settings in NSEC3PARAM. 85 Hash algorithm must be OK. And a NSEC3 with soa bit 86 must map to the zone apex. */ 87 rrset_type* paramset = domain_find_rrset(zone->apex, zone, TYPE_NSEC3PARAM); 88 if(!paramset || !paramset->rrs || !paramset->rr_count) 89 return 0; 90 tmpregion = region_create(xalloc, free); 91 for(i=0; i<paramset->rr_count; i++) 92 { 93 rr_type* rr = ¶mset->rrs[i]; 94 const dname_type* hashed_apex; 95 rrset_type* nsec3_rrset; 96 size_t j; 97 98 if(rdata_atom_data(rr->rdatas[0])[0] != NSEC3_SHA1_HASH) { 99 log_msg(LOG_ERR, "%s NSEC3PARAM entry %d has unknown hash algo %d", 100 dname_to_string(domain_dname(zone->apex), NULL), (int)i, 101 rdata_atom_data(rr->rdatas[0])[0]); 102 continue; 103 } 104 if(rdata_atom_data(rr->rdatas[1])[0] != 0) { 105 /* draft-nsec3-09: NSEC3PARAM records with flags 106 field value other than zero MUST be ignored. */ 107 continue; 108 } 109 /* check hash of apex -> NSEC3 with soa bit on */ 110 hashed_apex = nsec3_hash_dname_param(tmpregion, 111 zone, domain_dname(zone->apex), ¶mset->rrs[i]); 112 domain = domain_table_find(namedb->domains, hashed_apex); 113 if(!domain) { 114 log_msg(LOG_ERR, "%s NSEC3PARAM entry %d has no hash(apex).", 115 dname_to_string(domain_dname(zone->apex), NULL), (int)i); 116 log_msg(LOG_ERR, "hash(apex)= %s", 117 dname_to_string(hashed_apex, NULL)); 118 continue; 119 } 120 nsec3_rrset = domain_find_rrset(domain, zone, TYPE_NSEC3); 121 if(!nsec3_rrset) { 122 log_msg(LOG_ERR, "%s NSEC3PARAM entry %d: hash(apex) has no NSEC3 RRset", 123 dname_to_string(domain_dname(zone->apex), NULL), (int)i); 124 continue; 125 } 126 /* find SOA bit enabled nsec3, with the same settings */ 127 for(j=0; j<nsec3_rrset->rr_count; j++) 128 { 129 const unsigned char *salt1, *salt2; 130 int saltlen1, saltlen2, iter1, iter2; 131 if(!nsec3_has_soa(&nsec3_rrset->rrs[j])) 132 continue; 133 /* check params OK. Ignores the optout bit. */ 134 detect_nsec3_params(rr, &salt1, &saltlen1, &iter1); 135 detect_nsec3_params(&nsec3_rrset->rrs[j], 136 &salt2, &saltlen2, &iter2); 137 if(saltlen1 == saltlen2 && iter1 == iter2 && 138 rdata_atom_data(rr->rdatas[0])[0] == /* algo */ 139 rdata_atom_data(nsec3_rrset->rrs[j].rdatas[0])[0] 140 && memcmp(salt1, salt2, saltlen1) == 0) { 141 /* found it */ 142 DEBUG(DEBUG_QUERY, 1, (LOG_INFO, 143 "detected NSEC3 for zone %s saltlen=%d iter=%d", 144 dname_to_string(domain_dname( 145 zone->apex),0), saltlen2, iter2)); 146 region_destroy(tmpregion); 147 return &nsec3_rrset->rrs[j]; 148 } 149 } 150 log_msg(LOG_ERR, "%s NSEC3PARAM entry %d: hash(apex) no NSEC3 with SOAbit", 151 dname_to_string(domain_dname(zone->apex), NULL), (int)i); 152 } 153 region_destroy(tmpregion); 154 return 0; 155 } 156 157 /* check that the rrset has an NSEC3 that uses the same parameters as the 158 zone is using. Pass NSEC3 rrset, and zone must have nsec3_rrset set. 159 if you pass NULL then 0 is returned. */ 160 static int 161 nsec3_rrset_params_ok(rr_type* base, rrset_type* rrset) 162 { 163 rdata_atom_type* prd; 164 rdata_atom_type* rd; 165 size_t i; 166 if(!rrset) 167 return 0; /* without rrset, no matching params either */ 168 assert(rrset && rrset->zone && (base || rrset->zone->nsec3_soa_rr)); 169 if(!base) 170 base = rrset->zone->nsec3_soa_rr; 171 prd = base->rdatas; 172 for(i=0; i<rrset->rr_count; ++i) 173 { 174 rd = rrset->rrs[i].rdatas; 175 assert(rrset->rrs[i].type == TYPE_NSEC3); 176 if(rdata_atom_data(rd[0])[0] == 177 rdata_atom_data(prd[0])[0] && /* hash algo */ 178 rdata_atom_data(rd[2])[0] == 179 rdata_atom_data(prd[2])[0] && /* iterations 0 */ 180 rdata_atom_data(rd[2])[1] == 181 rdata_atom_data(prd[2])[1] && /* iterations 1 */ 182 rdata_atom_data(rd[3])[0] == 183 rdata_atom_data(prd[3])[0] && /* salt length */ 184 memcmp(rdata_atom_data(rd[3])+1, 185 rdata_atom_data(prd[3])+1, rdata_atom_data(rd[3])[0]) 186 == 0 ) 187 { 188 /* this NSEC3 matches nsec3 parameters from zone */ 189 return 1; 190 } 191 } 192 return 0; 193 } 194 195 int 196 nsec3_find_cover(namedb_type* db, zone_type* zone, 197 const dname_type* hashname, domain_type** result) 198 { 199 rrset_type *rrset; 200 domain_type *walk; 201 domain_type *closest_match; 202 domain_type *closest_encloser; 203 int exact; 204 205 assert(result); 206 assert(zone->nsec3_soa_rr); 207 208 exact = domain_table_search( 209 db->domains, hashname, &closest_match, &closest_encloser); 210 /* exact match of hashed domain name + it has an NSEC3? */ 211 if(exact && 212 nsec3_rrset_params_ok(NULL, 213 domain_find_rrset(closest_encloser, zone, TYPE_NSEC3))) { 214 *result = closest_encloser; 215 assert(*result != 0); 216 return 1; 217 } 218 219 /* find covering NSEC3 record, lexicographically before the closest match */ 220 /* use nsec3_lookup to jumpstart the search */ 221 walk = closest_match->nsec3_lookup; 222 rrset = 0; 223 while(walk && dname_is_subdomain(domain_dname(walk), domain_dname(zone->apex))) 224 { 225 if(nsec3_rrset_params_ok(NULL, 226 domain_find_rrset(walk, zone, TYPE_NSEC3))) { 227 /* this rrset is OK NSEC3, exit while */ 228 rrset = domain_find_rrset(walk, zone, TYPE_NSEC3); 229 break; 230 } 231 walk = domain_previous(walk); 232 } 233 if(rrset) 234 *result = walk; 235 else { 236 /* 237 * There are no NSEC3s before the closest match. 238 * so the hash name is before the first NSEC3 record in the zone. 239 * use last NSEC3, which covers the wraparound in hash space 240 * 241 * Since the zone has an NSEC3 with the SOA bit set for NSEC3 to turn on, 242 * there is also a last nsec3, so find_cover always assigns *result!=0. 243 */ 244 *result = zone->nsec3_last; 245 } 246 assert(*result != 0); 247 return 0; 248 } 249 250 static void 251 prehash_domain(namedb_type* db, zone_type* zone, 252 domain_type* domain, region_type* region) 253 { 254 /* find it */ 255 domain_type* result = 0; 256 const dname_type *wcard, *wcard_child, *hashname; 257 int exact; 258 259 if(!zone->nsec3_soa_rr) 260 { 261 /* set to 0 (in case NSEC3 removed after an update) */ 262 domain->nsec3_is_exact = 0; 263 domain->nsec3_cover = NULL; 264 domain->nsec3_wcard_child_cover = NULL; 265 return; 266 } 267 268 hashname = nsec3_hash_dname(region, zone, domain_dname(domain)); 269 exact = nsec3_find_cover(db, zone, hashname, &result); 270 domain->nsec3_cover = result; 271 if(exact) 272 domain->nsec3_is_exact = 1; 273 else domain->nsec3_is_exact = 0; 274 275 /* find cover for *.domain for wildcard denial */ 276 wcard = dname_parse(region, "*"); 277 wcard_child = dname_concatenate(region, wcard, domain_dname(domain)); 278 hashname = nsec3_hash_dname(region, zone, wcard_child); 279 exact = nsec3_find_cover(db, zone, hashname, &result); 280 domain->nsec3_wcard_child_cover = result; 281 282 if(exact && !domain_wildcard_child(domain)) 283 { 284 /* We found an exact match for the *.domain NSEC3 hash, 285 * but the domain wildcard child (*.domain) does not exist. 286 * Thus there is a hash collision. It will cause servfail 287 * for NXdomain queries below this domain. 288 */ 289 log_msg(LOG_WARNING, "prehash: collision of wildcard " 290 "denial for %s. Sign zone with different salt " 291 "to remove collision.", 292 dname_to_string(domain_dname(domain),0)); 293 } 294 } 295 296 static void 297 prehash_ds(namedb_type* db, zone_type* zone, 298 domain_type* domain, region_type* region) 299 { 300 domain_type* result = 0; 301 const dname_type* hashname; 302 int exact; 303 304 if(!zone->nsec3_soa_rr) { 305 domain->nsec3_ds_parent_is_exact = 0; 306 domain->nsec3_ds_parent_cover = NULL; 307 return; 308 } 309 310 /* hash again, other zone could have different hash parameters */ 311 hashname = nsec3_hash_dname(region, zone, domain_dname(domain)); 312 exact = nsec3_find_cover(db, zone, hashname, &result); 313 if(exact) 314 domain->nsec3_ds_parent_is_exact = 1; 315 else domain->nsec3_ds_parent_is_exact = 0; 316 domain->nsec3_ds_parent_cover = result; 317 } 318 319 static void 320 prehash_zone(struct namedb* db, struct zone* zone) 321 { 322 domain_type *walk; 323 domain_type *last_nsec3_node; 324 region_type *temp_region; 325 assert(db && zone); 326 327 /* find zone settings */ 328 zone->nsec3_soa_rr = find_zone_nsec3(db, zone); 329 if(!zone->nsec3_soa_rr) { 330 zone->nsec3_last = 0; 331 return; 332 } 333 334 temp_region = region_create(xalloc, free); 335 336 /* go through entire zone and setup nsec3_lookup speedup */ 337 walk = zone->apex; 338 last_nsec3_node = NULL; 339 /* since we walk in sorted order, we pass all NSEC3s in sorted 340 order and we can set the lookup ptrs */ 341 while(walk && dname_is_subdomain( 342 domain_dname(walk), domain_dname(zone->apex))) 343 { 344 zone_type* z = domain_find_zone(walk); 345 if(z && z==zone) 346 { 347 if(domain_find_rrset(walk, zone, TYPE_NSEC3)) 348 last_nsec3_node = walk; 349 walk->nsec3_lookup = last_nsec3_node; 350 } 351 walk = domain_next(walk); 352 } 353 zone->nsec3_last = last_nsec3_node; 354 355 /* go through entire zone */ 356 walk = zone->apex; 357 while(walk && dname_is_subdomain( 358 domain_dname(walk), domain_dname(zone->apex))) 359 { 360 zone_type* z; 361 if(!walk->is_existing || domain_has_only_NSEC3(walk, zone)) { 362 walk->nsec3_cover = NULL; 363 walk->nsec3_wcard_child_cover = NULL; 364 walk = domain_next(walk); 365 continue; 366 } 367 z = domain_find_zone(walk); 368 if(z && z==zone && !domain_is_glue(walk, zone)) 369 { 370 prehash_domain(db, zone, walk, temp_region); 371 region_free_all(temp_region); 372 } 373 /* prehash the DS (parent zone) */ 374 if(domain_find_rrset(walk, zone, TYPE_DS) || 375 (domain_find_rrset(walk, zone, TYPE_NS) && 376 walk != zone->apex)) 377 { 378 assert(walk != zone->apex /* DS must be above zone cut */); 379 prehash_ds(db, zone, walk, temp_region); 380 region_free_all(temp_region); 381 } 382 walk = domain_next(walk); 383 } 384 region_destroy(temp_region); 385 } 386 387 void 388 prehash(struct namedb* db, int updated_only) 389 { 390 zone_type *z; 391 time_t end, start = time(NULL); 392 int count = 0; 393 for(z = db->zones; z; z = z->next) 394 { 395 if(!updated_only || z->updated) { 396 prehash_zone(db, z); 397 if(z->nsec3_soa_rr) 398 count++; 399 } 400 } 401 end = time(NULL); 402 if(count > 0) 403 VERBOSITY(1, (LOG_INFO, "nsec3-prepare took %d " 404 "seconds for %d zones.", (int)(end-start), count)); 405 } 406 407 /* add the NSEC3 rrset to the query answer at the given domain */ 408 static void 409 nsec3_add_rrset(struct query *query, struct answer *answer, 410 rr_section_type section, struct domain* domain) 411 { 412 if(domain) { 413 rrset_type* rrset = domain_find_rrset(domain, query->zone, TYPE_NSEC3); 414 if(rrset) 415 answer_add_rrset(answer, section, domain, rrset); 416 } 417 } 418 419 /* this routine does hashing at query-time. slow. */ 420 static void 421 nsec3_add_nonexist_proof(struct query *query, struct answer *answer, 422 struct domain *encloser, struct namedb* db, const dname_type* qname) 423 { 424 const dname_type *to_prove, *hashed; 425 domain_type *cover=0; 426 assert(encloser); 427 /* if query=a.b.c.d encloser=c.d. then proof needed for b.c.d. */ 428 /* if query=a.b.c.d encloser=*.c.d. then proof needed for b.c.d. */ 429 to_prove = dname_partial_copy(query->region, qname, 430 dname_label_match_count(qname, domain_dname(encloser))+1); 431 /* generate proof that one label below closest encloser does not exist */ 432 hashed = nsec3_hash_dname(query->region, query->zone, to_prove); 433 if(nsec3_find_cover(db, query->zone, hashed, &cover)) 434 { 435 /* exact match, hash collision */ 436 /* the hashed name of the query corresponds to an existing name. */ 437 log_msg(LOG_ERR, "nsec3 hash collision for name=%s", 438 dname_to_string(to_prove, NULL)); 439 RCODE_SET(query->packet, RCODE_SERVFAIL); 440 return; 441 } 442 else 443 { 444 /* cover proves the qname does not exist */ 445 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, cover); 446 } 447 } 448 449 static void 450 nsec3_add_closest_encloser_proof( 451 struct query *query, struct answer *answer, 452 struct domain *closest_encloser, struct namedb* db, 453 const dname_type* qname) 454 { 455 if(!closest_encloser) 456 return; 457 /* prove that below closest encloser nothing exists */ 458 nsec3_add_nonexist_proof(query, answer, closest_encloser, db, qname); 459 /* proof that closest encloser exists */ 460 if(closest_encloser->nsec3_is_exact) 461 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 462 closest_encloser->nsec3_cover); 463 } 464 465 void 466 nsec3_answer_wildcard(struct query *query, struct answer *answer, 467 struct domain *wildcard, struct namedb* db, const dname_type* qname) 468 { 469 if(!wildcard) 470 return; 471 if(!query->zone->nsec3_soa_rr) 472 return; 473 nsec3_add_nonexist_proof(query, answer, wildcard, db, qname); 474 } 475 476 static void 477 nsec3_add_ds_proof(struct query *query, struct answer *answer, 478 struct domain *domain, int delegpt) 479 { 480 /* assert we are above the zone cut */ 481 assert(domain != query->zone->apex); 482 if(domain->nsec3_ds_parent_is_exact) { 483 /* use NSEC3 record from above the zone cut. */ 484 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 485 domain->nsec3_ds_parent_cover); 486 } else if (!delegpt && domain->nsec3_is_exact) { 487 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 488 domain->nsec3_cover); 489 } else { 490 /* prove closest provable encloser */ 491 domain_type* par = domain->parent; 492 domain_type* prev_par = 0; 493 494 while(par && !par->nsec3_is_exact) 495 { 496 prev_par = par; 497 par = par->parent; 498 } 499 assert(par); /* parent zone apex must be provable, thus this ends */ 500 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 501 par->nsec3_cover); 502 /* we took several steps to go to the provable parent, so 503 the one below it has no exact nsec3, disprove it. 504 disprove is easy, it has a prehashed cover ptr. */ 505 if(prev_par) { 506 assert(prev_par != domain && !prev_par->nsec3_is_exact); 507 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 508 prev_par->nsec3_cover); 509 } 510 /* add optout range from parent zone */ 511 /* note: no check of optout bit, resolver checks it */ 512 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 513 domain->nsec3_ds_parent_cover); 514 } 515 } 516 517 void 518 nsec3_answer_nodata(struct query *query, struct answer *answer, 519 struct domain *original) 520 { 521 if(!query->zone->nsec3_soa_rr) 522 return; 523 /* nodata when asking for secure delegation */ 524 if(query->qtype == TYPE_DS) 525 { 526 if(original == query->zone->apex) { 527 /* DS at zone apex, but server not authoritative for parent zone */ 528 /* so answer at the child zone level */ 529 if(original->nsec3_is_exact) 530 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 531 original->nsec3_cover); 532 return; 533 } 534 /* query->zone must be the parent zone */ 535 nsec3_add_ds_proof(query, answer, original, 0); 536 } 537 /* the nodata is result from a wildcard match */ 538 else if (original==original->wildcard_child_closest_match 539 && label_is_wildcard(dname_name(domain_dname(original)))) { 540 /* denial for wildcard is already there */ 541 /* add parent proof to have a closest encloser proof for wildcard parent */ 542 if(original->parent && original->parent->nsec3_is_exact) 543 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 544 original->parent->nsec3_cover); 545 /* proof for wildcard itself */ 546 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 547 original->nsec3_cover); 548 } 549 else { /* add nsec3 to prove rrset does not exist */ 550 if(original->nsec3_is_exact) 551 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 552 original->nsec3_cover); 553 } 554 } 555 556 void 557 nsec3_answer_delegation(struct query *query, struct answer *answer) 558 { 559 if(!query->zone->nsec3_soa_rr) 560 return; 561 nsec3_add_ds_proof(query, answer, query->delegation_domain, 1); 562 } 563 564 int 565 domain_has_only_NSEC3(struct domain* domain, struct zone* zone) 566 { 567 /* check for only NSEC3/RRSIG */ 568 rrset_type* rrset = domain->rrsets; 569 int nsec3_seen = 0, rrsig_seen = 0; 570 while(rrset) 571 { 572 if(!zone || rrset->zone == zone) 573 { 574 if(rrset->rrs[0].type == TYPE_NSEC3) 575 nsec3_seen = 1; 576 else if(rrset->rrs[0].type == TYPE_RRSIG) 577 rrsig_seen = 1; 578 else 579 return 0; 580 } 581 rrset = rrset->next; 582 } 583 return nsec3_seen; 584 } 585 586 void 587 nsec3_answer_authoritative(struct domain** match, struct query *query, 588 struct answer *answer, struct domain* closest_encloser, 589 struct namedb* db, const dname_type* qname) 590 { 591 if(!query->zone->nsec3_soa_rr) 592 return; 593 assert(match); 594 /* there is a match, this has 1 RRset, which is NSEC3, but qtype is not. */ 595 if(*match && 596 #if 0 597 query->qtype != TYPE_NSEC3 && 598 #endif 599 domain_has_only_NSEC3(*match, query->zone)) 600 { 601 /* act as if the NSEC3 domain did not exist, name error */ 602 *match = 0; 603 /* all nsec3s are directly below the apex, that is closest encloser */ 604 if(query->zone->apex->nsec3_is_exact) 605 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 606 query->zone->apex->nsec3_cover); 607 /* disprove the nsec3 record. */ 608 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, closest_encloser->nsec3_cover); 609 /* disprove a wildcard */ 610 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, query->zone->apex-> 611 nsec3_wcard_child_cover); 612 if (domain_wildcard_child(query->zone->apex)) { 613 /* wildcard exists below the domain */ 614 /* wildcard and nsec3 domain clash. server failure. */ 615 RCODE_SET(query->packet, RCODE_SERVFAIL); 616 } 617 return; 618 } 619 if(!*match) { 620 /* name error, domain does not exist */ 621 nsec3_add_closest_encloser_proof(query, answer, closest_encloser, 622 db, qname); 623 nsec3_add_rrset(query, answer, AUTHORITY_SECTION, 624 closest_encloser->nsec3_wcard_child_cover); 625 } 626 } 627 628 #endif /* NSEC3 */ 629