/*
 * Part of DNS zone file validator `validns`.
 *
 * Copyright 2025-2026 OARC, Inc.
 * Copyright 2011-2025 Anton Berezin <tobez@tobez.org>
 * Modified BSD license.
 * (See LICENSE file in the distribution.)
 *
 */
#include "config.h"
#include <ctype.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <Judy.h>
#include <openssl/evp.h>

#include "common.h"
#include "mempool.h"
#include "carp.h"
#include "textparse.h"
#include "rr.h"
#include "cbtree.h"

static char* rdtype2str_map[] = {
    [T_A]           = "A",
    [T_NS]          = "NS",
    [T_CNAME]       = "CNAME",
    [T_SOA]         = "SOA",
    [T_MB]          = "MB",
    [T_MG]          = "MG",
    [T_MR]          = "MR",
    [T_PTR]         = "PTR",
    [T_HINFO]       = "HINFO",
    [T_MINFO]       = "MINFO",
    [T_MX]          = "MX",
    [T_TXT]         = "TXT",
    [T_RP]          = "RP",
    [T_AFSDB]       = "AFSDB",
    [T_X25]         = "X25",
    [T_ISDN]        = "ISDN",
    [T_RT]          = "RT",
    [T_NSAP]        = "NSAP",
    [T_PX]          = "PX",
    [T_AAAA]        = "AAAA",
    [T_LOC]         = "LOC",
    [T_SRV]         = "SRV",
    [T_NAPTR]       = "NAPTR",
    [T_KX]          = "KX",
    [T_CERT]        = "CERT",
    [T_DNAME]       = "DNAME",
    [T_DS]          = "DS",
    [T_SSHFP]       = "SSHFP",
    [T_IPSECKEY]    = "IPSECKEY",
    [T_RRSIG]       = "RRSIG",
    [T_NSEC]        = "NSEC",
    [T_DNSKEY]      = "DNSKEY",
    [T_DHCID]       = "DHCID",
    [T_NSEC3]       = "NSEC3",
    [T_NSEC3PARAM]  = "NSEC3PARAM",
    [T_TLSA]        = "TLSA",
    [T_SMIMEA]      = "SMIMEA",
    [T_CDS]         = "CDS",
    [T_CDNSKEY]     = "CDNSKEY",
    [T_ZONEMD]      = "ZONEMD",
    [T_SPF]         = "SPF",
    [T_NID]         = "NID",
    [T_L32]         = "L32",
    [T_L64]         = "L64",
    [T_LP]          = "LP",
    [T_CAA]         = "CAA",
    [T_WALLET]      = "WALLET",
    [T_DLV]         = "DLV",
};
struct cbtree zone_data = {NULL};
char *zone_apex = NULL;
int zone_apex_l = 0;
int rr_counts[T_MAX+1];

char *rdtype2str(int type)
{
    char s[10];
    char *r;
    if (type < 0 || type > 65535) {
        return "???";
    }
    if (type > T_MAX) {
        sprintf(s, "TYPE%d", type);
        return quickstrdup_temp(s);
    }
    r = rdtype2str_map[type];
    if (r)  return r;
    sprintf(s, "TYPE%d", type);
    return quickstrdup_temp(s);
}

static unsigned char *name2findable_name(char *s)
{
    int l = strlen(s);
    unsigned char *res = getmem_temp(l+1);
    unsigned char *r = res;
    int i;

    if (l > 0 && s[l-1] == '.')
        l--;
    while (--l >= 0) {
        i = l;
        while (i >= 0 && s[i] != '.')
            i--;
        memcpy(r, s+i+1, l-i);
        r += l-i;
        *r = '\x01';
        r++;
        l = i;
    }
    if (r > res)    r--;
    *r = 0;
    return res;
}

struct binary_data name2wire_name(char *s)
{
    unsigned char *res = getmem_temp(strlen(s)+2);
    unsigned char *r = res;
    unsigned char *c = res;
    struct binary_data toret;

    r++;
    *c = 0;
    while (*s) {
        if (*s != '.') {
            *r++ = *s++;
        } else {
            *c = (unsigned char)(r-c-1);
            c = r;
            *c = 0;
            r++;
            s++;
        }
    }
    *c = (unsigned char)(r-c-1);
    toret.length = r-res;
    toret.data = (char*)res;
    if (toret.length == 2) /* "." is just 00, not 00 00 */
        toret.length = 1;
    return toret;
}

static struct named_rr *find_or_create_named_rr(char *name)
{
    struct named_rr *named_rr = find_named_rr(name);

