Logo Search packages:      
Sourcecode: unbound version File versions  Download package

val_nsec3.c

Go to the documentation of this file.
/*
 * validator/val_nsec3.c - validator NSEC3 denial of existance functions.
 *
 * Copyright (c) 2007, NLnet Labs. All rights reserved.
 *
 * This software is open source.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of the NLNET LABS nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * \file
 *
 * This file contains helper functions for the validator module.
 * The functions help with NSEC3 checking, the different NSEC3 proofs
 * for denial of existance, and proofs for presence of types.
 */
#include "config.h"
#include "validator/val_nsec3.h"
#include "validator/validator.h"
#include "validator/val_kentry.h"
#include "util/regional.h"
#include "util/rbtree.h"
#include "util/module.h"
#include "util/net_help.h"
#include "util/data/packed_rrset.h"
#include "util/data/dname.h"
#include "util/data/msgreply.h"
/* we include nsec.h for the bitmap_has_type function */
#include "validator/val_nsec.h"

/** 
 * This function we get from ldns-compat or from base system 
 * it returns the number of data bytes stored at the target, or <0 on error.
 */
int b32_ntop_extended_hex(uint8_t const *src, size_t srclength,
      char *target, size_t targsize);
/** 
 * This function we get from ldns-compat or from base system 
 * it returns the number of data bytes stored at the target, or <0 on error.
 */
int b32_pton_extended_hex(char const *src, size_t hashed_owner_str_len, 
      uint8_t *target, size_t targsize);

/**
 * Closest encloser (ce) proof results
 * Contains the ce and the next-closer (nc) proof.
 */
00074 struct ce_response {
      /** the closest encloser name */
00076       uint8_t* ce;
      /** length of ce */
00078       size_t ce_len;
      /** NSEC3 record that proved ce. rrset */
00080       struct ub_packed_rrset_key* ce_rrset;
      /** NSEC3 record that proved ce. rr number */
00082       int ce_rr;
      /** NSEC3 record that proved nc. rrset */
00084       struct ub_packed_rrset_key* nc_rrset;
      /** NSEC3 record that proved nc. rr*/
00086       int nc_rr;
};

/**
 * Filter conditions for NSEC3 proof
 * Used to iterate over the applicable NSEC3 RRs.
 */
00093 struct nsec3_filter {
      /** Zone name, only NSEC3 records for this zone are considered */
00095       uint8_t* zone;
      /** length of the zonename */
00097       size_t zone_len;
      /** the list of NSEC3s to filter; array */
00099       struct ub_packed_rrset_key** list;
      /** number of rrsets in list */
00101       size_t num;
      /** class of records for the NSEC3, only this class applies */
00103       uint16_t fclass;
};

/** return number of rrs in an rrset */
static size_t
00108 rrset_get_count(struct ub_packed_rrset_key* rrset)
{
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
        if(!d) return 0;
        return d->count;
}

/** return if nsec3 RR has unknown flags */
static int
00118 nsec3_unknown_flags(struct ub_packed_rrset_key* rrset, int r)
{
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
      log_assert(d && r < (int)d->count);
      if(d->rr_len[r] < 2+2)
            return 0; /* malformed */
      return (int)(d->rr_data[r][2+1] & NSEC3_UNKNOWN_FLAGS);
}

/** return if nsec3 RR has the optout flag */
static int
00130 nsec3_has_optout(struct ub_packed_rrset_key* rrset, int r)
{
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
      log_assert(d && r < (int)d->count);
      if(d->rr_len[r] < 2+2)
            return 0; /* malformed */
      return (int)(d->rr_data[r][2+1] & NSEC3_OPTOUT);
}

/** return nsec3 RR algorithm */
static int
00142 nsec3_get_algo(struct ub_packed_rrset_key* rrset, int r)
{
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
      log_assert(d && r < (int)d->count);
      if(d->rr_len[r] < 2+1)
            return 0; /* malformed */
      return (int)(d->rr_data[r][2+0]);
}

/** return if nsec3 RR has known algorithm */
static int
00154 nsec3_known_algo(struct ub_packed_rrset_key* rrset, int r)
{
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
      log_assert(d && r < (int)d->count);
      if(d->rr_len[r] < 2+1)
            return 0; /* malformed */
      switch(d->rr_data[r][2+0]) {
            case NSEC3_HASH_SHA1:
                  return 1;
      }
      return 0;
}

/** return nsec3 RR iteration count */
static size_t
00170 nsec3_get_iter(struct ub_packed_rrset_key* rrset, int r)
{
      uint16_t i;
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
      log_assert(d && r < (int)d->count);
      if(d->rr_len[r] < 2+4)
            return 0; /* malformed */
      memmove(&i, d->rr_data[r]+2+2, sizeof(i));
      i = ntohs(i);
      return (size_t)i;
}

/** return nsec3 RR salt */
static int
00185 nsec3_get_salt(struct ub_packed_rrset_key* rrset, int r,
      uint8_t** salt, size_t* saltlen)
{
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
      log_assert(d && r < (int)d->count);
      if(d->rr_len[r] < 2+5) {
            *salt = 0;
            *saltlen = 0;
            return 0; /* malformed */
      }
      *saltlen = (size_t)d->rr_data[r][2+4];
      if(d->rr_len[r] < 2+5+(size_t)*saltlen) {
            *salt = 0;
            *saltlen = 0;
            return 0; /* malformed */
      }
      *salt = d->rr_data[r]+2+5;
      return 1;
}

