construct/ircd/rfc1035.cc

710 lines
20 KiB
C++

// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.
ircd::mutable_buffer
ircd::rfc1035::make_query(const mutable_buffer &out,
const uint16_t &id,
const question &q)
{
return make_query(out, id, {&q, 1});
}
ircd::mutable_buffer
ircd::rfc1035::make_query(const mutable_buffer &out,
const uint16_t &id,
const vector_view<const question> &questions)
{
header h{0};
h.id = id;
h.rd = true;
h.qdcount = hton(uint16_t(questions.size()));
return make_query(out, h, questions);
}
ircd::mutable_buffer
ircd::rfc1035::make_query(const mutable_buffer &out,
const header &header,
const vector_view<const question> &questions)
{
assert(ntoh(header.qdcount) == questions.size());
window_buffer sb{out};
sb([&header](const mutable_buffer &buf)
{
const const_buffer headbuf
{
reinterpret_cast<const char *>(&header), sizeof(header)
};
return copy(buf, headbuf);
});
for(const auto &question : questions)
sb([&question](const mutable_buffer &buf)
{
return size(question.print(buf));
});
return sb.completed();
}
ircd::rfc1035::question::question(const string_view &fqdn,
const uint16_t &qtype)
:qtype{qtype}
,name
{
// The name string_view is reduced to not view the null terminator
// making it easier for external users. We'll account for this
// internally. If this->name was a const_buffer it would have the
// null, but doing it this way with the string_view convention prevents
// the null from leaking into the rest of the system when users want
// to know the question.
namebuf, size(make_name(namebuf, fqdn)) - 1
}
{
}
ircd::const_buffer
ircd::rfc1035::question::parse(const const_buffer &in)
{
if(unlikely(size(in) < 2 + 2 + 2))
throw error
{
"Answer input buffer underflow"
};
const auto namelen(parse_name(namebuf, in));
name = string_view{namebuf, namelen - 1};
const char *pos(data(in) + namelen);
if(unlikely(pos + 2 + 2 > end(in)))
throw error
{
"Question input buffer is incomplete (%zu bytes)", size(in)
};
qtype = ntoh(*reinterpret_cast<const uint16_t *>(pos)); pos += 2;
qclass = ntoh(*reinterpret_cast<const uint16_t *>(pos)); pos += 2;
assert(size_t(pos - data(in)) <= size(in));
return { data(in), pos };
}
ircd::mutable_buffer
ircd::rfc1035::question::print(const mutable_buffer &buf)
const
{
const size_t required
{
size(name) + 1 + 2 + 2
};
if(unlikely(size(buf) < required))
throw error
{
"Not enough space in question buffer; %zu bytes required", required
};
char *pos
{
// const_buffer must cover the null terminator which the name
// string_view does not cover ... but it's there.
data(buf) + copy(buf, const_buffer{namebuf, size(name) + 1})
};
assert(pos > data(buf));
assert(*(pos - 1) == '\0');
*reinterpret_cast<uint16_t *>(pos) = hton(qtype); pos += 2;
*reinterpret_cast<uint16_t *>(pos) = hton(qclass); pos += 2;
assert(size_t(std::distance(data(buf), pos)) == required);
return mutable_buffer
{
data(buf), pos
};
}
ircd::const_buffer
ircd::rfc1035::answer::parse(const const_buffer &in)
{
if(unlikely(size(in) < 2 + 2 + 2 + 4 + 2))
throw error
{
"Answer input buffer underflow"
};
const auto namelen(parse_name(namebuf, in));
name = string_view{namebuf, namelen - 1};
const char *pos(data(in) + namelen);
if(unlikely(pos + 2 + 2 + 4 + 2 > end(in)))
throw error
{
"Answer input buffer is incomplete (%zu bytes)", size(in)
};
qtype = ntoh(*reinterpret_cast<const uint16_t *>(pos)); pos += 2;
qclass = ntoh(*reinterpret_cast<const uint16_t *>(pos)); pos += 2;
ttl = ntoh(*reinterpret_cast<const uint32_t *>(pos)); pos += 4;
rdlength = ntoh(*reinterpret_cast<const uint16_t *>(pos)); pos += 2;
if(unlikely(qclass != 1))
throw error
{
"Resource record not for IN (internet); corrupt data?"
};
if(unlikely(pos + rdlength > end(in)))
throw error
{
"Answer input buffer has incomplete data (rdlength: %u)", rdlength
};
rdata = const_buffer{pos, rdlength};
pos += rdlength;
assert(size_t(pos - data(in)) <= size(in));
return { data(in), pos };
}
//
// Record
//
ircd::rfc1035::record::record(const uint16_t &type)
:type{type}
{
}
ircd::rfc1035::record::record(const answer &answer)
:type{answer.qtype}
,ttl{answer.ttl}
,rdata{answer.rdata}
{
}
/// vtable
ircd::rfc1035::record::~record()
noexcept
{
}
//
// A
//
ircd::rfc1035::record::A::A()
:record{1}
{
}
ircd::rfc1035::record::A::A(const answer &answer)
:record{answer}
{
assert(size(rdata) == 4);
if(unlikely(size(rdata) < 4))
throw error
{
"A record data underflow"
};
ip4 = ntoh(*reinterpret_cast<const uint32_t *>(data(rdata)));
}
void
ircd::rfc1035::record::A::append(json::stack::object &out)
const
{
char ipbuf[64];
json::stack::member
{
out, "ip", net::string_ip4(ipbuf, ip4)
};
json::stack::member
{
out, "ttl", json::value(ttl)
};
}
bool
ircd::rfc1035::operator!=(const record::A &a, const record::A &b)
{
return !(a == b);
}
bool
ircd::rfc1035::operator==(const record::A &a, const record::A &b)
{
return a.ip4 == b.ip4;
}
//
// AAAA
//
ircd::rfc1035::record::AAAA::AAAA()
:record{28}
{
}
ircd::rfc1035::record::AAAA::AAAA(const answer &answer)
:record{answer}
{
assert(size(rdata) == 16);
if(unlikely(size(rdata) < 16))
throw error
{
"AAAA record data underflow"
};
uint128_t ip6;
#if __has_builtin(__builtin_memcpy_inline)
__builtin_memcpy_inline(&ip6, data(rdata), sizeof(ip6));
#else
__builtin_memcpy(&ip6, data(rdata), sizeof(ip6));
#endif
this->ip6 = ntoh(ip6);
}
void
ircd::rfc1035::record::AAAA::append(json::stack::object &out)
const
{
char ipbuf[64];
json::stack::member
{
out, "ip", net::string_ip6(ipbuf, ip6)
};
json::stack::member
{
out, "ttl", json::value(ttl)
};
}
bool
ircd::rfc1035::operator!=(const record::AAAA &a, const record::AAAA &b)
{
return !(a == b);
}
bool
ircd::rfc1035::operator==(const record::AAAA &a, const record::AAAA &b)
{
return a.ip6 == b.ip6;
}
//
// CNAME
//
ircd::rfc1035::record::CNAME::CNAME()
:record{5}
{
}
ircd::rfc1035::record::CNAME::CNAME(const answer &answer)
:record{answer}
{
if(unlikely(size(rdata) < 1))
throw error
{
"CNAME record data underflow"
};
const auto len{parse_name(namebuf, rdata)};
name = string_view{namebuf, len - 1};
}
void
ircd::rfc1035::record::CNAME::append(json::stack::object &out)
const
{
json::stack::member
{
out, "name", name
};
json::stack::member
{
out, "ttl", json::value(ttl)
};
}
bool
ircd::rfc1035::operator!=(const record::CNAME &a, const record::CNAME &b)
{
return !(a == b);
}
bool
ircd::rfc1035::operator==(const record::CNAME &a, const record::CNAME &b)
{
return a.name == b.name;
}
//
// SRV
//
ircd::rfc1035::record::SRV::SRV()
:record{33}
{
}
ircd::rfc1035::record::SRV::SRV(const answer &answer)
:record{answer}
{
if(unlikely(size(rdata) < 2 + 2 + 2 + 1))
throw error
{
"SRV record data underflow"
};
const char *pos(data(rdata));
priority = ntoh(*reinterpret_cast<const uint16_t *>(pos)); pos += 2;
weight = ntoh(*reinterpret_cast<const uint16_t *>(pos)); pos += 2;
port = ntoh(*reinterpret_cast<const uint16_t *>(pos)); pos += 2;
const const_buffer tgtbuf{pos, end(rdata)};
const auto len{parse_name(this->tgtbuf, tgtbuf)};
tgt = string_view{this->tgtbuf, len - 1};
pos += len;
valid_name(tgt); // throws
assert(std::distance(pos, end(rdata)) >= 0);
}
void
ircd::rfc1035::record::SRV::append(json::stack::object &out)
const
{
json::stack::member
{
out, "port", json::value(port)
};
json::stack::member
{
out, "prio", json::value(priority)
};
json::stack::member
{
out, "tgt", tgt
};
json::stack::member
{
out, "ttl", json::value(ttl)
};
json::stack::member
{
out, "weight", json::value(weight)
};
}
bool
ircd::rfc1035::operator!=(const record::SRV &a, const record::SRV &b)
{
return !(a == b);
}
bool
ircd::rfc1035::operator==(const record::SRV &a, const record::SRV &b)
{
return a.tgt == b.tgt &&
a.port == b.port &&
a.weight == b.weight &&
a.priority == b.priority;
}
//
// Util / misc
//
ircd::const_buffer
ircd::rfc1035::make_name(const mutable_buffer &out,
const string_view &fqdn)
{
assert(!empty(out));
char *pos{data(out)};
*pos = '\0';
ircd::tokens(fqdn, '.', [&pos, &out](const string_view &label)
{
char labelbuf[LABEL_MAX + 1];
if(unlikely(size(label) > LABEL_MAX))
throw error
{
"Single part of domain cannot exceed %zu characters",
LABEL_MAX
};
*pos++ = size(label);
*pos = '\0';
pos += strlcpy
{
pos,
tolower(labelbuf, label),
size_t(std::max(std::distance(pos, end(out)), 0L)),
};
});
// The null terminator is included in the returned buffer view
assert(*pos == '\0');
++pos;
return { data(out), pos };
}
size_t
ircd::rfc1035::parse_name(const mutable_buffer &out,
const const_buffer &in)
{
if(unlikely(size(out) < NAME_BUFSIZE))
throw error
{
"Name output buffer is %zu but RFC1035 requires %zu",
size(out),
NAME_BUFSIZE
};
if(unlikely(empty(in)))
throw error
{
"Name input buffer underflow"
};
const char *pos(data(in));
if(*pos & uint8_t(192))
{
//throw error{"Pointer format not implemented"};
out[0] = '\0';
pos += 2;
return pos - begin(in);
}
out[0] = '\0';
for(uint8_t len(*pos++); len && pos + len < end(in); len = *pos++)
{
const string_view label{pos, len};
if(unlikely(size(label) > LABEL_MAX))
throw error
{
"Single part of domain cannot exceed %zu characters",
LABEL_MAX
};
strlcat(out, label);
strlcat(out, ".");
pos += len;
}
assert(ssize_t(pos - begin(in)) > 0);
const size_t ret(pos - data(in));
if(ret >= size(out))
throw error
{
"Name of length %zu larger than %zu and would be truncated",
size_t(pos - data(in)),
size(out)
};
assert(ret <= size(in));
assert(ret <= size(out));
return ret;
}
void
ircd::rfc1035::valid_name(const string_view &name)
{
rfc3986::valid_domain(rstrip(name, '.', 1));
}
void
ircd::rfc1035::valid_label(const string_view &label)
{
rfc3986::valid_hostname(label);
}
bool
ircd::rfc1035::valid_name(std::nothrow_t,
const string_view &name)
{
return rfc3986::valid_domain(std::nothrow, rstrip(name, '.', 1));
}
bool
ircd::rfc1035::valid_label(std::nothrow_t,
const string_view &label)
{
return rfc3986::valid_hostname(std::nothrow, label);
}
[[gnu::cold]]
std::string
ircd::rfc1035::header::debug()
const
{
std::stringstream ss;
ss << "id : " << id << '\n';
ss << "opcode : " << uint(opcode) << '\n';
ss << "rcode : " << uint(rcode) << ' ' << (rfc1035::rcode[rcode]) << '\n';
ss << "rd : " << (rd? "recursive" : "not recursive") << '\n';
ss << "tc : " << (tc? "truncated" : "not truncated") << '\n';
ss << "aa : " << (aa? "authoritative" : "not authoritative") << '\n';
ss << "qr : " << (qr? "query" : "response") << '\n';
ss << "cd : " << (cd? "checking disabled" : "checking enabled") << '\n';
ss << "ad : " << (ad? "authentic data" : "not authentic data") << '\n';
ss << "ra : " << (ra? "recursion available" : "recursion unavailable") << '\n';
ss << "qdcount : " << qdcount << '\n';
ss << "ancount : " << ancount << '\n';
ss << "nscount : " << nscount << '\n';
ss << "arcount : " << arcount << '\n';
return ss.str();
}
[[clang::always_destroy]]
decltype(ircd::rfc1035::rcode)
ircd::rfc1035::rcode
{
"NoError No Error [RFC1035]", // 0
"FormErr Format Error [RFC1035]", // 1
"ServFail Server Failure [RFC1035]", // 2
"NXDomain Non-Existent Domain [RFC1035]", // 3
"NotImp Not Implemented [RFC1035]", // 4
"Refused Query Refused [RFC1035]", // 5
"YXDomain Name Exists when it should not [RFC2136][RFC6672]", // 6
"YXRRSet RR Set Exists when it should not [RFC2136]", // 7
"NXRRSet RR Set that should exist does not [RFC2136]", // 8
"NotAuth Not Authorized [RFC2845]", // 9
"NotZone Name not contained in zone [RFC2136]", // 10
"Unassigned", // 11
"Unassigned", // 12
"Unassigned", // 13
"Unassigned", // 14
"Unassigned", // 15
"BADVERS Bad OPT Version [RFC6891]", // 16
"BADSIG TSIG Signature Failure [RFC2845]", // 17
"BADKEY Key not recognized [RFC2845]", // 18
"BADTIME Signature out of time window [RFC2845]", // 19
"BADMODE Bad TKEY Mode [RFC2930]", // 20
"BADNAME Duplicate key name [RFC2930]", // 21
"BADALG Algorithm not supported [RFC2930]", // 22
"BADTRUNC Bad Truncation [RFC4635]", // 23
"BADCOOKIE Bad/missing Server Cookie [RFC7873]", // 24
};
[[clang::always_destroy]]
decltype(ircd::rfc1035::qtype)
ircd::rfc1035::qtype
{
{ "A", 1 }, // a host address [RFC1035]
{ "NS", 2 }, // an authoritative name server [RFC1035]
{ "MD", 3 }, // a mail destination (OBSOLETE - use MX) [RFC1035]
{ "MF", 4 }, // a mail forwarder (OBSOLETE - use MX) [RFC1035]
{ "CNAME", 5 }, // the canonical name for an alias [RFC1035]
{ "SOA", 6 }, // marks the start of a zone of authority [RFC1035]
{ "MB", 7 }, // a mailbox domain name (EXPERIMENTAL) [RFC1035]
{ "MG", 8 }, // a mail group member (EXPERIMENTAL) [RFC1035]
{ "MR", 9 }, // a mail rename domain name (EXPERIMENTAL) [RFC1035]
{ "NULL", 10 }, // a null RR (EXPERIMENTAL) [RFC1035]
{ "WKS", 11 }, // a well known service description [RFC1035]
{ "PTR", 12 }, // a domain name pointer [RFC1035]
{ "HINFO", 13 }, // host information [RFC1035]
{ "MINFO", 14 }, // mailbox or mail list information [RFC1035]
{ "MX", 15 }, // mail exchange [RFC1035]
{ "TXT", 16 }, // text strings [RFC1035]
{ "RP", 17 }, // for Responsible Person [RFC1183]
{ "AFSDB", 18 }, // for AFS Data Base location [RFC1183][RFC5864]
{ "X25", 19 }, // for X.25 PSDN address [RFC1183]
{ "ISDN", 20 }, // for ISDN address [RFC1183]
{ "RT", 21 }, // for Route Through [RFC1183]
{ "NSAP", 22 }, // for NSAP address, NSAP style A record [RFC1706]
{ "NSAP-PTR", 23 }, // for domain name pointer, NSAP style [RFC1348][RFC1637][RFC1706]
{ "SIG", 24 }, // for security signature [RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2931][RFC3110][RFC3008]
{ "KEY", 25 }, // for security key [RFC4034][RFC3755][RFC2535][RFC2536][RFC2537][RFC2539][RFC3008][RFC3110]
{ "PX", 26 }, // X.400 mail mapping information [RFC2163]
{ "GPOS", 27 }, // Geographical Position [RFC1712]
{ "AAAA", 28 }, // IP6 Address [RFC3596]
{ "LOC", 29 }, // Location Information [RFC1876]
{ "NXT", 30 }, // Next Domain (OBSOLETE) [RFC3755][RFC2535]
{ "EID", 31 }, // Endpoint Identifier [Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]
{ "NIMLOC", 32 }, // Nimrod Locator [1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt]
{ "SRV", 33 }, // Server Selection [1][RFC2782]
{ "ATMA", 34 }, // ATM Address [ATM Forum Technical Committee]
{ "NAPTR", 35 }, // Naming Authority Pointer [RFC2915][RFC2168][RFC3403]
{ "KX", 36 }, // Key Exchanger [RFC2230]
{ "CERT", 37 }, // CERT [RFC4398]
{ "A6", 38 }, // A6 (OBSOLETE - use AAAA) [RFC3226][RFC2874][RFC6563]
{ "DNAME", 39 }, // DNAME [RFC6672]
{ "SINK", 40 }, // SINK [Donald_E_Eastlake][http://tools.ietf.org/html/draft-eastlake-kitchen-sink] 1997-11
{ "OPT", 41 }, // OPT [RFC6891][RFC3225]
{ "APL", 42 }, // APL [RFC3123]
{ "DS", 43 }, // Delegation Signer [RFC4034][RFC3658]
{ "SSHFP", 44 }, // SSH Key Fingerprint [RFC4255]
{ "IPSECKEY", 45 }, // IPSECKEY [RFC4025]
{ "RRSIG", 46 }, // RRSIG [RFC4034][RFC3755]
{ "NSEC", 47 }, // NSEC [RFC4034][RFC3755]
{ "DNSKEY", 48 }, // DNSKEY [RFC4034][RFC3755]
{ "DHCID", 49 }, // DHCID [RFC4701]
{ "NSEC3", 50 }, // NSEC3 [RFC5155]
{ "NSEC3PARAM", 51 }, // NSEC3PARAM [RFC5155]
{ "TLSA", 52 }, // TLSA [RFC6698]
{ "SMIMEA", 53 }, // S/MIME cert association [RFC8162]
{ "HIP", 55 }, // Host Identity Protocol [RFC8005]
{ "NINFO", 56 }, // NINFO [Jim_Reid] NINFO/ninfo-completed-template
{ "RKEY", 57 }, // RKEY [Jim_Reid] RKEY/rkey-completed-template
{ "TALINK", 58 }, // Trust Anchor LINK [Wouter_Wijngaards] TALINK/talink-completed-template
{ "CDS", 59 }, // Child DS [RFC7344] CDS/cds-completed-template
{ "CDNSKEY", 60 }, // DNSKEY(s) the Child wants reflected in DS [RFC7344]
{ "OPENPGPKEY", 61 }, // OpenPGP Key [RFC7929] OPENPGPKEY/openpgpkey-completed-template
{ "CSYNC", 62 }, // Child-To-Parent Synchronization [RFC7477]
{ "SPF", 99 }, // [RFC7208]
{ "UINFO", 100 }, // IANA-Reserved
{ "UID", 101 }, // IANA-Reserved
{ "GID", 102 }, // IANA-Reserved
{ "UNSPEC", 103 }, // IANA-Reserved
{ "NID", 104 }, // [RFC6742] ILNP/nid-completed-template
{ "L32", 105 }, // [RFC6742] ILNP/l32-completed-template
{ "L64", 106 }, // [RFC6742] ILNP/l64-completed-template
{ "LP", 107 }, // [RFC6742] ILNP/lp-completed-template
{ "EUI48", 108 }, // an EUI-48 address [RFC7043]
{ "EUI64", 109 }, // an EUI-64 address [RFC7043]
{ "TKEY", 249 }, // Transaction Key [RFC2930]
{ "TSIG", 250 }, // Transaction Signature [RFC2845]
{ "IXFR", 251 }, // incremental transfer [RFC1995]
{ "AXFR", 252 }, // transfer of an entire zone [RFC1035][RFC5936]
{ "MAILB", 253 }, // mailbox-related RRs (MB, MG or MR) [RFC1035]
{ "MAILA", 254 }, // mail agent RRs (OBSOLETE - see MX) [RFC1035]
{ "*", 255 }, // A request for all records the server/cache has available [RFC1035][RFC6895]
{ "URI", 256 }, // URI [RFC7553] URI/uri-completed-template
{ "CAA", 257 }, // Certification Authority Restriction [RFC6844]
{ "AVC", 258 }, // Application Visibility and Control [Wolfgang_Riedel]
{ "DOA", 259 }, // Digital Object Architecture [draft-durand-doa-over-dns]
{ "TA", 32768 }, // DNSSEC Trust Authorities [Sam_Weiler][http://cameo.library.cmu.edu/]
};
[[clang::always_destroy]]
decltype(ircd::rfc1035::rqtype)
ircd::rfc1035::rqtype{[]
{
std::map<uint16_t, string_view> ret;
std::transform(begin(qtype), end(qtype), std::inserter(ret, ret.end()), []
(const auto &pair) -> std::pair<uint16_t, string_view>
{
return { pair.second, pair.first };
});
return ret;
}()};