]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
Make the constructors taking a pthread_rwlock_t * private.
[thirdparty/pdns.git] / pdns / ws-auth.cc
CommitLineData
12c86877 1/*
6edbf68a
PL
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
9054d8a4 25#include "utility.hh"
d267d1bf 26#include "dynlistener.hh"
2470b36e 27#include "ws-auth.hh"
e611a06c 28#include "json.hh"
12c86877
BH
29#include "webserver.hh"
30#include "logger.hh"
31#include "statbag.hh"
32#include "misc.hh"
6f16f97c 33#include "base64.hh"
12c86877
BH
34#include "arguments.hh"
35#include "dns.hh"
6cc98ddf 36#include "comment.hh"
e611a06c 37#include "ueberbackend.hh"
dcc65f25 38#include <boost/format.hpp>
fa8fd4d2 39
9ac4a7c6 40#include "namespaces.hh"
6ec5e728 41#include "ws-api.hh"
ba1a571d 42#include "version.hh"
d29d5db7 43#include "dnsseckeeper.hh"
3c3c006b 44#include <iomanip>
0f0e73fe 45#include "zoneparser-tng.hh"
a426cb89 46#include "common_startup.hh"
bf269e28 47#include "auth-caches.hh"
519f5484 48#include "threadname.hh"
ac5298aa 49#include "tsigutils.hh"
8537b9f0 50
24afabad 51using json11::Json;
12c86877
BH
52
53extern StatBag S;
54
311cb9a2 55static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp);
995473c8 56static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptrs);
f63168e6
CH
57static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr);
58
646bcd7d
CH
59// QTypes that MUST NOT have multiple records of the same type in a given RRset.
60static const std::set<uint16_t> onlyOneEntryTypes = { QType::CNAME, QType::DNAME, QType::SOA };
61// QTypes that MUST NOT be used with any other QType on the same name.
5e73d266 62static const std::set<uint16_t> exclusiveEntryTypes = { QType::CNAME };
646bcd7d 63
8a70e507 64AuthWebServer::AuthWebServer() :
eace2c24 65 d_start(time(nullptr)),
8a70e507
CHB
66 d_min10(0),
67 d_min5(0),
eace2c24 68 d_min1(0)
12c86877 69{
536ab56f 70 if(arg().mustDo("webserver") || arg().mustDo("api")) {
bbef8f04 71 d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port"));
29997a3c 72 d_ws->setApiKey(arg()["api-key"]);
0c3b088c 73 d_ws->setPassword(arg()["webserver-password"]);
8ca656a8 74 d_ws->setLogLevel(arg()["webserver-loglevel"]);
0010aefa
PL
75
76 NetmaskGroup acl;
77 acl.toMasks(::arg()["webserver-allow-from"]);
78 d_ws->setACL(acl);
79
214b034e
PD
80 d_ws->setMaxBodySize(::arg().asNum("webserver-max-bodysize"));
81
825fa717
CH
82 d_ws->bind();
83 }
12c86877
BH
84}
85
dea47634 86void AuthWebServer::go()
12c86877 87{
536ab56f 88 S.doRings();
0ddde5fb
RG
89 std::thread webT(std::bind(&AuthWebServer::webThread, this));
90 webT.detach();
91 std::thread statT(std::bind(&AuthWebServer::statThread, this));
92 statT.detach();
12c86877
BH
93}
94
dea47634 95void AuthWebServer::statThread()
12c86877
BH
96{
97 try {
519f5484 98 setThreadName("pdns/statHelper");
12c86877
BH
99 for(;;) {
100 d_queries.submit(S.read("udp-queries"));
101 d_cachehits.submit(S.read("packetcache-hit"));
102 d_cachemisses.submit(S.read("packetcache-miss"));
103 d_qcachehits.submit(S.read("query-cache-hit"));
104 d_qcachemisses.submit(S.read("query-cache-miss"));
105 Utility::sleep(1);
106 }
107 }
108 catch(...) {
e6a9dde5 109 g_log<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl;
5bd2ea7b 110 _exit(1);
12c86877
BH
111 }
112}
113
9f3fdaa0
CH
114static string htmlescape(const string &s) {
115 string result;
116 for(string::const_iterator it=s.begin(); it!=s.end(); ++it) {
117 switch (*it) {
118 case '&':
c86a96f9 119 result += "&amp;";
9f3fdaa0
CH
120 break;
121 case '<':
122 result += "&lt;";
123 break;
124 case '>':
125 result += "&gt;";
126 break;
c7f59d62
PL
127 case '"':
128 result += "&quot;";
129 break;
9f3fdaa0
CH
130 default:
131 result += *it;
132 }
133 }
134 return result;
135}
136
050e6877 137static void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
12c86877
BH
138{
139 int tot=0;
140 int entries=0;
101b5d5d 141 vector<pair <string,unsigned int> >ring=S.getRing(ringname);
12c86877 142
1071abdd 143 for(vector<pair<string, unsigned int> >::const_iterator i=ring.begin(); i!=ring.end();++i) {
12c86877
BH
144 tot+=i->second;
145 entries++;
146 }
147
1071abdd 148 ret<<"<div class=\"panel\">";
c7f59d62 149 ret<<"<span class=resetring><i></i><a href=\"?resetring="<<htmlescape(ringname)<<"\">Reset</a></span>"<<endl;
1071abdd
CH
150 ret<<"<h2>"<<title<<"</h2>"<<endl;
151 ret<<"<div class=ringmeta>";
c7f59d62 152 ret<<"<a class=topXofY href=\"?ring="<<htmlescape(ringname)<<"\">Showing: Top "<<limit<<" of "<<entries<<"</a>"<<endl;
1071abdd 153 ret<<"<span class=resizering>Resize: ";
bb3c3f50 154 unsigned int sizes[]={10,100,500,1000,10000,500000,0};
12c86877
BH
155 for(int i=0;sizes[i];++i) {
156 if(S.getRingSize(ringname)!=sizes[i])
c7f59d62 157 ret<<"<a href=\"?resizering="<<htmlescape(ringname)<<"&amp;size="<<sizes[i]<<"\">"<<sizes[i]<<"</a> ";
12c86877
BH
158 else
159 ret<<"("<<sizes[i]<<") ";
160 }
1071abdd 161 ret<<"</span></div>";
12c86877 162
1071abdd 163 ret<<"<table class=\"data\">";
12c86877 164 int printed=0;
f5cb7e61 165 int total=max(1,tot);
bb3c3f50 166 for(vector<pair<string,unsigned int> >::const_iterator i=ring.begin();limit && i!=ring.end();++i,--limit) {
dea47634 167 ret<<"<tr><td>"<<htmlescape(i->first)<<"</td><td>"<<i->second<<"</td><td align=right>"<< AuthWebServer::makePercentage(i->second*100.0/total)<<"</td>"<<endl;
12c86877
BH
168 printed+=i->second;
169 }
170 ret<<"<tr><td colspan=3></td></tr>"<<endl;
171 if(printed!=tot)
dea47634 172 ret<<"<tr><td><b>Rest:</b></td><td><b>"<<tot-printed<<"</b></td><td align=right><b>"<< AuthWebServer::makePercentage((tot-printed)*100.0/total)<<"</b></td>"<<endl;
12c86877 173
e2a77e08 174 ret<<"<tr><td><b>Total:</b></td><td><b>"<<tot<<"</b></td><td align=right><b>100%</b></td>";
1071abdd 175 ret<<"</table></div>"<<endl;
12c86877
BH
176}
177
dea47634 178void AuthWebServer::printvars(ostringstream &ret)
12c86877 179{
1071abdd 180 ret<<"<div class=panel><h2>Variables</h2><table class=\"data\">"<<endl;
12c86877
BH
181
182 vector<string>entries=S.getEntries();
183 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
184 ret<<"<tr><td>"<<*i<<"</td><td>"<<S.read(*i)<<"</td><td>"<<S.getDescrip(*i)<<"</td>"<<endl;
185 }
e2a77e08 186
1071abdd 187 ret<<"</table></div>"<<endl;
12c86877
BH
188}
189
dea47634 190void AuthWebServer::printargs(ostringstream &ret)
12c86877 191{
e2a77e08 192 ret<<"<table border=1><tr><td colspan=3 bgcolor=\"#0000ff\"><font color=\"#ffffff\">Arguments</font></td>"<<endl;
12c86877
BH
193
194 vector<string>entries=arg().list();
195 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
196 ret<<"<tr><td>"<<*i<<"</td><td>"<<arg()[*i]<<"</td><td>"<<arg().getHelp(*i)<<"</td>"<<endl;
197 }
198}
199
dea47634 200string AuthWebServer::makePercentage(const double& val)
b6f57093
BH
201{
202 return (boost::format("%.01f%%") % val).str();
203}
204
dea47634 205void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
12c86877 206{
583ea80d
CH
207 if(!req->getvars["resetring"].empty()) {
208 if (S.ringExists(req->getvars["resetring"]))
209 S.resetRing(req->getvars["resetring"]);
d7b8730e 210 resp->status = 302;
0665b7e6 211 resp->headers["Location"] = req->url.path;
80d59cd1 212 return;
12c86877 213 }
583ea80d 214 if(!req->getvars["resizering"].empty()){
335da0ba 215 int size=std::stoi(req->getvars["size"]);
583ea80d 216 if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000)
335da0ba 217 S.resizeRing(req->getvars["resizering"], std::stoi(req->getvars["size"]));
d7b8730e 218 resp->status = 302;
0665b7e6 219 resp->headers["Location"] = req->url.path;
80d59cd1 220 return;
12c86877
BH
221 }
222
223 ostringstream ret;
224
1071abdd
CH
225 ret<<"<!DOCTYPE html>"<<endl;
226 ret<<"<html><head>"<<endl;
227 ret<<"<title>PowerDNS Authoritative Server Monitor</title>"<<endl;
228 ret<<"<link rel=\"stylesheet\" href=\"style.css\"/>"<<endl;
229 ret<<"</head><body>"<<endl;
230
231 ret<<"<div class=\"row\">"<<endl;
232 ret<<"<div class=\"headl columns\">";
a1caa8b8 233 ret<<"<a href=\"/\" id=\"appname\">PowerDNS "<<htmlescape(VERSION);
1071abdd 234 if(!arg()["config-name"].empty()) {
a1caa8b8 235 ret<<" ["<<htmlescape(arg()["config-name"])<<"]";
1071abdd
CH
236 }
237 ret<<"</a></div>"<<endl;
238 ret<<"<div class=\"headr columns\"></div></div>";
239 ret<<"<div class=\"row\"><div class=\"all columns\">";
12c86877
BH
240
241 time_t passed=time(0)-s_starttime;
242
e2a77e08
KM
243 ret<<"<p>Uptime: "<<
244 humanDuration(passed)<<
245 "<br>"<<endl;
12c86877 246
395b07ea 247 ret<<"Queries/second, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
3e1cd1f4 248 (int)d_queries.get1()<<", "<<
249 (int)d_queries.get5()<<", "<<
250 (int)d_queries.get10()<<". Max queries/second: "<<(int)d_queries.getMax()<<
12c86877 251 "<br>"<<endl;
1d6b70f9 252
f6154a3b 253 if(d_cachemisses.get10()+d_cachehits.get10()>0)
b6f57093 254 ret<<"Cache hitrate, 1, 5, 10 minute averages: "<<
f6154a3b
CH
255 makePercentage((d_cachehits.get1()*100.0)/((d_cachehits.get1())+(d_cachemisses.get1())))<<", "<<
256 makePercentage((d_cachehits.get5()*100.0)/((d_cachehits.get5())+(d_cachemisses.get5())))<<", "<<
257 makePercentage((d_cachehits.get10()*100.0)/((d_cachehits.get10())+(d_cachemisses.get10())))<<
b6f57093 258 "<br>"<<endl;
12c86877 259
f6154a3b 260 if(d_qcachemisses.get10()+d_qcachehits.get10()>0)
395b07ea 261 ret<<"Backend query cache hitrate, 1, 5, 10 minute averages: "<<std::setprecision(2)<<
f6154a3b
CH
262 makePercentage((d_qcachehits.get1()*100.0)/((d_qcachehits.get1())+(d_qcachemisses.get1())))<<", "<<
263 makePercentage((d_qcachehits.get5()*100.0)/((d_qcachehits.get5())+(d_qcachemisses.get5())))<<", "<<
264 makePercentage((d_qcachehits.get10()*100.0)/((d_qcachehits.get10())+(d_qcachemisses.get10())))<<
b6f57093 265 "<br>"<<endl;
12c86877 266
395b07ea 267 ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
3e1cd1f4 268 (int)d_qcachemisses.get1()<<", "<<
269 (int)d_qcachemisses.get5()<<", "<<
270 (int)d_qcachemisses.get10()<<". Max queries/second: "<<(int)d_qcachemisses.getMax()<<
12c86877
BH
271 "<br>"<<endl;
272
1071abdd 273 ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
583ea80d 274 if(req->getvars["ring"].empty()) {
eb029b8e
PL
275 auto entries = S.listRings();
276 for(const auto &i: entries) {
277 printtable(ret, i, S.getRingTitle(i));
278 }
12c86877 279
f6154a3b 280 printvars(ret);
12c86877 281 if(arg().mustDo("webserver-print-arguments"))
f6154a3b 282 printargs(ret);
12c86877 283 }
bea69e32 284 else if(S.ringExists(req->getvars["ring"]))
583ea80d 285 printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100);
12c86877 286
1071abdd 287 ret<<"</div></div>"<<endl;
620e780a 288 ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 - 2019 <a href=\"https://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
12c86877
BH
289 ret<<"</body></html>"<<endl;
290
80d59cd1 291 resp->body = ret.str();
61f5d289 292 resp->status = 200;
12c86877
BH
293}
294
1d6b70f9
CH
295/** Helper to build a record content as needed. */
296static inline string makeRecordContent(const QType& qtype, const string& content, bool noDot) {
297 // noDot: for backend storage, pass true. for API users, pass false.
32122aab 298 auto drc = DNSRecordContent::mastermake(qtype.getCode(), QClass::IN, content);
7fe1a82b 299 return drc->getZoneRepresentation(noDot);
1d6b70f9
CH
300}
301
302/** "Normalize" record content for API consumers. */
303static inline string makeApiRecordContent(const QType& qtype, const string& content) {
304 return makeRecordContent(qtype, content, false);
305}
306
307/** "Normalize" record content for backend storage. */
308static inline string makeBackendRecordContent(const QType& qtype, const string& content) {
309 return makeRecordContent(qtype, content, true);
310}
311
07a32d18 312static Json::object getZoneInfo(const DomainInfo& di, DNSSECKeeper* dk) {
290a083d 313 string zoneId = apiZoneNameToId(di.zone);
d622042f 314 vector<string> masters;
c8b929d9
RG
315 masters.reserve(di.masters.size());
316 for(const auto& m : di.masters) {
d622042f 317 masters.push_back(m.toStringWithPortExcept(53));
c8b929d9 318 }
24ded6cc 319
07a32d18 320 auto obj = Json::object {
62a9a74c
CH
321 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
322 { "id", zoneId },
16e25450 323 { "url", "/api/v1/servers/localhost/zones/" + zoneId },
62a9a74c
CH
324 { "name", di.zone.toString() },
325 { "kind", di.getKindString() },
62a9a74c 326 { "account", di.account },
c8b929d9 327 { "masters", std::move(masters) },
62a9a74c
CH
328 { "serial", (double)di.serial },
329 { "notified_serial", (double)di.notified_serial },
330 { "last_check", (double)di.last_check }
331 };
07a32d18
CH
332 if (dk) {
333 obj["dnssec"] = dk->isSecuredZone(di.zone);
334 obj["edited_serial"] = (double)calculateEditSOA(di.serial, *dk, di.zone);
335 }
336 return obj;
c04b5870
CH
337}
338
986e4858
PL
339static bool shouldDoRRSets(HttpRequest* req) {
340 if (req->getvars.count("rrsets") == 0 || req->getvars["rrsets"] == "true")
341 return true;
342 if (req->getvars["rrsets"] == "false")
343 return false;
344 throw ApiException("'rrsets' request parameter value '"+req->getvars["rrsets"]+"' is not supported");
345}
346
311cb9a2 347static void fillZone(UeberBackend& B, const DNSName& zonename, HttpResponse* resp, bool doRRSets) {
1abb81f4 348 DomainInfo di;
77bfe8de
PL
349 if(!B.getDomainInfo(zonename, di)) {
350 throw HttpNotFoundException();
351 }
1abb81f4 352
adef67eb 353 DNSSECKeeper dk(&B);
ce846be6 354 Json::object doc = getZoneInfo(di, &dk);
62a9a74c 355 // extra stuff getZoneInfo doesn't do for us (more expensive)
d29d5db7
CH
356 string soa_edit_api;
357 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
62a9a74c 358 doc["soa_edit_api"] = soa_edit_api;
6bb25159
MS
359 string soa_edit;
360 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
62a9a74c 361 doc["soa_edit"] = soa_edit;
986e4858
PL
362 string nsec3param;
363 di.backend->getDomainMetadataOne(zonename, "NSEC3PARAM", nsec3param);
364 doc["nsec3param"] = nsec3param;
365 string nsec3narrow;
366 bool nsec3narrowbool = false;
367 di.backend->getDomainMetadataOne(zonename, "NSEC3NARROW", nsec3narrow);
368 if (nsec3narrow == "1")
369 nsec3narrowbool = true;
370 doc["nsec3narrow"] = nsec3narrowbool;
0ce25a64 371 doc["dnssec"] = dk.isSecuredZone(zonename);
986e4858
PL
372
373 string api_rectify;
374 di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
375 doc["api_rectify"] = (api_rectify == "1");
376
eecc9ed5
PL
377 // TSIG
378 vector<string> tsig_master, tsig_slave;
379 di.backend->getDomainMetadata(zonename, "TSIG-ALLOW-AXFR", tsig_master);
380 di.backend->getDomainMetadata(zonename, "AXFR-MASTER-TSIG", tsig_slave);
381
382 Json::array tsig_master_keys;
383 for (const auto& keyname : tsig_master) {
384 tsig_master_keys.push_back(apiZoneNameToId(DNSName(keyname)));
385 }
386 doc["master_tsig_key_ids"] = tsig_master_keys;
387
388 Json::array tsig_slave_keys;
389 for (const auto& keyname : tsig_slave) {
390 tsig_slave_keys.push_back(apiZoneNameToId(DNSName(keyname)));
391 }
392 doc["slave_tsig_key_ids"] = tsig_slave_keys;
393
986e4858
PL
394 if (doRRSets) {
395 vector<DNSResourceRecord> records;
396 vector<Comment> comments;
397
398 // load all records + sort
399 {
400 DNSResourceRecord rr;
401 di.backend->list(zonename, di.id, true); // incl. disabled
402 while(di.backend->get(rr)) {
403 if (!rr.qtype.getCode())
404 continue; // skip empty non-terminals
405 records.push_back(rr);
406 }
407 sort(records.begin(), records.end(), [](const DNSResourceRecord& a, const DNSResourceRecord& b) {
f2d6dcc0
RG
408 /* if you ever want to update this comparison function,
409 please be aware that you will also need to update the conditions in the code merging
410 the records and comments below */
986e4858
PL
411 if (a.qname == b.qname) {
412 return b.qtype < a.qtype;
413 }
414 return b.qname < a.qname;
415 });
6754ef71 416 }
6754ef71 417
986e4858
PL
418 // load all comments + sort
419 {
420 Comment comment;
421 di.backend->listComments(di.id);
422 while(di.backend->getComment(comment)) {
423 comments.push_back(comment);
424 }
425 sort(comments.begin(), comments.end(), [](const Comment& a, const Comment& b) {
f2d6dcc0
RG
426 /* if you ever want to update this comparison function,
427 please be aware that you will also need to update the conditions in the code merging
428 the records and comments below */
986e4858
PL
429 if (a.qname == b.qname) {
430 return b.qtype < a.qtype;
431 }
432 return b.qname < a.qname;
433 });
6754ef71 434 }
6754ef71 435
986e4858
PL
436 Json::array rrsets;
437 Json::object rrset;
438 Json::array rrset_records;
439 Json::array rrset_comments;
440 DNSName current_qname;
441 QType current_qtype;
442 uint32_t ttl;
443 auto rit = records.begin();
444 auto cit = comments.begin();
445
446 while (rit != records.end() || cit != comments.end()) {
5481c77c
PD
447 // if you think this should be rit < cit instead of cit < rit, note the b < a instead of a < b in the sort comparison functions above
448 if (cit == comments.end() || (rit != records.end() && (rit->qname == cit->qname ? (cit->qtype < rit->qtype || cit->qtype == rit->qtype) : cit->qname < rit->qname))) {
986e4858
PL
449 current_qname = rit->qname;
450 current_qtype = rit->qtype;
451 ttl = rit->ttl;
452 } else {
453 current_qname = cit->qname;
454 current_qtype = cit->qtype;
455 ttl = 0;
456 }
6754ef71 457
986e4858
PL
458 while(rit != records.end() && rit->qname == current_qname && rit->qtype == current_qtype) {
459 ttl = min(ttl, rit->ttl);
460 rrset_records.push_back(Json::object {
461 { "disabled", rit->disabled },
462 { "content", makeApiRecordContent(rit->qtype, rit->content) }
463 });
464 rit++;
465 }
466 while (cit != comments.end() && cit->qname == current_qname && cit->qtype == current_qtype) {
467 rrset_comments.push_back(Json::object {
468 { "modified_at", (double)cit->modified_at },
469 { "account", cit->account },
470 { "content", cit->content }
471 });
472 cit++;
473 }
474
475 rrset["name"] = current_qname.toString();
476 rrset["type"] = current_qtype.getName();
477 rrset["records"] = rrset_records;
478 rrset["comments"] = rrset_comments;
479 rrset["ttl"] = (double)ttl;
480 rrsets.push_back(rrset);
481 rrset.clear();
482 rrset_records.clear();
483 rrset_comments.clear();
6754ef71
CH
484 }
485
986e4858 486 doc["rrsets"] = rrsets;
6754ef71
CH
487 }
488
669822d0 489 resp->setBody(doc);
1abb81f4
CH
490}
491
6ec5e728
CH
492void productServerStatisticsFetch(map<string,string>& out)
493{
a45303b8 494 vector<string> items = S.getEntries();
ff05fd12 495 for(const string& item : items) {
335da0ba 496 out[item] = std::to_string(S.read(item));
a45303b8
CH
497 }
498
499 // add uptime
335da0ba 500 out["uptime"] = std::to_string(time(0) - s_starttime);
c67bf8c5
CH
501}
502
5376a5d7
RG
503boost::optional<uint64_t> productServerStatisticsFetch(const std::string& name)
504{
505 try {
506 // ::read() calls ::exists() which throws a PDNSException when the key does not exist
507 return S.read(name);
508 }
509 catch(...) {
510 return boost::none;
511 }
512}
513
24ded6cc
CHB
514static void validateGatheredRRType(const DNSResourceRecord& rr) {
515 if (rr.qtype.getCode() == QType::OPT || rr.qtype.getCode() == QType::TSIG) {
516 throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": invalid type given");
517 }
518}
519
311cb9a2 520static void gatherRecords(UeberBackend& B, const string& logprefix, const Json container, const DNSName& qname, const QType qtype, const int ttl, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
f63168e6 521 DNSResourceRecord rr;
6754ef71
CH
522 rr.qname = qname;
523 rr.qtype = qtype;
524 rr.auth = 1;
525 rr.ttl = ttl;
24ded6cc
CHB
526
527 validateGatheredRRType(rr);
7f201325 528 const auto& items = container["records"].array_items();
7f201325 529 for(const auto& record : items) {
1f68b185 530 string content = stringFromJson(record, "content");
70aad565
PL
531 rr.disabled = false;
532 if(!record["disabled"].is_null()) {
533 rr.disabled = boolFromJson(record, "disabled");
534 }
1f68b185 535
1f68b185
CH
536 // validate that the client sent something we can actually parse, and require that data to be dotted.
537 try {
538 if (rr.qtype.getCode() != QType::AAAA) {
539 string tmp = makeApiRecordContent(rr.qtype, content);
540 if (!pdns_iequals(tmp, content)) {
541 throw std::runtime_error("Not in expected format (parsed as '"+tmp+"')");
542 }
543 } else {
544 struct in6_addr tmpbuf;
545 if (inet_pton(AF_INET6, content.c_str(), &tmpbuf) != 1 || content.find('.') != string::npos) {
546 throw std::runtime_error("Invalid IPv6 address");
1e5b9ab9 547 }
f63168e6 548 }
1f68b185
CH
549 rr.content = makeBackendRecordContent(rr.qtype, content);
550 }
551 catch(std::exception& e)
552 {
553 throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.getName()+" '"+content+"': "+e.what());
554 }
f63168e6 555
1f68b185
CH
556 if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
557 boolFromJson(record, "set-ptr", false) == true) {
3a0f6fdd
CH
558
559 g_log<<Logger::Warning<<logprefix<<"API call uses deprecated set-ptr feature, please remove it"<<endl;
560
1f68b185
CH
561 DNSResourceRecord ptr;
562 makePtr(rr, &ptr);
f63168e6 563
1f68b185 564 // verify that there's a zone for the PTR
1f68b185 565 SOAData sd;
cec52de6 566 if (!B.getAuth(ptr.qname, QType(QType::PTR), &sd, false))
1f68b185 567 throw ApiException("Could not find domain for PTR '"+ptr.qname.toString()+"' requested for '"+ptr.content+"'");
f63168e6 568
1f68b185
CH
569 ptr.domain_id = sd.domain_id;
570 new_ptrs.push_back(ptr);
f63168e6 571 }
1f68b185
CH
572
573 new_records.push_back(rr);
f63168e6
CH
574 }
575}
576
6754ef71 577static void gatherComments(const Json container, const DNSName& qname, const QType qtype, vector<Comment>& new_comments) {
f63168e6 578 Comment c;
6754ef71
CH
579 c.qname = qname;
580 c.qtype = qtype;
f63168e6
CH
581
582 time_t now = time(0);
1f68b185 583 for (auto comment : container["comments"].array_items()) {
1f68b185
CH
584 c.modified_at = intFromJson(comment, "modified_at", now);
585 c.content = stringFromJson(comment, "content");
586 c.account = stringFromJson(comment, "account");
587 new_comments.push_back(c);
f63168e6
CH
588 }
589}
6cc98ddf 590
986e4858
PL
591static void checkDefaultDNSSECAlgos() {
592 int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algorithm"]);
593 int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algorithm"]);
594 int k_size = arg().asNum("default-ksk-size");
595 int z_size = arg().asNum("default-zsk-size");
596
597 // Sanity check DNSSEC parameters
598 if (::arg()["default-zsk-algorithm"] != "") {
599 if (k_algo == -1)
600 throw ApiException("default-ksk-algorithm setting is set to unknown algorithm: " + ::arg()["default-ksk-algorithm"]);
601 else if (k_algo <= 10 && k_size == 0)
602 throw ApiException("default-ksk-algorithm is set to an algorithm("+::arg()["default-ksk-algorithm"]+") that requires a non-zero default-ksk-size!");
603 }
604
605 if (::arg()["default-zsk-algorithm"] != "") {
606 if (z_algo == -1)
607 throw ApiException("default-zsk-algorithm setting is set to unknown algorithm: " + ::arg()["default-zsk-algorithm"]);
608 else if (z_algo <= 10 && z_size == 0)
609 throw ApiException("default-zsk-algorithm is set to an algorithm("+::arg()["default-zsk-algorithm"]+") that requires a non-zero default-zsk-size!");
610 }
611}
612
89a7e706
PL
613static void throwUnableToSecure(const DNSName& zonename) {
614 throw ApiException("No backend was able to secure '" + zonename.toString() + "', most likely because no DNSSEC"
615 + "capable backends are loaded, or because the backends have DNSSEC disabled. Check your configuration.");
616}
617
f43646f5 618static void updateDomainSettingsFromDocument(UeberBackend& B, const DomainInfo& di, const DNSName& zonename, const Json document, bool rectifyTransaction=true) {
49570562 619 vector<string> zonemaster;
986e4858 620 bool shouldRectify = false;
1f68b185
CH
621 for(auto value : document["masters"].array_items()) {
622 string master = value.string_value();
623 if (master.empty())
624 throw ApiException("Master can not be an empty string");
27efa4be
PL
625 try {
626 ComboAddress m(master);
627 } catch (const PDNSException &e) {
628 throw ApiException("Master (" + master + ") is not an IP address: " + e.reason);
629 }
49570562 630 zonemaster.push_back(master);
bb9fd223
CH
631 }
632
49570562
KD
633 if (zonemaster.size()) {
634 di.backend->setMaster(zonename, boost::join(zonemaster, ","));
986e4858
PL
635 }
636 if (document["kind"].is_string()) {
637 di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
638 }
1f68b185
CH
639 if (document["soa_edit_api"].is_string()) {
640 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].string_value());
d29d5db7 641 }
1f68b185
CH
642 if (document["soa_edit"].is_string()) {
643 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].string_value());
6bb25159 644 }
c00908f1
PL
645 try {
646 bool api_rectify = boolFromJson(document, "api_rectify");
647 di.backend->setDomainMetadataOne(zonename, "API-RECTIFY", api_rectify ? "1" : "0");
986e4858 648 }
819861fa 649 catch (const JsonException&) {}
c00908f1 650
1f68b185
CH
651 if (document["account"].is_string()) {
652 di.backend->setAccount(zonename, document["account"].string_value());
79532aa7 653 }
986e4858
PL
654
655 DNSSECKeeper dk(&B);
656 bool dnssecInJSON = false;
657 bool dnssecDocVal = false;
658
659 try {
660 dnssecDocVal = boolFromJson(document, "dnssec");
661 dnssecInJSON = true;
662 }
819861fa 663 catch (const JsonException&) {}
986e4858
PL
664
665 bool isDNSSECZone = dk.isSecuredZone(zonename);
666
667 if (dnssecInJSON) {
668 if (dnssecDocVal) {
669 if (!isDNSSECZone) {
670 checkDefaultDNSSECAlgos();
671
672 int k_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-ksk-algorithm"]);
673 int z_algo = DNSSECKeeper::shorthand2algorithm(::arg()["default-zsk-algorithm"]);
674 int k_size = arg().asNum("default-ksk-size");
675 int z_size = arg().asNum("default-zsk-size");
676
677 if (k_algo != -1) {
678 int64_t id;
679 if (!dk.addKey(zonename, true, k_algo, id, k_size)) {
89a7e706 680 throwUnableToSecure(zonename);
986e4858
PL
681 }
682 }
683
684 if (z_algo != -1) {
685 int64_t id;
686 if (!dk.addKey(zonename, false, z_algo, id, z_size)) {
89a7e706 687 throwUnableToSecure(zonename);
986e4858
PL
688 }
689 }
690
691 // Used later for NSEC3PARAM
692 isDNSSECZone = dk.isSecuredZone(zonename);
693
694 if (!isDNSSECZone) {
89a7e706 695 throwUnableToSecure(zonename);
986e4858
PL
696 }
697 shouldRectify = true;
698 }
699 } else {
700 // "dnssec": false in json
701 if (isDNSSECZone) {
cbe8b186
PL
702 string info, error;
703 if (!dk.unSecureZone(zonename, error, info)) {
704 throw ApiException("Error while un-securing zone '"+ zonename.toString()+"': " + error);
705 }
706 isDNSSECZone = dk.isSecuredZone(zonename);
707 if (isDNSSECZone) {
708 throw ApiException("Unable to un-secure zone '"+ zonename.toString()+"'");
709 }
710 shouldRectify = true;
986e4858
PL
711 }
712 }
713 }
714
715 if(document["nsec3param"].string_value().length() > 0) {
716 shouldRectify = true;
717 NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value());
718 string error_msg = "";
719 if (!isDNSSECZone) {
720 throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"', but zone is not DNSSEC secured.");
721 }
722 if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) {
723 throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"' are invalid. " + error_msg);
724 }
725 if (!dk.setNSEC3PARAM(zonename, ns3pr, boolFromJson(document, "nsec3narrow", false))) {
726 throw ApiException("NSEC3PARAMs provided for zone '" + zonename.toString() +
727 "' passed our basic sanity checks, but cannot be used with the current backend.");
728 }
729 }
730
a843c67e
KM
731 if (shouldRectify && !dk.isPresigned(zonename)) {
732 // Rectify
733 string api_rectify;
734 di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify);
b8cd24cc
SH
735 if (api_rectify.empty()) {
736 if (::arg().mustDo("default-api-rectify")) {
737 api_rectify = "1";
738 }
739 }
a843c67e
KM
740 if (api_rectify == "1") {
741 string info;
742 string error_msg;
f43646f5 743 if (!dk.rectifyZone(zonename, error_msg, info, rectifyTransaction)) {
a843c67e
KM
744 throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
745 }
746 }
747
748 // Increase serial
749 string soa_edit_api_kind;
750 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
751 if (!soa_edit_api_kind.empty()) {
752 SOAData sd;
76e1255a 753 if (!B.getSOAUncached(zonename, sd))
a843c67e
KM
754 return;
755
756 string soa_edit_kind;
757 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
758
759 DNSResourceRecord rr;
760 if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
761 if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
762 throw ApiException("Hosting backend does not support editing records.");
763 }
764 }
a843c67e 765 }
986e4858 766 }
1096d493
PL
767
768 if (!document["master_tsig_key_ids"].is_null()) {
769 vector<string> metadata;
770 for(auto value : document["master_tsig_key_ids"].array_items()) {
dc30b8fd 771 auto keyname(apiZoneIdToName(value.string_value()));
3deb283e
RG
772 DNSName keyAlgo;
773 string keyContent;
dc30b8fd
PL
774 B.getTSIGKey(keyname, &keyAlgo, &keyContent);
775 if (keyAlgo.empty() || keyContent.empty()) {
776 throw ApiException("A TSIG key with the name '"+keyname.toLogString()+"' does not exist");
777 }
778 metadata.push_back(keyname.toString());
1096d493
PL
779 }
780 if (!di.backend->setDomainMetadata(zonename, "TSIG-ALLOW-AXFR", metadata)) {
b69468fc 781 throw HttpInternalServerErrorException("Unable to set new TSIG master keys for zone '" + zonename.toLogString() + "'");
1096d493
PL
782 }
783 }
784 if (!document["slave_tsig_key_ids"].is_null()) {
785 vector<string> metadata;
786 for(auto value : document["slave_tsig_key_ids"].array_items()) {
dc30b8fd 787 auto keyname(apiZoneIdToName(value.string_value()));
3deb283e
RG
788 DNSName keyAlgo;
789 string keyContent;
dc30b8fd
PL
790 B.getTSIGKey(keyname, &keyAlgo, &keyContent);
791 if (keyAlgo.empty() || keyContent.empty()) {
792 throw ApiException("A TSIG key with the name '"+keyname.toLogString()+"' does not exist");
793 }
794 metadata.push_back(keyname.toString());
1096d493
PL
795 }
796 if (!di.backend->setDomainMetadata(zonename, "AXFR-MASTER-TSIG", metadata)) {
b69468fc 797 throw HttpInternalServerErrorException("Unable to set new TSIG slave keys for zone '" + zonename.toLogString() + "'");
1096d493
PL
798 }
799 }
bb9fd223
CH
800}
801
24e11043
CJ
802static bool isValidMetadataKind(const string& kind, bool readonly) {
803 static vector<string> builtinOptions {
804 "ALLOW-AXFR-FROM",
805 "AXFR-SOURCE",
806 "ALLOW-DNSUPDATE-FROM",
807 "TSIG-ALLOW-DNSUPDATE",
808 "FORWARD-DNSUPDATE",
809 "SOA-EDIT-DNSUPDATE",
4c5b6925 810 "NOTIFY-DNSUPDATE",
24e11043
CJ
811 "ALSO-NOTIFY",
812 "AXFR-MASTER-TSIG",
813 "GSS-ALLOW-AXFR-PRINCIPAL",
814 "GSS-ACCEPTOR-PRINCIPAL",
815 "IXFR",
816 "LUA-AXFR-SCRIPT",
817 "NSEC3NARROW",
818 "NSEC3PARAM",
819 "PRESIGNED",
820 "PUBLISH-CDNSKEY",
821 "PUBLISH-CDS",
c750c26e 822 "SLAVE-RENOTIFY",
24e11043
CJ
823 "SOA-EDIT",
824 "TSIG-ALLOW-AXFR",
825 "TSIG-ALLOW-DNSUPDATE"
826 };
827
828 // the following options do not allow modifications via API
829 static vector<string> protectedOptions {
986e4858 830 "API-RECTIFY",
2fabf5ce 831 "AXFR-MASTER-TSIG",
24e11043
CJ
832 "NSEC3NARROW",
833 "NSEC3PARAM",
834 "PRESIGNED",
2fabf5ce
PL
835 "LUA-AXFR-SCRIPT",
836 "TSIG-ALLOW-AXFR"
24e11043
CJ
837 };
838
9ac4e6d5
PL
839 if (kind.find("X-") == 0)
840 return true;
841
24e11043
CJ
842 bool found = false;
843
d8043c73 844 for (const string& s : builtinOptions) {
24e11043 845 if (kind == s) {
d8043c73 846 for (const string& s2 : protectedOptions) {
24e11043
CJ
847 if (!readonly && s == s2)
848 return false;
849 }
850 found = true;
851 break;
852 }
853 }
854
855 return found;
856}
857
858static void apiZoneMetadata(HttpRequest* req, HttpResponse *resp) {
859 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
d38e81e6 860
24e11043 861 UeberBackend B;
d38e81e6 862 DomainInfo di;
77bfe8de
PL
863 if (!B.getDomainInfo(zonename, di)) {
864 throw HttpNotFoundException();
865 }
24e11043
CJ
866
867 if (req->method == "GET") {
868 map<string, vector<string> > md;
869 Json::array document;
870
871 if (!B.getAllDomainMetadata(zonename, md))
872 throw HttpNotFoundException();
873
874 for (const auto& i : md) {
875 Json::array entries;
876 for (string j : i.second)
877 entries.push_back(j);
878
879 Json::object key {
880 { "type", "Metadata" },
881 { "kind", i.first },
882 { "metadata", entries }
883 };
884
885 document.push_back(key);
886 }
887
888 resp->setBody(document);
2054afbb 889 } else if (req->method == "POST") {
24e11043
CJ
890 auto document = req->json();
891 string kind;
892 vector<string> entries;
893
894 try {
895 kind = stringFromJson(document, "kind");
819861fa 896 } catch (const JsonException&) {
24e11043
CJ
897 throw ApiException("kind is not specified or not a string");
898 }
899
900 if (!isValidMetadataKind(kind, false))
901 throw ApiException("Unsupported metadata kind '" + kind + "'");
902
903 vector<string> vecMetadata;
c6720e79
CJ
904
905 if (!B.getDomainMetadata(zonename, kind, vecMetadata))
906 throw ApiException("Could not retrieve metadata entries for domain '" +
907 zonename.toString() + "'");
908
24e11043
CJ
909 auto& metadata = document["metadata"];
910 if (!metadata.is_array())
911 throw ApiException("metadata is not specified or not an array");
912
913 for (const auto& i : metadata.array_items()) {
914 if (!i.is_string())
915 throw ApiException("metadata must be strings");
c6720e79
CJ
916 else if (std::find(vecMetadata.cbegin(),
917 vecMetadata.cend(),
918 i.string_value()) == vecMetadata.cend()) {
919 vecMetadata.push_back(i.string_value());
920 }
24e11043
CJ
921 }
922
923 if (!B.setDomainMetadata(zonename, kind, vecMetadata))
c6720e79
CJ
924 throw ApiException("Could not update metadata entries for domain '" +
925 zonename.toString() + "'");
926
927 Json::array respMetadata;
928 for (const string& s : vecMetadata)
929 respMetadata.push_back(s);
930
931 Json::object key {
932 { "type", "Metadata" },
933 { "kind", document["kind"] },
934 { "metadata", respMetadata }
935 };
24e11043 936
24e11043 937 resp->status = 201;
c6720e79 938 resp->setBody(key);
24e11043
CJ
939 } else
940 throw HttpMethodNotAllowedException();
941}
942
943static void apiZoneMetadataKind(HttpRequest* req, HttpResponse* resp) {
944 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
d38e81e6 945
24e11043 946 UeberBackend B;
d38e81e6 947 DomainInfo di;
77bfe8de
PL
948 if (!B.getDomainInfo(zonename, di)) {
949 throw HttpNotFoundException();
950 }
d38e81e6
PL
951
952 string kind = req->parameters["kind"];
24e11043
CJ
953
954 if (req->method == "GET") {
955 vector<string> metadata;
956 Json::object document;
957 Json::array entries;
958
959 if (!B.getDomainMetadata(zonename, kind, metadata))
960 throw HttpNotFoundException();
961 else if (!isValidMetadataKind(kind, true))
962 throw ApiException("Unsupported metadata kind '" + kind + "'");
963
964 document["type"] = "Metadata";
965 document["kind"] = kind;
966
967 for (const string& i : metadata)
968 entries.push_back(i);
969
970 document["metadata"] = entries;
971 resp->setBody(document);
2054afbb 972 } else if (req->method == "PUT") {
24e11043
CJ
973 auto document = req->json();
974
975 if (!isValidMetadataKind(kind, false))
976 throw ApiException("Unsupported metadata kind '" + kind + "'");
977
978 vector<string> vecMetadata;
979 auto& metadata = document["metadata"];
980 if (!metadata.is_array())
981 throw ApiException("metadata is not specified or not an array");
982
983 for (const auto& i : metadata.array_items()) {
984 if (!i.is_string())
985 throw ApiException("metadata must be strings");
986 vecMetadata.push_back(i.string_value());
987 }
988
989 if (!B.setDomainMetadata(zonename, kind, vecMetadata))
990 throw ApiException("Could not update metadata entries for domain '" + zonename.toString() + "'");
991
992 Json::object key {
993 { "type", "Metadata" },
994 { "kind", kind },
995 { "metadata", metadata }
996 };
997
998 resp->setBody(key);
2054afbb 999 } else if (req->method == "DELETE") {
24e11043
CJ
1000 if (!isValidMetadataKind(kind, false))
1001 throw ApiException("Unsupported metadata kind '" + kind + "'");
1002
1003 vector<string> md; // an empty vector will do it
1004 if (!B.setDomainMetadata(zonename, kind, md))
1005 throw ApiException("Could not delete metadata for domain '" + zonename.toString() + "' (" + kind + ")");
1006 } else
1007 throw HttpMethodNotAllowedException();
1008}
1009
71de13d7
PL
1010// Throws 404 if the key with inquireKeyId does not exist
1011static void apiZoneCryptoKeysCheckKeyExists(DNSName zonename, int inquireKeyId, DNSSECKeeper *dk) {
1012 DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false);
1013 bool found = false;
1014 for(const auto& value : keyset) {
1015 if (value.second.id == (unsigned) inquireKeyId) {
1016 found = true;
1017 break;
1018 }
1019 }
1020 if (!found) {
1021 throw HttpNotFoundException();
1022 }
1023}
1024
60b0a236
BZ
1025static void apiZoneCryptokeysGET(DNSName zonename, int inquireKeyId, HttpResponse *resp, DNSSECKeeper *dk) {
1026 DNSSECKeeper::keyset_t keyset=dk->getKeys(zonename, false);
4b7f120a 1027
997cab68
BZ
1028 bool inquireSingleKey = inquireKeyId >= 0;
1029
24afabad 1030 Json::array doc;
29704f66 1031 for(const auto& value : keyset) {
997cab68 1032 if (inquireSingleKey && (unsigned)inquireKeyId != value.second.id) {
29704f66 1033 continue;
38809e97 1034 }
24afabad 1035
b6bd795c 1036 string keyType;
60b0a236 1037 switch (value.second.keyType) {
b6bd795c
PL
1038 case DNSSECKeeper::KSK: keyType="ksk"; break;
1039 case DNSSECKeeper::ZSK: keyType="zsk"; break;
1040 case DNSSECKeeper::CSK: keyType="csk"; break;
1041 }
1042
24afabad 1043 Json::object key {
997cab68
BZ
1044 { "type", "Cryptokey" },
1045 { "id", (int)value.second.id },
1046 { "active", value.second.active },
33918299 1047 { "published", value.second.published },
997cab68
BZ
1048 { "keytype", keyType },
1049 { "flags", (uint16_t)value.first.d_flags },
5d9c6182
PL
1050 { "dnskey", value.first.getDNSKEY().getZoneRepresentation() },
1051 { "algorithm", DNSSECKeeper::algorithm2name(value.first.d_algorithm) },
1052 { "bits", value.first.getKey()->getBits() }
24afabad
CH
1053 };
1054
b6bd795c 1055 if (value.second.keyType == DNSSECKeeper::KSK || value.second.keyType == DNSSECKeeper::CSK) {
24afabad 1056 Json::array dses;
690b86b7 1057 for(const uint8_t keyid : { DNSSECKeeper::DIGEST_SHA1, DNSSECKeeper::DIGEST_SHA256, DNSSECKeeper::DIGEST_GOST, DNSSECKeeper::DIGEST_SHA384 })
997cab68
BZ
1058 try {
1059 dses.push_back(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), keyid).getZoneRepresentation());
1060 } catch (...) {}
24afabad 1061 key["ds"] = dses;
4b7f120a 1062 }
29704f66
CH
1063
1064 if (inquireSingleKey) {
1065 key["privatekey"] = value.first.getKey()->convertToISC();
1066 resp->setBody(key);
1067 return;
1068 }
24afabad 1069 doc.push_back(key);
4b7f120a
MS
1070 }
1071
29704f66
CH
1072 if (inquireSingleKey) {
1073 // we came here because we couldn't find the requested key.
1074 throw HttpNotFoundException();
1075 }
4b7f120a 1076 resp->setBody(doc);
997cab68
BZ
1077
1078}
1079
1080/*
1081 * This method handles DELETE requests for URL /api/v1/servers/:server_id/zones/:zone_name/cryptokeys/:cryptokey_id .
1082 * It deletes a key from :zone_name specified by :cryptokey_id.
1083 * Server Answers:
60b0a236 1084 * Case 1: the backend returns true on removal. This means the key is gone.
75191dc4 1085 * The server returns 204 No Content, no body.
955cbfd0 1086 * Case 2: the backend returns false on removal. An error occurred.
75191dc4
PL
1087 * The server returns 422 Unprocessable Entity with message "Could not DELETE :cryptokey_id".
1088 * Case 3: the key or zone does not exist.
1089 * The server returns 404 Not Found
997cab68 1090 * */
60b0a236
BZ
1091static void apiZoneCryptokeysDELETE(DNSName zonename, int inquireKeyId, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
1092 if (dk->removeKey(zonename, inquireKeyId)) {
1093 resp->body = "";
75191dc4 1094 resp->status = 204;
997cab68
BZ
1095 } else {
1096 resp->setErrorResult("Could not DELETE " + req->parameters["key_id"], 422);
1097 }
1098}
1099
1100/*
1101 * This method adds a key to a zone by generate it or content parameter.
1102 * Parameter:
1103 * {
5d9c6182 1104 * "privatekey" : "key The format used is compatible with BIND and NSD/LDNS" <string>
997cab68
BZ
1105 * "keytype" : "ksk|zsk" <string>
1106 * "active" : "true|false" <value>
5d9c6182 1107 * "algorithm" : "key generation algorithm name as default"<string> https://doc.powerdns.com/md/authoritative/dnssec/#supported-algorithms
997cab68
BZ
1108 * "bits" : number of bits <int>
1109 * }
1110 *
1111 * Response:
1112 * Case 1: keytype isn't ksk|zsk
1113 * The server returns 422 Unprocessable Entity {"error" : "Invalid keytype 'keytype'"}
60b0a236
BZ
1114 * Case 2: 'bits' must be a positive integer value.
1115 * The server returns 422 Unprocessable Entity {"error" : "'bits' must be a positive integer value."}
5d9c6182 1116 * Case 3: The "algorithm" isn't supported
997cab68 1117 * The server returns 422 Unprocessable Entity {"error" : "Unknown algorithm: 'algo'"}
60b0a236 1118 * Case 4: Algorithm <= 10 and no bits were passed
997cab68 1119 * The server returns 422 Unprocessable Entity {"error" : "Creating an algorithm algo key requires the size (in bits) to be passed"}
60b0a236
BZ
1120 * Case 5: The wrong keysize was passed
1121 * The server returns 422 Unprocessable Entity {"error" : "The algorithm does not support the given bit size."}
1122 * Case 6: If the server cant guess the keysize
1123 * The server returns 422 Unprocessable Entity {"error" : "Can not guess key size for algorithm"}
1124 * Case 7: The key-creation failed
997cab68 1125 * The server returns 422 Unprocessable Entity {"error" : "Adding key failed, perhaps DNSSEC not enabled in configuration?"}
60b0a236
BZ
1126 * Case 8: The key in content has the wrong format
1127 * The server returns 422 Unprocessable Entity {"error" : "Key could not be parsed. Make sure your key format is correct."}
1128 * Case 9: The wrong combination of fields is submitted
1129 * The server returns 422 Unprocessable Entity {"error" : "Either you submit just the 'content' field or you leave 'content' empty and submit the other fields."}
1130 * Case 10: No content and everything was fine
1131 * The server returns 201 Created and all public data about the new cryptokey
1132 * Case 11: With specified content
1133 * The server returns 201 Created and all public data about the added cryptokey
997cab68
BZ
1134 */
1135
60b0a236 1136static void apiZoneCryptokeysPOST(DNSName zonename, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
997cab68 1137 auto document = req->json();
5d9c6182
PL
1138 string privatekey_fieldname = "privatekey";
1139 auto privatekey = document["privatekey"];
1140 if (privatekey.is_null()) {
1141 // Fallback to the old "content" behaviour
1142 privatekey = document["content"];
1143 privatekey_fieldname = "content";
1144 }
997cab68 1145 bool active = boolFromJson(document, "active", false);
33918299 1146 bool published = boolFromJson(document, "published", true);
997cab68 1147 bool keyOrZone;
60b0a236 1148
eefd15b3 1149 if (stringFromJson(document, "keytype") == "ksk" || stringFromJson(document, "keytype") == "csk") {
997cab68
BZ
1150 keyOrZone = true;
1151 } else if (stringFromJson(document, "keytype") == "zsk") {
1152 keyOrZone = false;
1153 } else {
1154 throw ApiException("Invalid keytype " + stringFromJson(document, "keytype"));
1155 }
1156
b727e19b 1157 int64_t insertedId = -1;
997cab68 1158
5d9c6182 1159 if (privatekey.is_null()) {
43215ca6 1160 int bits = keyOrZone ? ::arg().asNum("default-ksk-size") : ::arg().asNum("default-zsk-size");
60b0a236
BZ
1161 auto docbits = document["bits"];
1162 if (!docbits.is_null()) {
1163 if (!docbits.is_number() || (fmod(docbits.number_value(), 1.0) != 0) || docbits.int_value() < 0) {
1164 throw ApiException("'bits' must be a positive integer value");
1165 } else {
1166 bits = docbits.int_value();
1167 }
1168 }
43215ca6 1169 int algorithm = DNSSECKeeper::shorthand2algorithm(keyOrZone ? ::arg()["default-ksk-algorithm"] : ::arg()["default-zsk-algorithm"]);
5d9c6182 1170 auto providedAlgo = document["algorithm"];
997cab68 1171 if (providedAlgo.is_string()) {
60b0a236
BZ
1172 algorithm = DNSSECKeeper::shorthand2algorithm(providedAlgo.string_value());
1173 if (algorithm == -1)
997cab68 1174 throw ApiException("Unknown algorithm: " + providedAlgo.string_value());
997cab68
BZ
1175 } else if (providedAlgo.is_number()) {
1176 algorithm = providedAlgo.int_value();
60b0a236
BZ
1177 } else if (!providedAlgo.is_null()) {
1178 throw ApiException("Unknown algorithm: " + providedAlgo.string_value());
997cab68
BZ
1179 }
1180
60b0a236 1181 try {
33918299 1182 if (!dk->addKey(zonename, keyOrZone, algorithm, insertedId, bits, active, published)) {
b727e19b
RG
1183 throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
1184 }
60b0a236 1185 } catch (std::runtime_error& error) {
997cab68
BZ
1186 throw ApiException(error.what());
1187 }
997cab68
BZ
1188 if (insertedId < 0)
1189 throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
5d9c6182
PL
1190 } else if (document["bits"].is_null() && document["algorithm"].is_null()) {
1191 auto keyData = stringFromJson(document, privatekey_fieldname);
997cab68
BZ
1192 DNSKEYRecordContent dkrc;
1193 DNSSECPrivateKey dpk;
60b0a236 1194 try {
997cab68
BZ
1195 shared_ptr<DNSCryptoKeyEngine> dke(DNSCryptoKeyEngine::makeFromISCString(dkrc, keyData));
1196 dpk.d_algorithm = dkrc.d_algorithm;
5d9c6182 1197 // TODO remove in 4.2.0
902c4e9c
CH
1198 if(dpk.d_algorithm == DNSSECKeeper::RSASHA1NSEC3SHA1)
1199 dpk.d_algorithm = DNSSECKeeper::RSASHA1;
997cab68
BZ
1200
1201 if (keyOrZone)
1202 dpk.d_flags = 257;
1203 else
1204 dpk.d_flags = 256;
1205
1206 dpk.setKey(dke);
997cab68 1207 }
60b0a236
BZ
1208 catch (std::runtime_error& error) {
1209 throw ApiException("Key could not be parsed. Make sure your key format is correct.");
1210 } try {
33918299 1211 if (!dk->addKey(zonename, dpk,insertedId, active, published)) {
b727e19b
RG
1212 throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
1213 }
60b0a236 1214 } catch (std::runtime_error& error) {
997cab68
BZ
1215 throw ApiException(error.what());
1216 }
1217 if (insertedId < 0)
1218 throw ApiException("Adding key failed, perhaps DNSSEC not enabled in configuration?");
60b0a236 1219 } else {
5d9c6182 1220 throw ApiException("Either you submit just the 'privatekey' field or you leave 'privatekey' empty and submit the other fields.");
997cab68 1221 }
60b0a236 1222 apiZoneCryptokeysGET(zonename, insertedId, resp, dk);
997cab68 1223 resp->status = 201;
60b0a236 1224}
997cab68
BZ
1225
1226/*
1227 * This method handles PUT (execute) requests for URL /api/v1/servers/:server_id/zones/:zone_name/cryptokeys/:cryptokey_id .
1228 * It de/activates a key from :zone_name specified by :cryptokey_id.
1229 * Server Answers:
60b0a236 1230 * Case 1: invalid JSON data
997cab68 1231 * The server returns 400 Bad Request
60b0a236
BZ
1232 * Case 2: the backend returns true on de/activation. This means the key is de/active.
1233 * The server returns 204 No Content
955cbfd0 1234 * Case 3: the backend returns false on de/activation. An error occurred.
997cab68
BZ
1235 * The sever returns 422 Unprocessable Entity with message "Could not de/activate Key: :cryptokey_id in Zone: :zone_name"
1236 * */
60b0a236 1237static void apiZoneCryptokeysPUT(DNSName zonename, int inquireKeyId, HttpRequest *req, HttpResponse *resp, DNSSECKeeper *dk) {
997cab68
BZ
1238 //throws an exception if the Body is empty
1239 auto document = req->json();
1240 //throws an exception if the key does not exist or is not a bool
1241 bool active = boolFromJson(document, "active");
33918299 1242 bool published = boolFromJson(document, "published", true);
60b0a236
BZ
1243 if (active) {
1244 if (!dk->activateKey(zonename, inquireKeyId)) {
997cab68
BZ
1245 resp->setErrorResult("Could not activate Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
1246 return;
1247 }
1248 } else {
60b0a236 1249 if (!dk->deactivateKey(zonename, inquireKeyId)) {
997cab68
BZ
1250 resp->setErrorResult("Could not deactivate Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
1251 return;
1252 }
1253 }
33918299
RG
1254
1255 if (published) {
1256 if (!dk->publishKey(zonename, inquireKeyId)) {
1257 resp->setErrorResult("Could not publish Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
1258 return;
1259 }
1260 } else {
1261 if (!dk->unpublishKey(zonename, inquireKeyId)) {
1262 resp->setErrorResult("Could not unpublish Key: " + req->parameters["key_id"] + " in Zone: " + zonename.toString(), 422);
1263 return;
1264 }
1265 }
1266
60b0a236
BZ
1267 resp->body = "";
1268 resp->status = 204;
1269 return;
997cab68
BZ
1270}
1271
1272/*
1273 * This method chooses the right functionality for the request. It also checks for a cryptokey_id which has to be passed
1274 * by URL /api/v1/servers/:server_id/zones/:zone_name/cryptokeys/:cryptokey_id .
1275 * If the the HTTP-request-method isn't supported, the function returns a response with the 405 code (method not allowed).
1276 * */
1277static void apiZoneCryptokeys(HttpRequest *req, HttpResponse *resp) {
1278 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
1279
60b0a236
BZ
1280 UeberBackend B;
1281 DNSSECKeeper dk(&B);
1282 DomainInfo di;
77bfe8de
PL
1283 if (!B.getDomainInfo(zonename, di)) {
1284 throw HttpNotFoundException();
1285 }
60b0a236 1286
997cab68
BZ
1287 int inquireKeyId = -1;
1288 if (req->parameters.count("key_id")) {
1289 inquireKeyId = std::stoi(req->parameters["key_id"]);
71de13d7 1290 apiZoneCryptoKeysCheckKeyExists(zonename, inquireKeyId, &dk);
997cab68
BZ
1291 }
1292
1293 if (req->method == "GET") {
60b0a236 1294 apiZoneCryptokeysGET(zonename, inquireKeyId, resp, &dk);
2054afbb 1295 } else if (req->method == "DELETE") {
60b0a236
BZ
1296 if (inquireKeyId == -1)
1297 throw HttpBadRequestException();
1298 apiZoneCryptokeysDELETE(zonename, inquireKeyId, req, resp, &dk);
2054afbb 1299 } else if (req->method == "POST") {
60b0a236 1300 apiZoneCryptokeysPOST(zonename, req, resp, &dk);
2054afbb 1301 } else if (req->method == "PUT") {
60b0a236
BZ
1302 if (inquireKeyId == -1)
1303 throw HttpBadRequestException();
1304 apiZoneCryptokeysPUT(zonename, inquireKeyId, req, resp, &dk);
997cab68
BZ
1305 } else {
1306 throw HttpMethodNotAllowedException(); //Returns method not allowed
1307 }
4b7f120a
MS
1308}
1309
1f68b185 1310static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResourceRecord>& new_records, DNSName zonename) {
0f0e73fe
MS
1311 DNSResourceRecord rr;
1312 vector<string> zonedata;
1f68b185 1313 stringtok(zonedata, zonestring, "\r\n");
0f0e73fe
MS
1314
1315 ZoneParserTNG zpt(zonedata, zonename);
ba3d53d1 1316 zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
0f0e73fe
MS
1317
1318 bool seenSOA=false;
1319
1320 string comment = "Imported via the API";
1321
1322 try {
1323 while(zpt.get(rr, &comment)) {
1324 if(seenSOA && rr.qtype.getCode() == QType::SOA)
1325 continue;
1326 if(rr.qtype.getCode() == QType::SOA)
1327 seenSOA=true;
24ded6cc 1328 validateGatheredRRType(rr);
0f0e73fe 1329
0f0e73fe
MS
1330 new_records.push_back(rr);
1331 }
1332 }
1333 catch(std::exception& ae) {
1af62161 1334 throw ApiException("An error occurred while parsing the zonedata: "+string(ae.what()));
0f0e73fe
MS
1335 }
1336}
1337
ef2ea4bf 1338/** Throws ApiException if records which violate RRset constraints are present.
e3675a8a 1339 * NOTE: sorts records in-place.
646bcd7d
CH
1340 *
1341 * Constraints being checked:
1342 * *) no exact duplicates
1343 * *) no duplicates for QTypes that can only be present once per RRset
1344 * *) hostnames are hostnames
e3675a8a 1345 */
646bcd7d 1346static void checkNewRecords(vector<DNSResourceRecord>& records) {
e3675a8a
CH
1347 sort(records.begin(), records.end(),
1348 [](const DNSResourceRecord& rec_a, const DNSResourceRecord& rec_b) -> bool {
f2d6dcc0
RG
1349 /* we need _strict_ weak ordering */
1350 return std::tie(rec_a.qname, rec_a.qtype, rec_a.content) < std::tie(rec_b.qname, rec_b.qtype, rec_b.content);
e3675a8a
CH
1351 }
1352 );
646bcd7d 1353
e3675a8a
CH
1354 DNSResourceRecord previous;
1355 for(const auto& rec : records) {
646bcd7d
CH
1356 if (previous.qname == rec.qname) {
1357 if (previous.qtype == rec.qtype) {
1358 if (onlyOneEntryTypes.count(rec.qtype.getCode()) != 0) {
1359 throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.getName()+" has more than one record");
1360 }
1361 if (previous.content == rec.content) {
1362 throw ApiException("Duplicate record in RRset " + rec.qname.toString() + " IN " + rec.qtype.getName() + " with content \"" + rec.content + "\"");
1363 }
1364 } else if (exclusiveEntryTypes.count(rec.qtype.getCode()) != 0 || exclusiveEntryTypes.count(previous.qtype.getCode()) != 0) {
1365 throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.getName()+": Conflicts with another RRset");
1366 }
1367 }
1368
1369 // Check if the DNSNames that should be hostnames, are hostnames
1370 try {
1371 checkHostnameCorrectness(rec);
1372 } catch (const std::exception& e) {
1373 throw ApiException("RRset "+rec.qname.toString()+" IN "+rec.qtype.getName() + " " + e.what());
e3675a8a 1374 }
646bcd7d 1375
e3675a8a
CH
1376 previous = rec;
1377 }
1378}
1379
6f16f97c
PL
1380static void checkTSIGKey(UeberBackend& B, const DNSName& keyname, const DNSName& algo, const string& content) {
1381 DNSName algoFromDB;
1382 string contentFromDB;
1383 B.getTSIGKey(keyname, &algoFromDB, &contentFromDB);
1384 if (!contentFromDB.empty() || !algoFromDB.empty()) {
f7b99555 1385 throw HttpConflictException("A TSIG key with the name '"+keyname.toLogString()+"' already exists");
fa07a3cf
PL
1386 }
1387
6f16f97c
PL
1388 TSIGHashEnum the;
1389 if (!getTSIGHashEnum(algo, the)) {
1390 throw ApiException("Unknown TSIG algorithm: " + algo.toLogString());
1391 }
fa07a3cf 1392
6f16f97c
PL
1393 string b64out;
1394 if (B64Decode(content, b64out) == -1) {
1395 throw ApiException("TSIG content '" + content + "' cannot be base64-decoded");
fa07a3cf 1396 }
6f16f97c 1397}
fa07a3cf 1398
6f16f97c
PL
1399static Json::object makeJSONTSIGKey(const DNSName& keyname, const DNSName& algo, const string& content) {
1400 Json::object tsigkey = {
1401 { "name", keyname.toStringNoDot() },
1402 { "id", apiZoneNameToId(keyname) },
1403 { "algorithm", algo.toStringNoDot() },
1404 { "key", content },
1405 { "type", "TSIGKey" }
1406 };
1407 return tsigkey;
1408}
fa07a3cf 1409
519a6288
PL
1410static Json::object makeJSONTSIGKey(const struct TSIGKey& key, bool doContent=true) {
1411 return makeJSONTSIGKey(key.name, key.algorithm, doContent ? key.key : "");
6f16f97c
PL
1412}
1413
f711ef76 1414static void apiServerTSIGKeys(HttpRequest* req, HttpResponse* resp) {
6f16f97c
PL
1415 UeberBackend B;
1416 if (req->method == "GET") {
1417 vector<struct TSIGKey> keys;
1418
1419 if (!B.getTSIGKeys(keys)) {
8204102e 1420 throw HttpInternalServerErrorException("Unable to retrieve TSIG keys");
6f16f97c
PL
1421 }
1422
1423 Json::array doc;
1424
1425 for(const auto &key : keys) {
519a6288 1426 doc.push_back(makeJSONTSIGKey(key, false));
6f16f97c
PL
1427 }
1428 resp->setBody(doc);
9c69eb3f 1429 } else if (req->method == "POST") {
6f16f97c
PL
1430 auto document = req->json();
1431 DNSName keyname(stringFromJson(document, "name"));
1432 DNSName algo(stringFromJson(document, "algorithm"));
8204102e 1433 string content = document["key"].string_value();
6f16f97c
PL
1434
1435 if (content.empty()) {
ac5298aa
PL
1436 try {
1437 content = makeTSIGKey(algo);
1438 } catch (const PDNSException& e) {
1439 throw HttpBadRequestException(e.reason);
6f16f97c 1440 }
6f16f97c
PL
1441 }
1442
f7b99555 1443 // Will throw an ApiException or HttpConflictException on error
6f16f97c
PL
1444 checkTSIGKey(B, keyname, algo, content);
1445
1446 if(!B.setTSIGKey(keyname, algo, content)) {
8204102e 1447 throw HttpInternalServerErrorException("Unable to add TSIG key");
6f16f97c
PL
1448 }
1449
1450 resp->status = 201;
1451 resp->setBody(makeJSONTSIGKey(keyname, algo, content));
81c39bc6
PL
1452 } else {
1453 throw HttpMethodNotAllowedException();
6f16f97c 1454 }
fa07a3cf
PL
1455}
1456
f711ef76 1457static void apiServerTSIGKeyDetail(HttpRequest* req, HttpResponse* resp) {
fa07a3cf 1458 UeberBackend B;
81c39bc6 1459 DNSName keyname = apiZoneIdToName(req->parameters["id"]);
fa07a3cf
PL
1460 DNSName algo;
1461 string content;
1462
1463 if (!B.getTSIGKey(keyname, &algo, &content)) {
8204102e 1464 throw HttpNotFoundException("TSIG key with name '"+keyname.toLogString()+"' not found");
fa07a3cf
PL
1465 }
1466
81c39bc6
PL
1467 struct TSIGKey tsk;
1468 tsk.name = keyname;
1469 tsk.algorithm = algo;
1470 tsk.key = content;
1471
1472 if (req->method == "GET") {
1473 resp->setBody(makeJSONTSIGKey(tsk));
9c69eb3f 1474 } else if (req->method == "PUT") {
097370b2
PL
1475 json11::Json document;
1476 if (!req->body.empty()) {
1477 document = req->json();
1478 }
81c39bc6
PL
1479 if (document["name"].is_string()) {
1480 tsk.name = DNSName(document["name"].string_value());
1481 }
1482 if (document["algorithm"].is_string()) {
1483 tsk.algorithm = DNSName(document["algorithm"].string_value());
1484
1485 TSIGHashEnum the;
1486 if (!getTSIGHashEnum(tsk.algorithm, the)) {
1487 throw ApiException("Unknown TSIG algorithm: " + tsk.algorithm.toLogString());
1488 }
1489 }
1490 if (document["key"].is_string()) {
1491 string new_content = document["key"].string_value();
1492 string decoded;
1493 if (B64Decode(new_content, decoded) == -1) {
1494 throw ApiException("Can not base64 decode key content '" + new_content + "'");
1495 }
1496 tsk.key = new_content;
1497 }
1498 if (!B.setTSIGKey(tsk.name, tsk.algorithm, tsk.key)) {
8204102e 1499 throw HttpInternalServerErrorException("Unable to save TSIG Key");
81c39bc6
PL
1500 }
1501 if (tsk.name != keyname) {
1502 // Remove the old key
1503 if (!B.deleteTSIGKey(keyname)) {
8204102e 1504 throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'");
81c39bc6
PL
1505 }
1506 }
1507 resp->setBody(makeJSONTSIGKey(tsk));
9c69eb3f 1508 } else if (req->method == "DELETE") {
81c39bc6 1509 if (!B.deleteTSIGKey(keyname)) {
8204102e 1510 throw HttpInternalServerErrorException("Unable to remove TSIG key '" + keyname.toStringNoDot() + "'");
81c39bc6
PL
1511 } else {
1512 resp->body = "";
1513 resp->status = 204;
1514 }
1515 } else {
1516 throw HttpMethodNotAllowedException();
1517 }
fa07a3cf
PL
1518}
1519
80d59cd1 1520static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
e2dba705 1521 UeberBackend B;
53942520 1522 DNSSECKeeper dk(&B);
2054afbb 1523 if (req->method == "POST") {
e2dba705 1524 DomainInfo di;
1f68b185 1525 auto document = req->json();
c576d0c5 1526 DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
1d6b70f9 1527 apiCheckNameAllowedCharacters(zonename.toString());
e3675a8a 1528 zonename.makeUsLowerCase();
4ebf78b1 1529
1d6b70f9 1530 bool exists = B.getDomainInfo(zonename, di);
e2dba705 1531 if(exists)
331d3062 1532 throw HttpConflictException();
e2dba705 1533
bb9fd223 1534 // validate 'kind' is set
4bdff352 1535 DomainInfo::DomainKind zonekind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
bb9fd223 1536
6754ef71
CH
1537 string zonestring = document["zone"].string_value();
1538 auto rrsets = document["rrsets"];
1539 if (rrsets.is_array() && zonestring != "")
1540 throw ApiException("You cannot give rrsets AND zone data as text");
0f0e73fe 1541
1f68b185 1542 auto nameservers = document["nameservers"];
6fbecaef
PL
1543 if (!nameservers.is_null() && !nameservers.is_array() && zonekind != DomainInfo::Slave)
1544 throw ApiException("Nameservers is not a list");
e2dba705 1545
f63168e6 1546 string soa_edit_api_kind;
1f68b185
CH
1547 if (document["soa_edit_api"].is_string()) {
1548 soa_edit_api_kind = document["soa_edit_api"].string_value();
a6448d95
CH
1549 }
1550 else {
1551 soa_edit_api_kind = "DEFAULT";
1552 }
1f68b185 1553 string soa_edit_kind = document["soa_edit"].string_value();
e90b4e38 1554
f63168e6
CH
1555 // if records/comments are given, load and check them
1556 bool have_soa = false;
33e6c3e9 1557 bool have_zone_ns = false;
f63168e6
CH
1558 vector<DNSResourceRecord> new_records;
1559 vector<Comment> new_comments;
1560 vector<DNSResourceRecord> new_ptrs;
0f0e73fe 1561
6754ef71
CH
1562 if (rrsets.is_array()) {
1563 for (const auto& rrset : rrsets.array_items()) {
1564 DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
1565 apiCheckQNameAllowedCharacters(qname.toString());
1566 QType qtype;
1567 qtype = stringFromJson(rrset, "type");
1568 if (qtype.getCode() == 0) {
1569 throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
1570 }
1571 if (rrset["records"].is_array()) {
1572 int ttl = intFromJson(rrset, "ttl");
311cb9a2 1573 gatherRecords(B, req->logprefix, rrset, qname, qtype, ttl, new_records, new_ptrs);
6754ef71
CH
1574 }
1575 if (rrset["comments"].is_array()) {
1576 gatherComments(rrset, qname, qtype, new_comments);
1577 }
1578 }
0f0e73fe 1579 } else if (zonestring != "") {
1f68b185 1580 gatherRecordsFromZone(zonestring, new_records, zonename);
0f0e73fe
MS
1581 }
1582
1f68b185 1583 for(auto& rr : new_records) {
e3675a8a 1584 rr.qname.makeUsLowerCase();
1d6b70f9 1585 if (!rr.qname.isPartOf(zonename) && rr.qname != zonename)
561434a6 1586 throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": Name is out of zone");
cb9b5901 1587 apiCheckQNameAllowedCharacters(rr.qname.toString());
f63168e6 1588
1d6b70f9 1589 if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
f63168e6 1590 have_soa = true;
a6448d95 1591 increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
f63168e6 1592 }
33e6c3e9
CH
1593 if (rr.qtype.getCode() == QType::NS && rr.qname==zonename) {
1594 have_zone_ns = true;
1595 }
f63168e6 1596 }
f7bfeb30
CH
1597
1598 // synthesize RRs as needed
1599 DNSResourceRecord autorr;
1d6b70f9 1600 autorr.qname = zonename;
f7bfeb30
CH
1601 autorr.auth = 1;
1602 autorr.ttl = ::arg().asNum("default-ttl");
e2dba705 1603
4de11a54 1604 if (!have_soa && zonekind != DomainInfo::Slave) {
f63168e6 1605 // synthesize a SOA record so the zone "really" exists
13f9e280 1606 string soa = (boost::format("%s %s %ul")
1d6b70f9
CH
1607 % ::arg()["default-soa-name"]
1608 % (::arg().isEmpty("default-soa-mail") ? (DNSName("hostmaster.") + zonename).toString() : ::arg()["default-soa-mail"])
1f68b185 1609 % document["serial"].int_value()
1d6b70f9 1610 ).str();
f63168e6 1611 SOAData sd;
1d6b70f9 1612 fillSOAData(soa, sd); // fills out default values for us
13f9e280
CH
1613 autorr.qtype = QType::SOA;
1614 autorr.content = makeSOAContent(sd)->getZoneRepresentation(true);
f7bfeb30
CH
1615 increaseSOARecord(autorr, soa_edit_api_kind, soa_edit_kind);
1616 new_records.push_back(autorr);
f63168e6
CH
1617 }
1618
1619 // create NS records if nameservers are given
1f68b185
CH
1620 for (auto value : nameservers.array_items()) {
1621 string nameserver = value.string_value();
1622 if (nameserver.empty())
1623 throw ApiException("Nameservers must be non-empty strings");
1624 if (!isCanonical(nameserver))
1625 throw ApiException("Nameserver is not canonical: '" + nameserver + "'");
1626 try {
1627 // ensure the name parses
8f955653 1628 autorr.content = DNSName(nameserver).toStringRootDot();
1f68b185
CH
1629 } catch (...) {
1630 throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "'");
4bdff352 1631 }
13f9e280 1632 autorr.qtype = QType::NS;
1f68b185 1633 new_records.push_back(autorr);
33e6c3e9
CH
1634 if (have_zone_ns) {
1635 throw ApiException("Nameservers list MUST NOT be mixed with zone-level NS in rrsets");
1636 }
e2dba705
CH
1637 }
1638
646bcd7d 1639 checkNewRecords(new_records);
e3675a8a 1640
986e4858
PL
1641 if (boolFromJson(document, "dnssec", false)) {
1642 checkDefaultDNSSECAlgos();
1643
1644 if(document["nsec3param"].string_value().length() > 0) {
1645 NSEC3PARAMRecordContent ns3pr(document["nsec3param"].string_value());
1646 string error_msg = "";
1647 if (!dk.checkNSEC3PARAM(ns3pr, error_msg)) {
1648 throw ApiException("NSEC3PARAMs provided for zone '"+zonename.toString()+"' are invalid. " + error_msg);
1649 }
1650 }
1651 }
1652
f63168e6 1653 // no going back after this
1d6b70f9
CH
1654 if(!B.createDomain(zonename))
1655 throw ApiException("Creating domain '"+zonename.toString()+"' failed");
f63168e6 1656
1d6b70f9
CH
1657 if(!B.getDomainInfo(zonename, di))
1658 throw ApiException("Creating domain '"+zonename.toString()+"' failed: lookup of domain ID failed");
f63168e6 1659
f43646f5
PD
1660 di.backend->startTransaction(zonename, di.id);
1661
9440a9f0
CH
1662 // updateDomainSettingsFromDocument does NOT fill out the default we've established above.
1663 if (!soa_edit_api_kind.empty()) {
1664 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
1665 }
1666
abb873ee 1667 for(auto rr : new_records) {
f63168e6 1668 rr.domain_id = di.id;
c9b43446 1669 di.backend->feedRecord(rr, DNSName());
e2dba705 1670 }
1d6b70f9 1671 for(Comment& c : new_comments) {
f63168e6
CH
1672 c.domain_id = di.id;
1673 di.backend->feedComment(c);
1674 }
e2dba705 1675
f43646f5 1676 updateDomainSettingsFromDocument(B, di, zonename, document, false);
e2dba705 1677
f63168e6
CH
1678 di.backend->commitTransaction();
1679
3fe7c7d6
CH
1680 storeChangedPTRs(B, new_ptrs);
1681
311cb9a2 1682 fillZone(B, zonename, resp, shouldDoRRSets(req));
64a36f0d 1683 resp->status = 201;
e2dba705
CH
1684 return;
1685 }
1686
c67bf8c5
CH
1687 if(req->method != "GET")
1688 throw HttpMethodNotAllowedException();
1689
c67bf8c5 1690 vector<DomainInfo> domains;
e543cc8f 1691
37e01df4
CH
1692 if (req->getvars.count("zone")) {
1693 string zone = req->getvars["zone"];
e543cc8f
CH
1694 apiCheckNameAllowedCharacters(zone);
1695 DNSName zonename = apiNameToDNSName(zone);
1696 zonename.makeUsLowerCase();
1697 DomainInfo di;
1698 if (B.getDomainInfo(zonename, di)) {
1699 domains.push_back(di);
1700 }
1701 } else {
72abd9ef
PL
1702 try {
1703 B.getAllDomains(&domains, true); // incl. disabled
1704 } catch(const PDNSException &e) {
1705 throw HttpInternalServerErrorException("Could not retrieve all domain information: " + e.reason);
1706 }
e543cc8f 1707 }
c67bf8c5 1708
07a32d18
CH
1709 bool with_dnssec = true;
1710 if (req->getvars.count("dnssec")) {
1711 // can send ?dnssec=false to improve performance.
1712 string dnssec_flag = req->getvars["dnssec"];
1713 if (dnssec_flag == "false") {
1714 with_dnssec = false;
1715 }
1716 }
1717
62a9a74c 1718 Json::array doc;
c8b929d9 1719 doc.reserve(domains.size());
62a9a74c 1720 for(const DomainInfo& di : domains) {
07a32d18 1721 doc.push_back(getZoneInfo(di, with_dnssec ? &dk : nullptr));
c67bf8c5 1722 }
669822d0 1723 resp->setBody(doc);
c67bf8c5
CH
1724}
1725
05776d2f 1726static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
290a083d 1727 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
05776d2f 1728
77bfe8de
PL
1729 UeberBackend B;
1730 DomainInfo di;
27efa4be
PL
1731 try {
1732 if (!B.getDomainInfo(zonename, di)) {
1733 throw HttpNotFoundException();
1734 }
1735 } catch(const PDNSException &e) {
1736 throw HttpInternalServerErrorException("Could not retrieve Domain Info: " + e.reason);
77bfe8de
PL
1737 }
1738
2054afbb 1739 if(req->method == "PUT") {
7c0ba3d2 1740 // update domain settings
7c0ba3d2 1741
f43646f5
PD
1742 di.backend->startTransaction(zonename, -1);
1743 updateDomainSettingsFromDocument(B, di, zonename, req->json(), false);
1744 di.backend->commitTransaction();
7c0ba3d2 1745
f0e76cee
CH
1746 resp->body = "";
1747 resp->status = 204; // No Content, but indicate success
7c0ba3d2
CH
1748 return;
1749 }
2054afbb 1750 else if(req->method == "DELETE") {
a462a01d 1751 // delete domain
a462a01d 1752 if(!di.backend->deleteDomain(zonename))
290a083d 1753 throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
a462a01d 1754
5a4fa722 1755 // clear caches
40b3959a 1756 DNSSECKeeper::clearCaches(zonename);
318434b7 1757 purgeAuthCaches(zonename.toString() + "$");
5a4fa722 1758
a462a01d
CH
1759 // empty body on success
1760 resp->body = "";
37663c3b 1761 resp->status = 204; // No Content: declare that the zone is gone now
a462a01d 1762 return;
2054afbb 1763 } else if (req->method == "PATCH") {
311cb9a2 1764 patchZone(B, req, resp);
6cc98ddf
CH
1765 return;
1766 } else if (req->method == "GET") {
311cb9a2 1767 fillZone(B, zonename, resp, shouldDoRRSets(req));
6cc98ddf 1768 return;
a462a01d 1769 }
6cc98ddf 1770 throw HttpMethodNotAllowedException();
05776d2f
CH
1771}
1772
a83004d3 1773static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
290a083d 1774 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a83004d3
CH
1775
1776 if(req->method != "GET")
1777 throw HttpMethodNotAllowedException();
1778
1779 ostringstream ss;
1780
1781 UeberBackend B;
1782 DomainInfo di;
77bfe8de
PL
1783 if (!B.getDomainInfo(zonename, di)) {
1784 throw HttpNotFoundException();
1785 }
a83004d3
CH
1786
1787 DNSResourceRecord rr;
1788 SOAData sd;
1789 di.backend->list(zonename, di.id);
1790 while(di.backend->get(rr)) {
1791 if (!rr.qtype.getCode())
1792 continue; // skip empty non-terminals
1793
a83004d3 1794 ss <<
675fa24c 1795 rr.qname.toString() << "\t" <<
a83004d3 1796 rr.ttl << "\t" <<
dc420da6 1797 "IN" << "\t" <<
a83004d3 1798 rr.qtype.getName() << "\t" <<
1d6b70f9 1799 makeApiRecordContent(rr.qtype, rr.content) <<
a83004d3
CH
1800 endl;
1801 }
1802
1803 if (req->accept_json) {
41873e7c 1804 resp->setBody(Json::object { { "zone", ss.str() } });
a83004d3
CH
1805 } else {
1806 resp->headers["Content-Type"] = "text/plain; charset=us-ascii";
1807 resp->body = ss.str();
1808 }
1809}
1810
a426cb89 1811static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
290a083d 1812 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a426cb89 1813
2054afbb 1814 if(req->method != "PUT")
a426cb89
CH
1815 throw HttpMethodNotAllowedException();
1816
1817 UeberBackend B;
1818 DomainInfo di;
77bfe8de
PL
1819 if (!B.getDomainInfo(zonename, di)) {
1820 throw HttpNotFoundException();
1821 }
a426cb89
CH
1822
1823 if(di.masters.empty())
290a083d 1824 throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain (or has no master defined)");
a426cb89 1825
d720eb8a 1826 shuffle(di.masters.begin(), di.masters.end(), pdns::dns_random_engine());
a426cb89 1827 Communicator.addSuckRequest(zonename, di.masters.front());
9b0f144f 1828 resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front().toLogString());
a426cb89
CH
1829}
1830
1831static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) {
290a083d 1832 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a426cb89 1833
2054afbb 1834 if(req->method != "PUT")
a426cb89
CH
1835 throw HttpMethodNotAllowedException();
1836
1837 UeberBackend B;
1838 DomainInfo di;
77bfe8de
PL
1839 if (!B.getDomainInfo(zonename, di)) {
1840 throw HttpNotFoundException();
1841 }
a426cb89 1842
3497eb18 1843 if(!Communicator.notifyDomain(zonename, &B))
a426cb89
CH
1844 throw ApiException("Failed to add to the queue - see server log");
1845
692829aa 1846 resp->setSuccessResult("Notification queued");
a426cb89
CH
1847}
1848
4bc8379e
PL
1849static void apiServerZoneRectify(HttpRequest* req, HttpResponse* resp) {
1850 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
1851
1852 if(req->method != "PUT")
1853 throw HttpMethodNotAllowedException();
1854
1855 UeberBackend B;
1856 DomainInfo di;
77bfe8de
PL
1857 if (!B.getDomainInfo(zonename, di)) {
1858 throw HttpNotFoundException();
1859 }
4bc8379e
PL
1860
1861 DNSSECKeeper dk(&B);
1862
1863 if (!dk.isSecuredZone(zonename))
1864 throw ApiException("Zone '" + zonename.toString() + "' is not DNSSEC signed, not rectifying.");
1865
1866 if (di.kind == DomainInfo::Slave)
1867 throw ApiException("Zone '" + zonename.toString() + "' is a slave zone, not rectifying.");
1868
1869 string error_msg = "";
59102608
RG
1870 string info;
1871 if (!dk.rectifyZone(zonename, error_msg, info, true))
4bc8379e
PL
1872 throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
1873
1874 resp->setSuccessResult("Rectified");
1875}
1876
d1587ceb
CH
1877static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) {
1878 if (rr.qtype.getCode() == QType::A) {
1879 uint32_t ip;
1880 if (!IpToU32(rr.content, &ip)) {
1881 throw ApiException("PTR: Invalid IP address given");
1882 }
1d6b70f9 1883 ptr->qname = DNSName((boost::format("%u.%u.%u.%u.in-addr.arpa.")
d1587ceb
CH
1884 % ((ip >> 24) & 0xff)
1885 % ((ip >> 16) & 0xff)
1886 % ((ip >> 8) & 0xff)
1887 % ((ip ) & 0xff)
1d6b70f9 1888 ).str());
d1587ceb
CH
1889 } else if (rr.qtype.getCode() == QType::AAAA) {
1890 ComboAddress ca(rr.content);
5fb3aa58 1891 char buf[3];
d1587ceb 1892 ostringstream ss;
5fb3aa58
CH
1893 for (int octet = 0; octet < 16; ++octet) {
1894 if (snprintf(buf, sizeof(buf), "%02x", ca.sin6.sin6_addr.s6_addr[octet]) != (sizeof(buf)-1)) {
1895 // this should be impossible: no byte should give more than two digits in hex format
1896 throw PDNSException("Formatting IPv6 address failed");
1897 }
1898 ss << buf[0] << '.' << buf[1] << '.';
d1587ceb 1899 }
5fb3aa58
CH
1900 string tmp = ss.str();
1901 tmp.resize(tmp.size()-1); // remove last dot
1902 // reverse and append arpa domain
1d6b70f9 1903 ptr->qname = DNSName(string(tmp.rbegin(), tmp.rend())) + DNSName("ip6.arpa.");
d1587ceb 1904 } else {
675fa24c 1905 throw ApiException("Unsupported PTR source '" + rr.qname.toString() + "' type '" + rr.qtype.getName() + "'");
d1587ceb
CH
1906 }
1907
1908 ptr->qtype = "PTR";
1909 ptr->ttl = rr.ttl;
1910 ptr->disabled = rr.disabled;
8f955653 1911 ptr->content = rr.qname.toStringRootDot();
d1587ceb
CH
1912}
1913
995473c8
CH
1914static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptrs) {
1915 for(const DNSResourceRecord& rr : new_ptrs) {
995473c8 1916 SOAData sd;
cec52de6 1917 if (!B.getAuth(rr.qname, QType(QType::PTR), &sd, false))
995473c8
CH
1918 throw ApiException("Could not find domain for PTR '"+rr.qname.toString()+"' requested for '"+rr.content+"' (while saving)");
1919
1920 string soa_edit_api_kind;
1921 string soa_edit_kind;
1922 bool soa_changed = false;
1923 DNSResourceRecord soarr;
1924 sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT-API", soa_edit_api_kind);
1925 sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT", soa_edit_kind);
1926 if (!soa_edit_api_kind.empty()) {
13f9e280 1927 soa_changed = makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, soarr);
995473c8
CH
1928 }
1929
1930 sd.db->startTransaction(sd.qname);
1931 if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
1932 sd.db->abortTransaction();
1933 throw ApiException("PTR-Hosting backend for "+rr.qname.toString()+"/"+rr.qtype.getName()+" does not support editing records.");
1934 }
1935
1936 if (soa_changed) {
1937 sd.db->replaceRRSet(sd.domain_id, soarr.qname, soarr.qtype, vector<DNSResourceRecord>(1, soarr));
1938 }
1939
1940 sd.db->commitTransaction();
bf269e28 1941 purgeAuthCachesExact(rr.qname);
995473c8
CH
1942 }
1943}
1944
311cb9a2 1945static void patchZone(UeberBackend& B, HttpRequest* req, HttpResponse* resp) {
a41a789a
KM
1946 bool zone_disabled;
1947 SOAData sd;
b3905a3d 1948 DomainInfo di;
290a083d 1949 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
77bfe8de
PL
1950 if (!B.getDomainInfo(zonename, di)) {
1951 throw HttpNotFoundException();
1952 }
b3905a3d 1953
f63168e6
CH
1954 vector<DNSResourceRecord> new_records;
1955 vector<Comment> new_comments;
d708640f
CH
1956 vector<DNSResourceRecord> new_ptrs;
1957
1f68b185 1958 Json document = req->json();
b3905a3d 1959
1f68b185
CH
1960 auto rrsets = document["rrsets"];
1961 if (!rrsets.is_array())
d708640f 1962 throw ApiException("No rrsets given in update request");
b3905a3d 1963
d708640f 1964 di.backend->startTransaction(zonename);
6cc98ddf 1965
d708640f 1966 try {
d29d5db7 1967 string soa_edit_api_kind;
a6448d95 1968 string soa_edit_kind;
d29d5db7 1969 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
a6448d95 1970 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
d29d5db7
CH
1971 bool soa_edit_done = false;
1972
2b048cc7
PD
1973 set<pair<DNSName, QType>> seen;
1974
6754ef71
CH
1975 for (const auto& rrset : rrsets.array_items()) {
1976 string changetype = toUpper(stringFromJson(rrset, "changetype"));
c576d0c5 1977 DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
cb9b5901 1978 apiCheckQNameAllowedCharacters(qname.toString());
6754ef71 1979 QType qtype;
d708640f 1980 qtype = stringFromJson(rrset, "type");
6754ef71
CH
1981 if (qtype.getCode() == 0) {
1982 throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
1983 }
d708640f 1984
2b048cc7
PD
1985 if(seen.count({qname, qtype}))
1986 {
3216c7db 1987 throw ApiException("Duplicate RRset "+qname.toString()+" IN "+qtype.getName());
2b048cc7
PD
1988 }
1989 seen.insert({qname, qtype});
d708640f 1990
d708640f 1991 if (changetype == "DELETE") {
b7f21ab1 1992 // delete all matching qname/qtype RRs (and, implicitly comments).
d708640f
CH
1993 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
1994 throw ApiException("Hosting backend does not support editing records.");
6cc98ddf 1995 }
d708640f
CH
1996 }
1997 else if (changetype == "REPLACE") {
1d6b70f9 1998 // we only validate for REPLACE, as DELETE can be used to "fix" out of zone records.
e325f20c 1999 if (!qname.isPartOf(zonename) && qname != zonename)
edda67a2 2000 throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Name is out of zone");
34df6ecc 2001
6754ef71
CH
2002 bool replace_records = rrset["records"].is_array();
2003 bool replace_comments = rrset["comments"].is_array();
f63168e6 2004
6754ef71
CH
2005 if (!replace_records && !replace_comments) {
2006 throw ApiException("No change for RRset " + qname.toString() + " IN " + qtype.getName());
2007 }
f63168e6 2008
6754ef71
CH
2009 new_records.clear();
2010 new_comments.clear();
f63168e6 2011
6754ef71
CH
2012 if (replace_records) {
2013 // ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
2014 int ttl = intFromJson(rrset, "ttl");
2015 // new_ptrs is merged.
311cb9a2 2016 gatherRecords(B, req->logprefix, rrset, qname, qtype, ttl, new_records, new_ptrs);
6754ef71
CH
2017
2018 for(DNSResourceRecord& rr : new_records) {
2019 rr.domain_id = di.id;
2020 if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
2021 soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
6754ef71 2022 }
d708640f 2023 }
646bcd7d 2024 checkNewRecords(new_records);
6cc98ddf
CH
2025 }
2026
6754ef71
CH
2027 if (replace_comments) {
2028 gatherComments(rrset, qname, qtype, new_comments);
f63168e6 2029
6754ef71
CH
2030 for(Comment& c : new_comments) {
2031 c.domain_id = di.id;
2032 }
d708640f 2033 }
b3905a3d 2034
d708640f 2035 if (replace_records) {
03b1cc25 2036 bool ent_present = false;
5e73d266
AT
2037 bool dname_seen = false, ns_seen = false;
2038
162fcac6 2039 di.backend->lookup(QType(QType::ANY), qname, di.id);
8560f36a
CH
2040 DNSResourceRecord rr;
2041 while (di.backend->get(rr)) {
6096ad14 2042 if (rr.qtype.getCode() == QType::ENT) {
03b1cc25 2043 ent_present = true;
002c4fe1
RG
2044 /* that's fine, we will override it */
2045 continue;
03b1cc25 2046 }
5e73d266
AT
2047 if (qtype == QType::DNAME || rr.qtype == QType::DNAME)
2048 dname_seen = true;
2049 if (qtype == QType::NS || rr.qtype == QType::NS)
2050 ns_seen = true;
646bcd7d
CH
2051 if (qtype.getCode() != rr.qtype.getCode()
2052 && (exclusiveEntryTypes.count(qtype.getCode()) != 0
2053 || exclusiveEntryTypes.count(rr.qtype.getCode()) != 0)) {
e4e8ca30
PD
2054
2055 // leave database handle in a consistent state
2056 while (di.backend->get(rr))
2057 ;
2058
646bcd7d 2059 throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Conflicts with pre-existing RRset");
8560f36a
CH
2060 }
2061 }
2062
5e73d266
AT
2063 if (dname_seen && ns_seen && qname != zonename) {
2064 throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Cannot have both NS and DNAME except in zone apex");
2065 }
03b1cc25
CH
2066 if (!new_records.empty() && ent_present) {
2067 QType qt_ent{0};
2068 if (!di.backend->replaceRRSet(di.id, qname, qt_ent, new_records)) {
2069 throw ApiException("Hosting backend does not support editing records.");
2070 }
2071 }
d708640f
CH
2072 if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
2073 throw ApiException("Hosting backend does not support editing records.");
2074 }
2075 }
2076 if (replace_comments) {
2077 if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
2078 throw ApiException("Hosting backend does not support editing comments.");
2079 }
2080 }
6cc98ddf 2081 }
d708640f
CH
2082 else
2083 throw ApiException("Changetype not understood");
6cc98ddf 2084 }
d29d5db7 2085
a41a789a 2086 zone_disabled = (!B.getSOAUncached(zonename, sd));
d29d5db7 2087
a41a789a
KM
2088 // edit SOA (if needed)
2089 if (!zone_disabled && !soa_edit_api_kind.empty() && !soa_edit_done) {
d29d5db7 2090 DNSResourceRecord rr;
13f9e280
CH
2091 if (makeIncreasedSOARecord(sd, soa_edit_api_kind, soa_edit_kind, rr)) {
2092 if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
2093 throw ApiException("Hosting backend does not support editing records.");
2094 }
d29d5db7 2095 }
3ae63ca8 2096
478de03b
KW
2097 // return old and new serials in headers
2098 resp->headers["X-PDNS-Old-Serial"] = std::to_string(sd.serial);
3ae63ca8 2099 fillSOAData(rr.content, sd);
478de03b 2100 resp->headers["X-PDNS-New-Serial"] = std::to_string(sd.serial);
d29d5db7
CH
2101 }
2102
d708640f
CH
2103 } catch(...) {
2104 di.backend->abortTransaction();
2105 throw;
2106 }
986e4858 2107
0ca59ad9 2108 // Rectify
b0486ea5 2109 DNSSECKeeper dk(&B);
a41a789a 2110 if (!zone_disabled && !dk.isPresigned(zonename)) {
0ca59ad9
KM
2111 string api_rectify;
2112 if (!di.backend->getDomainMetadataOne(zonename, "API-RECTIFY", api_rectify) && ::arg().mustDo("default-api-rectify")) {
2113 api_rectify = "1";
2114 }
2115 if (api_rectify == "1") {
2116 string info;
2117 string error_msg;
2118 if (!dk.rectifyZone(zonename, error_msg, info, false)) {
2119 throw ApiException("Failed to rectify '" + zonename.toString() + "' " + error_msg);
2120 }
2121 }
986e4858
PL
2122 }
2123
d708640f 2124 di.backend->commitTransaction();
b3905a3d 2125
27e14380 2126 purgeAuthCaches(zonename.toString() + "$");
d1587ceb 2127
d708640f 2128 // now the PTRs
995473c8 2129 storeChangedPTRs(B, new_ptrs);
b3905a3d 2130
f0e76cee
CH
2131 resp->body = "";
2132 resp->status = 204; // No Content, but indicate success
2133 return;
b3905a3d
CH
2134}
2135
b1902fab
CH
2136static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
2137 if(req->method != "GET")
2138 throw HttpMethodNotAllowedException();
2139
583ea80d 2140 string q = req->getvars["q"];
720ed2bd 2141 string sMax = req->getvars["max"];
0bd91de6 2142 string sObjectType = req->getvars["object_type"];
45250285 2143
720ed2bd
AT
2144 int maxEnts = 100;
2145 int ents = 0;
2146
45250285 2147 // the following types of data can be searched for using the api
0bd91de6 2148 enum class ObjectType
45250285
JE
2149 {
2150 ALL,
2151 ZONE,
2152 RECORD,
2153 COMMENT
0bd91de6 2154 } objectType;
45250285 2155
b1902fab
CH
2156 if (q.empty())
2157 throw ApiException("Query q can't be blank");
606c8752 2158 if (!sMax.empty())
335da0ba 2159 maxEnts = std::stoi(sMax);
720ed2bd
AT
2160 if (maxEnts < 1)
2161 throw ApiException("Maximum entries must be larger than 0");
b1902fab 2162
0bd91de6
CH
2163 if (sObjectType.empty())
2164 objectType = ObjectType::ALL;
2165 else if (sObjectType == "all")
2166 objectType = ObjectType::ALL;
2167 else if (sObjectType == "zone")
2168 objectType = ObjectType::ZONE;
2169 else if (sObjectType == "record")
2170 objectType = ObjectType::RECORD;
2171 else if (sObjectType == "comment")
2172 objectType = ObjectType::COMMENT;
45250285 2173 else
0bd91de6 2174 throw ApiException("object_type must be one of the following options: all, zone, record, comment");
45250285 2175
720ed2bd 2176 SimpleMatch sm(q,true);
b1902fab 2177 UeberBackend B;
b1902fab 2178 vector<DomainInfo> domains;
720ed2bd
AT
2179 vector<DNSResourceRecord> result_rr;
2180 vector<Comment> result_c;
1d6b70f9
CH
2181 map<int,DomainInfo> zoneIdZone;
2182 map<int,DomainInfo>::iterator val;
00963dea 2183 Json::array doc;
b1902fab 2184
720ed2bd 2185 B.getAllDomains(&domains, true);
d2d194a9 2186
720ed2bd 2187 for(const DomainInfo di: domains)
1d6b70f9 2188 {
0bd91de6 2189 if ((objectType == ObjectType::ALL || objectType == ObjectType::ZONE) && ents < maxEnts && sm.match(di.zone)) {
00963dea
CH
2190 doc.push_back(Json::object {
2191 { "object_type", "zone" },
2192 { "zone_id", apiZoneNameToId(di.zone) },
2193 { "name", di.zone.toString() }
2194 });
720ed2bd 2195 ents++;
b1902fab 2196 }
1d6b70f9 2197 zoneIdZone[di.id] = di; // populate cache
720ed2bd 2198 }
b1902fab 2199
0bd91de6 2200 if ((objectType == ObjectType::ALL || objectType == ObjectType::RECORD) && B.searchRecords(q, maxEnts, result_rr))
720ed2bd
AT
2201 {
2202 for(const DNSResourceRecord& rr: result_rr)
2203 {
7cbc5255
CH
2204 if (!rr.qtype.getCode())
2205 continue; // skip empty non-terminals
2206
00963dea
CH
2207 auto object = Json::object {
2208 { "object_type", "record" },
2209 { "name", rr.qname.toString() },
2210 { "type", rr.qtype.getName() },
2211 { "ttl", (double)rr.ttl },
2212 { "disabled", rr.disabled },
2213 { "content", makeApiRecordContent(rr.qtype, rr.content) }
2214 };
720ed2bd 2215 if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) {
00963dea
CH
2216 object["zone_id"] = apiZoneNameToId(val->second.zone);
2217 object["zone"] = val->second.zone.toString();
720ed2bd 2218 }
00963dea 2219 doc.push_back(object);
b1902fab 2220 }
720ed2bd 2221 }
b1902fab 2222
0bd91de6 2223 if ((objectType == ObjectType::ALL || objectType == ObjectType::COMMENT) && B.searchComments(q, maxEnts, result_c))
720ed2bd
AT
2224 {
2225 for(const Comment &c: result_c)
2226 {
00963dea
CH
2227 auto object = Json::object {
2228 { "object_type", "comment" },
25dcc05f 2229 { "name", c.qname.toString() },
00963dea
CH
2230 { "content", c.content }
2231 };
720ed2bd 2232 if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) {
00963dea
CH
2233 object["zone_id"] = apiZoneNameToId(val->second.zone);
2234 object["zone"] = val->second.zone.toString();
720ed2bd 2235 }
00963dea 2236 doc.push_back(object);
b1902fab
CH
2237 }
2238 }
4bd3d119 2239
b1902fab
CH
2240 resp->setBody(doc);
2241}
2242
050e6877 2243static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
2054afbb 2244 if(req->method != "PUT")
a426cb89 2245 throw HttpMethodNotAllowedException();
80d59cd1 2246
c0f6a1da
CH
2247 DNSName canon = apiNameToDNSName(req->getvars["domain"]);
2248
bf269e28 2249 uint64_t count = purgeAuthCachesExact(canon);
f682752a 2250 resp->setBody(Json::object {
bf269e28
RG
2251 { "count", (int) count },
2252 { "result", "Flushed cache." }
f682752a 2253 });
ddc84d12
CH
2254}
2255
dea47634 2256void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 2257{
80d59cd1
CH
2258 resp->headers["Cache-Control"] = "max-age=86400";
2259 resp->headers["Content-Type"] = "text/css";
c67bf8c5 2260
1071abdd 2261 ostringstream ret;
1071abdd
CH
2262 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
2263 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
2264 ret<<"a { color: #0959c2; }"<<endl;
2265 ret<<"a:hover { color: #3B8EC8; }"<<endl;
2266 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
2267 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
2268 ret<<".row:after { clear: both; }"<<endl;
2269 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
2270 ret<<".all { width: 100%; }"<<endl;
2271 ret<<".headl { width: 60%; }"<<endl;
2272 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
2273 ret<<"background-image: url(data:image/png;base64,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);";
2274 ret<<" width: 154px; height: 20px; }"<<endl;
2275 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
2276 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
2277 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
2278 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
2279 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
2280 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
2281 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
2282 ret<<"table.data tr:hover { background: white; }"<<endl;
2283 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
2284 ret<<".resetring {float: right; }"<<endl;
2285 ret<<".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFCQn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvylUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK+bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CYII=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; background-repeat: no-repeat; }"<<endl;
2286 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
2287 ret<<".resizering {float: right;}"<<endl;
80d59cd1 2288 resp->body = ret.str();
c146576d 2289 resp->status = 200;
1071abdd
CH
2290}
2291
dea47634 2292void AuthWebServer::webThread()
12c86877
BH
2293{
2294 try {
519f5484 2295 setThreadName("pdns/webserver");
479e0976 2296 if(::arg().mustDo("api")) {
c0f6a1da 2297 d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush);
46d06a12 2298 d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig);
46d06a12
PL
2299 d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServerSearchData);
2300 d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics);
f711ef76
PL
2301 d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys/<id>", &apiServerTSIGKeyDetail);
2302 d_ws->registerApiHandler("/api/v1/servers/localhost/tsigkeys", &apiServerTSIGKeys);
46d06a12
PL
2303 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", &apiServerZoneAxfrRetrieve);
2304 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", &apiZoneCryptokeys);
2305 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", &apiZoneCryptokeys);
2306 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", &apiServerZoneExport);
24e11043
CJ
2307 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata/<kind>", &apiZoneMetadataKind);
2308 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/metadata", &apiZoneMetadata);
46d06a12 2309 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", &apiServerZoneNotify);
4bc8379e 2310 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/rectify", &apiServerZoneRectify);
46d06a12
PL
2311 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServerZoneDetail);
2312 d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZones);
2313 d_ws->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail);
2314 d_ws->registerApiHandler("/api/v1/servers", &apiServer);
9e6d2033 2315 d_ws->registerApiHandler("/api", &apiDiscovery);
c67bf8c5 2316 }
536ab56f
CH
2317 if (::arg().mustDo("webserver")) {
2318 d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
2319 d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
2320 }
96d299db 2321 d_ws->go();
12c86877
BH
2322 }
2323 catch(...) {
e6a9dde5 2324 g_log<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
5bd2ea7b 2325 _exit(1);
12c86877
BH
2326 }
2327}