# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
#
# SPDX-License-Identifier: MPL-2.0
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0.  If a copy of the MPL was not distributed with this
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
#
# See the COPYRIGHT file distributed with this work for additional
# information regarding copyright ownership.

import os
import re

import pytest

import isctest

pytestmark = pytest.mark.extra_artifacts(
    [
        "verify.out.*",
        "zones/K*",
        "zones/dsset-*",
        "zones/*.bad",
        "zones/*.good",
        "zones/*.out*",
        "zones/*.tmp",
        "zones/updated*",
    ]
)

VERIFY = os.environ.get("VERIFY")


@pytest.mark.parametrize(
    "zone",
    [
        "ksk-only.nsec3",
        "ksk-only.nsec",
        "ksk+zsk.nsec3.apex-dname",
        "ksk+zsk.nsec3",
        "ksk+zsk.nsec.apex-dname",
        "ksk+zsk.nsec",
        "ksk+zsk.optout",
        "zsk-only.nsec3",
        "zsk-only.nsec",
    ],
)
def test_verify_good_zone_files(zone):
    isctest.run.cmd([VERIFY, "-z", "-o", zone, f"zones/{zone}.good"], log_stdout=True)


def test_verify_good_zone_nsec_next_name_case_mismatch():
    isctest.run.cmd(
        [
            VERIFY,
            "-o",
            "nsec-next-name-case-mismatch",
            "zones/nsec-next-name-case-mismatch.good",
        ],
        log_stdout=True,
    )


def get_bad_zone_output(zone):
    only_opt = ["-z"] if re.match(r"[zk]sk-only", zone) else []
    output = isctest.run.cmd(
        [VERIFY, *only_opt, "-o", zone, f"zones/{zone}.bad"],
        raise_on_exception=False,
        log_stdout=True,
    )
    stream = (output.stdout + output.stderr).decode("utf-8").replace("\n", "")
    return stream


@pytest.mark.parametrize(
    "zone",
    [
        "ksk-only.dnskeyonly",
        "ksk+zsk.dnskeyonly",
        "zsk-only.dnskeyonly",
    ],
)
def test_verify_bad_zone_files_dnskeyonly(zone):
    assert re.match(r".*DNSKEY is not signed.*", get_bad_zone_output(zone))


@pytest.mark.parametrize(
    "zone",
    [
        "ksk-only.nsec3.expired",
        "ksk-only.nsec.expired",
        "ksk+zsk.nsec3.expired",
        "ksk+zsk.nsec.expired",
        "ksk+zsk.nsec.ksk-expired",
        "zsk-only.nsec3.expired",
        "zsk-only.nsec.expired",
        "ksk+zsk.nsec3.ksk-expired",
    ],
)
def test_verify_bad_zone_files_expired(zone):
    assert re.match(
        r".*signature has expired.*|.*No self-signed .*DNSKEY found.*",
        get_bad_zone_output(zone),
    )


@pytest.mark.parametrize(
    "zone",
    [
        "ksk+zsk.nsec.out-of-zone-nsec",
        "ksk+zsk.nsec.below-bottom-of-zone-nsec",
        "ksk+zsk.nsec.below-dname-nsec",
    ],
)
def test_verify_bad_zone_files_unexpected_nsec_rrset(zone):
    assert re.match(r".*unexpected NSEC RRset at.*", get_bad_zone_output(zone))


def test_verify_bad_zone_files_bad_nsec_record():
    assert re.match(
        r".*Bad NSEC record for.*, next name mismatch.*",
        get_bad_zone_output("ksk+zsk.nsec.broken-chain"),
    )


def test_verify_bad_zone_files_bad_bitmap():
    assert re.match(
        r".*bit map mismatch.*", get_bad_zone_output("ksk+zsk.nsec.bad-bitmap")
    )


def test_verify_bad_zone_files_missing_nsec3_record():
    assert re.match(
        r".*Missing NSEC3 record for.*",
        get_bad_zone_output("ksk+zsk.nsec3.missing-empty"),
    )


def test_verify_bad_zone_files_no_dnssec_keys():
    assert re.match(
        r".*Zone contains no DNSSEC keys.*", get_bad_zone_output("unsigned")
    )


def test_verify_bad_zone_files_unequal_nsec3_chains():
    assert re.match(
        r".*Expected and found NSEC3 chains not equal.*",
        get_bad_zone_output("ksk+zsk.nsec3.extra-nsec3"),
    )


# checking error message when -o is not used
# and a SOA record not at top of zone is found
def test_verify_soa_not_at_top_error():
    # when -o is not used, origin is set to zone file name,
    # which should cause an error in this case
    output = isctest.run.cmd(
        [VERIFY, "zones/ksk+zsk.nsec.good"], raise_on_exception=False
    ).stderr.decode("utf-8")
    assert "not at top of zone" in output
    assert "use -o to specify a different zone origin" in output


# checking error message when an invalid -o is specified
# and a SOA record not at top of zone is found
def test_verify_invalid_o_option_soa_not_at_top_error():
    output = isctest.run.cmd(
        [VERIFY, "-o", "invalid.origin", "zones/ksk+zsk.nsec.good"],
        raise_on_exception=False,
    ).stderr.decode("utf-8")
    assert "not at top of zone" in output
    assert "use -o to specify a different zone origin" not in output


# checking dnssec-verify -J reads journal file
def test_verify_j_reads_journal_file():
    output = isctest.run.cmd(
        [
            VERIFY,
            "-o",
            "updated",
            "-J",
            "zones/updated.other.jnl",
            "zones/updated.other",
        ]
    ).stdout.decode("utf-8")
    assert "Loading zone 'updated' from file 'zones/updated.other'" in output