/** return nsec3 RR next hashed owner name */
static int
00208 nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r,
      uint8_t** next, size_t* nextlen)
{
      size_t saltlen;
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
      log_assert(d && r < (int)d->count);
      if(d->rr_len[r] < 2+5) {
            *next = 0;
            *nextlen = 0;
            return 0; /* malformed */
      }
      saltlen = (size_t)d->rr_data[r][2+4];
      if(d->rr_len[r] < 2+5+saltlen+1) {
            *next = 0;
            *nextlen = 0;
            return 0; /* malformed */
      }
      *nextlen = (size_t)d->rr_data[r][2+5+saltlen];
      if(d->rr_len[r] < 2+5+saltlen+1+*nextlen) {
            *next = 0;
            *nextlen = 0;
            return 0; /* malformed */
      }
      *next = d->rr_data[r]+2+5+saltlen+1;
      return 1;
}

/** see if NSEC3 RR contains given type */
static int
00238 nsec3_has_type(struct ub_packed_rrset_key* rrset, int r, uint16_t type)
{
      uint8_t* bitmap;
      size_t bitlen, skiplen;
        struct packed_rrset_data* d = (struct packed_rrset_data*)
              rrset->entry.data;
      log_assert(d && r < (int)d->count);
      skiplen = 2+4;
      /* skip salt */
      if(d->rr_len[r] < skiplen+1)
            return 0; /* malformed, too short */
      skiplen += 1+(size_t)d->rr_data[r][skiplen]; 
      /* skip next hashed owner */
      if(d->rr_len[r] < skiplen+1)
            return 0; /* malformed, too short */
      skiplen += 1+(size_t)d->rr_data[r][skiplen]; 
      if(d->rr_len[r] < skiplen)
            return 0; /* malformed, too short */
      bitlen = d->rr_len[r] - skiplen;
      bitmap = d->rr_data[r]+skiplen;
      return nsecbitmap_has_type_rdata(bitmap, bitlen, type);
}
      
/** 
 * Iterate through NSEC3 list, per RR 
 * This routine gives the next RR in the list (or sets rrset null). 
 * Usage:
 *
 * size_t rrsetnum;
 * int rrnum;
 * struct ub_packed_rrset_key* rrset;
 * for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset; 
 *    rrset=filter_next(filter, &rrsetnum, &rrnum))
 *          do_stuff;
 * 
 * Also filters out 
 *    o unknown flag NSEC3s
 *    o unknown algorithm NSEC3s.
 * @param filter: nsec3 filter structure.
 * @param rrsetnum: in/out rrset number to look at.
 * @param rrnum: in/out rr number in rrset to look at.
 * @returns ptr to the next rrset (or NULL at end).
 */
static struct ub_packed_rrset_key*
00282 filter_next(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum)
{
      size_t i;
      int r;
      uint8_t* nm;
      size_t nmlen;
      if(!filter->zone) /* empty list */
            return NULL;
      for(i=*rrsetnum; i<filter->num; i++) {
            /* see if RRset qualifies */
            if(ntohs(filter->list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 ||
                  ntohs(filter->list[i]->rk.rrset_class) != 
                  filter->fclass) 
                  continue;
            /* check RRset zone */
            nm = filter->list[i]->rk.dname;
            nmlen = filter->list[i]->rk.dname_len;
            dname_remove_label(&nm, &nmlen);
            if(query_dname_compare(nm, filter->zone) != 0)
                  continue;
            if(i == *rrsetnum)
                  r = (*rrnum) + 1; /* continue at next RR */
            else  r = 0;            /* new RRset start at first RR */
            for(; r < (int)rrset_get_count(filter->list[i]); r++) {
                  /* skip unknown flags, algo */
                  if(nsec3_unknown_flags(filter->list[i], r) ||
                        !nsec3_known_algo(filter->list[i], r))
                        continue;
                  /* this one is a good target */
                  *rrsetnum = i;
                  *rrnum = r;
                  return filter->list[i];
            }
      }
      return NULL;
}

/**
 * Start iterating over NSEC3 records.
 * @param filter: the filter structure, must have been filter_init-ed.
 * @param rrsetnum: can be undefined on call, inited.
 * @param rrnum: can be undefined on call, inited.
 * @return first rrset of an NSEC3, together with rrnum this points to
 *    the first RR to examine. Is NULL on empty list.
 */
static struct ub_packed_rrset_key*
00328 filter_first(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum)
{
      *rrsetnum = 0;
      *rrnum = -1;
      return filter_next(filter, rrsetnum, rrnum);
}

/** see if at least one RR is known (flags, algo) */
static int
00337 nsec3_rrset_has_known(struct ub_packed_rrset_key* s)
{
      int r;
      for(r=0; r < (int)rrset_get_count(s); r++) {
            if(!nsec3_unknown_flags(s, r) && nsec3_known_algo(s, r))
                  return 1;
      }
      return 0;
}

/** 
 * Initialize the filter structure.
 * Finds the zone by looking at available NSEC3 records and best match.
 *    (skips the unknown flag and unknown algo NSEC3s).
 *
 * @param filter: nsec3 filter structure.
 * @param list: list of rrsets, an array of them.
 * @param num: number of rrsets in list.
 * @param qinfo: 
 *    query name to match a zone for.
 *    query type (if DS a higher zone must be chosen)
 *    qclass, to filter NSEC3s with.
 */
static void
00361 filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key** list,
      size_t num, struct query_info* qinfo)
{
      size_t i;
      uint8_t* nm;
      size_t nmlen;
      filter->zone = NULL;
      filter->zone_len = 0;
      filter->list = list;
      filter->num = num;
      filter->fclass = qinfo->qclass;
      for(i=0; i<num; i++) {
            /* ignore other stuff in the list */
            if(ntohs(list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 ||
                  ntohs(list[i]->rk.rrset_class) != qinfo->qclass) 
                  continue;
            /* skip unknown flags, algo */
            if(!nsec3_rrset_has_known(list[i]))
                  continue;

            /* since NSECs are base32.zonename, we can find the zone
             * name by stripping off the first label of the record */
            nm = list[i]->rk.dname;
            nmlen = list[i]->rk.dname_len;
            dname_remove_label(&nm, &nmlen);
            /* if we find a domain that can prove about the qname,
             * and if this domain is closer to the qname */
            if(dname_subdomain_c(qinfo->qname, nm) && (!filter->zone ||
                  dname_subdomain_c(nm, filter->zone))) {
                  /* for a type DS do not accept a zone equal to qname*/
                  if(qinfo->qtype == LDNS_RR_TYPE_DS && 
                        query_dname_compare(qinfo->qname, nm) == 0)
                        continue;
                  filter->zone = nm;
                  filter->zone_len = nmlen;
            }
      }
}

/**
 * Find max iteration count using config settings and key size
 * @param ve: validator environment with iteration count config settings.
 * @param bits: key size
 * @return max iteration count
 */
static size_t
00407 get_max_iter(struct val_env* ve, size_t bits)
{
      int i;
      log_assert(ve->nsec3_keyiter_count > 0);
      /* round up to nearest config keysize, linear search, keep it small */
      for(i=0; i<ve->nsec3_keyiter_count; i++) {
            if(bits <= ve->nsec3_keysize[i])
                  return ve->nsec3_maxiter[i];
      }
      /* else, use value for biggest key */
      return ve->nsec3_maxiter[ve->nsec3_keyiter_count-1];
}

/** 
 * Determine if any of the NSEC3 rrs iteration count is too high, from key.
 * @param ve: validator environment with iteration count config settings.
 * @param filter: what NSEC3s to loop over.
 * @param kkey: key entry used for verification; used for iteration counts.
 * @return 1 if some nsec3s are above the max iteration count.
 */
static int
00428 nsec3_iteration_count_high(struct val_env* ve, struct nsec3_filter* filter, 
      struct key_entry_key* kkey)
{
      size_t rrsetnum;
      int rrnum;
      struct ub_packed_rrset_key* rrset;
      /* first determine the max number of iterations */
      size_t bits = key_entry_keysize(kkey);
      size_t max_iter = get_max_iter(ve, bits);
      verbose(VERB_ALGO, "nsec3: keysize %d bits, max iterations %d",
            (int)bits, (int)max_iter);

      for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset; 
            rrset=filter_next(filter, &rrsetnum, &rrnum)) {
            if(nsec3_get_iter(rrset, rrnum) > max_iter)
                  return 1;
      }
      return 0;
}