    if (!named_rr) {
        struct named_rr **named_rr_slot;
        char *s;

        named_rr = getmem(sizeof(struct named_rr));
        named_rr->name = quickstrdup(name);
        named_rr->rr_sets = NULL;
        named_rr->line = file_info->line;
        named_rr->file_name = file_info->name;
        named_rr->flags = 0;
        named_rr->parent = NULL;

        if (strchr(name, '/') != NULL)
            named_rr->flags |= NAME_FLAG_CONTAINS_SLASH;

        named_rr_slot = (void *)cbtree_insert(&zone_data, (char *)name2findable_name(name));
        if (!named_rr_slot)
            croak(2, "find_or_create_named_rr: tree insertion failed");
        if (*named_rr_slot)
            croak(3, "find_or_create_named_rr: assertion error, %s should not be there", name);
        *named_rr_slot = named_rr;
        G.stats.names_count++;

        s = strchr(name, '.');
        if (s && s[1] != '\0') {
            named_rr->parent = find_or_create_named_rr(s+1);
        }
    }

    return named_rr;
}

static struct rr_set *find_or_create_rr_set(struct named_rr *named_rr, int rdtype)
{
    struct rr_set *rr_set = find_rr_set_in_named_rr(named_rr, rdtype);
    if (!rr_set) {
        struct rr_set **rr_set_slot;

        rr_set = getmem(sizeof(struct rr_set));
        rr_set->head = NULL;
        rr_set->tail = NULL;
        rr_set->named_rr = named_rr;
        rr_set->rdtype = rdtype;
        rr_set->count = 0;

        JLI(rr_set_slot, named_rr->rr_sets, rdtype);
        if (rr_set_slot == PJERR)
            croak(2, "find_or_create_rr_set: JLI failed");
        if (*rr_set_slot)
            croak(3, "find_or_create_rr_set: assertion error, %s/%s should not be there",
                  named_rr->name, rdtype2str(rdtype));
        *rr_set_slot = rr_set;
        G.stats.rrset_count++;
    }
    return rr_set;
}

int name_belongs_to_zone(const char *name)
{
    int name_l;

    name_l = strlen(name);
    if (zone_apex && name_l >= zone_apex_l) {
        if (strcmp(zone_apex, name+name_l-zone_apex_l) != 0) {
            return 0;
        } else if (name_l > zone_apex_l && zone_apex_l > 1 && name[name_l-zone_apex_l-1] != '.') {
            return 0;
        }
    } else {
        if (zone_apex) {
            return 0;
        } else {
            // XXX this is actually very bad, zone apex is not know
            return 0;
        }
    }
    return 1;
}

struct binary_data call_get_wired(struct rr *rr)
{
    rr_wire_func get_wired;

    if (rr->rdtype > T_MAX || rr->is_generic)
        get_wired = any_wirerdata;
    else
        get_wired = rr_methods[rr->rdtype].rr_wire;
    if (!get_wired) return bad_binary_data();
    return get_wired(rr);
}

struct rr *store_record(int rdtype, char *name, long ttl, void *rrptr)
{
    struct rr *rr = rrptr;
    struct named_rr *named_rr;
    struct rr_set *rr_set;
    int name_l;
    int apex_assigned = 0;
    int is_generic = 0;

    if (rdtype < 0) {
        rdtype = -rdtype;
        is_generic = 1;
    }
    name_l = strlen(name);
    if (name_l > 511)
        return bitch("name is too long: %s", name);

    if (G.stats.rr_count == 0) {
        if (rdtype != T_SOA) {
            return bitch("the first record in the zone must be an SOA record");
        } else {
            zone_apex = name;
            zone_apex_l = name_l;
            apex_assigned = 1;
        }
    }
    if (zone_apex && name_l >= zone_apex_l) {
        if (strcmp(zone_apex, name+name_l-zone_apex_l) != 0) {
            return bitch("%s does not belong to zone %s", name, zone_apex);
        } else if (name_l > zone_apex_l && zone_apex_l > 1 && name[name_l-zone_apex_l-1] != '.') {
            return bitch("%s does not belong to zone %s", name, zone_apex);
        }
    } else {
        if (zone_apex) {
            return bitch("%s does not belong to zone %s", name, zone_apex);
        } else {
            croakx(3, "assertion error: %s does not belong to a zone", name);
        }
    }

    named_rr = find_or_create_named_rr(name);
    if (apex_assigned) {
        named_rr->flags |= NAME_FLAG_APEX;
    }
    rr_set = find_or_create_rr_set(named_rr, rdtype);

