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