/* nsec3_cache_compare for rbtree */
int
00450 nsec3_hash_cmp(const void* c1, const void* c2) 
{
      struct nsec3_cached_hash* h1 = (struct nsec3_cached_hash*)c1;
      struct nsec3_cached_hash* h2 = (struct nsec3_cached_hash*)c2;
      uint8_t* s1, *s2;
      size_t s1len, s2len;
      int c = query_dname_compare(h1->dname, h2->dname);
      if(c != 0)
            return c;
      /* compare parameters */
      /* if both malformed, its equal, robustness */
      if(nsec3_get_algo(h1->nsec3, h1->rr) !=
            nsec3_get_algo(h2->nsec3, h2->rr)) {
            if(nsec3_get_algo(h1->nsec3, h1->rr) <
                  nsec3_get_algo(h2->nsec3, h2->rr))
                  return -1;
            return 1;
      }
      if(nsec3_get_iter(h1->nsec3, h1->rr) !=
            nsec3_get_iter(h2->nsec3, h2->rr)) {
            if(nsec3_get_iter(h1->nsec3, h1->rr) <
                  nsec3_get_iter(h2->nsec3, h2->rr))
                  return -1;
            return 1;
      }
      (void)nsec3_get_salt(h1->nsec3, h1->rr, &s1, &s1len);
      (void)nsec3_get_salt(h2->nsec3, h2->rr, &s2, &s2len);
      if(s1len != s2len) {
            if(s1len < s2len)
                  return -1;
            return 1;
      }
      return memcmp(s1, s2, s1len);
}

/** perform hash of name */
static int
00487 nsec3_calc_hash(struct regional* region, ldns_buffer* buf, 
      struct nsec3_cached_hash* c)
{
      int algo = nsec3_get_algo(c->nsec3, c->rr);
      size_t iter = nsec3_get_iter(c->nsec3, c->rr);
      uint8_t* salt;
      size_t saltlen, i;
      if(!nsec3_get_salt(c->nsec3, c->rr, &salt, &saltlen))
            return -1;
      /* prepare buffer for first iteration */
      ldns_buffer_clear(buf);
      ldns_buffer_write(buf, c->dname, c->dname_len);
      query_dname_tolower(ldns_buffer_begin(buf));
      ldns_buffer_write(buf, salt, saltlen);
      ldns_buffer_flip(buf);
      switch(algo) {
#ifdef SHA_DIGEST_LENGTH
            case NSEC3_HASH_SHA1:
                  c->hash_len = SHA_DIGEST_LENGTH;
                  c->hash = (uint8_t*)regional_alloc(region, 
                        c->hash_len);
                  if(!c->hash)
                        return 0;
                  (void)SHA1((unsigned char*)ldns_buffer_begin(buf),
                        (unsigned long)ldns_buffer_limit(buf),
                        (unsigned char*)c->hash);
                  for(i=0; i<iter; i++) {
                        ldns_buffer_clear(buf);
                        ldns_buffer_write(buf, c->hash, c->hash_len);
                        ldns_buffer_write(buf, salt, saltlen);
                        ldns_buffer_flip(buf);
                        (void)SHA1(
                              (unsigned char*)ldns_buffer_begin(buf),
                              (unsigned long)ldns_buffer_limit(buf),
                              (unsigned char*)c->hash);
                  }
                  break;
#endif /* SHA_DIGEST_LENGTH */
            default:
                  log_err("nsec3 hash of unknown algo %d", algo);
                  return -1;
      }
      return 1;
}