    rr->rdtype = rdtype;
    rr->ttl = ttl;
    rr->line = file_info->line;
    rr->file_name = file_info->name;
    rr->is_generic = is_generic;

    if (rr_set->count > 0) {
        struct binary_data new_d, old_d;
        struct rr *old_rr;

        new_d = call_get_wired(rr);
        if (new_d.length < 0) goto after_dup_check;

        old_rr = rr_set->tail;
        while (old_rr) {
            old_d = call_get_wired(old_rr);
            if (old_d.length == new_d.length &&
                memcmp(old_d.data, new_d.data, old_d.length) == 0)
            {
                G.stats.skipped_dup_rr_count++;
                return old_rr;
            }
            old_rr = old_rr->next;
        }
    }
after_dup_check:
    if (rdtype == T_SOA) {
        if (G.stats.soa_rr_count++) {
            return bitch("there could only be one SOA in a zone");
        }
    }

    rr->rr_set = rr_set;
    rr->next = NULL;
    rr->prev = rr_set->head;
    rr_set->head = rr;
    if (rr->prev)
        rr->prev->next = rr;
    if (!rr_set->tail)
        rr_set->tail = rr;

    rr_set->count++;

    if (G.opt.verbose) {
        char *rdata;
        const char *type_name;
        if (rdtype > T_MAX || rr->is_generic)
            rdata = any_human(rr);
        else
            rdata = rr_methods[rdtype].rr_human(rr);
        if (rr->is_generic) {
            char type_buf[32];
            sprintf(type_buf, "TYPE%d", rdtype);
            type_name = quickstrdup_temp(type_buf);
        } else {
            type_name = rdtype2str(rdtype);
        }
        fprintf(stderr, "-> %s:%d: %s IN %ld %s",
                file_info->name, file_info->line,
                name, ttl, type_name);
        if (rdata) {
            fprintf(stderr, " %s\n", rdata);
        } else {
            fprintf(stderr, "\n");
        }
    }

    G.stats.rr_count++;
    named_rr->flags |= NAME_FLAG_HAS_RECORDS;

    return rr;
}

struct named_rr *find_named_rr(char *name)
{
    struct named_rr **named_rr_slot;

    named_rr_slot = (void*) cbtree_find(&zone_data, (char *)name2findable_name(name));
    if (named_rr_slot)
        return *named_rr_slot;
    return NULL;
}

struct named_rr *find_next_named_rr(struct named_rr *named_rr)
{
    struct named_rr *res;

    if (cbtree_next(&zone_data, (char *)name2findable_name(named_rr->name), (intptr_t *)&res) == NULL)
        return NULL;

    return res;
}

struct rr_set *find_rr_set(int rdtype, char *name)
{
    struct named_rr *named_rr;

    named_rr = find_named_rr(name);
    if (!named_rr)
        return NULL;

    return find_rr_set_in_named_rr(named_rr, rdtype);
}

struct rr_set *find_rr_set_in_named_rr(struct named_rr *named_rr, int rdtype)
{
    struct rr_set **rr_set_slot;

    JLG(rr_set_slot, named_rr->rr_sets, rdtype);
    if (rr_set_slot)
        return *rr_set_slot;
    return NULL;
}

uint32_t get_rr_set_count(struct named_rr *named_rr)
{
    uint32_t count;
    JLC(count, named_rr->rr_sets, 0, -1);
    return count;
}

struct rr *rr_parse_any(char *name, long ttl, int type, char *s)
{
    struct rr_any *rr = getmem(sizeof(*rr));
    long long len;

    if (*s++ != '\\') {
invalid:
        return bitch("invalid custom type rdata");
    }
    if (*s++ != '#')
        goto invalid;
    if (*s && !isspace(*s) && *s != ';' && *s != ')')
        goto invalid;
    s = skip_white_space(s);
    if (!s) return NULL;

    len = extract_integer(&s, "custom data size", NULL);
    if (len < 0) return NULL;
    if (len > 65535) goto invalid;

    rr->data = extract_hex_binary_data(&s, "custom data", EXTRACT_EAT_WHITESPACE);
    if (rr->data.length < 0)    return NULL;
    if (rr->data.length != len)
        return bitch("custom data is longer than specified");

    if (*s) {
        return bitch("garbage after valid %s data", rdtype2str(type));
    }

    return store_record(-type, name, ttl, rr);
}

