src_dir / 'bindparserclasses.hh',
src_dir / 'burtle.hh',
src_dir / 'cachecleaner.hh',
+ src_dir / 'check-zone.cc',
+ src_dir / 'check-zone.hh',
src_dir / 'circular_buffer.hh',
src_dir / 'comment.hh',
src_dir / 'communicator.cc',
bindparser.cc \
burtle.hh \
cachecleaner.hh \
+ check-zone.cc check-zone.hh \
circular_buffer.hh \
comment.hh \
communicator.cc communicator.hh \
bindlexer.l \
bindparser.yy \
cachecleaner.hh \
+ check-zone.cc check-zone.hh \
circular_buffer.hh \
credentials.cc credentials.hh \
dbdnsseckeeper.cc \
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "dns.hh"
+#include "dnsrecords.hh"
+
+#include "check-zone.hh"
+
+namespace Check
+{
+
+static void rejectRecord(const DNSResourceRecord& rec, const std::string& why)
+{
+ throw CheckException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + why);
+}
+
+void checkRRSet(vector<DNSResourceRecord>& records, const ZoneName& zone)
+{
+ // QTypes that MUST NOT have multiple records of the same type in a given RRset.
+ static const std::set<uint16_t> onlyOneEntryTypes = {QType::CNAME, QType::DNAME, QType::SOA};
+ // QTypes that MUST be at apex.
+ static const std::set<uint16_t> atApexTypes = {QType::SOA, QType::DNSKEY};
+ // QTypes that are NOT allowed at apex.
+ static const std::set<uint16_t> nonApexTypes = {QType::DS};
+
+ sort(records.begin(), records.end(),
+ [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
+ /* we need _strict_ weak ordering */
+ return std::tie(rec_a.qname, rec_a.qtype, rec_a.content) < std::tie(rec_b.qname, rec_b.qtype, rec_b.content);
+ });
+
+ DNSResourceRecord previous;
+ for (const auto& rec : records) {
+ if (previous.qname == rec.qname) {
+ if (previous.qtype == rec.qtype) {
+ if (onlyOneEntryTypes.count(rec.qtype.getCode()) != 0) {
+ rejectRecord(rec, " has more than one record");
+ }
+ if (previous.content == rec.content) {
+ throw CheckException("Duplicate record in RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + " with content \"" + rec.content + "\"");
+ }
+ }
+ else if (QType::exclusiveEntryTypes.count(rec.qtype.getCode()) != 0 || QType::exclusiveEntryTypes.count(previous.qtype.getCode()) != 0) {
+ rejectRecord(rec, ": Conflicts with another RRset");
+ }
+ }
+
+ if (rec.qname == zone.operator const DNSName&()) {
+ if (nonApexTypes.count(rec.qtype.getCode()) != 0) {
+ rejectRecord(rec, " is not allowed at apex");
+ }
+ }
+ else if (atApexTypes.count(rec.qtype.getCode()) != 0) {
+ rejectRecord(rec, " is only allowed at apex");
+ }
+
+ // Check if the DNSNames that should be hostnames, are hostnames
+ try {
+ checkHostnameCorrectness(rec);
+ }
+ catch (const std::exception& e) {
+ rejectRecord(rec, std::string{": "} + e.what());
+ }
+
+ previous = rec;
+ }
+}
+
+} // namespace Check
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+class CheckException : public runtime_error
+{
+public:
+ CheckException(const string& what_arg) :
+ runtime_error(what_arg) {}
+};
+
+namespace Check
+{
+
+/** Throws CheckException if records which violate RRset constraints are present.
+ * NOTE: sorts records in-place.
+ *
+ * Constraints being checked:
+ * *) no exact duplicates
+ * *) no duplicates for QTypes that can only be present once per RRset
+ * *) hostnames are hostnames
+ */
+void checkRRSet(vector<DNSResourceRecord>& records, const ZoneName& zone);
+
+} // namespace Check
#include "ipcipher.hh"
#include "misc.hh"
#include "zonemd.hh"
+#include "check-zone.hh"
#include <fstream>
#include <utility>
#include <cerrno>
}
// add-record ZONE name type [ttl] "content" ["content"]
-static int addOrReplaceRecord(bool isAdd, const vector<string>& cmds) {
+static int addOrReplaceRecord(bool isAdd, const vector<string>& cmds)
+{
DNSResourceRecord rr;
vector<DNSResourceRecord> newrrs;
ZoneName zone(cmds.at(0));
}
}
- di.backend->startTransaction(zone, UnknownDomainID);
-
- // Enforce that CNAME records can not be mixed with any other.
- // If we add a CNAME: there should be no existing records except for one
- // possible previous CNAME, which will get replaced.
- // If we add something else: there should be no existing CNAME record.
- bool reject{false};
- if (rr.qtype.getCode() == QType::CNAME) {
- di.backend->lookup(QType(QType::ANY), rr.qname, static_cast<int>(di.id));
- while (di.backend->get(oldrr)) {
- if (oldrr.qtype.getCode() == QType::CNAME) {
- if (not isAdd) {
- // Replacement operation: ok to replace CNAME with another
- continue;
- }
- }
- reject = true;
- di.backend->lookupEnd();
- break;
- }
- if (reject) {
- cerr<<"Attempting to add CNAME to "<<rr.qname<<" which already has existing records"<<endl;
- return EXIT_FAILURE;
- }
- }
- else {
- di.backend->lookup(QType(QType::CNAME), rr.qname, static_cast<int>(di.id));
- while (di.backend->get(oldrr)) {
- reject = true;
- di.backend->lookupEnd();
- break;
- }
- if (reject) {
- cerr<<"Attempting to add record to "<<rr.qname<<" which already has a CNAME record"<<endl;
- return EXIT_FAILURE;
- }
- }
-
// Synthesize the new records.
for(auto i = contentStart ; i < cmds.size() ; ++i) {
rr.content = DNSRecordContent::make(rr.qtype.getCode(), QClass::IN, cmds.at(i))->getZoneRepresentation(true);
}
}
- if(isAdd) {
+ di.backend->startTransaction(zone, UnknownDomainID);
+
+ if (isAdd) {
// the 'add' case; preserve existing records, making sure to discard
// would-be new records which contents are identical to the existing ones.
vector<DNSResourceRecord> oldrrs;
- di.backend->lookup(rr.qtype, rr.qname, static_cast<int>(di.id));
+ di.backend->lookup(QType(QType::ANY), rr.qname, static_cast<int>(di.id));
while (di.backend->get(oldrr)) {
oldrrs.push_back(oldrr);
for (auto iter = newrrs.begin(); iter != newrrs.end(); ++iter) {
oldrrs.insert(oldrrs.end(), newrrs.begin(), newrrs.end());
newrrs = std::move(oldrrs);
}
+
+ try {
+ Check::checkRRSet(newrrs, zone);
+ }
+ catch (CheckException& e) {
+ cerr << e.what() << endl;
+ return EXIT_FAILURE;
+ }
+ if (isAdd) {
+ // We had collected all record types earlier in order to be able to
+ // perform the proper checks. Trim the list to only keep those of the
+ // qtype we are modifying, for the sake of the replaceRRSet call below.
+ newrrs.erase(
+ std::remove_if(newrrs.begin(), newrrs.end(),
+ [&rr](const DNSResourceRecord& rec) -> bool { return rec.qtype != rr.qtype; }),
+ newrrs.end());
+ }
else {
cout<<"All existing records for "<<rr.qname<<" IN "<<rr.qtype.toString()<<" will be replaced"<<endl;
}
return "CLASS" + std::to_string(qclass);
}
}
+
+const std::set<uint16_t> QType::exclusiveEntryTypes = {
+ QType::CNAME
+};
const static map<const string, uint16_t> names;
const static map<uint16_t, const string> numbers;
+ // QTypes that MUST NOT be used with any other QType on the same name.
+ const static std::set<uint16_t> exclusiveEntryTypes;
+
private:
uint16_t code;
#include "auth-zonecache.hh"
#include "threadname.hh"
#include "tsigutils.hh"
+#include "check-zone.hh"
using json11::Json;
static void patchZone(UeberBackend& backend, const ZoneName& zonename, DomainInfo& domainInfo, const vector<Json>& rrsets, HttpResponse* resp);
-// QTypes that MUST NOT have multiple records of the same type in a given RRset.
-static const std::set<uint16_t> onlyOneEntryTypes = {QType::CNAME, QType::DNAME, QType::SOA};
-// QTypes that MUST NOT be used with any other QType on the same name.
-static const std::set<uint16_t> exclusiveEntryTypes = {QType::CNAME};
-// QTypes that MUST be at apex.
-static const std::set<uint16_t> atApexTypes = {QType::SOA, QType::DNSKEY};
-// QTypes that are NOT allowed at apex.
-static const std::set<uint16_t> nonApexTypes = {QType::DS};
-
AuthWebServer::AuthWebServer() :
d_start(time(nullptr))
*/
static void checkNewRecords(vector<DNSResourceRecord>& records, const ZoneName& zone)
{
- sort(records.begin(), records.end(),
- [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
- /* we need _strict_ weak ordering */
- return std::tie(rec_a.qname, rec_a.qtype, rec_a.content) < std::tie(rec_b.qname, rec_b.qtype, rec_b.content);
- });
-
- DNSResourceRecord previous;
- for (const auto& rec : records) {
- if (previous.qname == rec.qname) {
- if (previous.qtype == rec.qtype) {
- if (onlyOneEntryTypes.count(rec.qtype.getCode()) != 0) {
- throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + " has more than one record");
- }
- if (previous.content == rec.content) {
- throw ApiException("Duplicate record in RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + " with content \"" + rec.content + "\"");
- }
- }
- else if (exclusiveEntryTypes.count(rec.qtype.getCode()) != 0 || exclusiveEntryTypes.count(previous.qtype.getCode()) != 0) {
- throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + ": Conflicts with another RRset");
- }
- }
-
- if (rec.qname == zone.operator const DNSName&()) {
- if (nonApexTypes.count(rec.qtype.getCode()) != 0) {
- throw ApiException("Record " + rec.qname.toString() + " IN " + rec.qtype.toString() + " is not allowed at apex");
- }
- }
- else if (atApexTypes.count(rec.qtype.getCode()) != 0) {
- throw ApiException("Record " + rec.qname.toString() + " IN " + rec.qtype.toString() + " is only allowed at apex");
- }
-
- // Check if the DNSNames that should be hostnames, are hostnames
- try {
- checkHostnameCorrectness(rec);
- }
- catch (const std::exception& e) {
- throw ApiException("RRset " + rec.qname.toString() + " IN " + rec.qtype.toString() + ": " + e.what());
- }
-
- previous = rec;
+ try {
+ Check::checkRRSet(records, zone);
+ }
+ catch (CheckException& e) {
+ throw ApiException(e.what());
}
}
dname_seen |= resourceRecord.qtype == QType::DNAME;
ns_seen |= resourceRecord.qtype == QType::NS;
if (qtype.getCode() != resourceRecord.qtype.getCode()
- && (exclusiveEntryTypes.count(qtype.getCode()) != 0
- || exclusiveEntryTypes.count(resourceRecord.qtype.getCode()) != 0)) {
+ && (QType::exclusiveEntryTypes.count(qtype.getCode()) != 0
+ || QType::exclusiveEntryTypes.count(resourceRecord.qtype.getCode()) != 0)) {
// leave database handle in a consistent state
domainInfo.backend->lookupEnd();
throw ApiException("RRset " + qname.toString() + " IN " + qtype.toString() + ": Conflicts with pre-existing RRset");
os.system("$PDNSUTIL --config-dir=configs/auth create-zone noacceptor.net")
os.system("$PDNSUTIL --config-dir=configs/auth create-zone wrongacceptor.net")
- os.system("$PDNSUTIL --config-dir=configs/auth add-record example.net example.net SOA 3600 'ns1.example.net otto.example.net 2022010403 10800 3600 604800 3600'")
- os.system("$PDNSUTIL --config-dir=configs/auth add-record noacceptor.net noacceptor.net SOA 3600 'ns1.noacceptor.net otto.example.net 2022010403 10800 3600 604800 3600'")
- os.system("$PDNSUTIL --config-dir=configs/auth add-record wrongacceptor.net wrongacceptor.net SOA 3600 'ns1.wrongacceptor.net otto.example.net 2022010403 10800 3600 604800 3600'")
+ os.system("$PDNSUTIL --config-dir=configs/auth replace-rrset example.net example.net SOA 3600 'ns1.example.net otto.example.net 2022010403 10800 3600 604800 3600'")
+ os.system("$PDNSUTIL --config-dir=configs/auth replace-rrset noacceptor.net noacceptor.net SOA 3600 'ns1.noacceptor.net otto.example.net 2022010403 10800 3600 604800 3600'")
+ os.system("$PDNSUTIL --config-dir=configs/auth replace-rrset wrongacceptor.net wrongacceptor.net SOA 3600 'ns1.wrongacceptor.net otto.example.net 2022010403 10800 3600 604800 3600'")
os.system("$PDNSUTIL --config-dir=configs/auth set-meta example.net GSS-ACCEPTOR-PRINCIPAL DNS/ns1.example.net@EXAMPLE.COM")
os.system("$PDNSUTIL --config-dir=configs/auth set-meta wrongacceptor.net GSS-ACCEPTOR-PRINCIPAL DNS/ns1.example.net@EXAMPLE.COM")
Ignoring duplicate record content "127.0.0.2"
New rrset:
host2.bug.less. 3600 IN A 127.0.0.2
-Attempting to add record to cname.bug.less which already has a CNAME record
-Attempting to add CNAME to host.bug.less which already has existing records
+RRset cname.bug.less. IN CNAME: Conflicts with another RRset
+RRset host.bug.less. IN CNAME: Conflicts with another RRset
New rrset:
host2.bug.less. 3600 IN A 127.0.0.2
host2.bug.less. 3600 IN A 127.0.0.3