/** perform b32 encoding of hash */
static int
00534 nsec3_calc_b32(struct regional* region, ldns_buffer* buf, 
      struct nsec3_cached_hash* c)
{
      int r;
      ldns_buffer_clear(buf);
      r = b32_ntop_extended_hex(c->hash, c->hash_len,
            (char*)ldns_buffer_begin(buf), ldns_buffer_limit(buf));
      if(r < 1) {
            log_err("b32_ntop_extended_hex: error in encoding: %d", r);
            return 0;
      }
      c->b32_len = (size_t)r;
      c->b32 = regional_alloc_init(region, ldns_buffer_begin(buf), 
            c->b32_len);
      if(!c->b32)
            return 0;
      return 1;
}

int
00554 nsec3_hash_name(rbtree_t* table, struct regional* region, ldns_buffer* buf,
      struct ub_packed_rrset_key* nsec3, int rr, uint8_t* dname, 
      size_t dname_len, struct nsec3_cached_hash** hash)
{
      struct nsec3_cached_hash* c;
      struct nsec3_cached_hash looki;
      rbnode_t* n;
      int r;
      looki.node.key = &looki;
      looki.nsec3 = nsec3;
      looki.rr = rr;
      looki.dname = dname;
      looki.dname_len = dname_len;
      /* lookup first in cache */
      c = (struct nsec3_cached_hash*)rbtree_search(table, &looki);
      if(c) {
            *hash = c;
            return 1;
      }
      /* create a new entry */
      c = (struct nsec3_cached_hash*)regional_alloc(region, sizeof(*c));
      if(!c) return 0;
      c->node.key = c;
      c->nsec3 = nsec3;
      c->rr = rr;
      c->dname = dname;
      c->dname_len = dname_len;
      r = nsec3_calc_hash(region, buf, c);
      if(r != 1)
            return r;
      r = nsec3_calc_b32(region, buf, c);
      if(r != 1)
            return r;
      n = rbtree_insert(table, &c->node);
      log_assert(n); /* cannot be duplicate, just did lookup */
      *hash = c;
      return 1;
}

/**
 * compare a label lowercased
 */
static int
00597 label_compare_lower(uint8_t* lab1, uint8_t* lab2, size_t lablen)
{
      size_t i;
      for(i=0; i<lablen; i++) {
            if(tolower((int)*lab1) != tolower((int)*lab2)) {
                  if(tolower((int)*lab1) < tolower((int)*lab2))
                        return -1;
                  return 1;
            }
            lab1++;
            lab2++;
      }
      return 0;
}

/**
 * Compare a hashed name with the owner name of an NSEC3 RRset.
 * @param flt: filter with zone name.
 * @param hash: the hashed name.
 * @param s: rrset with owner name.
 * @return true if matches exactly, false if not.
 */
static int
00620 nsec3_hash_matches_owner(struct nsec3_filter* flt, 
      struct nsec3_cached_hash* hash, struct ub_packed_rrset_key* s)
{
      uint8_t* nm = s->rk.dname;
      /* compare, does hash of name based on params in this NSEC3
       * match the owner name of this NSEC3? 
       * name must be: <hashlength>base32 . zone name 
       * so; first label must not be root label (not zero length),
       * and match the b32 encoded hash length, 
       * and the label content match the b32 encoded hash
       * and the rest must be the zone name.
       */
      if(hash->b32_len != 0 && (size_t)nm[0] == hash->b32_len &&
            label_compare_lower(nm+1, hash->b32, hash->b32_len) == 0 &&
            query_dname_compare(nm+(size_t)nm[0]+1, flt->zone) == 0) {
            return 1;
      }
      return 0;
}

/**
 * Find matching NSEC3
 * Find the NSEC3Record that matches a hash of a name.
 * @param env: module environment with temporary region and buffer.
 * @param flt: the NSEC3 RR filter, contains zone name and RRs.
 * @param ct: cached hashes table.
 * @param nm: name to look for.
 * @param nmlen: length of name.
 * @param rrset: nsec3 that matches is returned here.
 * @param rr: rr number in nsec3 rrset that matches.
 * @return true if a matching NSEC3 is found, false if not.
 */
static int
00653 find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt,
      rbtree_t* ct, uint8_t* nm, size_t nmlen, 
      struct ub_packed_rrset_key** rrset, int* rr)
{
      size_t i_rs;
      int i_rr;
      struct ub_packed_rrset_key* s;
      struct nsec3_cached_hash* hash;
      int r;