char* any_human(struct rr *rrv)
{
    RRCAST(any);
    static const char hex[] = "0123456789abcdef";
    char *buf;
    char *p;
    int i;
    int buf_size;
    unsigned char *data = (unsigned char *)rr->data.data;

    /* Format: "\# <length> <hexdata>"
     * Size: 3 + max_digits(65535) + 1 + (2 * data.length) + 1
     *     = 3 + 5 + 1 + (2 * data.length) + 1
     *     = 10 + (2 * data.length)
     * Add extra padding for safety
     */
    buf_size = 20 + (2 * rr->data.length);
    buf = getmem_temp(buf_size);
    p = buf;

    p += sprintf(p, "\\# %d ", rr->data.length);

    for (i = 0; i < rr->data.length; i++) {
        *p++ = hex[data[i] >> 4];
        *p++ = hex[data[i] & 0x0f];
    }
    *p = '\0';

    return buf;
}

struct binary_data any_wirerdata(struct rr *rrv)
{
    RRCAST(any);

    return compose_binary_data("d", 1, rr->data);
}

struct rr_methods unknown_methods = { NULL, any_human, any_wirerdata, NULL, NULL };

int str2rdtype(char *rdtype, int *is_generic)
{
    if (!rdtype) return -1;
    if (is_generic) *is_generic = 0;
    switch (*rdtype) {
    case 'a':
        if (strcmp(rdtype, "a") == 0) {
            return T_A;
        } else if (strcmp(rdtype, "aaaa") == 0) {
            return T_AAAA;
        } else if (strcmp(rdtype, "afsdb") == 0) {
            return T_AFSDB;
        }
        break;
    case 'c':
        if (strcmp(rdtype, "cname") == 0) {
            return T_CNAME;
        } else if (strcmp(rdtype, "cert") == 0) {
            return T_CERT;
        } else if (strcmp(rdtype, "caa") == 0) {
            return T_CAA;
        } else if (strcmp(rdtype, "cds") == 0) {
            return T_CDS;
        } else if (strcmp(rdtype, "cdnskey") == 0) {
            return T_CDNSKEY;
        }
        break;
    case 'd':
        if (strcmp(rdtype, "ds") == 0) {
            return T_DS;
        } else if (strcmp(rdtype, "dnskey") == 0) {
            return T_DNSKEY;
        } else if (strcmp(rdtype, "dname") == 0) {
            return T_DNAME;
        } else if (strcmp(rdtype, "dlv") == 0) {
            return T_DLV;
        } else if (strcmp(rdtype, "dhcid") == 0) {
            return T_DHCID;
        }
        break;
    case 'h':
        if (strcmp(rdtype, "hinfo") == 0) {
            return T_HINFO;
        }
        break;
    case 'i':
        if (strcmp(rdtype, "ipseckey") == 0) {
            return T_IPSECKEY;
        } else if (strcmp(rdtype, "isdn") == 0) {
            return T_ISDN;
        }
        break;
    case 'k':
        if (strcmp(rdtype, "kx") == 0) {
            return T_KX;
        }
        break;
    case 'l':
        if (strcmp(rdtype, "loc") == 0) {
            return T_LOC;
        } else if (strcmp(rdtype, "l32") == 0) {
            return T_L32;
        } else if (strcmp(rdtype, "l64") == 0) {
            return T_L64;
        } else if (strcmp(rdtype, "lp") == 0) {
            return T_LP;
        }
        break;
    case 'm':
        if (strcmp(rdtype, "mx") == 0) {
            return T_MX;
        } else if (strcmp(rdtype, "mb") == 0) {
            return T_MB;
        } else if (strcmp(rdtype, "mg") == 0) {
            return T_MG;
        } else if (strcmp(rdtype, "minfo") == 0) {
            return T_MINFO;
        } else if (strcmp(rdtype, "mr") == 0) {
            return T_MR;
        }
        break;
    case 'n':
        if (strcmp(rdtype, "ns") == 0) {
            return T_NS;
        } else if (strcmp(rdtype, "naptr") == 0) {
            return T_NAPTR;
        } else if (strcmp(rdtype, "nsec") == 0) {
            return T_NSEC;
        } else if (strcmp(rdtype, "nsec3") == 0) {
            return T_NSEC3;
        } else if (strcmp(rdtype, "nid") == 0) {
            return T_NID;
        } else if (strcmp(rdtype, "nsec3param") == 0) {
            return T_NSEC3PARAM;
        } else if (strcmp(rdtype, "nsap") == 0) {
            return T_NSAP;
        }
        break;
    case 'p':
        if (strcmp(rdtype, "ptr") == 0) {
            return T_PTR;
        } else if (strcmp(rdtype, "px") == 0) {
            return T_PX;
        }
        break;
    case 'r':
        if (strcmp(rdtype, "rrsig") == 0) {
            return T_RRSIG;
        } else if (strcmp(rdtype, "rp") == 0) {
            return T_RP;
        } else if (strcmp(rdtype, "rt") == 0) {
            return T_RT;
        }
        break;
    case 's':
        if (strcmp(rdtype, "soa") == 0) {
            return T_SOA;
        } else if (strcmp(rdtype, "srv") == 0) {
            return T_SRV;
        } else if (strcmp(rdtype, "spf") == 0) {
            return T_SPF;
        } else if (strcmp(rdtype, "sshfp") == 0) {
            return T_SSHFP;
        } else if (strcmp(rdtype, "smimea") == 0) {
            return T_SMIMEA;
        }
        break;
    case 't':
        if (strcmp(rdtype, "txt") == 0) {
            return T_TXT;
        } else if (strcmp(rdtype, "tlsa") == 0) {
            return T_TLSA;
        } else if (strncmp(rdtype, "type", 4) == 0) {
            long type = strtol(rdtype+4, NULL, 10);
            if (is_generic) *is_generic = 1;
            if (type <= 0 || type > 65535)
                bitch("invalid rdtype %s", rdtype);
            return type;
        }
        break;
    case 'w':
        if (strcmp(rdtype, "wallet") == 0) {
            return T_WALLET;
        }
        break;
    case 'x':
        if (strcmp(rdtype, "x25") == 0) {
            return T_X25;
        }
        break;
    case 'z':
        if (strcmp(rdtype, "zonemd") == 0) {
            return T_ZONEMD;
        }
        break;
    }
    bitch("invalid or unsupported rdtype %s", rdtype);
    return -1;
}

