/***
  This file is part of systemd.

  Copyright 2014 Zbigniew Jędrzejewski-Szmek

  systemd is free software; you can redistribute it and/or modify it
  under the terms of the GNU Lesser General Public License as published by
  the Free Software Foundation; either version 2.1 of the License, or
  (at your option) any later version.

  systemd is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License
  along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/

#include <sys/socket.h>

#include "dns-type.h"
#include "parse-util.h"
#include "string-util.h"

typedef const struct {
        uint16_t type;
        const char *name;
} dns_type;

static const struct dns_type_name *
lookup_dns_type (register const char *str, register GPERF_LEN_TYPE len);

#include "dns_type-from-name.h"
#include "dns_type-to-name.h"

int dns_type_from_string(const char *s) {
        const struct dns_type_name *sc;

        assert(s);

        sc = lookup_dns_type(s, strlen(s));
        if (sc)
                return sc->id;

        s = startswith_no_case(s, "TYPE");
        if (s) {
                unsigned x;

                if (safe_atou(s, &x) >= 0 &&
                    x <= UINT16_MAX)
                        return (int) x;
        }

        return _DNS_TYPE_INVALID;
}

bool dns_type_is_pseudo(uint16_t type) {

        /* Checks whether the specified type is a "pseudo-type". What
         * a "pseudo-type" precisely is, is defined only very weakly,
         * but apparently entails all RR types that are not actually
         * stored as RRs on the server and should hence also not be
         * cached. We use this list primarily to validate NSEC type
         * bitfields, and to verify what to cache. */

        return IN_SET(type,
                      0, /* A Pseudo RR type, according to RFC 2931 */
                      DNS_TYPE_ANY,
                      DNS_TYPE_AXFR,
                      DNS_TYPE_IXFR,
                      DNS_TYPE_OPT,
                      DNS_TYPE_TSIG,
                      DNS_TYPE_TKEY
        );
}

bool dns_class_is_pseudo(uint16_t class) {
        return class == DNS_TYPE_ANY;
}

bool dns_type_is_valid_query(uint16_t type) {

        /* The types valid as questions in packets */

        return !IN_SET(type,
                       0,
                       DNS_TYPE_OPT,
                       DNS_TYPE_TSIG,
                       DNS_TYPE_TKEY,

                       /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as
                        * they aren't really payload, but signatures for payload, and cannot be validated on their
                        * own. After all they are the signatures, and have no signatures of their own validating
                        * them. */
                       DNS_TYPE_RRSIG);
}

bool dns_type_is_zone_transer(uint16_t type) {

        /* Zone transfers, either normal or incremental */

        return IN_SET(type,
                      DNS_TYPE_AXFR,
                      DNS_TYPE_IXFR);
}

bool dns_type_is_valid_rr(uint16_t type) {

        /* The types valid as RR in packets (but not necessarily
         * stored on servers). */

        return !IN_SET(type,
                       DNS_TYPE_ANY,
                       DNS_TYPE_AXFR,
                       DNS_TYPE_IXFR);
}

bool dns_class_is_valid_rr(uint16_t class) {
        return class != DNS_CLASS_ANY;
}

bool dns_type_may_redirect(uint16_t type) {
        /* The following record types should never be redirected using
         * CNAME/DNAME RRs. See
         * <https://tools.ietf.org/html/rfc4035#section-2.5>. */

        if (dns_type_is_pseudo(type))
                return false;

        return !IN_SET(type,
                       DNS_TYPE_CNAME,
                       DNS_TYPE_DNAME,
                       DNS_TYPE_NSEC3,
                       DNS_TYPE_NSEC,
                       DNS_TYPE_RRSIG,
                       DNS_TYPE_NXT,
                       DNS_TYPE_SIG,
                       DNS_TYPE_KEY);
}

bool dns_type_may_wildcard(uint16_t type) {

        /* The following records may not be expanded from wildcard RRsets */

        if (dns_type_is_pseudo(type))
                return false;

        return !IN_SET(type,
                       DNS_TYPE_NSEC3,
                       DNS_TYPE_SOA,

                       /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */
                       DNS_TYPE_DNAME);
}