      /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */
      for(s=filter_first(flt, &i_rs, &i_rr); s; 
            s=filter_next(flt, &i_rs, &i_rr)) {
            /* get name hashed for this NSEC3 RR */
            r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer,
                  s, i_rr, nm, nmlen, &hash);
            if(r == 0) {
                  log_err("nsec3: malloc failure");
                  break; /* alloc failure */
            } else if(r < 0)
                  continue; /* malformed NSEC3 */
            else if(nsec3_hash_matches_owner(flt, hash, s)) {
                  *rrset = s; /* rrset with this name */
                  *rr = i_rr; /* matches hash with these parameters */
                  return 1;
            }
      }
      *rrset = NULL;
      *rr = 0;
      return 0;
}

/**
 * nsec3Covers
 * Given a hash and a candidate NSEC3Record, determine if that NSEC3Record
 * covers the hash. Covers specifically means that the hash is in between
 * the owner and next hashes and does not equal either.
 *
 * @param flt: the NSEC3 RR filter, contains zone name.
 * @param hash: the hash of the name
 * @param rrset: the rrset of the NSEC3.
 * @param rr: which rr in the rrset.
 * @param buf: temporary buffer.
 * @return true if covers, false if not.
 */
static int
00699 nsec3_covers(struct nsec3_filter* flt, struct nsec3_cached_hash* hash,
      struct ub_packed_rrset_key* rrset, int rr, ldns_buffer* buf)
{
      uint8_t* next, *owner;
      size_t nextlen;
      int len;
      if(!nsec3_get_nextowner(rrset, rr, &next, &nextlen))
            return 0; /* malformed RR proves nothing */

      /* check the owner name is a hashed value . apex
       * base32 encoded values must have equal length. 
       * hash_value and next hash value must have equal length. */
      if(nextlen != hash->hash_len || hash->hash_len==0||hash->b32_len==0|| 
            (size_t)*rrset->rk.dname != hash->b32_len ||
            query_dname_compare(rrset->rk.dname+1+
                  (size_t)*rrset->rk.dname, flt->zone) != 0)
            return 0; /* bad lengths or owner name */

      /* This is the "normal case: owner < next and owner < hash < next */
      if(label_compare_lower(rrset->rk.dname+1, hash->b32, 
            hash->b32_len) < 0 && 
            memcmp(hash->hash, next, nextlen) < 0)
            return 1;

      /* convert owner name from text to binary */
      ldns_buffer_clear(buf);
      owner = ldns_buffer_begin(buf);
      len = b32_pton_extended_hex((char*)rrset->rk.dname+1, hash->b32_len, 
            owner, ldns_buffer_limit(buf));
      if(len<1)
            return 0; /* bad owner name in some way */
      if((size_t)len != hash->hash_len || (size_t)len != nextlen)
            return 0; /* wrong length */

      /* this is the end of zone case: next <= owner && 
       *    (hash > owner || hash < next) 
       * this also covers the only-apex case of next==owner.
       */
      if(memcmp(next, owner, nextlen) <= 0 &&
            ( memcmp(hash->hash, owner, nextlen) > 0 ||
              memcmp(hash->hash, next, nextlen) < 0)) {
            return 1;
      }
      return 0;
}

/**
 * findCoveringNSEC3
 * Given a name, find a covering NSEC3 from among a list of NSEC3s.
 *
 * @param env: module environment with temporary region and buffer.
 * @param flt: the NSEC3 RR filter, contains zone name and RRs.
 * @param ct: cached hashes table.
 * @param nm: name to check if covered.
 * @param nmlen: length of name.
 * @param rrset: covering NSEC3 rrset is returned here.
 * @param rr: rr of cover is returned here.
 * @return true if a covering NSEC3 is found, false if not.
 */
static int
00759 find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt,
        rbtree_t* ct, uint8_t* nm, size_t nmlen, 
      struct ub_packed_rrset_key** rrset, int* rr)
{
      size_t i_rs;
      int i_rr;
      struct ub_packed_rrset_key* s;
      struct nsec3_cached_hash* hash;
      int r;

      /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */
      for(s=filter_first(flt, &i_rs, &i_rr); s; 
            s=filter_next(flt, &i_rs, &i_rr)) {
            /* get name hashed for this NSEC3 RR */
            r = nsec3_hash_name(ct, env->scratch, env->scratch_buffer,
                  s, i_rr, nm, nmlen, &hash);
            if(r == 0) {
                  log_err("nsec3: malloc failure");
                  break; /* alloc failure */
            } else if(r < 0)
                  continue; /* malformed NSEC3 */
            else if(nsec3_covers(flt, hash, s, i_rr, 
                  env->scratch_buffer)) {
                  *rrset = s; /* rrset with this name */
                  *rr = i_rr; /* covers hash with these parameters */
                  return 1;
            }
      }
      *rrset = NULL;
      *rr = 0;
      return 0;
}

/**
 * findClosestEncloser
 * Given a name and a list of NSEC3s, find the candidate closest encloser.
 * This will be the first ancestor of 'name' (including itself) to have a
 * matching NSEC3 RR.
 * @param env: module environment with temporary region and buffer.
 * @param flt: the NSEC3 RR filter, contains zone name and RRs.
 * @param ct: cached hashes table.
 * @param qinfo: query that is verified for.
 * @param ce: closest encloser information is returned in here.
 * @return true if a closest encloser candidate is found, false if not.
 */