void validate_rrset(struct rr_set *rr_set)
{
    struct rr *rr;
    int ttl;

    /* This can happen when rr_set was allocated but
     * nothing was added to it due to an error. */
    if (rr_set->count == 0) return;
    rr = rr_set->tail;
    if (!rr) {
        croakx(4, "assertion failed: %s %s is null, but count is %d",
               rdtype2str(rr_set->rdtype), rr_set->named_rr->name,
               rr_set->count);
    }
    if (rr_set->rdtype < T_MAX && rr_methods[rr_set->rdtype].rr_validate_set)
        rr_methods[rr_set->rdtype].rr_validate_set(rr_set);
    ttl = rr->ttl;

    while (rr) {
        validate_record(rr);
        if (ttl != rr->ttl) {
            if (rr->rdtype != T_RRSIG) /* RRSIG is an exception */
                moan(rr->file_name, rr->line, "TTL values differ within an RR set");
        }
        rr = rr->next;
    }
}

void debug(struct named_rr *named_rr, char *s)
{
    fprintf(stderr, "%s %s", s, named_rr->name);
    if ((named_rr->flags & NAME_FLAG_APEX))
        fprintf(stderr, ", apex");
    if ((named_rr->flags & NAME_FLAG_HAS_RECORDS))
        fprintf(stderr, ", has records");
    if ((named_rr->flags & NAME_FLAG_DELEGATION))
        fprintf(stderr, ", delegation");
    if ((named_rr->flags & NAME_FLAG_NOT_AUTHORITATIVE))
        fprintf(stderr, ", not auth");
    if ((named_rr->flags & NAME_FLAG_NSEC3_ONLY))
        fprintf(stderr, ", nsec3 only");
    if ((named_rr->flags & NAME_FLAG_KIDS_WITH_RECORDS))
        fprintf(stderr, ", kid records");
    if ((named_rr->flags & NAME_FLAG_SIGNED_DELEGATION))
        fprintf(stderr, ", signed delegation");
    if ((named_rr->flags & NAME_FLAG_APEX_PARENT))
        fprintf(stderr, ", apex parent");
    fprintf(stderr, "\n");
}

