"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "pdns/ws-auth.cc" between
pdns-auth-4.1.13.tar.gz and pdns-auth-4.2.0.tar.gz

About: PowerDNS Authoritative Nameserver is a versatile nameserver which supports a large number of backends (that can either be plain zone files or be more dynamic in nature).

ws-auth.cc  (pdns-auth-4.1.13):ws-auth.cc  (pdns-auth-4.2.0)
skipping to change at line 33 skipping to change at line 33
#include "config.h" #include "config.h"
#endif #endif
#include "utility.hh" #include "utility.hh"
#include "dynlistener.hh" #include "dynlistener.hh"
#include "ws-auth.hh" #include "ws-auth.hh"
#include "json.hh" #include "json.hh"
#include "webserver.hh" #include "webserver.hh"
#include "logger.hh" #include "logger.hh"
#include "statbag.hh" #include "statbag.hh"
#include "misc.hh" #include "misc.hh"
#include "base64.hh"
#include "arguments.hh" #include "arguments.hh"
#include "dns.hh" #include "dns.hh"
#include "comment.hh" #include "comment.hh"
#include "ueberbackend.hh" #include "ueberbackend.hh"
#include <boost/format.hpp> #include <boost/format.hpp>
#include "namespaces.hh" #include "namespaces.hh"
#include "ws-api.hh" #include "ws-api.hh"
#include "version.hh" #include "version.hh"
#include "dnsseckeeper.hh" #include "dnsseckeeper.hh"
#include <iomanip> #include <iomanip>
#include "zoneparser-tng.hh" #include "zoneparser-tng.hh"
#include "common_startup.hh" #include "common_startup.hh"
#include "auth-caches.hh" #include "auth-caches.hh"
#include "threadname.hh"
#include "tsigutils.hh"
using json11::Json; using json11::Json;
extern StatBag S; extern StatBag S;
static void patchZone(HttpRequest* req, HttpResponse* resp); static void patchZone(HttpRequest* req, HttpResponse* resp);
static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptr s); static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptr s);
static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr); static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr);
AuthWebServer::AuthWebServer() // 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::DNA
ME };
AuthWebServer::AuthWebServer() :
d_tid(0),
d_start(time(nullptr)),
d_min10(0),
d_min5(0),
d_min1(0)
{ {
d_start=time(0);
d_min10=d_min5=d_min1=0;
d_ws = 0;
d_tid = 0;
if(arg().mustDo("webserver") || arg().mustDo("api")) { if(arg().mustDo("webserver") || arg().mustDo("api")) {
d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port ")); 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"]);
d_ws->setACL(acl);
d_ws->setMaxBodySize(::arg().asNum("webserver-max-bodysize"));
d_ws->bind(); d_ws->bind();
} }
} }
void AuthWebServer::go() void AuthWebServer::go()
{ {
S.doRings(); S.doRings();
pthread_create(&d_tid, 0, webThreadHelper, this); pthread_create(&d_tid, 0, webThreadHelper, this);
pthread_create(&d_tid, 0, statThreadHelper, this); pthread_create(&d_tid, 0, statThreadHelper, this);
} }
void AuthWebServer::statThread() void AuthWebServer::statThread()
{ {
try { try {
setThreadName("pdns/statHelper");
for(;;) { for(;;) {
d_queries.submit(S.read("udp-queries")); d_queries.submit(S.read("udp-queries"));
d_cachehits.submit(S.read("packetcache-hit")); d_cachehits.submit(S.read("packetcache-hit"));
d_cachemisses.submit(S.read("packetcache-miss")); d_cachemisses.submit(S.read("packetcache-miss"));
d_qcachehits.submit(S.read("query-cache-hit")); d_qcachehits.submit(S.read("query-cache-hit"));
d_qcachemisses.submit(S.read("query-cache-miss")); d_qcachemisses.submit(S.read("query-cache-miss"));
Utility::sleep(1); Utility::sleep(1);
} }
} }
catch(...) { catch(...) {
L<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl; g_log<<Logger::Error<<"Webserver statThread caught an exception, dying"<<end l;
_exit(1); _exit(1);
} }
} }
void *AuthWebServer::statThreadHelper(void *p) void *AuthWebServer::statThreadHelper(void *p)
{ {
AuthWebServer *self=static_cast<AuthWebServer *>(p); AuthWebServer *self=static_cast<AuthWebServer *>(p);
self->statThread(); self->statThread();
return 0; // never reached return 0; // never reached
} }
skipping to change at line 268 skipping to change at line 288
"<br>"<<endl; "<br>"<<endl;
ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<< ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
(int)d_qcachemisses.get1()<<", "<< (int)d_qcachemisses.get1()<<", "<<
(int)d_qcachemisses.get5()<<", "<< (int)d_qcachemisses.get5()<<", "<<
(int)d_qcachemisses.get10()<<". Max queries/second: "<<(int)d_qcachemisses.g etMax()<< (int)d_qcachemisses.get10()<<". Max queries/second: "<<(int)d_qcachemisses.g etMax()<<
"<br>"<<endl; "<br>"<<endl;
ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<< S.read("latency")/1000.0<<"ms</p><br>"<<endl; ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<< S.read("latency")/1000.0<<"ms</p><br>"<<endl;
if(req->getvars["ring"].empty()) { if(req->getvars["ring"].empty()) {
vector<string>entries=S.listRings(); auto entries = S.listRings();
for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) for(const auto &i: entries) {
printtable(ret,*i,S.getRingTitle(*i)); printtable(ret, i, S.getRingTitle(i));
}
printvars(ret); printvars(ret);
if(arg().mustDo("webserver-print-arguments")) if(arg().mustDo("webserver-print-arguments"))
printargs(ret); printargs(ret);
} }
else if(S.ringExists(req->getvars["ring"])) else if(S.ringExists(req->getvars["ring"]))
printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100 ); printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100 );
ret<<"</div></div>"<<endl; ret<<"</div></div>"<<endl;
ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 - 2018 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl; ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 - 2019 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
ret<<"</body></html>"<<endl; ret<<"</body></html>"<<endl;
resp->body = ret.str(); resp->body = ret.str();
resp->status = 200; resp->status = 200;
} }
/** Helper to build a record content as needed. */ /** Helper to build a record content as needed. */
static inline string makeRecordContent(const QType& qtype, const string& content , bool noDot) { static inline string makeRecordContent(const QType& qtype, const string& content , bool noDot) {
// noDot: for backend storage, pass true. for API users, pass false. // noDot: for backend storage, pass true. for API users, pass false.
auto drc = DNSRecordContent::mastermake(qtype.getCode(), QClass::IN, content); auto drc = DNSRecordContent::mastermake(qtype.getCode(), QClass::IN, content);
skipping to change at line 306 skipping to change at line 327
return makeRecordContent(qtype, content, false); return makeRecordContent(qtype, content, false);
} }
/** "Normalize" record content for backend storage. */ /** "Normalize" record content for backend storage. */
static inline string makeBackendRecordContent(const QType& qtype, const string& content) { static inline string makeBackendRecordContent(const QType& qtype, const string& content) {
return makeRecordContent(qtype, content, true); return makeRecordContent(qtype, content, true);
} }
static Json::object getZoneInfo(const DomainInfo& di, DNSSECKeeper *dk) { static Json::object getZoneInfo(const DomainInfo& di, DNSSECKeeper *dk) {
string zoneId = apiZoneNameToId(di.zone); string zoneId = apiZoneNameToId(di.zone);
vector<string> masters;
for(const auto& m : di.masters)
masters.push_back(m.toStringWithPortExcept(53));
return Json::object { return Json::object {
// id is the canonical lookup key, which doesn't actually match the name (in some cases) // id is the canonical lookup key, which doesn't actually match the name (in some cases)
{ "id", zoneId }, { "id", zoneId },
{ "url", "/api/v1/servers/localhost/zones/" + zoneId }, { "url", "/api/v1/servers/localhost/zones/" + zoneId },
{ "name", di.zone.toString() }, { "name", di.zone.toString() },
{ "kind", di.getKindString() }, { "kind", di.getKindString() },
{ "dnssec", dk->isSecuredZone(di.zone) }, { "dnssec", dk->isSecuredZone(di.zone) },
{ "account", di.account }, { "account", di.account },
{ "masters", di.masters }, { "masters", masters },
{ "serial", (double)di.serial }, { "serial", (double)di.serial },
{ "edited_serial", (double)calculateEditSOA(di.serial, *dk, di.zone) },
{ "notified_serial", (double)di.notified_serial }, { "notified_serial", (double)di.notified_serial },
{ "last_check", (double)di.last_check } { "last_check", (double)di.last_check }
}; };
} }
static bool shouldDoRRSets(HttpRequest* req) { static bool shouldDoRRSets(HttpRequest* req) {
if (req->getvars.count("rrsets") == 0 || req->getvars["rrsets"] == "true") if (req->getvars.count("rrsets") == 0 || req->getvars["rrsets"] == "true")
return true; return true;
if (req->getvars["rrsets"] == "false") if (req->getvars["rrsets"] == "false")
return false; return false;
throw ApiException("'rrsets' request parameter value '"+req->getvars["rrsets"] +"' is not supported"); throw ApiException("'rrsets' request parameter value '"+req->getvars["rrsets"] +"' is not supported");
} }
static void fillZone(const DNSName& zonename, HttpResponse* resp, bool doRRSets) { static void fillZone(const DNSName& zonename, HttpResponse* resp, bool doRRSets) {
UeberBackend B; UeberBackend B;
DomainInfo di; DomainInfo di;
if(!B.getDomainInfo(zonename, di)) if(!B.getDomainInfo(zonename, di)) {
throw ApiException("Could not find domain '"+zonename.toString()+"'"); throw HttpNotFoundException();
}
DNSSECKeeper dk(&B); DNSSECKeeper dk(&B);
Json::object doc = getZoneInfo(di, &dk); Json::object doc = getZoneInfo(di, &dk);
// extra stuff getZoneInfo doesn't do for us (more expensive) // extra stuff getZoneInfo doesn't do for us (more expensive)
string soa_edit_api; string soa_edit_api;
di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api); di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
doc["soa_edit_api"] = soa_edit_api; doc["soa_edit_api"] = soa_edit_api;
string soa_edit; string soa_edit;
di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit); di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
doc["soa_edit"] = soa_edit; doc["soa_edit"] = soa_edit;
skipping to change at line 358 skipping to change at line 385
bool nsec3narrowbool = false; bool nsec3narrowbool = false;
di.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow); di.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow);
if (nsec3narrow == "1") if (nsec3narrow == "1")
nsec3narrowbool = true; nsec3narrowbool = true;
doc["nsec3narrow"] = nsec3narrowbool; doc["nsec3narrow"] = nsec3narrowbool;
string api_rectify; string api_rectify;
di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify); di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
doc["api_rectify"] = (api_rectify == "1"); doc["api_rectify"] = (api_rectify == "1");
// TSIG
vector<string> tsig_master, tsig_slave;
di.backend->getDomainMetadata(zonename, "TSIG-ALLOW-AXFR", tsig_master);
di.backend->getDomainMetadata(zonename, "AXFR-MASTER-TSIG", tsig_slave);
Json::array tsig_master_keys;
for (const auto& keyname : tsig_master) {
tsig_master_keys.push_back(apiZoneNameToId(DNSName(keyname)));
}
doc["master_tsig_key_ids"] = tsig_master_keys;
Json::array tsig_slave_keys;
for (const auto& keyname : tsig_slave) {
tsig_slave_keys.push_back(apiZoneNameToId(DNSName(keyname)));
}
doc["slave_tsig_key_ids"] = tsig_slave_keys;
if (doRRSets) { if (doRRSets) {
vector<DNSResourceRecord> records; vector<DNSResourceRecord> records;
vector<Comment> comments; vector<Comment> comments;
// load all records + sort // load all records + sort
{ {
DNSResourceRecord rr; DNSResourceRecord rr;
di.backend->list(zonename, di.id, true); // incl. disabled di.backend->list(zonename, di.id, true); // incl. disabled
while(di.backend->get(rr)) { while(di.backend->get(rr)) {
if (!rr.qtype.getCode()) if (!rr.qtype.getCode())
skipping to change at line 467 skipping to change at line 511
{ {
vector<string> items = S.getEntries(); vector<string> items = S.getEntries();
for(const string& item : items) { for(const string& item : items) {
out[item] = std::to_string(S.read(item)); out[item] = std::to_string(S.read(item));
} }
// add uptime // add uptime
out["uptime"] = std::to_string(time(0) - s_starttime); 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) { static void validateGatheredRRType(const DNSResourceRecord& rr) {
if (rr.qtype.getCode() == QType::OPT || rr.qtype.getCode() == QType::TSIG) { if (rr.qtype.getCode() == QType::OPT || rr.qtype.getCode() == QType::TSIG) {
throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": invalid type given"); throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": invalid type given");
} }
} }
static void gatherRecords(const Json container, const DNSName& qname, const QTyp static void gatherRecords(const string& logprefix, const Json container, const D
e qtype, const int ttl, vector<DNSResourceRecord>& new_records, vector<DNSResour NSName& qname, const QType qtype, const int ttl, vector<DNSResourceRecord>& new_
ceRecord>& new_ptrs) { records, vector<DNSResourceRecord>& new_ptrs) {
static const std::set<uint16_t> onlyOneEntryTypes = { QType::CNAME, QType::SOA
};
UeberBackend B; UeberBackend B;
DNSResourceRecord rr; DNSResourceRecord rr;
rr.qname = qname; rr.qname = qname;
rr.qtype = qtype; rr.qtype = qtype;
rr.auth = 1; rr.auth = 1;
rr.ttl = ttl; rr.ttl = ttl;
validateGatheredRRType(rr); validateGatheredRRType(rr);
const auto& items = container["records"].array_items(); 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) { for(const auto& record : items) {
string content = stringFromJson(record, "content"); string content = stringFromJson(record, "content");
rr.disabled = boolFromJson(record, "disabled"); rr.disabled = boolFromJson(record, "disabled");
// validate that the client sent something we can actually parse, and requir e that data to be dotted. // validate that the client sent something we can actually parse, and requir e that data to be dotted.
try { try {
if (rr.qtype.getCode() != QType::AAAA) { if (rr.qtype.getCode() != QType::AAAA) {
string tmp = makeApiRecordContent(rr.qtype, content); string tmp = makeApiRecordContent(rr.qtype, content);
if (!pdns_iequals(tmp, content)) { if (!pdns_iequals(tmp, content)) {
throw std::runtime_error("Not in expected format (parsed as '"+tmp+"') "); throw std::runtime_error("Not in expected format (parsed as '"+tmp+"') ");
skipping to change at line 514 skipping to change at line 564
} }
rr.content = makeBackendRecordContent(rr.qtype, content); rr.content = makeBackendRecordContent(rr.qtype, content);
} }
catch(std::exception& e) catch(std::exception& e)
{ {
throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.getName()+" '"+content+"': "+e.what()); throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.getName()+" '"+content+"': "+e.what());
} }
if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) && if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
boolFromJson(record, "set-ptr", false) == true) { boolFromJson(record, "set-ptr", false) == true) {
g_log<<Logger::Warning<<logprefix<<"API call uses deprecated set-ptr featu
re, please remove it"<<endl;
DNSResourceRecord ptr; DNSResourceRecord ptr;
makePtr(rr, &ptr); makePtr(rr, &ptr);
// verify that there's a zone for the PTR // verify that there's a zone for the PTR
SOAData sd; SOAData sd;
if (!B.getAuth(ptr.qname, QType(QType::PTR), &sd, false)) if (!B.getAuth(ptr.qname, QType(QType::PTR), &sd, false))
throw ApiException("Could not find domain for PTR '"+ptr.qname.toString( )+"' requested for '"+ptr.content+"'"); throw ApiException("Could not find domain for PTR '"+ptr.qname.toString( )+"' requested for '"+ptr.content+"'");
ptr.domain_id = sd.domain_id; ptr.domain_id = sd.domain_id;
new_ptrs.push_back(ptr); new_ptrs.push_back(ptr);
skipping to change at line 571 skipping to change at line 624
else if (z_algo <= 10 && z_size == 0) else if (z_algo <= 10 && z_size == 0)
throw ApiException("default-zsk-algorithm is set to an algorithm("+::arg() ["default-zsk-algorithm"]+") that requires a non-zero default-zsk-size!"); throw ApiException("default-zsk-algorithm is set to an algorithm("+::arg() ["default-zsk-algorithm"]+") that requires a non-zero default-zsk-size!");
} }
} }
static void throwUnableToSecure(const DNSName& zonename) { static void throwUnableToSecure(const DNSName& zonename) {
throw ApiException("No backend was able to secure '" + zonename.toString() + " ', most likely because no DNSSEC" throw ApiException("No backend was able to secure '" + zonename.toString() + " ', most likely because no DNSSEC"
+ "capable backends are loaded, or because the backends have DNSSEC disabl ed. Check your configuration."); + "capable backends are loaded, or because the backends have DNSSEC disabl ed. Check your configuration.");
} }
static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo&
di, const DNSName& zonename, const Json document) { di, const DNSName& zonename, const Json document, bool rectifyTransaction=true)
string zonemaster; {
vector<string> zonemaster;
bool shouldRectify = false; bool shouldRectify = false;
for(auto value : document["masters"].array_items()) { for(auto value : document["masters"].array_items()) {
string master = value.string_value(); string master = value.string_value();
if (master.empty()) if (master.empty())
throw ApiException("Master can not be an empty string"); 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.re
ason);
}
zonemaster.push_back(master);
} }
if (zonemaster != "") { if (zonemaster.size()) {
di.backend->setMaster(zonename, zonemaster); di.backend->setMaster(zonename, boost::join(zonemaster, ","));
} }
if (document["kind"].is_string()) { if (document["kind"].is_string()) {
di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(docume nt, "kind"))); di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(docume nt, "kind")));
} }
if (document["soa_edit_api"].is_string()) { if (document["soa_edit_api"].is_string()) {
di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edi t_api"].string_value()); di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edi t_api"].string_value());
} }
if (document["soa_edit"].is_string()) { if (document["soa_edit"].is_string()) {
di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"]. string_value()); di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"]. string_value());
} }
try { try {
bool api_rectify = boolFromJson(document, "api_rectify"); bool api_rectify = boolFromJson(document, "api_rectify");
di.backend->setDomainMetadataOne(zonename, "API-RECTIFY", api_rectify ? "1" : "0"); di.backend->setDomainMetadataOne(zonename, "API-RECTIFY", api_rectify ? "1" : "0");
} }
catch (JsonException) {} catch (const JsonException&) {}
if (document["account"].is_string()) { if (document["account"].is_string()) {
di.backend->setAccount(zonename, document["account"].string_value()); di.backend->setAccount(zonename, document["account"].string_value());
} }
DNSSECKeeper dk(&B); DNSSECKeeper dk(&B);
bool dnssecInJSON = false; bool dnssecInJSON = false;
bool dnssecDocVal = false; bool dnssecDocVal = false;
try { try {
dnssecDocVal = boolFromJson(document, "dnssec"); dnssecDocVal = boolFromJson(document, "dnssec");
dnssecInJSON = true; dnssecInJSON = true;
} }
catch (JsonException) {} catch (const JsonException&) {}
bool isDNSSECZone = dk.isSecuredZone(zonename); bool isDNSSECZone = dk.isSecuredZone(zonename);
if (dnssecInJSON) { if (dnssecInJSON) {
if (dnssecDocVal) { if (dnssecDocVal) {
if (!isDNSSECZone) { if (!isDNSSECZone) {
checkDefaultDNSSECAlgos(); checkDefaultDNSSECAlgos();
int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algo rithm"]); int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algo rithm"]);
int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algo rithm"]); int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algo rithm"]);
skipping to change at line 683 skipping to change at line 741
if (!dk.setNSEC3PARAM(zonename, ns3pr, boolFromJson(document, "nsec3narrow", false))) { if (!dk.setNSEC3PARAM(zonename, ns3pr, boolFromJson(document, "nsec3narrow", false))) {
throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() + throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() +
"' passed our basic sanity checks, but cannot be used with the current backend."); "' passed our basic sanity checks, but cannot be used with the current backend.");
} }
} }
if (shouldRectify && !dk.isPresigned(zonename)) { if (shouldRectify && !dk.isPresigned(zonename)) {
// Rectify // Rectify
string api_rectify; string api_rectify;
di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify); di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
if (api_rectify.empty()) {
if (::arg().mustDo("default-api-rectify")) {
api_rectify = "1";
}
}
if (api_rectify == "1") { if (api_rectify == "1") {
string info; string info;
string error_msg; string error_msg;
if (!dk.rectifyZone(zonename, error_msg, info, true)) { if (!dk.rectifyZone(zonename, error_msg, info, rectifyTransaction)) {
throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg); throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
} }
} }
// Increase serial // Increase serial
string soa_edit_api_kind; string soa_edit_api_kind;
di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind ); di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind );
if (!soa_edit_api_kind.empty()) { if (!soa_edit_api_kind.empty()) {
SOAData sd; SOAData sd;
if (!B.getSOAUncached(zonename, sd)) if (!B.getSOAUncached(zonename, sd))
return; return;
string soa_edit_kind; string soa_edit_kind;
di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind); di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
DNSResourceRecord rr; DNSResourceRecord rr;
rr.qname = sd.qname; if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
rr.content = serializeSOAData(sd);
rr.qtype = "SOA";
rr.domain_id = sd.domain_id;
rr.auth = 1;
rr.ttl = sd.ttl;
if (increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind)) {
// fixup dots after serializeSOAData/increaseSOARecord
rr.content = makeBackendRecordContent(rr.qtype, rr.content);
if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResou rceRecord>(1, rr))) { if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResou rceRecord>(1, rr))) {
throw ApiException("Hosting backend does not support editing records." ); throw ApiException("Hosting backend does not support editing records." );
} }
} }
} }
} }
if (!document["master_tsig_key_ids"].is_null()) {
vector<string> metadata;
DNSName keyAlgo;
string keyContent;
for(auto value : document["master_tsig_key_ids"].array_items()) {
auto keyname(apiZoneIdToName(value.string_value()));
B.getTSIGKey(keyname, &keyAlgo, &keyContent);
if (keyAlgo.empty() || keyContent.empty()) {
throw ApiException("A TSIG key with the name '"+keyname.toLogString()+"'
does not exist");
}
metadata.push_back(keyname.toString());
}
if (!di.backend->setDomainMetadata(zonename, "TSIG-ALLOW-AXFR", metadata)) {
throw HttpInternalServerErrorException("Unable to set new TSIG master keys
for zone '" + zonename.toLogString() + "'");
}
}
if (!document["slave_tsig_key_ids"].is_null()) {
vector<string> metadata;
DNSName keyAlgo;
string keyContent;
for(auto value : document["slave_tsig_key_ids"].array_items()) {
auto keyname(apiZoneIdToName(value.string_value()));
B.getTSIGKey(keyname, &keyAlgo, &keyContent);
if (keyAlgo.empty() || keyContent.empty()) {
throw ApiException("A TSIG key with the name '"+keyname.toLogString()+"'
does not exist");
}
metadata.push_back(keyname.toString());
}
if (!di.backend->setDomainMetadata(zonename, "AXFR-MASTER-TSIG", metadata))
{
throw HttpInternalServerErrorException("Unable to set new TSIG slave keys
for zone '" + zonename.toLogString() + "'");
}
}
} }
static bool isValidMetadataKind(const string& kind, bool readonly) { static bool isValidMetadataKind(const string& kind, bool readonly) {
static vector<string> builtinOptions { static vector<string> builtinOptions {
"ALLOW-AXFR-FROM", "ALLOW-AXFR-FROM",
"AXFR-SOURCE", "AXFR-SOURCE",
"ALLOW-DNSUPDATE-FROM", "ALLOW-DNSUPDATE-FROM",
"TSIG-ALLOW-DNSUPDATE", "TSIG-ALLOW-DNSUPDATE",
"FORWARD-DNSUPDATE", "FORWARD-DNSUPDATE",
"SOA-EDIT-DNSUPDATE", "SOA-EDIT-DNSUPDATE",
skipping to change at line 748 skipping to change at line 836
"PUBLISH-CDNSKEY", "PUBLISH-CDNSKEY",
"PUBLISH-CDS", "PUBLISH-CDS",
"SOA-EDIT", "SOA-EDIT",
"TSIG-ALLOW-AXFR", "TSIG-ALLOW-AXFR",
"TSIG-ALLOW-DNSUPDATE" "TSIG-ALLOW-DNSUPDATE"
}; };
// the following options do not allow modifications via API // the following options do not allow modifications via API
static vector<string> protectedOptions { static vector<string> protectedOptions {
"API-RECTIFY", "API-RECTIFY",
"AXFR-MASTER-TSIG",
"NSEC3NARROW", "NSEC3NARROW",
"NSEC3PARAM", "NSEC3PARAM",
"PRESIGNED", "PRESIGNED",
"LUA-AXFR-SCRIPT" "LUA-AXFR-SCRIPT",
"TSIG-ALLOW-AXFR"
}; };
if (kind.find("X-") == 0) if (kind.find("X-") == 0)
return true; return true;
bool found = false; bool found = false;
for (const string& s : builtinOptions) { for (const string& s : builtinOptions) {
if (kind == s) { if (kind == s) {
for (const string& s2 : protectedOptions) { for (const string& s2 : protectedOptions) {
skipping to change at line 778 skipping to change at line 868
} }
return found; return found;
} }
static void apiZoneMetadata(HttpRequest* req, HttpResponse *resp) { static void apiZoneMetadata(HttpRequest* req, HttpResponse *resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
UeberBackend B; UeberBackend B;
DomainInfo di; DomainInfo di;
if (!B.getDomainInfo(zonename, di)) if (!B.getDomainInfo(zonename, di)) {
throw ApiException("Could not find domain '"+zonename.toString()+"'"); throw HttpNotFoundException();
}
if (req->method == "GET") { if (req->method == "GET") {
map<string, vector<string> > md; map<string, vector<string> > md;
Json::array document; Json::array document;
if (!B.getAllDomainMetadata(zonename, md)) if (!B.getAllDomainMetadata(zonename, md))
throw HttpNotFoundException(); throw HttpNotFoundException();
for (const auto& i : md) { for (const auto& i : md) {
Json::array entries; Json::array entries;
skipping to change at line 803 skipping to change at line 894
Json::object key { Json::object key {
{ "type", "Metadata" }, { "type", "Metadata" },
{ "kind", i.first }, { "kind", i.first },
{ "metadata", entries } { "metadata", entries }
}; };
document.push_back(key); document.push_back(key);
} }
resp->setBody(document); resp->setBody(document);
} else if (req->method == "POST" && !::arg().mustDo("api-readonly")) { } else if (req->method == "POST") {
auto document = req->json(); auto document = req->json();
string kind; string kind;
vector<string> entries; vector<string> entries;
try { try {
kind = stringFromJson(document, "kind"); kind = stringFromJson(document, "kind");
} catch (JsonException) { } catch (const JsonException&) {
throw ApiException("kind is not specified or not a string"); throw ApiException("kind is not specified or not a string");
} }
if (!isValidMetadataKind(kind, false)) if (!isValidMetadataKind(kind, false))
throw ApiException("Unsupported metadata kind '" + kind + "'"); throw ApiException("Unsupported metadata kind '" + kind + "'");
vector<string> vecMetadata; vector<string> vecMetadata;
if (!B.getDomainMetadata(zonename, kind, vecMetadata)) if (!B.getDomainMetadata(zonename, kind, vecMetadata))
throw ApiException("Could not retrieve metadata entries for domain '" + throw ApiException("Could not retrieve metadata entries for domain '" +
skipping to change at line 862 skipping to change at line 953
resp->setBody(key); resp->setBody(key);
} else } else
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
} }
static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) { static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
UeberBackend B; UeberBackend B;
DomainInfo di; DomainInfo di;
if (!B.getDomainInfo(zonename, di)) if (!B.getDomainInfo(zonename, di)) {
throw ApiException("Could not find domain '"+zonename.toString()+"'"); throw HttpNotFoundException();
}
string kind = req->parameters["kind"]; string kind = req->parameters["kind"];
if (req->method == "GET") { if (req->method == "GET") {
vector<string> metadata; vector<string> metadata;
Json::object document; Json::object document;
Json::array entries; Json::array entries;
if (!B.getDomainMetadata(zonename, kind, metadata)) if (!B.getDomainMetadata(zonename, kind, metadata))
throw HttpNotFoundException(); throw HttpNotFoundException();
skipping to change at line 885 skipping to change at line 977
throw ApiException("Unsupported metadata kind '" + kind + "'"); throw ApiException("Unsupported metadata kind '" + kind + "'");
document["type"] = "Metadata"; document["type"] = "Metadata";
document["kind"] = kind; document["kind"] = kind;
for (const string& i : metadata) for (const string& i : metadata)
entries.push_back(i); entries.push_back(i);
document["metadata"] = entries; document["metadata"] = entries;
resp->setBody(document); resp->setBody(document);
} else if (req->method == "PUT" && !::arg().mustDo("api-readonly")) { } else if (req->method == "PUT") {
auto document = req->json(); auto document = req->json();
if (!isValidMetadataKind(kind, false)) if (!isValidMetadataKind(kind, false))
throw ApiException("Unsupported metadata kind '" + kind + "'"); throw ApiException("Unsupported metadata kind '" + kind + "'");
vector<string> vecMetadata; vector<string> vecMetadata;
auto& metadata = document["metadata"]; auto& metadata = document["metadata"];
if (!metadata.is_array()) if (!metadata.is_array())
throw ApiException("metadata is not specified or not an array"); throw ApiException("metadata is not specified or not an array");
skipping to change at line 912 skipping to change at line 1004
if (!B.setDomainMetadata(zonename, kind, vecMetadata)) if (!B.setDomainMetadata(zonename, kind, vecMetadata))
throw ApiException("Could not update metadata entries for domain '" + zone name.toString() + "'"); throw ApiException("Could not update metadata entries for domain '" + zone name.toString() + "'");
Json::object key { Json::object key {
{ "type", "Metadata" }, { "type", "Metadata" },
{ "kind", kind }, { "kind", kind },
{ "metadata", metadata } { "metadata", metadata }
}; };
resp->setBody(key); resp->setBody(key);
} else if (req->method == "DELETE" && !::arg().mustDo("api-readonly")) { } else if (req->method == "DELETE") {
if (!isValidMetadataKind(kind, false)) if (!isValidMetadataKind(kind, false))
throw ApiException("Unsupported metadata kind '" + kind + "'"); throw ApiException("Unsupported metadata kind '" + kind + "'");
vector<string> md; // an empty vector will do it vector<string> md; // an empty vector will do it
if (!B.setDomainMetadata(zonename, kind, md)) if (!B.setDomainMetadata(zonename, kind, md))
throw ApiException("Could not delete metadata for domain '" + zonename.toS tring() + "' (" + kind + ")"); throw ApiException("Could not delete metadata for domain '" + zonename.toS tring() + "' (" + kind + ")");
} else } else
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
} }
// Throws 404 if the key with inquireKeyId does not exist
static void apiZoneCryptoKeysCheckKeyExists(DNSName zonename, int inquireKeyId,
DNSSECKeeper *dk) {
DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false);
bool found = false;
for(const auto& value : keyset) {
if (value.second.id == (unsigned) inquireKeyId) {
found = true;
break;
}
}
if (!found) {
throw HttpNotFoundException();
}
}
static void apiZoneCryptokeysGET(DNSName zonename, int inquireKeyId, HttpRespons e *resp, DNSSECKeeper *dk) { static void apiZoneCryptokeysGET(DNSName zonename, int inquireKeyId, HttpRespons e *resp, DNSSECKeeper *dk) {
DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false); DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false);
bool inquireSingleKey = inquireKeyId >= 0; bool inquireSingleKey = inquireKeyId >= 0;
Json::array doc; Json::array doc;
for(const auto& value : keyset) { for(const auto& value : keyset) {
if (inquireSingleKey && (unsigned)inquireKeyId != value.second.id) { if (inquireSingleKey && (unsigned)inquireKeyId != value.second.id) {
continue; continue;
} }
skipping to change at line 982 skipping to change at line 1089
} }
resp->setBody(doc); resp->setBody(doc);
} }
/* /*
* This method handles DELETE requests for URL /api/v1/servers/:server_id/zones/ :zone_name/cryptokeys/:cryptokey_id . * This method handles DELETE requests for URL /api/v1/servers/:server_id/zones/ :zone_name/cryptokeys/:cryptokey_id .
* It deletes a key from :zone_name specified by :cryptokey_id. * It deletes a key from :zone_name specified by :cryptokey_id.
* Server Answers: * Server Answers:
* Case 1: the backend returns true on removal. This means the key is gone. * Case 1: the backend returns true on removal. This means the key is gone.
* The server returns 200 OK, no body. * The server returns 204 No Content, no body.
* Case 2: the backend returns false on removal. An error occurred. * Case 2: the backend returns false on removal. An error occurred.
* The sever returns 422 Unprocessable Entity with message "Could not DELET * The server returns 422 Unprocessable Entity with message "Could not DELE
E :cryptokey_id". TE :cryptokey_id".
* Case 3: the key or zone does not exist.
* The server returns 404 Not Found
* */ * */
static void apiZoneCryptokeysDELETE(DNSName zonename, int inquireKeyId, HttpRequ est *req, HttpResponse *resp, DNSSECKeeper *dk) { static void apiZoneCryptokeysDELETE(DNSName zonename, int inquireKeyId, HttpRequ est *req, HttpResponse *resp, DNSSECKeeper *dk) {
if (dk->removeKey(zonename, inquireKeyId)) { if (dk->removeKey(zonename, inquireKeyId)) {
resp->body = ""; resp->body = "";
resp->status = 200; resp->status = 204;
} else { } else {
resp->setErrorResult("Could not DELETE " + req->parameters["key_id"], 422); resp->setErrorResult("Could not DELETE " + req->parameters["key_id"], 422);
} }
} }
/* /*
* This method adds a key to a zone by generate it or content parameter. * This method adds a key to a zone by generate it or content parameter.
* Parameter: * Parameter:
* { * {
* "privatekey" : "key The format used is compatible with BIND and NSD/LDNS" <s tring> * "privatekey" : "key The format used is compatible with BIND and NSD/LDNS" <s tring>
skipping to change at line 1092 skipping to change at line 1201
if (insertedId < 0) if (insertedId < 0)
throw ApiException("Adding key failed, perhaps DNSSEC not enabled in confi guration?"); throw ApiException("Adding key failed, perhaps DNSSEC not enabled in confi guration?");
} else if (document["bits"].is_null() && document["algorithm"].is_null()) { } else if (document["bits"].is_null() && document["algorithm"].is_null()) {
auto keyData = stringFromJson(document, privatekey_fieldname); auto keyData = stringFromJson(document, privatekey_fieldname);
DNSKEYRecordContent dkrc; DNSKEYRecordContent dkrc;
DNSSECPrivateKey dpk; DNSSECPrivateKey dpk;
try { try {
shared_ptr<DNSCryptoKeyEngine> dke(DNSCryptoKeyEngine::makeFromISCString(d krc, keyData)); shared_ptr<DNSCryptoKeyEngine> dke(DNSCryptoKeyEngine::makeFromISCString(d krc, keyData));
dpk.d_algorithm = dkrc.d_algorithm; dpk.d_algorithm = dkrc.d_algorithm;
// TODO remove in 4.2.0 // TODO remove in 4.2.0
if(dpk.d_algorithm == 7) if(dpk.d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1)
dpk.d_algorithm = 5; dpk.d_algorithm = DNSSECKeeper::RSASHA1;
if (keyOrZone) if (keyOrZone)
dpk.d_flags = 257; dpk.d_flags = 257;
else else
dpk.d_flags = 256; dpk.d_flags = 256;
dpk.setKey(dke); dpk.setKey(dke);
} }
catch (std::runtime_error& error) { catch (std::runtime_error& error) {
throw ApiException("Key could not be parsed. Make sure your key format is correct."); throw ApiException("Key could not be parsed. Make sure your key format is correct.");
skipping to change at line 1163 skipping to change at line 1272
* This method chooses the right functionality for the request. It also checks f or a cryptokey_id which has to be passed * This method chooses the right functionality for the request. It also checks f or a cryptokey_id which has to be passed
* by URL /api/v1/servers/:server_id/zones/:zone_name/cryptokeys/:cryptokey_id . * by URL /api/v1/servers/:server_id/zones/:zone_name/cryptokeys/:cryptokey_id .
* If the the HTTP-request-method isn't supported, the function returns a respon se with the 405 code (method not allowed). * If the the HTTP-request-method isn't supported, the function returns a respon se with the 405 code (method not allowed).
* */ * */
static void apiZoneCryptokeys(HttpRequest *req, HttpResponse *resp) { static void apiZoneCryptokeys(HttpRequest *req, HttpResponse *resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
UeberBackend B; UeberBackend B;
DNSSECKeeper dk(&B); DNSSECKeeper dk(&B);
DomainInfo di; DomainInfo di;
if (!B.getDomainInfo(zonename, di)) if (!B.getDomainInfo(zonename, di)) {
throw HttpBadRequestException(); throw HttpNotFoundException();
}
int inquireKeyId = -1; int inquireKeyId = -1;
if (req->parameters.count("key_id")) { if (req->parameters.count("key_id")) {
inquireKeyId = std::stoi(req->parameters["key_id"]); inquireKeyId = std::stoi(req->parameters["key_id"]);
apiZoneCryptoKeysCheckKeyExists(zonename, inquireKeyId, &dk);
} }
if (req->method == "GET") { if (req->method == "GET") {
apiZoneCryptokeysGET(zonename, inquireKeyId, resp, &dk); apiZoneCryptokeysGET(zonename, inquireKeyId, resp, &dk);
} else if (req->method == "DELETE" && !::arg().mustDo("api-readonly")) { } else if (req->method == "DELETE") {
if (inquireKeyId == -1) if (inquireKeyId == -1)
throw HttpBadRequestException(); throw HttpBadRequestException();
apiZoneCryptokeysDELETE(zonename, inquireKeyId, req, resp, &dk); apiZoneCryptokeysDELETE(zonename, inquireKeyId, req, resp, &dk);
} else if (req->method == "POST" && !::arg().mustDo("api-readonly")) { } else if (req->method == "POST") {
apiZoneCryptokeysPOST(zonename, req, resp, &dk); apiZoneCryptokeysPOST(zonename, req, resp, &dk);
} else if (req->method == "PUT" && !::arg().mustDo("api-readonly")) { } else if (req->method == "PUT") {
if (inquireKeyId == -1) if (inquireKeyId == -1)
throw HttpBadRequestException(); throw HttpBadRequestException();
apiZoneCryptokeysPUT(zonename, inquireKeyId, req, resp, &dk); apiZoneCryptokeysPUT(zonename, inquireKeyId, req, resp, &dk);
} else { } else {
throw HttpMethodNotAllowedException(); //Returns method not allowed throw HttpMethodNotAllowedException(); //Returns method not allowed
} }
} }
static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResou rceRecord>& new_records, DNSName zonename) { static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResou rceRecord>& new_records, DNSName zonename) {
DNSResourceRecord rr; DNSResourceRecord rr;
skipping to change at line 1215 skipping to change at line 1326
validateGatheredRRType(rr); validateGatheredRRType(rr);
new_records.push_back(rr); new_records.push_back(rr);
} }
} }
catch(std::exception& ae) { catch(std::exception& ae) {
throw ApiException("An error occurred while parsing the zonedata: "+string(a e.what())); throw ApiException("An error occurred while parsing the zonedata: "+string(a e.what()));
} }
} }
/** 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. * 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(), sort(records.begin(), records.end(),
[](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool { [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
/* we need _strict_ weak ordering */ /* 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); 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; DNSResourceRecord previous;
for(const auto& rec : records) { for(const auto& rec : records) {
if (previous.qtype == rec.qtype && previous.qname == rec.qname && previous.c if (previous.qname == rec.qname) {
ontent == rec.content) { if (previous.qtype == rec.qtype) {
throw ApiException("Duplicate record in RRset " + rec.qname.toString() + " if (onlyOneEntryTypes.count(rec.qtype.getCode()) != 0) {
IN " + rec.qtype.getName() + " with content \"" + rec.content + "\""); throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.getN
ame()+" 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 || exclusiv
eEntryTypes.count(previous.qtype.getCode()) != 0) {
throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.getNam
e()+": 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; previous = rec;
} }
} }
static void checkTSIGKey(UeberBackend& B, const DNSName& keyname, const DNSName&
algo, const string& content) {
DNSName algoFromDB;
string contentFromDB;
B.getTSIGKey(keyname, &algoFromDB, &contentFromDB);
if (!contentFromDB.empty() || !algoFromDB.empty()) {
throw HttpConflictException("A TSIG key with the name '"+keyname.toLogString
()+"' already exists");
}
TSIGHashEnum the;
if (!getTSIGHashEnum(algo, the)) {
throw ApiException("Unknown TSIG algorithm: " + algo.toLogString());
}
string b64out;
if (B64Decode(content, b64out) == -1) {
throw ApiException("TSIG content '" + content + "' cannot be base64-decoded"
);
}
}
static Json::object makeJSONTSIGKey(const DNSName& keyname, const DNSName& algo,
const string& content) {
Json::object tsigkey = {
{ "name", keyname.toStringNoDot() },
{ "id", apiZoneNameToId(keyname) },
{ "algorithm", algo.toStringNoDot() },
{ "key", content },
{ "type", "TSIGKey" }
};
return tsigkey;
}
static Json::object makeJSONTSIGKey(const struct TSIGKey& key, bool doContent=tr
ue) {
return makeJSONTSIGKey(key.name, key.algorithm, doContent ? key.key : "");
}
static void apiServerTSIGKeys(HttpRequest* req, HttpResponse* resp) {
UeberBackend B;
if (req->method == "GET") {
vector<struct TSIGKey> keys;
if (!B.getTSIGKeys(keys)) {
throw HttpInternalServerErrorException("Unable to retrieve TSIG keys");
}
Json::array doc;
for(const auto &key : keys) {
doc.push_back(makeJSONTSIGKey(key, false));
}
resp->setBody(doc);
} else if (req->method == "POST") {
auto document = req->json();
DNSName keyname(stringFromJson(document, "name"));
DNSName algo(stringFromJson(document, "algorithm"));
string content = document["key"].string_value();
if (content.empty()) {
try {
content = makeTSIGKey(algo);
} catch (const PDNSException& e) {
throw HttpBadRequestException(e.reason);
}
}
// Will throw an ApiException or HttpConflictException on error
checkTSIGKey(B, keyname, algo, content);
if(!B.setTSIGKey(keyname, algo, content)) {
throw HttpInternalServerErrorException("Unable to add TSIG key");
}
resp->status = 201;
resp->setBody(makeJSONTSIGKey(keyname, algo, content));
} else {
throw HttpMethodNotAllowedException();
}
}
static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) {
UeberBackend B;
DNSName keyname = apiZoneIdToName(req->parameters["id"]);
DNSName algo;
string content;
if (!B.getTSIGKey(keyname, &algo, &content)) {
throw HttpNotFoundException("TSIG key with name '"+keyname.toLogString()+"'
not found");
}
struct TSIGKey tsk;
tsk.name = keyname;
tsk.algorithm = algo;
tsk.key = content;
if (req->method == "GET") {
resp->setBody(makeJSONTSIGKey(tsk));
} else if (req->method == "PUT") {
json11::Json document;
if (!req->body.empty()) {
document = req->json();
}
if (document["name"].is_string()) {
tsk.name = DNSName(document["name"].string_value());
}
if (document["algorithm"].is_string()) {
tsk.algorithm = DNSName(document["algorithm"].string_value());
TSIGHashEnum the;
if (!getTSIGHashEnum(tsk.algorithm, the)) {
throw ApiException("Unknown TSIG algorithm: " + tsk.algorithm.toLogStrin
g());
}
}
if (document["key"].is_string()) {
string new_content = document["key"].string_value();
string decoded;
if (B64Decode(new_content, decoded) == -1) {
throw ApiException("Can not base64 decode key content '" + new_content +
"'");
}
tsk.key = new_content;
}
if (!B.setTSIGKey(tsk.name, tsk.algorithm, tsk.key)) {
throw HttpInternalServerErrorException("Unable to save TSIG Key");
}
if (tsk.name != keyname) {
// Remove the old key
if (!B.deleteTSIGKey(keyname)) {
throw HttpInternalServerErrorException("Unable to remove TSIG key '" + k
eyname.toStringNoDot() + "'");
}
}
resp->setBody(makeJSONTSIGKey(tsk));
} else if (req->method == "DELETE") {
if (!B.deleteTSIGKey(keyname)) {
throw HttpInternalServerErrorException("Unable to remove TSIG key '" + key
name.toStringNoDot() + "'");
} else {
resp->body = "";
resp->status = 204;
}
} else {
throw HttpMethodNotAllowedException();
}
}
static void apiServerZones(HttpRequest* req, HttpResponse* resp) { static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
UeberBackend B; UeberBackend B;
DNSSECKeeper dk(&B); DNSSECKeeper dk(&B);
if (req->method == "POST" && !::arg().mustDo("api-readonly")) { if (req->method == "POST") {
DomainInfo di; DomainInfo di;
auto document = req->json(); auto document = req->json();
DNSName zonename = apiNameToDNSName(stringFromJson(document, "name")); DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
apiCheckNameAllowedCharacters(zonename.toString()); apiCheckNameAllowedCharacters(zonename.toString());
zonename.makeUsLowerCase(); zonename.makeUsLowerCase();
bool exists = B.getDomainInfo(zonename, di); bool exists = B.getDomainInfo(zonename, di);
if(exists) if(exists)
throw ApiException("Domain '"+zonename.toString()+"' already exists"); throw HttpConflictException();
// validate 'kind' is set // validate 'kind' is set
DomainInfo::DomainKind zonekind = DomainInfo::stringToKind(stringFromJson(do cument, "kind")); DomainInfo::DomainKind zonekind = DomainInfo::stringToKind(stringFromJson(do cument, "kind"));
string zonestring = document["zone"].string_value(); string zonestring = document["zone"].string_value();
auto rrsets = document["rrsets"]; auto rrsets = document["rrsets"];
if (rrsets.is_array() && zonestring != "") if (rrsets.is_array() && zonestring != "")
throw ApiException("You cannot give rrsets AND zone data as text"); throw ApiException("You cannot give rrsets AND zone data as text");
auto nameservers = document["nameservers"]; auto nameservers = document["nameservers"];
skipping to change at line 1287 skipping to change at line 1561
for (const auto& rrset : rrsets.array_items()) { for (const auto& rrset : rrsets.array_items()) {
DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name")); DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
apiCheckQNameAllowedCharacters(qname.toString()); apiCheckQNameAllowedCharacters(qname.toString());
QType qtype; QType qtype;
qtype = stringFromJson(rrset, "type"); qtype = stringFromJson(rrset, "type");
if (qtype.getCode() == 0) { if (qtype.getCode() == 0) {
throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrs et, "type")+": unknown type given"); throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrs et, "type")+": unknown type given");
} }
if (rrset["records"].is_array()) { if (rrset["records"].is_array()) {
int ttl = intFromJson(rrset, "ttl"); int ttl = intFromJson(rrset, "ttl");
gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs); gatherRecords(req->logprefix, rrset, qname, qtype, ttl, new_records, n ew_ptrs);
} }
if (rrset["comments"].is_array()) { if (rrset["comments"].is_array()) {
gatherComments(rrset, qname, qtype, new_comments); gatherComments(rrset, qname, qtype, new_comments);
} }
} }
} else if (zonestring != "") { } else if (zonestring != "") {
gatherRecordsFromZone(zonestring, new_records, zonename); gatherRecordsFromZone(zonestring, new_records, zonename);
} }
for(auto& rr : new_records) { for(auto& rr : new_records) {
rr.qname.makeUsLowerCase(); rr.qname.makeUsLowerCase();
if (!rr.qname.isPartOf(zonename) && rr.qname != zonename) if (!rr.qname.isPartOf(zonename) && rr.qname != zonename)
throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName( )+": Name is out of zone"); throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName( )+": Name is out of zone");
apiCheckQNameAllowedCharacters(rr.qname.toString()); apiCheckQNameAllowedCharacters(rr.qname.toString());
if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) { if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
have_soa = true; have_soa = true;
increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind); increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
// fixup dots after serializeSOAData/increaseSOARecord
rr.content = makeBackendRecordContent(rr.qtype, rr.content);
} }
if (rr.qtype.getCode() == QType::NS && rr.qname==zonename) { if (rr.qtype.getCode() == QType::NS && rr.qname==zonename) {
have_zone_ns = true; have_zone_ns = true;
} }
} }
// synthesize RRs as needed // synthesize RRs as needed
DNSResourceRecord autorr; DNSResourceRecord autorr;
autorr.qname = zonename; autorr.qname = zonename;
autorr.auth = 1; autorr.auth = 1;
autorr.ttl = ::arg().asNum("default-ttl"); autorr.ttl = ::arg().asNum("default-ttl");
if (!have_soa && zonekind != DomainInfo::Slave) { if (!have_soa && zonekind != DomainInfo::Slave) {
// synthesize a SOA record so the zone "really" exists // synthesize a SOA record so the zone "really" exists
string soa = (boost::format("%s %s %lu") string soa = (boost::format("%s %s %ul")
% ::arg()["default-soa-name"] % ::arg()["default-soa-name"]
% (::arg().isEmpty("default-soa-mail") ? (DNSName("hostmaster.") + zonen ame).toString() : ::arg()["default-soa-mail"]) % (::arg().isEmpty("default-soa-mail") ? (DNSName("hostmaster.") + zonen ame).toString() : ::arg()["default-soa-mail"])
% document["serial"].int_value() % document["serial"].int_value()
).str(); ).str();
SOAData sd; SOAData sd;
fillSOAData(soa, sd); // fills out default values for us fillSOAData(soa, sd); // fills out default values for us
autorr.qtype = "SOA"; autorr.qtype = QType::SOA;
autorr.content = serializeSOAData(sd); autorr.content = makeSOAContent(sd)->getZoneRepresentation(true);
increaseSOARecord(autorr, soa_edit_api_kind, soa_edit_kind); increaseSOARecord(autorr, soa_edit_api_kind, soa_edit_kind);
// fixup dots after serializeSOAData/increaseSOARecord
autorr.content = makeBackendRecordContent(autorr.qtype, autorr.content);
new_records.push_back(autorr); new_records.push_back(autorr);
} }
// create NS records if nameservers are given // create NS records if nameservers are given
for (auto value : nameservers.array_items()) { for (auto value : nameservers.array_items()) {
string nameserver = value.string_value(); string nameserver = value.string_value();
if (nameserver.empty()) if (nameserver.empty())
throw ApiException("Nameservers must be non-empty strings"); throw ApiException("Nameservers must be non-empty strings");
if (!isCanonical(nameserver)) if (!isCanonical(nameserver))
throw ApiException("Nameserver is not canonical: '" + nameserver + "'"); throw ApiException("Nameserver is not canonical: '" + nameserver + "'");
try { try {
// ensure the name parses // ensure the name parses
autorr.content = DNSName(nameserver).toStringRootDot(); autorr.content = DNSName(nameserver).toStringRootDot();
} catch (...) { } catch (...) {
throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "' "); throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "' ");
} }
autorr.qtype = "NS"; autorr.qtype = QType::NS;
new_records.push_back(autorr); new_records.push_back(autorr);
if (have_zone_ns) { if (have_zone_ns) {
throw ApiException("Nameservers list MUST NOT be mixed with zone-level N S in rrsets"); throw ApiException("Nameservers list MUST NOT be mixed with zone-level N S in rrsets");
} }
} }
checkDuplicateRecords(new_records); checkNewRecords(new_records);
if (boolFromJson(document, "dnssec", false)) { if (boolFromJson(document, "dnssec", false)) {
checkDefaultDNSSECAlgos(); checkDefaultDNSSECAlgos();
if(document["nsec3param"].string_value().length() > 0) { if(document["nsec3param"].string_value().length() > 0) {
NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value()); NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value());
string error_msg = ""; string error_msg = "";
if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) { if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) {
throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString ()+"' are invalid. " + error_msg); throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString ()+"' are invalid. " + error_msg);
} }
} }
} }
// no going back after this // no going back after this
if(!B.createDomain(zonename)) if(!B.createDomain(zonename))
throw ApiException("Creating domain '"+zonename.toString()+"' failed"); throw ApiException("Creating domain '"+zonename.toString()+"' failed");
if(!B.getDomainInfo(zonename, di)) if(!B.getDomainInfo(zonename, di))
throw ApiException("Creating domain '"+zonename.toString()+"' failed: look up of domain ID failed"); throw ApiException("Creating domain '"+zonename.toString()+"' failed: look up of domain ID failed");
di.backend->startTransaction(zonename, di.id);
// updateDomainSettingsFromDocument does NOT fill out the default we've esta blished above. // updateDomainSettingsFromDocument does NOT fill out the default we've esta blished above.
if (!soa_edit_api_kind.empty()) { if (!soa_edit_api_kind.empty()) {
di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_ki nd); di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_ki nd);
} }
di.backend->startTransaction(zonename, di.id);
for(auto rr : new_records) { for(auto rr : new_records) {
rr.domain_id = di.id; rr.domain_id = di.id;
di.backend->feedRecord(rr, DNSName()); di.backend->feedRecord(rr, DNSName());
} }
for(Comment& c : new_comments) { for(Comment& c : new_comments) {
c.domain_id = di.id; c.domain_id = di.id;
di.backend->feedComment(c); di.backend->feedComment(c);
} }
updateDomainSettingsFromDocument(B, di, zonename, document); updateDomainSettingsFromDocument(B, di, zonename, document, false);
di.backend->commitTransaction(); di.backend->commitTransaction();
storeChangedPTRs(B, new_ptrs); storeChangedPTRs(B, new_ptrs);
fillZone(zonename, resp, shouldDoRRSets(req)); fillZone(zonename, resp, shouldDoRRSets(req));
resp->status = 201; resp->status = 201;
return; return;
} }
if(req->method != "GET") if(req->method != "GET")
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
vector<DomainInfo> domains; vector<DomainInfo> domains;
B.getAllDomains(&domains, true); // incl. disabled
if (req->getvars.count("zone")) {
string zone = req->getvars["zone"];
apiCheckNameAllowedCharacters(zone);
DNSName zonename = apiNameToDNSName(zone);
zonename.makeUsLowerCase();
DomainInfo di;
if (B.getDomainInfo(zonename, di)) {
domains.push_back(di);
}
} else {
try {
B.getAllDomains(&domains, true); // incl. disabled
} catch(const PDNSException &e) {
throw HttpInternalServerErrorException("Could not retrieve all domain info
rmation: " + e.reason);
}
}
Json::array doc; Json::array doc;
for(const DomainInfo& di : domains) { for(const DomainInfo& di : domains) {
doc.push_back(getZoneInfo(di, &dk)); doc.push_back(getZoneInfo(di, &dk));
} }
resp->setBody(doc); resp->setBody(doc);
} }
static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
if(req->method == "PUT" && !::arg().mustDo("api-readonly")) { UeberBackend B;
DomainInfo di;
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") {
// update domain settings // update domain settings
UeberBackend B;
DomainInfo di;
if(!B.getDomainInfo(zonename, di))
throw ApiException("Could not find domain '"+zonename.toString()+"'");
updateDomainSettingsFromDocument(B, di, zonename, req->json()); di.backend->startTransaction(zonename, -1);
updateDomainSettingsFromDocument(B, di, zonename, req->json(), false);
di.backend->commitTransaction();
resp->body = ""; resp->body = "";
resp->status = 204; // No Content, but indicate success resp->status = 204; // No Content, but indicate success
return; return;
} }
else if(req->method == "DELETE" && !::arg().mustDo("api-readonly")) { else if(req->method == "DELETE") {
// delete domain // delete domain
UeberBackend B;
DomainInfo di;
if(!B.getDomainInfo(zonename, di))
throw ApiException("Could not find domain '"+zonename.toString()+"'");
if(!di.backend->deleteDomain(zonename)) if(!di.backend->deleteDomain(zonename))
throw ApiException("Deleting domain '"+zonename.toString()+"' failed: back end delete failed/unsupported"); throw ApiException("Deleting domain '"+zonename.toString()+"' failed: back end delete failed/unsupported");
// clear caches
DNSSECKeeper dk(&B);
dk.clearCaches(zonename);
purgeAuthCaches(zonename.toString() + "$");
// empty body on success // empty body on success
resp->body = ""; resp->body = "";
resp->status = 204; // No Content: declare that the zone is gone now resp->status = 204; // No Content: declare that the zone is gone now
return; return;
} else if (req->method == "PATCH" && !::arg().mustDo("api-readonly")) { } else if (req->method == "PATCH") {
patchZone(req, resp); patchZone(req, resp);
return; return;
} else if (req->method == "GET") { } else if (req->method == "GET") {
fillZone(zonename, resp, shouldDoRRSets(req)); fillZone(zonename, resp, shouldDoRRSets(req));
return; return;
} }
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
} }
static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) { static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
if(req->method != "GET") if(req->method != "GET")
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
ostringstream ss; ostringstream ss;
UeberBackend B; UeberBackend B;
DomainInfo di; DomainInfo di;
if(!B.getDomainInfo(zonename, di)) if (!B.getDomainInfo(zonename, di)) {
throw ApiException("Could not find domain '"+zonename.toString()+"'"); throw HttpNotFoundException();
}
DNSResourceRecord rr; DNSResourceRecord rr;
SOAData sd; SOAData sd;
di.backend->list(zonename, di.id); di.backend->list(zonename, di.id);
while(di.backend->get(rr)) { while(di.backend->get(rr)) {
if (!rr.qtype.getCode()) if (!rr.qtype.getCode())
continue; // skip empty non-terminals continue; // skip empty non-terminals
ss << ss <<
rr.qname.toString() << "\t" << rr.qname.toString() << "\t" <<
rr.ttl << "\t" << rr.ttl << "\t" <<
"IN" << "\t" <<
rr.qtype.getName() << "\t" << rr.qtype.getName() << "\t" <<
makeApiRecordContent(rr.qtype, rr.content) << makeApiRecordContent(rr.qtype, rr.content) <<
endl; endl;
} }
if (req->accept_json) { if (req->accept_json) {
resp->setBody(Json::object { { "zone", ss.str() } }); resp->setBody(Json::object { { "zone", ss.str() } });
} else { } else {
resp->headers["Content-Type"] = "text/plain; charset=us-ascii"; resp->headers["Content-Type"] = "text/plain; charset=us-ascii";
resp->body = ss.str(); resp->body = ss.str();
} }
} }
static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) { static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
if(req->method != "PUT" || ::arg().mustDo("api-readonly")) if(req->method != "PUT")
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
UeberBackend B; UeberBackend B;
DomainInfo di; DomainInfo di;
if(!B.getDomainInfo(zonename, di)) if (!B.getDomainInfo(zonename, di)) {
throw ApiException("Could not find domain '"+zonename.toString()+"'"); throw HttpNotFoundException();
}
if(di.masters.empty()) if(di.masters.empty())
throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain ( or has no master defined)"); throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain ( or has no master defined)");
random_shuffle(di.masters.begin(), di.masters.end()); random_shuffle(di.masters.begin(), di.masters.end());
Communicator.addSuckRequest(zonename, di.masters.front()); Communicator.addSuckRequest(zonename, di.masters.front());
resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front()); resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front().toLogString());
} }
static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) { static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
if(req->method != "PUT" || ::arg().mustDo("api-readonly")) if(req->method != "PUT")
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
UeberBackend B; UeberBackend B;
DomainInfo di; DomainInfo di;
if(!B.getDomainInfo(zonename, di)) if (!B.getDomainInfo(zonename, di)) {
throw ApiException("Could not find domain '"+zonename.toString()+"'"); throw HttpNotFoundException();
}
if(!Communicator.notifyDomain(zonename)) if(!Communicator.notifyDomain(zonename, &B))
throw ApiException("Failed to add to the queue - see server log"); throw ApiException("Failed to add to the queue - see server log");
resp->setSuccessResult("Notification queued"); resp->setSuccessResult("Notification queued");
} }
static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) { static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) {
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
if(req->method != "PUT") if(req->method != "PUT")
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
UeberBackend B; UeberBackend B;
DomainInfo di; DomainInfo di;
if(!B.getDomainInfo(zonename, di)) if (!B.getDomainInfo(zonename, di)) {
throw ApiException("Could not find domain '"+zonename.toString()+"'"); throw HttpNotFoundException();
}
DNSSECKeeper dk(&B); DNSSECKeeper dk(&B);
if (!dk.isSecuredZone(zonename)) if (!dk.isSecuredZone(zonename))
throw ApiException("Zone '" + zonename.toString() + "' is not DNSSEC signed, not rectifying."); throw ApiException("Zone '" + zonename.toString() + "' is not DNSSEC signed, not rectifying.");
if (di.kind == DomainInfo::Slave) if (di.kind == DomainInfo::Slave)
throw ApiException("Zone '" + zonename.toString() + "' is a slave zone, not rectifying."); throw ApiException("Zone '" + zonename.toString() + "' is a slave zone, not rectifying.");
string error_msg = ""; string error_msg = "";
skipping to change at line 1608 skipping to change at line 1906
if (!B.getAuth(rr.qname, QType(QType::PTR), &sd, false)) if (!B.getAuth(rr.qname, QType(QType::PTR), &sd, false))
throw ApiException("Could not find domain for PTR '"+rr.qname.toString()+" ' requested for '"+rr.content+"' (while saving)"); throw ApiException("Could not find domain for PTR '"+rr.qname.toString()+" ' requested for '"+rr.content+"' (while saving)");
string soa_edit_api_kind; string soa_edit_api_kind;
string soa_edit_kind; string soa_edit_kind;
bool soa_changed = false; bool soa_changed = false;
DNSResourceRecord soarr; DNSResourceRecord soarr;
sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT-API", soa_edit_api_kind); sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT-API", soa_edit_api_kind);
sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT", soa_edit_kind); sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT", soa_edit_kind);
if (!soa_edit_api_kind.empty()) { if (!soa_edit_api_kind.empty()) {
soarr.qname = sd.qname; soa_changed = makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind,
soarr.content = serializeSOAData(sd); soarr);
soarr.qtype = "SOA";
soarr.domain_id = sd.domain_id;
soarr.auth = 1;
soarr.ttl = sd.ttl;
increaseSOARecord(soarr, soa_edit_api_kind, soa_edit_kind);
// fixup dots after serializeSOAData/increaseSOARecord
soarr.content = makeBackendRecordContent(soarr.qtype, soarr.content);
soa_changed = true;
} }
sd.db->startTransaction(sd.qname); sd.db->startTransaction(sd.qname);
if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourc eRecord>(1, rr))) { if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourc eRecord>(1, rr))) {
sd.db->abortTransaction(); sd.db->abortTransaction();
throw ApiException("PTR-Hosting backend for "+rr.qname.toString()+"/"+rr.q type.getName()+" does not support editing records."); throw ApiException("PTR-Hosting backend for "+rr.qname.toString()+"/"+rr.q type.getName()+" does not support editing records.");
} }
if (soa_changed) { if (soa_changed) {
sd.db->replaceRRSet(sd.domain_id, soarr.qname, soarr.qtype, vector<DNSReso urceRecord>(1, soarr)); sd.db->replaceRRSet(sd.domain_id, soarr.qname, soarr.qtype, vector<DNSReso urceRecord>(1, soarr));
skipping to change at line 1639 skipping to change at line 1928
sd.db->commitTransaction(); sd.db->commitTransaction();
purgeAuthCachesExact(rr.qname); purgeAuthCachesExact(rr.qname);
} }
} }
static void patchZone(HttpRequest* req, HttpResponse* resp) { static void patchZone(HttpRequest* req, HttpResponse* resp) {
UeberBackend B; UeberBackend B;
DomainInfo di; DomainInfo di;
DNSName zonename = apiZoneIdToName(req->parameters["id"]); DNSName zonename = apiZoneIdToName(req->parameters["id"]);
if (!B.getDomainInfo(zonename, di)) if (!B.getDomainInfo(zonename, di)) {
throw ApiException("Could not find domain '"+zonename.toString()+"'"); throw HttpNotFoundException();
}
vector<DNSResourceRecord> new_records; vector<DNSResourceRecord> new_records;
vector<Comment> new_comments; vector<Comment> new_comments;
vector<DNSResourceRecord> new_ptrs; vector<DNSResourceRecord> new_ptrs;
Json document = req->json(); Json document = req->json();
auto rrsets = document["rrsets"]; auto rrsets = document["rrsets"];
if (!rrsets.is_array()) if (!rrsets.is_array())
throw ApiException("No rrsets given in update request"); throw ApiException("No rrsets given in update request");
di.backend->startTransaction(zonename); di.backend->startTransaction(zonename);
try { try {
string soa_edit_api_kind; string soa_edit_api_kind;
string soa_edit_kind; string soa_edit_kind;
di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind ); di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind );
di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind); di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
bool soa_edit_done = false; bool soa_edit_done = false;
set<pair<DNSName, QType>> seen;
for (const auto& rrset : rrsets.array_items()) { for (const auto& rrset : rrsets.array_items()) {
string changetype = toUpper(stringFromJson(rrset, "changetype")); string changetype = toUpper(stringFromJson(rrset, "changetype"));
DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name")); DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
apiCheckQNameAllowedCharacters(qname.toString()); apiCheckQNameAllowedCharacters(qname.toString());
QType qtype; QType qtype;
qtype = stringFromJson(rrset, "type"); qtype = stringFromJson(rrset, "type");
if (qtype.getCode() == 0) { if (qtype.getCode() == 0) {
throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset , "type")+": unknown type given"); throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset , "type")+": unknown type given");
} }
if(seen.count({qname, qtype}))
{
throw ApiException("Duplicate RRset "+qname.toString()+" IN "+qtype.getN
ame());
}
seen.insert({qname, qtype});
if (changetype == "DELETE") { if (changetype == "DELETE") {
// delete all matching qname/qtype RRs (and, implicitly comments). // delete all matching qname/qtype RRs (and, implicitly comments).
if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRec ord>())) { if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRec ord>())) {
throw ApiException("Hosting backend does not support editing records." ); throw ApiException("Hosting backend does not support editing records." );
} }
} }
else if (changetype == "REPLACE") { else if (changetype == "REPLACE") {
// we only validate for REPLACE, as DELETE can be used to "fix" out of z one records. // we only validate for REPLACE, as DELETE can be used to "fix" out of z one records.
if (!qname.isPartOf(zonename) && qname != zonename) if (!qname.isPartOf(zonename) && qname != zonename)
throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Name is out of zone"); throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Name is out of zone");
skipping to change at line 1696 skipping to change at line 1994
throw ApiException("No change for RRset " + qname.toString() + " IN " + qtype.getName()); throw ApiException("No change for RRset " + qname.toString() + " IN " + qtype.getName());
} }
new_records.clear(); new_records.clear();
new_comments.clear(); new_comments.clear();
if (replace_records) { if (replace_records) {
// ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records. // 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"); int ttl = intFromJson(rrset, "ttl");
// new_ptrs is merged. // new_ptrs is merged.
gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs); gatherRecords(req->logprefix, rrset, qname, qtype, ttl, new_records, n ew_ptrs);
for(DNSResourceRecord& rr : new_records) { for(DNSResourceRecord& rr : new_records) {
rr.domain_id = di.id; rr.domain_id = di.id;
if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) { if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_ kind); soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_ kind);
rr.content = makeBackendRecordContent(rr.qtype, rr.content);
} }
} }
checkDuplicateRecords(new_records); checkNewRecords(new_records);
} }
if (replace_comments) { if (replace_comments) {
gatherComments(rrset, qname, qtype, new_comments); gatherComments(rrset, qname, qtype, new_comments);
for(Comment& c : new_comments) { for(Comment& c : new_comments) {
c.domain_id = di.id; c.domain_id = di.id;
} }
} }
if (replace_records) { if (replace_records) {
di.backend->lookup(QType(QType::ANY), qname); bool ent_present = false;
di.backend->lookup(QType(QType::ANY), qname, nullptr, di.id);
DNSResourceRecord rr; DNSResourceRecord rr;
while (di.backend->get(rr)) { while (di.backend->get(rr)) {
if (qtype.getCode() == QType::CNAME && rr.qtype.getCode() != QType:: if (rr.qtype.getCode() == QType::ENT) {
CNAME) { ent_present = true;
throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName( /* that's fine, we will override it */
)+": Conflicts with pre-existing non-CNAME RRset"); continue;
} else if (qtype.getCode() != QType::CNAME && rr.qtype.getCode() == }
QType::CNAME) { if (qtype.getCode() != rr.qtype.getCode()
throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName( && (exclusiveEntryTypes.count(qtype.getCode()) != 0
)+": Conflicts with pre-existing CNAME RRset"); || exclusiveEntryTypes.count(rr.qtype.getCode()) != 0)) {
throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName(
)+": Conflicts with pre-existing RRset");
} }
} }
if (!new_records.empty() && ent_present) {
QType qt_ent{0};
if (!di.backend->replaceRRSet(di.id, qname, qt_ent, new_records)) {
throw ApiException("Hosting backend does not support editing recor
ds.");
}
}
if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) { if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
throw ApiException("Hosting backend does not support editing records ."); throw ApiException("Hosting backend does not support editing records .");
} }
} }
if (replace_comments) { if (replace_comments) {
if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) { if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
throw ApiException("Hosting backend does not support editing comment s."); throw ApiException("Hosting backend does not support editing comment s.");
} }
} }
} }
else else
throw ApiException("Changetype not understood"); throw ApiException("Changetype not understood");
} }
// edit SOA (if needed) // edit SOA (if needed)
if (!soa_edit_api_kind.empty() && !soa_edit_done) { if (!soa_edit_api_kind.empty() && !soa_edit_done) {
SOAData sd; SOAData sd;
if (!B.getSOA(zonename, sd)) if (!B.getSOAUncached(zonename, sd))
throw ApiException("No SOA found for domain '"+zonename.toString()+"'"); throw ApiException("No SOA found for domain '"+zonename.toString()+"'");
DNSResourceRecord rr; DNSResourceRecord rr;
rr.qname = zonename; if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
rr.content = serializeSOAData(sd); if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResou
rr.qtype = "SOA"; rceRecord>(1, rr))) {
rr.domain_id = di.id; throw ApiException("Hosting backend does not support editing records."
rr.auth = 1; );
rr.ttl = sd.ttl; }
increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
// fixup dots after serializeSOAData/increaseSOARecord
rr.content = makeBackendRecordContent(rr.qtype, rr.content);
if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourc
eRecord>(1, rr))) {
throw ApiException("Hosting backend does not support editing records.");
} }
// return old and new serials in headers // return old and new serials in headers
resp->headers["X-PDNS-Old-Serial"] = std::to_string(sd.serial); resp->headers["X-PDNS-Old-Serial"] = std::to_string(sd.serial);
fillSOAData(rr.content, sd); fillSOAData(rr.content, sd);
resp->headers["X-PDNS-New-Serial"] = std::to_string(sd.serial); resp->headers["X-PDNS-New-Serial"] = std::to_string(sd.serial);
} }
} catch(...) { } catch(...) {
di.backend->abortTransaction(); di.backend->abortTransaction();
skipping to change at line 1801 skipping to change at line 2102
resp->status = 204; // No Content, but indicate success resp->status = 204; // No Content, but indicate success
return; return;
} }
static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) { static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
if(req->method != "GET") if(req->method != "GET")
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
string q = req->getvars["q"]; string q = req->getvars["q"];
string sMax = req->getvars["max"]; string sMax = req->getvars["max"];
string sObjectType = req->getvars["object_type"];
int maxEnts = 100; int maxEnts = 100;
int ents = 0; 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()) if (q.empty())
throw ApiException("Query q can't be blank"); throw ApiException("Query q can't be blank");
if (!sMax.empty()) if (!sMax.empty())
maxEnts = std::stoi(sMax); maxEnts = std::stoi(sMax);
if (maxEnts < 1) if (maxEnts < 1)
throw ApiException("Maximum entries must be larger than 0"); 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, z
one, record, comment");
SimpleMatch sm(q,true); SimpleMatch sm(q,true);
UeberBackend B; UeberBackend B;
vector<DomainInfo> domains; vector<DomainInfo> domains;
vector<DNSResourceRecord> result_rr; vector<DNSResourceRecord> result_rr;
vector<Comment> result_c; vector<Comment> result_c;
map<int,DomainInfo> zoneIdZone; map<int,DomainInfo> zoneIdZone;
map<int,DomainInfo>::iterator val; map<int,DomainInfo>::iterator val;
Json::array doc; Json::array doc;
B.getAllDomains(&domains, true); B.getAllDomains(&domains, true);
for(const DomainInfo di: domains) for(const DomainInfo di: domains)
{ {
if (ents < maxEnts && sm.match(di.zone)) { if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ent s < maxEnts && sm.match(di.zone)) {
doc.push_back(Json::object { doc.push_back(Json::object {
{ "object_type", "zone" }, { "object_type", "zone" },
{ "zone_id", apiZoneNameToId(di.zone) }, { "zone_id", apiZoneNameToId(di.zone) },
{ "name", di.zone.toString() } { "name", di.zone.toString() }
}); });
ents++; ents++;
} }
zoneIdZone[di.id] = di; // populate cache zoneIdZone[di.id] = di; // populate cache
} }
if (B.searchRecords(q, maxEnts, result_rr)) if ((objectType == ObjectType::ALL || objectType == ObjectType::RECORD) && B.s earchRecords(q, maxEnts, result_rr))
{ {
for(const DNSResourceRecord& rr: result_rr) for(const DNSResourceRecord& rr: result_rr)
{ {
if (!rr.qtype.getCode()) if (!rr.qtype.getCode())
continue; // skip empty non-terminals continue; // skip empty non-terminals
auto object = Json::object { auto object = Json::object {
{ "object_type", "record" }, { "object_type", "record" },
{ "name", rr.qname.toString() }, { "name", rr.qname.toString() },
{ "type", rr.qtype.getName() }, { "type", rr.qtype.getName() },
skipping to change at line 1858 skipping to change at line 2183
{ "content", makeApiRecordContent(rr.qtype, rr.content) } { "content", makeApiRecordContent(rr.qtype, rr.content) }
}; };
if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) { if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) {
object["zone_id"] = apiZoneNameToId(val->second.zone); object["zone_id"] = apiZoneNameToId(val->second.zone);
object["zone"] = val->second.zone.toString(); object["zone"] = val->second.zone.toString();
} }
doc.push_back(object); doc.push_back(object);
} }
} }
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) for(const Comment &c: result_c)
{ {
auto object = Json::object { auto object = Json::object {
{ "object_type", "comment" }, { "object_type", "comment" },
{ "name", c.qname.toString() }, { "name", c.qname.toString() },
{ "content", c.content } { "content", c.content }
}; };
if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) { if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) {
object["zone_id"] = apiZoneNameToId(val->second.zone); object["zone_id"] = apiZoneNameToId(val->second.zone);
object["zone"] = val->second.zone.toString(); object["zone"] = val->second.zone.toString();
} }
doc.push_back(object); doc.push_back(object);
} }
} }
resp->setBody(doc); resp->setBody(doc);
} }
void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) { void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
if(req->method != "PUT" || ::arg().mustDo("api-readonly")) if(req->method != "PUT")
throw HttpMethodNotAllowedException(); throw HttpMethodNotAllowedException();
DNSName canon = apiNameToDNSName(req->getvars["domain"]); DNSName canon = apiNameToDNSName(req->getvars["domain"]);
uint64_t count = purgeAuthCachesExact(canon); uint64_t count = purgeAuthCachesExact(canon);
resp->setBody(Json::object { resp->setBody(Json::object {
{ "count", (int) count }, { "count", (int) count },
{ "result", "Flushed cache." } { "result", "Flushed cache." }
}); });
} }
skipping to change at line 1930 skipping to change at line 2255
ret<<".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAA AANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFC Qn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvy lUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+ Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK +bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CY II=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; backg round-repeat: no-repeat; }"<<endl; ret<<".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAA AANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFC Qn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvy lUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+ Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK +bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CY II=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; backg round-repeat: no-repeat; }"<<endl;
ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0 KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGh nlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlp jQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ 6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6P Dhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl; ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0 KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGh nlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlp jQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ 6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6P Dhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
ret<<".resizering {float: right;}"<<endl; ret<<".resizering {float: right;}"<<endl;
resp->body = ret.str(); resp->body = ret.str();
resp->status = 200; resp->status = 200;
} }
void AuthWebServer::webThread() void AuthWebServer::webThread()
{ {
try { try {
setThreadName("pdns/webserver");
if(::arg().mustDo("api")) { if(::arg().mustDo("api")) {
d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServ erCacheFlush); d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServ erCacheFlush);
d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerCon fig); d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerCon fig);
d_ws->registerApiHandler("/api/v1/servers/localhost/search-log", &apiServe rSearchLog);
d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServ erSearchData); d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServ erSearchData);
d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServe rStatistics); d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServe rStatistics);
d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", &apiSe
rverTSIGKeyDetail);
d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", &apiServerT
SIGKeys);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrie ve", &apiServerZoneAxfrRetrieve); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrie ve", &apiServerZoneAxfrRetrieve);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/ <key_id>", &apiZoneCryptokeys); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/ <key_id>", &apiZoneCryptokeys);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys" , &apiZoneCryptokeys); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys" , &apiZoneCryptokeys);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", &a piServerZoneExport); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", &a piServerZoneExport);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<k ind>", &apiZoneMetadataKind); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<k ind>", &apiZoneMetadataKind);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", &apiZoneMetadata); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", &apiZoneMetadata);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", &a piServerZoneNotify); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", &a piServerZoneNotify);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/rectify", & apiServerZoneRectify); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/rectify", & apiServerZoneRectify);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServe rZoneDetail); d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServe rZoneDetail);
d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZone s); d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZone s);
skipping to change at line 1957 skipping to change at line 2284
d_ws->registerApiHandler("/api/v1/servers", &apiServer); d_ws->registerApiHandler("/api/v1/servers", &apiServer);
d_ws->registerApiHandler("/api", &apiDiscovery); d_ws->registerApiHandler("/api", &apiDiscovery);
} }
if (::arg().mustDo("webserver")) { if (::arg().mustDo("webserver")) {
d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunc tion, this, _1, _2)); d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunc tion, this, _1, _2));
d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, t his, _1, _2)); d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, t his, _1, _2));
} }
d_ws->go(); d_ws->go();
} }
catch(...) { catch(...) {
L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl; g_log<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<end l;
_exit(1); _exit(1);
} }
} }
 End of changes. 105 change blocks. 
153 lines changed or deleted 509 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)