bool dns_type_apex_only(uint16_t type) {

        /* Returns true for all RR types that may only appear signed in a zone apex */

        return IN_SET(type,
                      DNS_TYPE_SOA,
                      DNS_TYPE_NS,            /* this one can appear elsewhere, too, but not signed */
                      DNS_TYPE_DNSKEY,
                      DNS_TYPE_NSEC3PARAM);
}

bool dns_type_is_dnssec(uint16_t type) {
        return IN_SET(type,
                      DNS_TYPE_DS,
                      DNS_TYPE_DNSKEY,
                      DNS_TYPE_RRSIG,
                      DNS_TYPE_NSEC,
                      DNS_TYPE_NSEC3,
                      DNS_TYPE_NSEC3PARAM);
}

bool dns_type_is_obsolete(uint16_t type) {
        return IN_SET(type,
                      /* Obsoleted by RFC 973 */
                      DNS_TYPE_MD,
                      DNS_TYPE_MF,
                      DNS_TYPE_MAILA,

                      /* Kinda obsoleted by RFC 2505 */
                      DNS_TYPE_MB,
                      DNS_TYPE_MG,
                      DNS_TYPE_MR,
                      DNS_TYPE_MINFO,
                      DNS_TYPE_MAILB,

                      /* RFC1127 kinda obsoleted this by recommending against its use */
                      DNS_TYPE_WKS,

                      /* Declared historical by RFC 6563 */
                      DNS_TYPE_A6,

                      /* Obsoleted by DNSSEC-bis */
                      DNS_TYPE_NXT,

                      /* RFC 1035 removed support for concepts that needed this from RFC 883 */
                      DNS_TYPE_NULL);
}

bool dns_type_needs_authentication(uint16_t type) {

        /* Returns true for all (non-obsolete) RR types where records are not useful if they aren't
         * authenticated. I.e. everything that contains crypto keys. */

        return IN_SET(type,
                      DNS_TYPE_CERT,
                      DNS_TYPE_SSHFP,
                      DNS_TYPE_IPSECKEY,
                      DNS_TYPE_DS,
                      DNS_TYPE_DNSKEY,
                      DNS_TYPE_TLSA,
                      DNS_TYPE_CDNSKEY,
                      DNS_TYPE_OPENPGPKEY,
                      DNS_TYPE_CAA);
}

int dns_type_to_af(uint16_t t) {
        switch (t) {

        case DNS_TYPE_A:
                return AF_INET;

        case DNS_TYPE_AAAA:
                return AF_INET6;

        case DNS_TYPE_ANY:
                return AF_UNSPEC;

        default:
                return -EINVAL;
        }
}

const char *dns_class_to_string(uint16_t class) {

        switch (class) {

        case DNS_CLASS_IN:
                return "IN";

        case DNS_CLASS_ANY:
                return "ANY";
        }

        return NULL;
}

int dns_class_from_string(const char *s) {

        if (!s)
                return _DNS_CLASS_INVALID;

        if (strcaseeq(s, "IN"))
                return DNS_CLASS_IN;
        else if (strcaseeq(s, "ANY"))
                return DNS_CLASS_ANY;

        return _DNS_CLASS_INVALID;
}

const char* tlsa_cert_usage_to_string(uint8_t cert_usage) {

        switch (cert_usage) {

        case 0:
                return "CA constraint";

        case 1:
                return "Service certificate constraint";

        case 2:
                return "Trust anchor assertion";

        case 3:
                return "Domain-issued certificate";

        case 4 ... 254:
                return "Unassigned";

        case 255:
                return "Private use";
        }

        return NULL;  /* clang cannot count that we covered everything */
}

const char* tlsa_selector_to_string(uint8_t selector) {
        switch (selector) {

        case 0:
                return "Full Certificate";

        case 1:
                return "SubjectPublicKeyInfo";

        case 2 ... 254:
                return "Unassigned";

        case 255:
                return "Private use";
        }

        return NULL;
}

const char* tlsa_matching_type_to_string(uint8_t selector) {

        switch (selector) {

        case 0:
                return "No hash used";

        case 1:
                return "SHA-256";

        case 2:
                return "SHA-512";

        case 3 ... 254:
                return "Unassigned";

        case 255:
                return "Private use";
        }

        return NULL;
}