static int
validate_named_rr(const char *name, intptr_t *data, void *p)
{
    struct named_rr *named_rr = *((struct named_rr **)data);
    Word_t rdtype;
    struct rr_set **rr_set_p;
    int nsec3_present = 0;
    int nsec3_only = 1;
    static int seen_apex = 0;

    if ((named_rr->flags & NAME_FLAG_APEX))
        seen_apex = 1;
    if (!seen_apex)
        named_rr->flags |= NAME_FLAG_APEX_PARENT;

    if (named_rr->parent && (named_rr->parent->flags & (NAME_FLAG_DELEGATION|NAME_FLAG_NOT_AUTHORITATIVE)) != 0) {
        named_rr->flags |= NAME_FLAG_NOT_AUTHORITATIVE;
        if ((named_rr->flags & NAME_FLAG_HAS_RECORDS) != 0) {
            G.stats.not_authoritative++;
        }
    }

    if (G.nsec3_opt_out_present && (named_rr->flags & NAME_FLAG_DELEGATION)) {
        JLG(rr_set_p, named_rr->rr_sets, T_DS);
        if (!rr_set_p)
            named_rr->flags |= NAME_FLAG_NOT_AUTHORITATIVE;
    }
//debug(named_rr, ">>>>");

    rdtype = 0;
    JLF(rr_set_p, named_rr->rr_sets, rdtype);

    while (rr_set_p) {
        validate_rrset(*rr_set_p);
        if (rdtype == T_NSEC3)
            nsec3_present = 1;
        else if (rdtype != T_RRSIG)
            nsec3_only = 0;
        if (rdtype != T_NSEC3 && rdtype != T_RRSIG && rdtype != T_NS)
            named_rr->flags |= NAME_FLAG_THIS_WITH_RECORDS;
        if ((named_rr->flags & NAME_FLAG_NOT_AUTHORITATIVE) == 0 &&
            rdtype != T_NS && rdtype != T_NSEC3 && rdtype != T_RRSIG)
        {
            struct named_rr *nrr = named_rr;
            int skip_first = rdtype == T_NS;

            while (nrr && (nrr->flags & NAME_FLAG_KIDS_WITH_RECORDS) == 0) {
                if ((nrr->flags & NAME_FLAG_APEX_PARENT) || strlen(nrr->name) < zone_apex_l) {
                    nrr->flags |= NAME_FLAG_APEX_PARENT;
                    break;
                }
                if (!skip_first)
                    nrr->flags |= NAME_FLAG_KIDS_WITH_RECORDS;
                skip_first = 0;
                nrr = nrr->parent;
            }
        }
        if (rdtype == T_DS) {
            struct named_rr *nrr = named_rr;
            while (nrr && (nrr->flags & (NAME_FLAG_DELEGATION|NAME_FLAG_NOT_AUTHORITATIVE)) != 0) {
                // nrr->flags &= ~(NAME_FLAG_DELEGATION|NAME_FLAG_NOT_AUTHORITATIVE);
                nrr->flags |= NAME_FLAG_SIGNED_DELEGATION;
                nrr = nrr->parent;
            }
        }
        JLN(rr_set_p, named_rr->rr_sets, rdtype);
    }
    if (nsec3_present && nsec3_only) {
        named_rr->flags |= NAME_FLAG_NSEC3_ONLY;
    }
    return 1;
}


static void* nsec_validate_pass2(struct rr *rrv)
{
    RRCAST(nsec);
    struct named_rr *named_rr, *next_named_rr;

    named_rr = rr->rr.rr_set->named_rr;
    next_named_rr = find_next_named_rr(named_rr);
    /* Skip empty non-terminals and not authoritative records from consideration */
    while (next_named_rr) {
        if ((next_named_rr->flags & NAME_FLAG_HAS_RECORDS) == 0) {
            next_named_rr = find_next_named_rr(next_named_rr);
            continue;
        }
        if (next_named_rr->parent &&
            (next_named_rr->parent->flags & (NAME_FLAG_DELEGATION|NAME_FLAG_NOT_AUTHORITATIVE)) != 0)
        {
            named_rr->flags |= NAME_FLAG_NOT_AUTHORITATIVE;
            next_named_rr = find_next_named_rr(next_named_rr);
            continue;
        }
        break;
    }

    if (strcasecmp(rr->next_domain, zone_apex) == 0) {
        if (next_named_rr) {
            return moan(rr->rr.file_name, rr->rr.line, "NSEC says %s is the last name, but %s exists",
                        named_rr->name, next_named_rr->name);
        }
    } else {
        if (!next_named_rr) {
            return moan(rr->rr.file_name, rr->rr.line, "NSEC says %s comes after %s, but nothing does",
                        rr->next_domain, named_rr->name);
        } else if (strcasecmp(rr->next_domain, next_named_rr->name) != 0) {
            return moan(rr->rr.file_name, rr->rr.line, "NSEC says %s comes after %s, but %s does",
                        rr->next_domain, named_rr->name, next_named_rr->name);
        }
    }

    /* TODO: more checks */
    return rr;
}

