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