static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptrs);
static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr);
+// 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, QType::DNAME };
+
AuthWebServer::AuthWebServer() :
d_tid(0),
d_start(time(nullptr)),
d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port"));
d_ws->setApiKey(arg()["api-key"]);
d_ws->setPassword(arg()["webserver-password"]);
+ d_ws->setLogLevel(arg()["webserver-loglevel"]);
NetmaskGroup acl;
acl.toMasks(::arg()["webserver-allow-from"]);
ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
if(req->getvars["ring"].empty()) {
- vector<string>entries=S.listRings();
- for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i)
- printtable(ret,*i,S.getRingTitle(*i));
+ auto entries = S.listRings();
+ for(const auto &i: entries) {
+ printtable(ret, i, S.getRingTitle(i));
+ }
printvars(ret);
if(arg().mustDo("webserver-print-arguments"))
out["uptime"] = std::to_string(time(0) - s_starttime);
}
+boost::optional<uint64_t> productServerStatisticsFetch(const std::string& name)
+{
+ try {
+ // ::read() calls ::exists() which throws a PDNSException when the key does not exist
+ return S.read(name);
+ }
+ catch(...) {
+ return boost::none;
+ }
+}
+
static void validateGatheredRRType(const DNSResourceRecord& rr) {
if (rr.qtype.getCode() == QType::OPT || rr.qtype.getCode() == QType::TSIG) {
throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": invalid type given");
}
}
-static void gatherRecords(const Json container, const DNSName& qname, const QType qtype, const int ttl, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
- static const std::set<uint16_t> onlyOneEntryTypes = { QType::CNAME, QType::SOA };
+static void gatherRecords(const string& logprefix, const Json container, const DNSName& qname, const QType qtype, const int ttl, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
UeberBackend B;
DNSResourceRecord rr;
rr.qname = qname;
validateGatheredRRType(rr);
const auto& items = container["records"].array_items();
- if (onlyOneEntryTypes.count(qtype.getCode()) != 0 && items.size() > 1) {
- throw ApiException("RRset for "+rr.qname.toString()+"/"+rr.qtype.getName()+" has more than one record");
- }
-
for(const auto& record : items) {
string content = stringFromJson(record, "content");
rr.disabled = boolFromJson(record, "disabled");
if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
boolFromJson(record, "set-ptr", false) == true) {
+
+ g_log<<Logger::Warning<<logprefix<<"API call uses deprecated set-ptr feature, please remove it"<<endl;
+
DNSResourceRecord ptr;
makePtr(rr, &ptr);
}
static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& di, const DNSName& zonename, const Json document) {
- string zonemaster;
+ vector<string> zonemaster;
bool shouldRectify = false;
for(auto value : document["masters"].array_items()) {
string master = value.string_value();
if (master.empty())
throw ApiException("Master can not be an empty string");
- zonemaster += master + " ";
+ try {
+ ComboAddress m(master);
+ } catch (const PDNSException &e) {
+ throw ApiException("Master (" + master + ") is not an IP address: " + e.reason);
+ }
+ zonemaster.push_back(master);
}
- if (zonemaster != "") {
- di.backend->setMaster(zonename, zonemaster);
+ if (zonemaster.size()) {
+ di.backend->setMaster(zonename, boost::join(zonemaster, ","));
}
if (document["kind"].is_string()) {
di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
}
}
-/** Throws ApiException if records with duplicate name/type/content are present.
+/** Throws ApiException if records which violate RRset contraints 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
*/
-static void checkDuplicateRecords(vector<DNSResourceRecord>& records) {
+static void checkNewRecords(vector<DNSResourceRecord>& records) {
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.qtype == rec.qtype && previous.qname == rec.qname && previous.content == rec.content) {
- throw ApiException("Duplicate record in RRset " + rec.qname.toString() + " IN " + rec.qtype.getName() + " with content \"" + rec.content + "\"");
+ 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.getName()+" has more than one record");
+ }
+ if (previous.content == rec.content) {
+ throw ApiException("Duplicate record in RRset " + rec.qname.toString() + " IN " + rec.qtype.getName() + " 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.getName()+": Conflicts with another RRset");
+ }
}
+
+ // 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.getName() + " " + e.what());
+ }
+
previous = rec;
}
}
}
if (rrset["records"].is_array()) {
int ttl = intFromJson(rrset, "ttl");
- gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs);
+ gatherRecords(req->logprefix, rrset, qname, qtype, ttl, new_records, new_ptrs);
}
if (rrset["comments"].is_array()) {
gatherComments(rrset, qname, qtype, new_comments);
}
}
- checkDuplicateRecords(new_records);
+ checkNewRecords(new_records);
if (boolFromJson(document, "dnssec", false)) {
checkDefaultDNSSECAlgos();
domains.push_back(di);
}
} else {
- B.getAllDomains(&domains, true); // incl. disabled
+ try {
+ B.getAllDomains(&domains, true); // incl. disabled
+ } catch(const PDNSException &e) {
+ throw HttpInternalServerErrorException("Could not retrieve all domain information: " + e.reason);
+ }
}
Json::array doc;
UeberBackend B;
DomainInfo di;
- if (!B.getDomainInfo(zonename, di)) {
- throw HttpNotFoundException();
+ try {
+ if (!B.getDomainInfo(zonename, di)) {
+ throw HttpNotFoundException();
+ }
+ } catch(const PDNSException &e) {
+ throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason);
}
if(req->method == "PUT") {
if(!di.backend->deleteDomain(zonename))
throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
+ // clear caches
+ DNSSECKeeper dk(&B);
+ dk.clearCaches(zonename);
+ purgeAuthCaches(zonename.toString() + "$");
+
// empty body on success
resp->body = "";
resp->status = 204; // No Content: declare that the zone is gone now
// ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
int ttl = intFromJson(rrset, "ttl");
// new_ptrs is merged.
- gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs);
+ gatherRecords(req->logprefix, rrset, qname, qtype, ttl, new_records, new_ptrs);
for(DNSResourceRecord& rr : new_records) {
rr.domain_id = di.id;
if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
}
-
- // Check if the DNSNames that should be hostnames, are hostnames
- try {
- checkHostnameCorrectness(rr);
- } catch (const std::exception& e) {
- throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName() + " " + e.what());
- }
}
- checkDuplicateRecords(new_records);
+ checkNewRecords(new_records);
}
if (replace_comments) {
di.backend->lookup(QType(QType::ANY), qname);
DNSResourceRecord rr;
while (di.backend->get(rr)) {
- if (qtype.getCode() == 0) {
+ if (rr.qtype.getCode() == 0) {
ent_present = true;
+ /* that's fine, we will override it */
+ continue;
}
- if (qtype.getCode() == QType::CNAME && rr.qtype.getCode() != QType::CNAME) {
- throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Conflicts with pre-existing non-CNAME RRset");
- } else if (qtype.getCode() != QType::CNAME && rr.qtype.getCode() == QType::CNAME) {
- throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Conflicts with pre-existing CNAME RRset");
+ if (qtype.getCode() != rr.qtype.getCode()
+ && (exclusiveEntryTypes.count(qtype.getCode()) != 0
+ || exclusiveEntryTypes.count(rr.qtype.getCode()) != 0)) {
+ throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Conflicts with pre-existing RRset");
}
}
string q = req->getvars["q"];
string sMax = req->getvars["max"];
+ string sObjectType = req->getvars["object_type"];
+
int maxEnts = 100;
int ents = 0;
+ // the following types of data can be searched for using the api
+ enum class ObjectType
+ {
+ ALL,
+ ZONE,
+ RECORD,
+ COMMENT
+ } objectType;
+
if (q.empty())
throw ApiException("Query q can't be blank");
if (!sMax.empty())
if (maxEnts < 1)
throw ApiException("Maximum entries must be larger than 0");
+ if (sObjectType.empty())
+ objectType = ObjectType::ALL;
+ else if (sObjectType == "all")
+ objectType = ObjectType::ALL;
+ else if (sObjectType == "zone")
+ objectType = ObjectType::ZONE;
+ else if (sObjectType == "record")
+ objectType = ObjectType::RECORD;
+ else if (sObjectType == "comment")
+ objectType = ObjectType::COMMENT;
+ else
+ throw ApiException("object_type must be one of the following options: all, zone, record, comment");
+
SimpleMatch sm(q,true);
UeberBackend B;
vector<DomainInfo> domains;
for(const DomainInfo di: domains)
{
- if (ents < maxEnts && sm.match(di.zone)) {
+ if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ents < maxEnts && sm.match(di.zone)) {
doc.push_back(Json::object {
{ "object_type", "zone" },
{ "zone_id", apiZoneNameToId(di.zone) },
zoneIdZone[di.id] = di; // populate cache
}
- if (B.searchRecords(q, maxEnts, result_rr))
+ if ((objectType == ObjectType::ALL || objectType == ObjectType::RECORD) && B.searchRecords(q, maxEnts, result_rr))
{
for(const DNSResourceRecord& rr: result_rr)
{
}
}
- if (B.searchComments(q, maxEnts, result_c))
+ if ((objectType == ObjectType::ALL || objectType == ObjectType::COMMENT) && B.searchComments(q, maxEnts, result_c))
{
for(const Comment &c: result_c)
{