static int
second_pass_one_name(const char *name, intptr_t *data, void *p)
{
    struct named_rr *named_rr = *((struct named_rr **)data);
    struct rr_set **rr_set_p;

    freeall_temp();
    JLG(rr_set_p, named_rr->rr_sets, T_NSEC);
    if (rr_set_p && (*rr_set_p)->tail) {
        nsec_validate_pass2((*rr_set_p)->tail);
    }
    return 1;
}

void validate_zone(void)
{
    cbtree_allprefixed(&zone_data, "", validate_named_rr, NULL);
    cbtree_allprefixed(&zone_data, "", second_pass_one_name, NULL);

    if (zone_apex && G.dnssec_active && !G.nsec3_present)
        validate_nsec_chain();
}

void validate_record(struct rr *rr)
{
    freeall_temp();
    if (!rr->is_generic && rr->rdtype <= T_MAX)
        rr_counts[rr->rdtype]++;
    if (!rr->is_generic && rr->rdtype <= T_MAX && rr_methods[rr->rdtype].rr_validate)
        rr_methods[rr->rdtype].rr_validate(rr);
}

#define DIGEST_SHA1   1
#define DIGEST_SHA256 2
#define DIGEST_SHA384 3
#define DIGEST_SHA512 4

static int digest_available[5] = {0, -1, -1, -1, -1};

static int algorithm_digest_type(int alg)
{
    switch (alg) {
    case ALG_DSA:
    case ALG_RSASHA1:
    case ALG_DSA_NSEC3_SHA1:
    case ALG_RSASHA1_NSEC3_SHA1:
        return DIGEST_SHA1;
    case ALG_RSASHA256:
    case ALG_ECDSAP256SHA256:
        return DIGEST_SHA256;
    case ALG_ECDSAP384SHA384:
        return DIGEST_SHA384;
    case ALG_RSASHA512:
        return DIGEST_SHA512;
    default:
        return 0;
    }
}

static const char *digest_name(int digest_type)
{
    switch (digest_type) {
    case DIGEST_SHA1:
        return "SHA-1";
    case DIGEST_SHA256:
        return "SHA-256";
    case DIGEST_SHA384:
        return "SHA-384";
    case DIGEST_SHA512:
        return "SHA-512";
    default:
        return "unknown";
    }
}

static const char *algorithm_name(int alg)
{
    switch (alg) {
    case ALG_DSA:
        return "DSA";
    case ALG_RSASHA1:
        return "RSASHA1";
    case ALG_DSA_NSEC3_SHA1:
        return "DSA-NSEC3-SHA1";
    case ALG_RSASHA1_NSEC3_SHA1:
        return "RSASHA1-NSEC3-SHA1";
    case ALG_RSASHA256:
        return "RSASHA256";
    case ALG_RSASHA512:
        return "RSASHA512";
    case ALG_ECCGOST:
        return "ECC-GOST";
    case ALG_ECDSAP256SHA256:
        return "ECDSAP256SHA256";
    case ALG_ECDSAP384SHA384:
        return "ECDSAP384SHA384";
    case ALG_ED25519:
        return "ED25519";
    case ALG_ED448:
        return "ED448";
    default:
        return "unknown";
    }
}

static int is_digest_available(int digest_type)
{
#ifdef HAVE_EVP_PKEY_Q_KEYGEN
    const EVP_MD *md = NULL;
    EVP_PKEY *pkey = NULL;
    EVP_MD_CTX *ctx = NULL;
    int result = 0;

    if (digest_type < 1 || digest_type > 4)
        return 1;

    if (digest_available[digest_type] != -1)
        return digest_available[digest_type];

    switch (digest_type) {
    case DIGEST_SHA1:
        md = EVP_sha1();
        break;
    case DIGEST_SHA256:
        md = EVP_sha256();
        break;
    case DIGEST_SHA384:
        md = EVP_sha384();
        break;
    case DIGEST_SHA512:
        md = EVP_sha512();
        break;
    }

    if (!md) {
        digest_available[digest_type] = 0;
        return 0;
    }

    /* Generate minimal RSA key for probing signature support */
    pkey = EVP_PKEY_Q_keygen(NULL, NULL, "RSA", (size_t)512);
    if (!pkey) {
        digest_available[digest_type] = 0;
        return 0;
    }

    ctx = EVP_MD_CTX_new();
    if (!ctx) {
        EVP_PKEY_free(pkey);
        digest_available[digest_type] = 0;
        return 0;
    }

    /* This fails if signature verification with this digest is policy-blocked */
    result = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey) == 1;

    EVP_MD_CTX_free(ctx);
    EVP_PKEY_free(pkey);
    digest_available[digest_type] = result;
    return result;