static int
00805 nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt, 
      rbtree_t* ct, struct query_info* qinfo, struct ce_response* ce)
{
      uint8_t* nm = qinfo->qname;
      size_t nmlen = qinfo->qname_len;

      /* This scans from longest name to shortest, so the first match 
       * we find is the only viable candidate. */

      /* (David:) FIXME: modify so that the NSEC3 matching the zone apex need 
       * not be present. (Mark Andrews idea).
       * (Wouter:) But make sure you check for DNAME bit in zone apex,
       * if the NSEC3 you find is the only NSEC3 in the zone, then this
       * may be the case. */

      while(dname_subdomain_c(nm, flt->zone)) {
            if(find_matching_nsec3(env, flt, ct, nm, nmlen, 
                  &ce->ce_rrset, &ce->ce_rr)) {
                  ce->ce = nm;
                  ce->ce_len = nmlen;
                  return 1;
            }
            dname_remove_label(&nm, &nmlen);
      }
      return 0;
}

/**
 * Given a qname and its proven closest encloser, calculate the "next
 * closest" name. Basically, this is the name that is one label longer than
 * the closest encloser that is still a subdomain of qname.
 *
 * @param qname: query name.
 * @param qnamelen: length of qname.
 * @param ce: closest encloser
 * @param nm: result name.
 * @param nmlen: length of nm.
 */
static void
00844 next_closer(uint8_t* qname, size_t qnamelen, uint8_t* ce, 
      uint8_t** nm, size_t* nmlen)
{
      int strip = dname_count_labels(qname) - dname_count_labels(ce) -1;
      *nm = qname;
      *nmlen = qnamelen;
      if(strip>0)
            dname_remove_labels(nm, nmlen, strip);
}

/**
 * proveClosestEncloser
 * Given a List of nsec3 RRs, find and prove the closest encloser to qname.
 * @param env: module environment with temporary region and buffer.
 * @param flt: the NSEC3 RR filter, contains zone name and RRs.
 * @param ct: cached hashes table.
 * @param qinfo: query that is verified for.
 * @param prove_does_not_exist: If true, then if the closest encloser 
 *    turns out to be qname, then null is returned.
 *    If set true, and the return value is true, then you can be 
 *    certain that the ce.nc_rrset and ce.nc_rr are set properly.
 * @param ce: closest encloser information is returned in here.
 * @return false if no closest encloser could be proven.
 *    true if a closest encloser could be proven, ce is set.
 */
static int
00870 nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt, 
      rbtree_t* ct, struct query_info* qinfo, int prove_does_not_exist,
      struct ce_response* ce)
{
      uint8_t* nc;
      size_t nc_len;
      /* robust: clean out ce, in case it gets abused later */
      memset(ce, 0, sizeof(*ce));

      if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce)) {
            verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could "
                  "not find a candidate for the closest encloser.");
            return 0;
      }
      log_nametypeclass(VERB_ALGO, "ce candidate", ce->ce, 0, 0);

      if(query_dname_compare(ce->ce, qinfo->qname) == 0) {
            if(prove_does_not_exist) {
                  verbose(VERB_ALGO, "nsec3 proveClosestEncloser: "
                        "proved that qname existed, bad");
                  return 0;
            }
            /* otherwise, we need to nothing else to prove that qname 
             * is its own closest encloser. */
            return 1;
      }

      /* If the closest encloser is actually a delegation, then the 
       * response should have been a referral. If it is a DNAME, then 
       * it should have been a DNAME response. */
      if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_NS) &&
            !nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_SOA)) {
            verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest "
                  "encloser was a delegation, bad");
            return 0;
      }
      if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DNAME)) {
            verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest "
                  "encloser was a DNAME, bad");
            return 0;
      }
      
      /* Otherwise, we need to show that the next closer name is covered. */
      next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len);
      if(!find_covering_nsec3(env, flt, ct, nc, nc_len, 
            &ce->nc_rrset, &ce->nc_rr)) {
            verbose(VERB_ALGO, "nsec3: Could not find proof that the "
                      "candidate encloser was the closest encloser");
            return 0;
      }
      return 1;
}

/** allocate a wildcard for the closest encloser */
static uint8_t*
00925 nsec3_ce_wildcard(struct regional* region, uint8_t* ce, size_t celen,
      size_t* len)
{
      uint8_t* nm;
      if(celen > LDNS_MAX_DOMAINLEN - 2)
            return 0; /* too long */
      nm = (uint8_t*)regional_alloc(region, celen+2);
      if(!nm) {
            log_err("nsec3 wildcard: out of memory");
            return 0; /* alloc failure */
      }
      nm[0] = 1;
      nm[1] = (uint8_t)'*'; /* wildcard label */
      memmove(nm+2, ce, celen);
      *len = celen+2;
      return nm;
}

/** Do the name error proof */
static enum sec_status
00945 nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, 
      rbtree_t* ct, struct query_info* qinfo)
{
      struct ce_response ce;
      uint8_t* wc;
      size_t wclen;
      struct ub_packed_rrset_key* wc_rrset;
      int wc_rr;

      /* First locate and prove the closest encloser to qname. We will 
       * use the variant that fails if the closest encloser turns out 
       * to be qname. */
      if(!nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce)) {
            verbose(VERB_ALGO, "nsec3 nameerror proof: failed to prove "
                  "a closest encloser");
            return sec_status_bogus;
      }
      log_nametypeclass(VERB_ALGO, "nsec3 namerror: proven ce=", ce.ce,0,0);

      /* At this point, we know that qname does not exist. Now we need 
       * to prove that the wildcard does not exist. */
      log_assert(ce.ce);
      wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen);
      if(!wc || !find_covering_nsec3(env, flt, ct, wc, wclen, 
            &wc_rrset, &wc_rr)) {
            verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove "
                  "that the applicable wildcard did not exist.");
            return sec_status_bogus;
      }
      return sec_status_secure;
}

