/*
 * 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_SVCB]       = "SVCB",
    [T_HTTPS]      = "HTTPS",
    [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;
        } else if (strcmp(rdtype, "https") == 0) {
            return T_HTTPS;
        }
        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;
        } else if (strcmp(rdtype, "svcb") == 0) {
            return T_SVCB;
        }
        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 eddsa_available[2] = { -1, -1 }; /* [0]=ED25519, [1]=ED448 */

static int is_eddsa_available(int alg)
{
    int           idx;
    int           evp_type;
    int           key_len;
    EVP_PKEY*     pkey      = NULL;
    EVP_MD_CTX*   ctx       = NULL;
    unsigned char zeros[57] = { 0 };
    int           result    = 0;

    if (alg == ALG_ED25519) {
        idx = 0;
#ifdef EVP_PKEY_ED25519
        evp_type = EVP_PKEY_ED25519;
        key_len  = 32;
#else
        eddsa_available[0] = 0;
        return 0;
#endif
    } else if (alg == ALG_ED448) {
        idx = 1;
#ifdef EVP_PKEY_ED448
        evp_type = EVP_PKEY_ED448;
        key_len  = 57;
#else
        eddsa_available[1] = 0;
        return 0;
#endif
    } else {
        return 0;
    }

    if (eddsa_available[idx] != -1)
        return eddsa_available[idx];

    pkey = EVP_PKEY_new_raw_public_key(evp_type, NULL, zeros, key_len);
    if (!pkey) {
        eddsa_available[idx] = 0;
        return 0;
    }

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

    result = EVP_DigestVerifyInit(ctx, NULL, NULL, NULL, pkey) == 1;

    EVP_MD_CTX_free(ctx);
    EVP_PKEY_free(pkey);

    eddsa_available[idx] = result;
    return result;
}

static int algorithm_warned[256] = { 0 };

static int check_algorithm_available(int alg)
{
    int dtype;

    if (alg == ALG_ED25519 || alg == ALG_ED448) {
        if (!is_eddsa_available(alg)) {
            if (alg < 256 && !algorithm_warned[alg]) {
                bitch("%s signatures cannot be validated: not supported by this build of OpenSSL/LibreSSL or disabled by system crypto policy",
                    algorithm_name(alg));
                algorithm_warned[alg] = 1;
            }
            return 0;
        }
        return 1;
    }

    dtype = algorithm_digest_type(alg);

    if (dtype == 0)
        return 1;

    if (!is_digest_available(dtype)) {
        if (alg < 256 && !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* flags)
{
    int   alg = ALG_UNSUPPORTED;
    char* str_alg;

    *flags = 0;

    if (isdigit(**s)) {
        long long n = extract_integer(s, what, NULL);
        if (n < 0)
            return -1;
        alg = (int)n;
        if (algorithm_type(alg) == ALG_UNSUPPORTED) {
            *flags |= ALG_FLAG_UNKNOWN;
            return alg;
        }
    } else {
        str_alg = extract_label(s, what, "temporary");
        if (!str_alg)
            return -1;

        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 -1;
        }
    }

    if (!check_algorithm_available(alg))
        *flags |= ALG_FLAG_POLICY_OFF;

    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;
}