#else
    /* OpenSSL < 3.0 or LibreSSL: no crypto policy restrictions */
    (void)digest_type;
    return 1;
#endif
}

static int algorithm_warned[16] = {0};

static int check_algorithm_available(int alg)
{
    int dtype = algorithm_digest_type(alg);

    if (dtype == 0)
        return 1;

    if (!is_digest_available(dtype)) {
        if (alg < 16 && !algorithm_warned[alg]) {
            bitch("%s signatures cannot be validated: %s is disabled by system crypto policy",
                  algorithm_name(alg), digest_name(dtype));
            algorithm_warned[alg] = 1;
        }
        return 0;
    }
    return 1;
}

int extract_algorithm(char **s, char *what)
{
    int alg = ALG_UNSUPPORTED;
    char *str_alg;

    if (isdigit(**s)) {
        alg = extract_integer(s, what, NULL);
        if (algorithm_type(alg) == ALG_UNSUPPORTED) {
            bitch("bad or unsupported algorithm %d", alg);
            return ALG_UNSUPPORTED;
        }
    } else {
        str_alg = extract_label(s, what, "temporary");
        if (!str_alg) return ALG_UNSUPPORTED;

        if (strcmp(str_alg, "dsa") == 0)
            alg = ALG_DSA;
        else if (strcmp(str_alg, "rsasha1") == 0)
            alg = ALG_RSASHA1;
        else if (strcmp(str_alg, "dsa-nsec3-sha1") == 0)
            alg = ALG_DSA_NSEC3_SHA1;
        else if (strcmp(str_alg, "rsasha1-nsec3-sha1") == 0)
            alg = ALG_RSASHA1_NSEC3_SHA1;
        else if (strcmp(str_alg, "rsasha256") == 0)
            alg = ALG_RSASHA256;
        else if (strcmp(str_alg, "rsasha512") == 0)
            alg = ALG_RSASHA512;
        else if (strcmp(str_alg, "ecc-gost") == 0)
            alg = ALG_ECCGOST;
        else if (strcmp(str_alg, "ecdsap256sha256") == 0)
            alg = ALG_ECDSAP256SHA256;
        else if (strcmp(str_alg, "ecdsap384sha384") == 0)
            alg = ALG_ECDSAP384SHA384;
        else if (strcmp(str_alg, "ed25519") == 0)
            alg = ALG_ED25519;
        else if (strcmp(str_alg, "ed448") == 0)
            alg = ALG_ED448;
        else if (strcmp(str_alg, "privatedns") == 0)
            alg = ALG_PRIVATEDNS;
        else if (strcmp(str_alg, "privateoid") == 0)
            alg = ALG_PRIVATEOID;
        else {
            bitch("bad or unsupported algorithm %s", str_alg);
            return ALG_UNSUPPORTED;
        }
    }

    if (!check_algorithm_available(alg))
        return ALG_UNSUPPORTED;

    return alg;
}

int algorithm_type(int alg)
{
    switch (alg) {
    case ALG_DSA:
        return ALG_DSA_FAMILY;
    case ALG_RSASHA1:
        return ALG_RSA_FAMILY;
    case ALG_DSA_NSEC3_SHA1:
        return ALG_DSA_FAMILY;
    case ALG_RSASHA1_NSEC3_SHA1:
        return ALG_RSA_FAMILY;
    case ALG_RSASHA256:
        return ALG_RSA_FAMILY;
    case ALG_RSASHA512:
        return ALG_RSA_FAMILY;
        case ALG_ECCGOST:
                return ALG_ECC_FAMILY;
        case ALG_ECDSAP256SHA256:
                return ALG_ECC_FAMILY;
        case ALG_ECDSAP384SHA384:
                return ALG_ECC_FAMILY;
    case ALG_ED25519:
        return ALG_ECC_FAMILY;
    case ALG_ED448:
        return ALG_ECC_FAMILY;
    case ALG_PRIVATEDNS:
        return ALG_PRIVATE_FAMILY;
    case ALG_PRIVATEOID:
        return ALG_PRIVATE_FAMILY;
    }
    return ALG_UNSUPPORTED;
}