enum sec_status
00978 nsec3_prove_nameerror(struct module_env* env, struct val_env* ve,
      struct ub_packed_rrset_key** list, size_t num,
      struct query_info* qinfo, struct key_entry_key* kkey)
{
      rbtree_t ct;
      struct nsec3_filter flt;

      if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
            return sec_status_bogus; /* no valid NSEC3s, bogus */
      rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
      filter_init(&flt, list, num, qinfo); /* init RR iterator */
      if(!flt.zone)
            return sec_status_bogus; /* no RRs */
      if(nsec3_iteration_count_high(ve, &flt, kkey))
            return sec_status_insecure; /* iteration count too high */
      log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", 
            flt.zone, 0, 0);
      return nsec3_do_prove_nameerror(env, &flt, &ct, qinfo);
}

/* 
 * No code to handle qtype=NSEC3 specially. 
 * This existed in early drafts, but was later (-05) removed.
 */

/** Do the nodata proof */
static enum sec_status
01005 nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, 
      rbtree_t* ct, struct query_info* qinfo)
{
      struct ce_response ce;
      uint8_t* wc;
      size_t wclen;
      struct ub_packed_rrset_key* rrset;
      int rr;

      if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len, 
            &rrset, &rr)) {
            /* cases 1 and 2 */
            if(nsec3_has_type(rrset, rr, qinfo->qtype)) {
                  verbose(VERB_ALGO, "proveNodata: Matching NSEC3 "
                        "proved that type existed, bogus");
                  return sec_status_bogus;
            } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) {
                  verbose(VERB_ALGO, "proveNodata: Matching NSEC3 "
                        "proved that a CNAME existed, bogus");
                  return sec_status_bogus;
            }

            /* 
             * If type DS: filter_init zone find already found a parent
             *   zone, so this nsec3 is from a parent zone. 
             *   o can be not a delegation (unusual query for normal name,
             *    no DS anyway, but we can verify that).
             *   o can be a delegation (which is the usual DS check).
             *   o may not have the SOA bit set (only the top of the
             *    zone, which must have been above the name, has that).
             *    Except for the root; which is checked by itself.
             *
             * If not type DS: matching nsec3 must not be a delegation.
             */
            if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 
                  && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) {
                  verbose(VERB_ALGO, "proveNodata: apex NSEC3 "
                        "abused for no DS proof, bogus");
                  return sec_status_bogus;
            } else if(qinfo->qtype != LDNS_RR_TYPE_DS && 
                  nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) &&
                  !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) {
                  verbose(VERB_ALGO, "proveNodata: matching "
                        "NSEC3 is a delegation, bogus");
                  return sec_status_bogus;
            }
            return sec_status_secure;
      }

      /* For cases 3 - 5, we need the proven closest encloser, and it 
       * can't match qname. Although, at this point, we know that it 
       * won't since we just checked that. */
      if(!nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce)) {
            verbose(VERB_ALGO, "proveNodata: did not match qname, "
                      "nor found a proven closest encloser.");
            return sec_status_bogus;
      }

      /* Case 3: removed */

      /* Case 4: */
      log_assert(ce.ce);
      wc = nsec3_ce_wildcard(env->scratch, ce.ce, ce.ce_len, &wclen);
      if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr)) {
            /* found wildcard */
            if(nsec3_has_type(rrset, rr, qinfo->qtype)) {
                  verbose(VERB_ALGO, "nsec3 nodata proof: matching "
                        "wildcard had qtype, bogus");
                  return sec_status_bogus;
            } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) {
                  verbose(VERB_ALGO, "nsec3 nodata proof: matching "
                        "wildcard had a CNAME, bogus");
                  return sec_status_bogus;
            }
            if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 
                  && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) {
                  verbose(VERB_ALGO, "nsec3 nodata proof: matching "
                        "wildcard for no DS proof has a SOA, bogus");
                  return sec_status_bogus;
            } else if(qinfo->qtype != LDNS_RR_TYPE_DS && 
                  nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) &&
                  !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) {
                  verbose(VERB_ALGO, "nsec3 nodata proof: matching "
                        "wilcard is a delegation, bogus");
                  return sec_status_bogus;
            }
            return sec_status_secure;
      }

      /* Case 5: */
      if(qinfo->qtype != LDNS_RR_TYPE_DS) {
            verbose(VERB_ALGO, "proveNodata: could not find matching "
                  "NSEC3, nor matching wildcard, and qtype is not DS "
                  "-- no more options, bogus.");
            return sec_status_bogus;
      }

      /* We need to make sure that the covering NSEC3 is opt-out. */
      log_assert(ce.nc_rrset);
      if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) {
            verbose(VERB_ALGO, "proveNodata: covering NSEC3 was not "
                  "opt-out in an opt-out DS NOERROR/NODATA case.");
            return sec_status_bogus;
      }
      return sec_status_secure;
}

enum sec_status
01113 nsec3_prove_nodata(struct module_env* env, struct val_env* ve,
      struct ub_packed_rrset_key** list, size_t num,
      struct query_info* qinfo, struct key_entry_key* kkey)
{
      rbtree_t ct;
      struct nsec3_filter flt;

      if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
            return sec_status_bogus; /* no valid NSEC3s, bogus */
      rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
      filter_init(&flt, list, num, qinfo); /* init RR iterator */
      if(!flt.zone)
            return sec_status_bogus; /* no RRs */
      if(nsec3_iteration_count_high(ve, &flt, kkey))
            return sec_status_insecure; /* iteration count too high */
      return nsec3_do_prove_nodata(env, &flt, &ct, qinfo);
}

