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