/*
 * Part of DNS zone file validator `validns`.
            604800  NSEC    example.com. NS DS RRSIG NSEC
            604800  RRSIG   NSEC 10 3 604800 20130321184221 (
                    20130219184221 35615 example.com.
                    WWg7EiYoY8Hp593I2i5Mkl2ezg7YuAnq0y75
                    oymTCuEfGwh4OxbMT/mWNqAFL5Y8f0YoQOOY
                    wZP0m/sGK/EJN7ulNsfQyULY4WsyHIGlKMwT
                    KdyDXJLrmrzlmRnGv7pFb0bo53n3osE0uFfH
                    yMQYOkQRYfqa4yWXF9Nl48dy67frtVih0foy
                    9Mm76mmJSDUd/jGsYQmaoFGVU/a64rWapVQ9
                    O/mXPqr6Pw2ZCHecsF4ElMEp41YqG1DfR5QR
                    khTjvTlg4aTKvgX1YuvDhjUygSHit47xn2NC
                    2WwEZF+vYXT9DIUCMcKdVeb4bjWwUXbWNFqz
                    Ca3jb/mpOpUDFnrRPw== )
 *
 * Copyright 2025-2026 OARC, Inc.
 * Copyright 2011-2025 Anton Berezin <tobez@tobez.org>
 * Modified BSD license.
 * (See LICENSE file in the distribution.)
 *
 */
#include <ctype.h>
#include <sys/types.h>
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>

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

static struct rr* nsec_parse(char* name, long ttl, int type, char* s)
{
    struct rr_nsec*    rr = getmem(sizeof(*rr));
    struct binary_data bitmap;
    char*              str_type = NULL;
    int                ltype;

    rr->next_domain = extract_name(&s, "next domain", KEEP_CAPITALIZATION);
    /* TODO: validate next_domain, http://tools.ietf.org/html/rfc4034#section-4.1.1 */

    bitmap = new_set();
    while (s && *s) {
        str_type = extract_label(&s, "type list", "temporary");
        if (!str_type)
            return NULL;
        ltype = str2rdtype(str_type, NULL);
        if (ltype < 0)
            return NULL;
        add_bit_to_set(&bitmap, ltype);
    }
    if (!s)
        return NULL;
    if (!str_type) {
        return bitch("NSEC type list should not be empty");
    }
    rr->type_bitmap = compressed_set(&bitmap);
    G.dnssec_active = 1;

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

static char* nsec_human(struct rr* rrv)
{
    RRCAST(nsec);
    char  ss[1024];
    char* s = ss;
    int   l;
    char* base;
    int   i, k;
    int   type;
    char* type_name;

    l = snprintf(s, 1024, "%s", rr->next_domain);
    s += l;
    base = rr->type_bitmap.data;
    while (base - rr->type_bitmap.data < rr->type_bitmap.length) {
        for (i = 0; i < base[1]; i++) {
            for (k = 0; k <= 7; k++) {
                if (base[2 + i] & (0x80 >> k)) {
                    type      = ((unsigned char)base[0]) * 256 + i * 8 + k;
                    type_name = rdtype2str(type);
                    l         = snprintf(s, 1024 - (s - ss), " %s", type_name);
                    s += l;
                }
            }
        }
        base += base[1] + 2;
    }
    return quickstrdup_temp(ss);
}

static struct binary_data nsec_wirerdata(struct rr* rrv)
{
    RRCAST(nsec);

    return compose_binary_data("dd", 1,
        name2wire_name(rr->next_domain), rr->type_bitmap);
}

static void* nsec_validate(struct rr* rrv)
{
    RRCAST(nsec);
    struct named_rr* named_rr;

    named_rr = rr->rr.rr_set->named_rr;
    if (!check_typemap(rr->type_bitmap, named_rr, rrv))
        return NULL;

    return rr;
}

void validate_nsec_chain(void)
{
    struct rr_set*   rr_set;
    struct named_rr* named_rr;

    rr_set = find_rr_set(T_NSEC, zone_apex);
    if (!rr_set) {
        named_rr = find_named_rr(zone_apex);
        moan(named_rr->file_name, named_rr->line, "apex NSEC not found");
        return;
    }
    while (1) {
        char            name[1024];
        struct rr_nsec* rr = (struct rr_nsec*)rr_set->tail;
        char *          s, *t;

        if (strcasecmp(rr->next_domain, zone_apex) == 0) /* chain complete */
            break;
        freeall_temp();
        s = rr->next_domain;
        t = name;
        while (*s)
            *t++ = tolower(*s++);
        *t     = 0;
        rr_set = find_rr_set(T_NSEC, name);
        if (!rr_set) {
            moan(rr->rr.file_name, rr->rr.line, "broken NSEC chain %s -> %s",
                rr->rr.rr_set->named_rr->name, rr->next_domain);
            break;
        }
    }
    freeall_temp();
}

struct rr_methods nsec_methods = { nsec_parse, nsec_human, nsec_wirerdata, NULL, nsec_validate };