enum sec_status
01132 nsec3_prove_wildcard(struct module_env* env, struct val_env* ve,
        struct ub_packed_rrset_key** list, size_t num,
      struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc)
{
      rbtree_t ct;
      struct nsec3_filter flt;
      struct ce_response ce;
      uint8_t* nc;
      size_t nc_len;
      size_t wclen;
      (void)dname_count_size_labels(wc, &wclen);

      if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
            return sec_status_bogus; /* no valid NSEC3s, bogus */
      rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
      filter_init(&flt, list, num, qinfo); /* init RR iterator */
      if(!flt.zone)
            return sec_status_bogus; /* no RRs */
      if(nsec3_iteration_count_high(ve, &flt, kkey))
            return sec_status_insecure; /* iteration count too high */

      /* We know what the (purported) closest encloser is by just 
       * looking at the supposed generating wildcard. 
       * The *. has already been removed from the wc name.
       */
      memset(&ce, 0, sizeof(ce));
      ce.ce = wc;
      ce.ce_len = wclen;

      /* Now we still need to prove that the original data did not exist.
       * Otherwise, we need to show that the next closer name is covered. */
      next_closer(qinfo->qname, qinfo->qname_len, ce.ce, &nc, &nc_len);
      if(!find_covering_nsec3(env, &flt, &ct, nc, nc_len, 
            &ce.nc_rrset, &ce.nc_rr)) {
            verbose(VERB_ALGO, "proveWildcard: did not find a covering "
                  "NSEC3 that covered the next closer name.");
            return sec_status_bogus;
      }
      return sec_status_secure;
}

enum sec_status
01174 nsec3_prove_nods(struct module_env* env, struct val_env* ve,
      struct ub_packed_rrset_key** list, size_t num,
      struct query_info* qinfo, struct key_entry_key* kkey)
{
      rbtree_t ct;
      struct nsec3_filter flt;
      struct ce_response ce;
      struct ub_packed_rrset_key* rrset;
      int rr;
      log_assert(qinfo->qtype == LDNS_RR_TYPE_DS);

      if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
            return sec_status_bogus; /* no valid NSEC3s, bogus */
      rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
      filter_init(&flt, list, num, qinfo); /* init RR iterator */
      if(!flt.zone)
            return sec_status_bogus; /* no RRs */
      if(nsec3_iteration_count_high(ve, &flt, kkey))
            return sec_status_insecure; /* iteration count too high */

      /* Look for a matching NSEC3 to qname -- this is the normal 
       * NODATA case. */
      if(find_matching_nsec3(env, &flt, &ct, qinfo->qname, qinfo->qname_len, 
            &rrset, &rr)) {
            /* If the matching NSEC3 has the SOA bit set, it is from 
             * the wrong zone (the child instead of the parent). If 
             * it has the DS bit set, then we were lied to. */
            if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA) && 
                  qinfo->qname_len != 1) {
                  verbose(VERB_ALGO, "nsec3 provenods: NSEC3 is from"
                        " child zone, bogus");
                  return sec_status_bogus;
            } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_DS)) {
                  verbose(VERB_ALGO, "nsec3 provenods: NSEC3 has qtype"
                        " DS, bogus");
                  return sec_status_bogus;
            }
            /* If the NSEC3 RR doesn't have the NS bit set, then 
             * this wasn't a delegation point. */
            if(!nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS))
                  return sec_status_indeterminate;
            /* Otherwise, this proves no DS. */
            return sec_status_secure;
      }

      /* Otherwise, we are probably in the opt-out case. */
      if(!nsec3_prove_closest_encloser(env, &flt, &ct, qinfo, 1, &ce)) {
            verbose(VERB_ALGO, "nsec3 provenods: did not match qname, "
                      "nor found a proven closest encloser.");
            return sec_status_bogus;
      }

      /* we had the closest encloser proof, then we need to check that the
       * covering NSEC3 was opt-out -- the proveClosestEncloser step already
       * checked to see if the closest encloser was a delegation or DNAME.
       */
      log_assert(ce.nc_rrset);
      if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) {
            verbose(VERB_ALGO, "nsec3 provenods: covering NSEC3 was not "
                  "opt-out in an opt-out DS NOERROR/NODATA case.");
            return sec_status_bogus;
      }
      return sec_status_secure;
}

enum sec_status
01240 nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve,
      struct ub_packed_rrset_key** list, size_t num, 
      struct query_info* qinfo, struct key_entry_key* kkey, int* nodata)
{
      rbtree_t ct;
      struct nsec3_filter flt;
      *nodata = 0;

      if(!list || num == 0 || !kkey || !key_entry_isgood(kkey))
            return sec_status_bogus; /* no valid NSEC3s, bogus */
      rbtree_init(&ct, &nsec3_hash_cmp); /* init names-to-hash cache */
      filter_init(&flt, list, num, qinfo); /* init RR iterator */
      if(!flt.zone)
            return sec_status_bogus; /* no RRs */
      if(nsec3_iteration_count_high(ve, &flt, kkey))
            return sec_status_insecure; /* iteration count too high */

      /* try nxdomain and nodata after another, while keeping the
       * hash cache intact */

      if(nsec3_do_prove_nameerror(env, &flt, &ct, qinfo)==sec_status_secure)
            return sec_status_secure;
      if(nsec3_do_prove_nodata(env, &flt, &ct, qinfo)==sec_status_secure) {
            *nodata = 1;
            return sec_status_secure;
      }
      return sec_status_bogus;
}

Generated by  Doxygen 1.6.0   Back to index