]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
API: extract storeChangedPTRs out of patchZone
[thirdparty/pdns.git] / pdns / ws-auth.cc
CommitLineData
12c86877 1/*
32cb6fd4 2 Copyright (C) 2002 - 2016 PowerDNS.COM BV
12c86877
BH
3
4 This program is free software; you can redistribute it and/or modify
6ec5e728 5 it under the terms of the GNU General Public License version 2
9054d8a4 6 as published by the Free Software Foundation
12c86877 7
f782fe38
MH
8 Additionally, the license of this program contains a special
9 exception which allows to distribute the program in binary form when
10 it is linked against OpenSSL.
11
12c86877
BH
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
06bd9ccf 19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
9054d8a4 20*/
870a0fe4
AT
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#endif
9054d8a4 24#include "utility.hh"
d267d1bf 25#include "dynlistener.hh"
2470b36e 26#include "ws-auth.hh"
e611a06c 27#include "json.hh"
12c86877
BH
28#include "webserver.hh"
29#include "logger.hh"
e611a06c 30#include "packetcache.hh"
12c86877
BH
31#include "statbag.hh"
32#include "misc.hh"
33#include "arguments.hh"
34#include "dns.hh"
6cc98ddf 35#include "comment.hh"
e611a06c 36#include "ueberbackend.hh"
dcc65f25 37#include <boost/format.hpp>
fa8fd4d2 38
9ac4a7c6 39#include "namespaces.hh"
6ec5e728 40#include "ws-api.hh"
ba1a571d 41#include "version.hh"
d29d5db7 42#include "dnsseckeeper.hh"
3c3c006b 43#include <iomanip>
0f0e73fe 44#include "zoneparser-tng.hh"
a426cb89 45#include "common_startup.hh"
3c3c006b 46
8537b9f0 47
24afabad 48using json11::Json;
12c86877
BH
49
50extern StatBag S;
995473c8 51extern PacketCache PC;
12c86877 52
f63168e6 53static void patchZone(HttpRequest* req, HttpResponse* resp);
995473c8 54static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptrs);
f63168e6
CH
55static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr);
56
dea47634 57AuthWebServer::AuthWebServer()
12c86877
BH
58{
59 d_start=time(0);
96d299db 60 d_min10=d_min5=d_min1=0;
c81c2ea8 61 d_ws = 0;
f17c93b4 62 d_tid = 0;
825fa717 63 if(arg().mustDo("webserver")) {
bbef8f04 64 d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port"));
825fa717
CH
65 d_ws->bind();
66 }
12c86877
BH
67}
68
dea47634 69void AuthWebServer::go()
12c86877 70{
c81c2ea8
PD
71 if(arg().mustDo("webserver"))
72 {
73 S.doRings();
dea47634 74 pthread_create(&d_tid, 0, webThreadHelper, this);
c81c2ea8
PD
75 pthread_create(&d_tid, 0, statThreadHelper, this);
76 }
12c86877
BH
77}
78
dea47634 79void AuthWebServer::statThread()
12c86877
BH
80{
81 try {
82 for(;;) {
83 d_queries.submit(S.read("udp-queries"));
84 d_cachehits.submit(S.read("packetcache-hit"));
85 d_cachemisses.submit(S.read("packetcache-miss"));
86 d_qcachehits.submit(S.read("query-cache-hit"));
87 d_qcachemisses.submit(S.read("query-cache-miss"));
88 Utility::sleep(1);
89 }
90 }
91 catch(...) {
92 L<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl;
93 exit(1);
94 }
95}
96
dea47634 97void *AuthWebServer::statThreadHelper(void *p)
12c86877 98{
dea47634
CH
99 AuthWebServer *self=static_cast<AuthWebServer *>(p);
100 self->statThread();
12c86877
BH
101 return 0; // never reached
102}
103
dea47634 104void *AuthWebServer::webThreadHelper(void *p)
12c86877 105{
dea47634
CH
106 AuthWebServer *self=static_cast<AuthWebServer *>(p);
107 self->webThread();
12c86877
BH
108 return 0; // never reached
109}
110
9f3fdaa0
CH
111static string htmlescape(const string &s) {
112 string result;
113 for(string::const_iterator it=s.begin(); it!=s.end(); ++it) {
114 switch (*it) {
115 case '&':
c86a96f9 116 result += "&amp;";
9f3fdaa0
CH
117 break;
118 case '<':
119 result += "&lt;";
120 break;
121 case '>':
122 result += "&gt;";
123 break;
c7f59d62
PL
124 case '"':
125 result += "&quot;";
126 break;
9f3fdaa0
CH
127 default:
128 result += *it;
129 }
130 }
131 return result;
132}
133
12c86877
BH
134void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
135{
136 int tot=0;
137 int entries=0;
101b5d5d 138 vector<pair <string,unsigned int> >ring=S.getRing(ringname);
12c86877 139
1071abdd 140 for(vector<pair<string, unsigned int> >::const_iterator i=ring.begin(); i!=ring.end();++i) {
12c86877
BH
141 tot+=i->second;
142 entries++;
143 }
144
1071abdd 145 ret<<"<div class=\"panel\">";
c7f59d62 146 ret<<"<span class=resetring><i></i><a href=\"?resetring="<<htmlescape(ringname)<<"\">Reset</a></span>"<<endl;
1071abdd
CH
147 ret<<"<h2>"<<title<<"</h2>"<<endl;
148 ret<<"<div class=ringmeta>";
c7f59d62 149 ret<<"<a class=topXofY href=\"?ring="<<htmlescape(ringname)<<"\">Showing: Top "<<limit<<" of "<<entries<<"</a>"<<endl;
1071abdd 150 ret<<"<span class=resizering>Resize: ";
bb3c3f50 151 unsigned int sizes[]={10,100,500,1000,10000,500000,0};
12c86877
BH
152 for(int i=0;sizes[i];++i) {
153 if(S.getRingSize(ringname)!=sizes[i])
c7f59d62 154 ret<<"<a href=\"?resizering="<<htmlescape(ringname)<<"&amp;size="<<sizes[i]<<"\">"<<sizes[i]<<"</a> ";
12c86877
BH
155 else
156 ret<<"("<<sizes[i]<<") ";
157 }
1071abdd 158 ret<<"</span></div>";
12c86877 159
1071abdd 160 ret<<"<table class=\"data\">";
12c86877 161 int printed=0;
f5cb7e61 162 int total=max(1,tot);
bb3c3f50 163 for(vector<pair<string,unsigned int> >::const_iterator i=ring.begin();limit && i!=ring.end();++i,--limit) {
dea47634 164 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
165 printed+=i->second;
166 }
167 ret<<"<tr><td colspan=3></td></tr>"<<endl;
168 if(printed!=tot)
dea47634 169 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 170
e2a77e08 171 ret<<"<tr><td><b>Total:</b></td><td><b>"<<tot<<"</b></td><td align=right><b>100%</b></td>";
1071abdd 172 ret<<"</table></div>"<<endl;
12c86877
BH
173}
174
dea47634 175void AuthWebServer::printvars(ostringstream &ret)
12c86877 176{
1071abdd 177 ret<<"<div class=panel><h2>Variables</h2><table class=\"data\">"<<endl;
12c86877
BH
178
179 vector<string>entries=S.getEntries();
180 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
181 ret<<"<tr><td>"<<*i<<"</td><td>"<<S.read(*i)<<"</td><td>"<<S.getDescrip(*i)<<"</td>"<<endl;
182 }
e2a77e08 183
1071abdd 184 ret<<"</table></div>"<<endl;
12c86877
BH
185}
186
dea47634 187void AuthWebServer::printargs(ostringstream &ret)
12c86877 188{
e2a77e08 189 ret<<"<table border=1><tr><td colspan=3 bgcolor=\"#0000ff\"><font color=\"#ffffff\">Arguments</font></td>"<<endl;
12c86877
BH
190
191 vector<string>entries=arg().list();
192 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
193 ret<<"<tr><td>"<<*i<<"</td><td>"<<arg()[*i]<<"</td><td>"<<arg().getHelp(*i)<<"</td>"<<endl;
194 }
195}
196
dea47634 197string AuthWebServer::makePercentage(const double& val)
b6f57093
BH
198{
199 return (boost::format("%.01f%%") % val).str();
200}
201
dea47634 202void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
12c86877 203{
583ea80d
CH
204 if(!req->getvars["resetring"].empty()) {
205 if (S.ringExists(req->getvars["resetring"]))
206 S.resetRing(req->getvars["resetring"]);
80d59cd1 207 resp->status = 301;
0665b7e6 208 resp->headers["Location"] = req->url.path;
80d59cd1 209 return;
12c86877 210 }
583ea80d 211 if(!req->getvars["resizering"].empty()){
335da0ba 212 int size=std::stoi(req->getvars["size"]);
583ea80d 213 if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000)
335da0ba 214 S.resizeRing(req->getvars["resizering"], std::stoi(req->getvars["size"]));
80d59cd1 215 resp->status = 301;
0665b7e6 216 resp->headers["Location"] = req->url.path;
80d59cd1 217 return;
12c86877
BH
218 }
219
220 ostringstream ret;
221
1071abdd
CH
222 ret<<"<!DOCTYPE html>"<<endl;
223 ret<<"<html><head>"<<endl;
224 ret<<"<title>PowerDNS Authoritative Server Monitor</title>"<<endl;
225 ret<<"<link rel=\"stylesheet\" href=\"style.css\"/>"<<endl;
226 ret<<"</head><body>"<<endl;
227
228 ret<<"<div class=\"row\">"<<endl;
229 ret<<"<div class=\"headl columns\">";
a1caa8b8 230 ret<<"<a href=\"/\" id=\"appname\">PowerDNS "<<htmlescape(VERSION);
1071abdd 231 if(!arg()["config-name"].empty()) {
a1caa8b8 232 ret<<" ["<<htmlescape(arg()["config-name"])<<"]";
1071abdd
CH
233 }
234 ret<<"</a></div>"<<endl;
235 ret<<"<div class=\"headr columns\"></div></div>";
236 ret<<"<div class=\"row\"><div class=\"all columns\">";
12c86877
BH
237
238 time_t passed=time(0)-s_starttime;
239
e2a77e08
KM
240 ret<<"<p>Uptime: "<<
241 humanDuration(passed)<<
242 "<br>"<<endl;
12c86877 243
395b07ea 244 ret<<"Queries/second, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
f6154a3b
CH
245 d_queries.get1()<<", "<<
246 d_queries.get5()<<", "<<
247 d_queries.get10()<<". Max queries/second: "<<d_queries.getMax()<<
12c86877 248 "<br>"<<endl;
1d6b70f9 249
f6154a3b 250 if(d_cachemisses.get10()+d_cachehits.get10()>0)
b6f57093 251 ret<<"Cache hitrate, 1, 5, 10 minute averages: "<<
f6154a3b
CH
252 makePercentage((d_cachehits.get1()*100.0)/((d_cachehits.get1())+(d_cachemisses.get1())))<<", "<<
253 makePercentage((d_cachehits.get5()*100.0)/((d_cachehits.get5())+(d_cachemisses.get5())))<<", "<<
254 makePercentage((d_cachehits.get10()*100.0)/((d_cachehits.get10())+(d_cachemisses.get10())))<<
b6f57093 255 "<br>"<<endl;
12c86877 256
f6154a3b 257 if(d_qcachemisses.get10()+d_qcachehits.get10()>0)
395b07ea 258 ret<<"Backend query cache hitrate, 1, 5, 10 minute averages: "<<std::setprecision(2)<<
f6154a3b
CH
259 makePercentage((d_qcachehits.get1()*100.0)/((d_qcachehits.get1())+(d_qcachemisses.get1())))<<", "<<
260 makePercentage((d_qcachehits.get5()*100.0)/((d_qcachehits.get5())+(d_qcachemisses.get5())))<<", "<<
261 makePercentage((d_qcachehits.get10()*100.0)/((d_qcachehits.get10())+(d_qcachemisses.get10())))<<
b6f57093 262 "<br>"<<endl;
12c86877 263
395b07ea 264 ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
f6154a3b
CH
265 d_qcachemisses.get1()<<", "<<
266 d_qcachemisses.get5()<<", "<<
267 d_qcachemisses.get10()<<". Max queries/second: "<<d_qcachemisses.getMax()<<
12c86877
BH
268 "<br>"<<endl;
269
1071abdd 270 ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
583ea80d 271 if(req->getvars["ring"].empty()) {
12c86877
BH
272 vector<string>entries=S.listRings();
273 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i)
274 printtable(ret,*i,S.getRingTitle(*i));
275
f6154a3b 276 printvars(ret);
12c86877 277 if(arg().mustDo("webserver-print-arguments"))
f6154a3b 278 printargs(ret);
12c86877
BH
279 }
280 else
583ea80d 281 printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100);
12c86877 282
1071abdd 283 ret<<"</div></div>"<<endl;
32cb6fd4 284 ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 - 2016 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
12c86877
BH
285 ret<<"</body></html>"<<endl;
286
80d59cd1 287 resp->body = ret.str();
61f5d289 288 resp->status = 200;
12c86877
BH
289}
290
1d6b70f9
CH
291/** Helper to build a record content as needed. */
292static inline string makeRecordContent(const QType& qtype, const string& content, bool noDot) {
293 // noDot: for backend storage, pass true. for API users, pass false.
294 return DNSRecordContent::mastermake(qtype.getCode(), 1, content)->getZoneRepresentation(noDot);
295}
296
297/** "Normalize" record content for API consumers. */
298static inline string makeApiRecordContent(const QType& qtype, const string& content) {
299 return makeRecordContent(qtype, content, false);
300}
301
302/** "Normalize" record content for backend storage. */
303static inline string makeBackendRecordContent(const QType& qtype, const string& content) {
304 return makeRecordContent(qtype, content, true);
305}
306
62a9a74c 307static Json::object getZoneInfo(const DomainInfo& di) {
c04b5870 308 DNSSECKeeper dk;
290a083d 309 string zoneId = apiZoneNameToId(di.zone);
62a9a74c
CH
310 return Json::object {
311 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
312 { "id", zoneId },
313 { "url", "api/v1/servers/localhost/zones/" + zoneId },
314 { "name", di.zone.toString() },
315 { "kind", di.getKindString() },
316 { "dnssec", dk.isSecuredZone(di.zone) },
317 { "account", di.account },
318 { "masters", di.masters },
319 { "serial", (double)di.serial },
320 { "notified_serial", (double)di.notified_serial },
321 { "last_check", (double)di.last_check }
322 };
c04b5870
CH
323}
324
290a083d 325static void fillZone(const DNSName& zonename, HttpResponse* resp) {
1abb81f4 326 UeberBackend B;
1abb81f4 327 DomainInfo di;
73301d73 328 if(!B.getDomainInfo(zonename, di))
290a083d 329 throw ApiException("Could not find domain '"+zonename.toString()+"'");
1abb81f4 330
62a9a74c
CH
331 Json::object doc = getZoneInfo(di);
332 // extra stuff getZoneInfo doesn't do for us (more expensive)
d29d5db7
CH
333 string soa_edit_api;
334 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
62a9a74c 335 doc["soa_edit_api"] = soa_edit_api;
6bb25159
MS
336 string soa_edit;
337 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
62a9a74c 338 doc["soa_edit"] = soa_edit;
1abb81f4 339
6754ef71
CH
340 vector<DNSResourceRecord> records;
341 vector<Comment> comments;
1abb81f4 342
6754ef71
CH
343 // load all records + sort
344 {
345 DNSResourceRecord rr;
346 di.backend->list(zonename, di.id, true); // incl. disabled
347 while(di.backend->get(rr)) {
348 if (!rr.qtype.getCode())
349 continue; // skip empty non-terminals
350 records.push_back(rr);
351 }
352 sort(records.begin(), records.end(), [](const DNSResourceRecord& a, const DNSResourceRecord& b) {
353 if (a.qname == b.qname) {
354 return b.qtype < a.qtype;
355 }
356 return b.qname < a.qname;
357 });
358 }
359
360 // load all comments + sort
361 {
362 Comment comment;
363 di.backend->listComments(di.id);
364 while(di.backend->getComment(comment)) {
365 comments.push_back(comment);
366 }
367 sort(comments.begin(), comments.end(), [](const Comment& a, const Comment& b) {
368 if (a.qname == b.qname) {
369 return b.qtype < a.qtype;
370 }
371 return b.qname < a.qname;
372 });
373 }
374
375 Json::array rrsets;
376 Json::object rrset;
377 Json::array rrset_records;
378 Json::array rrset_comments;
379 DNSName current_qname;
380 QType current_qtype;
381 uint32_t ttl;
382 auto rit = records.begin();
383 auto cit = comments.begin();
384
385 while (rit != records.end() || cit != comments.end()) {
dedd3fc8 386 if (cit == comments.end() || (rit != records.end() && (cit->qname.toString() < rit->qname.toString() || cit->qtype < rit->qtype))) {
6754ef71
CH
387 current_qname = rit->qname;
388 current_qtype = rit->qtype;
389 ttl = rit->ttl;
390 } else {
391 current_qname = cit->qname;
392 current_qtype = cit->qtype;
393 ttl = 0;
394 }
395
396 while(rit != records.end() && rit->qname == current_qname && rit->qtype == current_qtype) {
397 ttl = min(ttl, rit->ttl);
398 rrset_records.push_back(Json::object {
399 { "disabled", rit->disabled },
400 { "content", makeApiRecordContent(rit->qtype, rit->content) }
401 });
402 rit++;
403 }
404 while (cit != comments.end() && cit->qname == current_qname && cit->qtype == current_qtype) {
405 rrset_comments.push_back(Json::object {
406 { "modified_at", (double)cit->modified_at },
407 { "account", cit->account },
408 { "content", cit->content }
409 });
410 cit++;
411 }
412
413 rrset["name"] = current_qname.toString();
414 rrset["type"] = current_qtype.getName();
415 rrset["records"] = rrset_records;
416 rrset["comments"] = rrset_comments;
417 rrset["ttl"] = (double)ttl;
418 rrsets.push_back(rrset);
419 rrset.clear();
420 rrset_records.clear();
421 rrset_comments.clear();
422 }
423
424 doc["rrsets"] = rrsets;
6cc98ddf 425
669822d0 426 resp->setBody(doc);
1abb81f4
CH
427}
428
6ec5e728
CH
429void productServerStatisticsFetch(map<string,string>& out)
430{
a45303b8 431 vector<string> items = S.getEntries();
ff05fd12 432 for(const string& item : items) {
335da0ba 433 out[item] = std::to_string(S.read(item));
a45303b8
CH
434 }
435
436 // add uptime
335da0ba 437 out["uptime"] = std::to_string(time(0) - s_starttime);
c67bf8c5
CH
438}
439
6754ef71 440static void gatherRecords(const Json container, const DNSName& qname, const QType qtype, const int ttl, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
f63168e6
CH
441 UeberBackend B;
442 DNSResourceRecord rr;
6754ef71
CH
443 rr.qname = qname;
444 rr.qtype = qtype;
445 rr.auth = 1;
446 rr.ttl = ttl;
1f68b185 447 for(auto record : container["records"].array_items()) {
1f68b185 448 string content = stringFromJson(record, "content");
1f68b185
CH
449 rr.disabled = boolFromJson(record, "disabled");
450
1f68b185
CH
451 // validate that the client sent something we can actually parse, and require that data to be dotted.
452 try {
453 if (rr.qtype.getCode() != QType::AAAA) {
454 string tmp = makeApiRecordContent(rr.qtype, content);
455 if (!pdns_iequals(tmp, content)) {
456 throw std::runtime_error("Not in expected format (parsed as '"+tmp+"')");
457 }
458 } else {
459 struct in6_addr tmpbuf;
460 if (inet_pton(AF_INET6, content.c_str(), &tmpbuf) != 1 || content.find('.') != string::npos) {
461 throw std::runtime_error("Invalid IPv6 address");
1e5b9ab9 462 }
f63168e6 463 }
1f68b185
CH
464 rr.content = makeBackendRecordContent(rr.qtype, content);
465 }
466 catch(std::exception& e)
467 {
468 throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.getName()+" '"+content+"': "+e.what());
469 }
f63168e6 470
1f68b185
CH
471 if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
472 boolFromJson(record, "set-ptr", false) == true) {
473 DNSResourceRecord ptr;
474 makePtr(rr, &ptr);
f63168e6 475
1f68b185
CH
476 // verify that there's a zone for the PTR
477 DNSPacket fakePacket;
478 SOAData sd;
479 fakePacket.qtype = QType::PTR;
480 if (!B.getAuth(&fakePacket, &sd, ptr.qname))
481 throw ApiException("Could not find domain for PTR '"+ptr.qname.toString()+"' requested for '"+ptr.content+"'");
f63168e6 482
1f68b185
CH
483 ptr.domain_id = sd.domain_id;
484 new_ptrs.push_back(ptr);
f63168e6 485 }
1f68b185
CH
486
487 new_records.push_back(rr);
f63168e6
CH
488 }
489}
490
6754ef71 491static void gatherComments(const Json container, const DNSName& qname, const QType qtype, vector<Comment>& new_comments) {
f63168e6 492 Comment c;
6754ef71
CH
493 c.qname = qname;
494 c.qtype = qtype;
f63168e6
CH
495
496 time_t now = time(0);
1f68b185 497 for (auto comment : container["comments"].array_items()) {
1f68b185
CH
498 c.modified_at = intFromJson(comment, "modified_at", now);
499 c.content = stringFromJson(comment, "content");
500 c.account = stringFromJson(comment, "account");
501 new_comments.push_back(c);
f63168e6
CH
502 }
503}
6cc98ddf 504
1f68b185
CH
505static void updateDomainSettingsFromDocument(const DomainInfo& di, const DNSName& zonename, const Json document) {
506 string zonemaster;
507 for(auto value : document["masters"].array_items()) {
508 string master = value.string_value();
509 if (master.empty())
510 throw ApiException("Master can not be an empty string");
511 zonemaster += master + " ";
bb9fd223
CH
512 }
513
514 di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
1f68b185 515 di.backend->setMaster(zonename, zonemaster);
d29d5db7 516
1f68b185
CH
517 if (document["soa_edit_api"].is_string()) {
518 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].string_value());
d29d5db7 519 }
1f68b185
CH
520 if (document["soa_edit"].is_string()) {
521 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].string_value());
6bb25159 522 }
1f68b185
CH
523 if (document["account"].is_string()) {
524 di.backend->setAccount(zonename, document["account"].string_value());
79532aa7 525 }
bb9fd223
CH
526}
527
4b7f120a
MS
528static void apiZoneCryptokeys(HttpRequest* req, HttpResponse* resp) {
529 if(req->method != "GET")
530 throw ApiException("Only GET is implemented");
531
29704f66
CH
532 bool inquireSingleKey = false;
533 int inquireKeyId;
534 if (req->parameters.count("key_id")) {
535 inquireSingleKey = true;
536 inquireKeyId = std::stoi(req->parameters["key_id"]);
537 }
538
290a083d 539 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
4b7f120a
MS
540
541 UeberBackend B;
29704f66 542 DNSSECKeeper dk(&B);
4b7f120a 543 DomainInfo di;
4b7f120a 544 if(!B.getDomainInfo(zonename, di))
29704f66 545 throw HttpNotFoundException();
4b7f120a 546
b6bd795c 547 DNSSECKeeper::keyset_t keyset=dk.getKeys(zonename, false);
4b7f120a 548
24afabad 549 Json::array doc;
29704f66
CH
550 for(const auto& value : keyset) {
551 if (inquireSingleKey && inquireKeyId != value.second.id) {
552 continue;
38809e97 553 }
24afabad 554
b6bd795c
PL
555 string keyType;
556 switch(value.second.keyType){
557 case DNSSECKeeper::KSK: keyType="ksk"; break;
558 case DNSSECKeeper::ZSK: keyType="zsk"; break;
559 case DNSSECKeeper::CSK: keyType="csk"; break;
560 }
561
24afabad
CH
562 Json::object key {
563 { "type", "Cryptokey" },
564 { "id", (int)value.second.id },
565 { "active", value.second.active },
b6bd795c
PL
566 { "keytype", keyType },
567 { "flags", (uint16_t)value.first.d_flags },
24afabad
CH
568 { "dnskey", value.first.getDNSKEY().getZoneRepresentation() }
569 };
570
b6bd795c 571 if (value.second.keyType == DNSSECKeeper::KSK || value.second.keyType == DNSSECKeeper::CSK) {
24afabad
CH
572 Json::array dses;
573 for(const int keyid : { 1, 2, 3, 4 })
4b7f120a 574 try {
24afabad
CH
575 dses.push_back(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), keyid).getZoneRepresentation());
576 } catch (...) {}
577 key["ds"] = dses;
4b7f120a 578 }
29704f66
CH
579
580 if (inquireSingleKey) {
581 key["privatekey"] = value.first.getKey()->convertToISC();
582 resp->setBody(key);
583 return;
584 }
24afabad 585 doc.push_back(key);
4b7f120a
MS
586 }
587
29704f66
CH
588 if (inquireSingleKey) {
589 // we came here because we couldn't find the requested key.
590 throw HttpNotFoundException();
591 }
4b7f120a
MS
592 resp->setBody(doc);
593}
594
1f68b185 595static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResourceRecord>& new_records, DNSName zonename) {
0f0e73fe
MS
596 DNSResourceRecord rr;
597 vector<string> zonedata;
1f68b185 598 stringtok(zonedata, zonestring, "\r\n");
0f0e73fe
MS
599
600 ZoneParserTNG zpt(zonedata, zonename);
601
602 bool seenSOA=false;
603
604 string comment = "Imported via the API";
605
606 try {
607 while(zpt.get(rr, &comment)) {
608 if(seenSOA && rr.qtype.getCode() == QType::SOA)
609 continue;
610 if(rr.qtype.getCode() == QType::SOA)
611 seenSOA=true;
612
0f0e73fe
MS
613 new_records.push_back(rr);
614 }
615 }
616 catch(std::exception& ae) {
617 throw ApiException("An error occured while parsing the zonedata: "+string(ae.what()));
618 }
619}
620
80d59cd1 621static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
e2dba705 622 UeberBackend B;
53942520 623 DNSSECKeeper dk(&B);
d07bf7ff 624 if (req->method == "POST" && !::arg().mustDo("api-readonly")) {
e2dba705 625 DomainInfo di;
1f68b185 626 auto document = req->json();
c576d0c5 627 DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
1d6b70f9 628 apiCheckNameAllowedCharacters(zonename.toString());
4ebf78b1 629
1d6b70f9 630 bool exists = B.getDomainInfo(zonename, di);
e2dba705 631 if(exists)
1d6b70f9 632 throw ApiException("Domain '"+zonename.toString()+"' already exists");
e2dba705 633
bb9fd223 634 // validate 'kind' is set
4bdff352 635 DomainInfo::DomainKind zonekind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
bb9fd223 636
6754ef71
CH
637 string zonestring = document["zone"].string_value();
638 auto rrsets = document["rrsets"];
639 if (rrsets.is_array() && zonestring != "")
640 throw ApiException("You cannot give rrsets AND zone data as text");
0f0e73fe 641
1f68b185
CH
642 auto nameservers = document["nameservers"];
643 if (!nameservers.is_array() && zonekind != DomainInfo::Slave)
f63168e6 644 throw ApiException("Nameservers list must be given (but can be empty if NS records are supplied)");
e2dba705 645
f63168e6 646 string soa_edit_api_kind;
1f68b185
CH
647 if (document["soa_edit_api"].is_string()) {
648 soa_edit_api_kind = document["soa_edit_api"].string_value();
a6448d95
CH
649 }
650 else {
651 soa_edit_api_kind = "DEFAULT";
652 }
1f68b185 653 string soa_edit_kind = document["soa_edit"].string_value();
e90b4e38 654
f63168e6
CH
655 // if records/comments are given, load and check them
656 bool have_soa = false;
657 vector<DNSResourceRecord> new_records;
658 vector<Comment> new_comments;
659 vector<DNSResourceRecord> new_ptrs;
0f0e73fe 660
6754ef71
CH
661 if (rrsets.is_array()) {
662 for (const auto& rrset : rrsets.array_items()) {
663 DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
664 apiCheckQNameAllowedCharacters(qname.toString());
665 QType qtype;
666 qtype = stringFromJson(rrset, "type");
667 if (qtype.getCode() == 0) {
668 throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
669 }
670 if (rrset["records"].is_array()) {
671 int ttl = intFromJson(rrset, "ttl");
672 gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs);
673 }
674 if (rrset["comments"].is_array()) {
675 gatherComments(rrset, qname, qtype, new_comments);
676 }
677 }
0f0e73fe 678 } else if (zonestring != "") {
1f68b185 679 gatherRecordsFromZone(zonestring, new_records, zonename);
0f0e73fe
MS
680 }
681
1f68b185 682 for(auto& rr : new_records) {
1d6b70f9 683 if (!rr.qname.isPartOf(zonename) && rr.qname != zonename)
561434a6 684 throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": Name is out of zone");
cb9b5901 685 apiCheckQNameAllowedCharacters(rr.qname.toString());
f63168e6 686
1d6b70f9 687 if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
f63168e6 688 have_soa = true;
a6448d95 689 increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
1d6b70f9
CH
690 // fixup dots after serializeSOAData/increaseSOARecord
691 rr.content = makeBackendRecordContent(rr.qtype, rr.content);
f63168e6
CH
692 }
693 }
f7bfeb30
CH
694
695 // synthesize RRs as needed
696 DNSResourceRecord autorr;
1d6b70f9 697 autorr.qname = zonename;
f7bfeb30
CH
698 autorr.auth = 1;
699 autorr.ttl = ::arg().asNum("default-ttl");
e2dba705 700
4de11a54 701 if (!have_soa && zonekind != DomainInfo::Slave) {
f63168e6 702 // synthesize a SOA record so the zone "really" exists
1d6b70f9
CH
703 string soa = (boost::format("%s %s %lu")
704 % ::arg()["default-soa-name"]
705 % (::arg().isEmpty("default-soa-mail") ? (DNSName("hostmaster.") + zonename).toString() : ::arg()["default-soa-mail"])
1f68b185 706 % document["serial"].int_value()
1d6b70f9 707 ).str();
f63168e6 708 SOAData sd;
1d6b70f9 709 fillSOAData(soa, sd); // fills out default values for us
f7bfeb30 710 autorr.qtype = "SOA";
1d6b70f9 711 autorr.content = serializeSOAData(sd);
f7bfeb30 712 increaseSOARecord(autorr, soa_edit_api_kind, soa_edit_kind);
1d6b70f9
CH
713 // fixup dots after serializeSOAData/increaseSOARecord
714 autorr.content = makeBackendRecordContent(autorr.qtype, autorr.content);
f7bfeb30 715 new_records.push_back(autorr);
f63168e6
CH
716 }
717
718 // create NS records if nameservers are given
1f68b185
CH
719 for (auto value : nameservers.array_items()) {
720 string nameserver = value.string_value();
721 if (nameserver.empty())
722 throw ApiException("Nameservers must be non-empty strings");
723 if (!isCanonical(nameserver))
724 throw ApiException("Nameserver is not canonical: '" + nameserver + "'");
725 try {
726 // ensure the name parses
727 autorr.content = DNSName(nameserver).toStringNoDot();
728 } catch (...) {
729 throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "'");
4bdff352 730 }
1f68b185
CH
731 autorr.qtype = "NS";
732 new_records.push_back(autorr);
e2dba705
CH
733 }
734
f63168e6 735 // no going back after this
1d6b70f9
CH
736 if(!B.createDomain(zonename))
737 throw ApiException("Creating domain '"+zonename.toString()+"' failed");
f63168e6 738
1d6b70f9
CH
739 if(!B.getDomainInfo(zonename, di))
740 throw ApiException("Creating domain '"+zonename.toString()+"' failed: lookup of domain ID failed");
f63168e6 741
9440a9f0
CH
742 // updateDomainSettingsFromDocument does NOT fill out the default we've established above.
743 if (!soa_edit_api_kind.empty()) {
744 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
745 }
746
1d6b70f9 747 di.backend->startTransaction(zonename, di.id);
f63168e6 748
abb873ee 749 for(auto rr : new_records) {
f63168e6 750 rr.domain_id = di.id;
e2dba705
CH
751 di.backend->feedRecord(rr);
752 }
1d6b70f9 753 for(Comment& c : new_comments) {
f63168e6
CH
754 c.domain_id = di.id;
755 di.backend->feedComment(c);
756 }
e2dba705 757
1d6b70f9 758 updateDomainSettingsFromDocument(di, zonename, document);
e2dba705 759
f63168e6
CH
760 di.backend->commitTransaction();
761
1d6b70f9 762 fillZone(zonename, resp);
64a36f0d 763 resp->status = 201;
e2dba705
CH
764 return;
765 }
766
c67bf8c5
CH
767 if(req->method != "GET")
768 throw HttpMethodNotAllowedException();
769
c67bf8c5 770 vector<DomainInfo> domains;
cea26350 771 B.getAllDomains(&domains, true); // incl. disabled
c67bf8c5 772
62a9a74c
CH
773 Json::array doc;
774 for(const DomainInfo& di : domains) {
775 doc.push_back(getZoneInfo(di));
c67bf8c5 776 }
669822d0 777 resp->setBody(doc);
c67bf8c5
CH
778}
779
05776d2f 780static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
290a083d 781 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
05776d2f 782
d07bf7ff 783 if(req->method == "PUT" && !::arg().mustDo("api-readonly")) {
7c0ba3d2
CH
784 // update domain settings
785 UeberBackend B;
786 DomainInfo di;
787 if(!B.getDomainInfo(zonename, di))
290a083d 788 throw ApiException("Could not find domain '"+zonename.toString()+"'");
7c0ba3d2 789
1f68b185 790 updateDomainSettingsFromDocument(di, zonename, req->json());
7c0ba3d2 791
669822d0 792 fillZone(zonename, resp);
7c0ba3d2
CH
793 return;
794 }
d07bf7ff 795 else if(req->method == "DELETE" && !::arg().mustDo("api-readonly")) {
a462a01d
CH
796 // delete domain
797 UeberBackend B;
798 DomainInfo di;
799 if(!B.getDomainInfo(zonename, di))
290a083d 800 throw ApiException("Could not find domain '"+zonename.toString()+"'");
a462a01d
CH
801
802 if(!di.backend->deleteDomain(zonename))
290a083d 803 throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
a462a01d
CH
804
805 // empty body on success
806 resp->body = "";
37663c3b 807 resp->status = 204; // No Content: declare that the zone is gone now
a462a01d 808 return;
d07bf7ff 809 } else if (req->method == "PATCH" && !::arg().mustDo("api-readonly")) {
d708640f 810 patchZone(req, resp);
6cc98ddf
CH
811 return;
812 } else if (req->method == "GET") {
813 fillZone(zonename, resp);
814 return;
a462a01d 815 }
7c0ba3d2 816
6cc98ddf 817 throw HttpMethodNotAllowedException();
05776d2f
CH
818}
819
a83004d3 820static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
290a083d 821 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a83004d3
CH
822
823 if(req->method != "GET")
824 throw HttpMethodNotAllowedException();
825
826 ostringstream ss;
827
828 UeberBackend B;
829 DomainInfo di;
830 if(!B.getDomainInfo(zonename, di))
290a083d 831 throw ApiException("Could not find domain '"+zonename.toString()+"'");
a83004d3
CH
832
833 DNSResourceRecord rr;
834 SOAData sd;
835 di.backend->list(zonename, di.id);
836 while(di.backend->get(rr)) {
837 if (!rr.qtype.getCode())
838 continue; // skip empty non-terminals
839
a83004d3 840 ss <<
675fa24c 841 rr.qname.toString() << "\t" <<
a83004d3
CH
842 rr.ttl << "\t" <<
843 rr.qtype.getName() << "\t" <<
1d6b70f9 844 makeApiRecordContent(rr.qtype, rr.content) <<
a83004d3
CH
845 endl;
846 }
847
848 if (req->accept_json) {
41873e7c 849 resp->setBody(Json::object { { "zone", ss.str() } });
a83004d3
CH
850 } else {
851 resp->headers["Content-Type"] = "text/plain; charset=us-ascii";
852 resp->body = ss.str();
853 }
854}
855
a426cb89 856static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
290a083d 857 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a426cb89
CH
858
859 if(req->method != "PUT")
860 throw HttpMethodNotAllowedException();
861
862 UeberBackend B;
863 DomainInfo di;
864 if(!B.getDomainInfo(zonename, di))
290a083d 865 throw ApiException("Could not find domain '"+zonename.toString()+"'");
a426cb89
CH
866
867 if(di.masters.empty())
290a083d 868 throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain (or has no master defined)");
a426cb89
CH
869
870 random_shuffle(di.masters.begin(), di.masters.end());
871 Communicator.addSuckRequest(zonename, di.masters.front());
692829aa 872 resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front());
a426cb89
CH
873}
874
875static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) {
290a083d 876 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a426cb89
CH
877
878 if(req->method != "PUT")
879 throw HttpMethodNotAllowedException();
880
881 UeberBackend B;
882 DomainInfo di;
883 if(!B.getDomainInfo(zonename, di))
290a083d 884 throw ApiException("Could not find domain '"+zonename.toString()+"'");
a426cb89
CH
885
886 if(!Communicator.notifyDomain(zonename))
887 throw ApiException("Failed to add to the queue - see server log");
888
692829aa 889 resp->setSuccessResult("Notification queued");
a426cb89
CH
890}
891
d1587ceb
CH
892static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) {
893 if (rr.qtype.getCode() == QType::A) {
894 uint32_t ip;
895 if (!IpToU32(rr.content, &ip)) {
896 throw ApiException("PTR: Invalid IP address given");
897 }
1d6b70f9 898 ptr->qname = DNSName((boost::format("%u.%u.%u.%u.in-addr.arpa.")
d1587ceb
CH
899 % ((ip >> 24) & 0xff)
900 % ((ip >> 16) & 0xff)
901 % ((ip >> 8) & 0xff)
902 % ((ip ) & 0xff)
1d6b70f9 903 ).str());
d1587ceb
CH
904 } else if (rr.qtype.getCode() == QType::AAAA) {
905 ComboAddress ca(rr.content);
5fb3aa58 906 char buf[3];
d1587ceb 907 ostringstream ss;
5fb3aa58
CH
908 for (int octet = 0; octet < 16; ++octet) {
909 if (snprintf(buf, sizeof(buf), "%02x", ca.sin6.sin6_addr.s6_addr[octet]) != (sizeof(buf)-1)) {
910 // this should be impossible: no byte should give more than two digits in hex format
911 throw PDNSException("Formatting IPv6 address failed");
912 }
913 ss << buf[0] << '.' << buf[1] << '.';
d1587ceb 914 }
5fb3aa58
CH
915 string tmp = ss.str();
916 tmp.resize(tmp.size()-1); // remove last dot
917 // reverse and append arpa domain
1d6b70f9 918 ptr->qname = DNSName(string(tmp.rbegin(), tmp.rend())) + DNSName("ip6.arpa.");
d1587ceb 919 } else {
675fa24c 920 throw ApiException("Unsupported PTR source '" + rr.qname.toString() + "' type '" + rr.qtype.getName() + "'");
d1587ceb
CH
921 }
922
923 ptr->qtype = "PTR";
924 ptr->ttl = rr.ttl;
925 ptr->disabled = rr.disabled;
675fa24c 926 ptr->content = rr.qname.toString();
d1587ceb
CH
927}
928
995473c8
CH
929static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptrs) {
930 for(const DNSResourceRecord& rr : new_ptrs) {
931 DNSPacket fakePacket;
932 SOAData sd;
933 sd.db = (DNSBackend *)-1; // getAuth() cache bypass
934 fakePacket.qtype = QType::PTR;
935
936 if (!B.getAuth(&fakePacket, &sd, rr.qname))
937 throw ApiException("Could not find domain for PTR '"+rr.qname.toString()+"' requested for '"+rr.content+"' (while saving)");
938
939 string soa_edit_api_kind;
940 string soa_edit_kind;
941 bool soa_changed = false;
942 DNSResourceRecord soarr;
943 sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT-API", soa_edit_api_kind);
944 sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT", soa_edit_kind);
945 if (!soa_edit_api_kind.empty()) {
946 soarr.qname = sd.qname;
947 soarr.content = serializeSOAData(sd);
948 soarr.qtype = "SOA";
949 soarr.domain_id = sd.domain_id;
950 soarr.auth = 1;
951 soarr.ttl = sd.ttl;
952 increaseSOARecord(soarr, soa_edit_api_kind, soa_edit_kind);
953 // fixup dots after serializeSOAData/increaseSOARecord
954 soarr.content = makeBackendRecordContent(soarr.qtype, soarr.content);
955 soa_changed = true;
956 }
957
958 sd.db->startTransaction(sd.qname);
959 if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
960 sd.db->abortTransaction();
961 throw ApiException("PTR-Hosting backend for "+rr.qname.toString()+"/"+rr.qtype.getName()+" does not support editing records.");
962 }
963
964 if (soa_changed) {
965 sd.db->replaceRRSet(sd.domain_id, soarr.qname, soarr.qtype, vector<DNSResourceRecord>(1, soarr));
966 }
967
968 sd.db->commitTransaction();
969 PC.purgeExact(rr.qname);
970 }
971}
972
d708640f 973static void patchZone(HttpRequest* req, HttpResponse* resp) {
b3905a3d
CH
974 UeberBackend B;
975 DomainInfo di;
290a083d 976 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
d708640f 977 if (!B.getDomainInfo(zonename, di))
290a083d 978 throw ApiException("Could not find domain '"+zonename.toString()+"'");
b3905a3d 979
f63168e6
CH
980 vector<DNSResourceRecord> new_records;
981 vector<Comment> new_comments;
d708640f
CH
982 vector<DNSResourceRecord> new_ptrs;
983
1f68b185 984 Json document = req->json();
b3905a3d 985
1f68b185
CH
986 auto rrsets = document["rrsets"];
987 if (!rrsets.is_array())
d708640f 988 throw ApiException("No rrsets given in update request");
b3905a3d 989
d708640f 990 di.backend->startTransaction(zonename);
6cc98ddf 991
d708640f 992 try {
d29d5db7 993 string soa_edit_api_kind;
a6448d95 994 string soa_edit_kind;
d29d5db7 995 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
a6448d95 996 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
d29d5db7
CH
997 bool soa_edit_done = false;
998
6754ef71
CH
999 for (const auto& rrset : rrsets.array_items()) {
1000 string changetype = toUpper(stringFromJson(rrset, "changetype"));
c576d0c5 1001 DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
cb9b5901 1002 apiCheckQNameAllowedCharacters(qname.toString());
6754ef71 1003 QType qtype;
d708640f 1004 qtype = stringFromJson(rrset, "type");
6754ef71
CH
1005 if (qtype.getCode() == 0) {
1006 throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
1007 }
d708640f 1008
d708640f
CH
1009 if (changetype == "DELETE") {
1010 // delete all matching qname/qtype RRs (and, implictly comments).
1011 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
1012 throw ApiException("Hosting backend does not support editing records.");
6cc98ddf 1013 }
d708640f
CH
1014 }
1015 else if (changetype == "REPLACE") {
1d6b70f9 1016 // we only validate for REPLACE, as DELETE can be used to "fix" out of zone records.
e325f20c 1017 if (!qname.isPartOf(zonename) && qname != zonename)
edda67a2 1018 throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Name is out of zone");
34df6ecc 1019
6754ef71
CH
1020 bool replace_records = rrset["records"].is_array();
1021 bool replace_comments = rrset["comments"].is_array();
f63168e6 1022
6754ef71
CH
1023 if (!replace_records && !replace_comments) {
1024 throw ApiException("No change for RRset " + qname.toString() + " IN " + qtype.getName());
1025 }
f63168e6 1026
6754ef71
CH
1027 new_records.clear();
1028 new_comments.clear();
f63168e6 1029
6754ef71
CH
1030 if (replace_records) {
1031 // ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
1032 int ttl = intFromJson(rrset, "ttl");
1033 // new_ptrs is merged.
1034 gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs);
1035
1036 for(DNSResourceRecord& rr : new_records) {
1037 rr.domain_id = di.id;
1038 if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
1039 soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
1040 rr.content = makeBackendRecordContent(rr.qtype, rr.content);
1041 }
d708640f 1042 }
6cc98ddf
CH
1043 }
1044
6754ef71
CH
1045 if (replace_comments) {
1046 gatherComments(rrset, qname, qtype, new_comments);
f63168e6 1047
6754ef71
CH
1048 for(Comment& c : new_comments) {
1049 c.domain_id = di.id;
1050 }
d708640f 1051 }
b3905a3d 1052
d708640f
CH
1053 if (replace_records) {
1054 if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
1055 throw ApiException("Hosting backend does not support editing records.");
1056 }
1057 }
1058 if (replace_comments) {
1059 if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
1060 throw ApiException("Hosting backend does not support editing comments.");
1061 }
1062 }
6cc98ddf 1063 }
d708640f
CH
1064 else
1065 throw ApiException("Changetype not understood");
6cc98ddf 1066 }
d29d5db7
CH
1067
1068 // edit SOA (if needed)
1069 if (!soa_edit_api_kind.empty() && !soa_edit_done) {
1070 SOAData sd;
1071 if (!B.getSOA(zonename, sd))
290a083d 1072 throw ApiException("No SOA found for domain '"+zonename.toString()+"'");
d29d5db7
CH
1073
1074 DNSResourceRecord rr;
1075 rr.qname = zonename;
1076 rr.content = serializeSOAData(sd);
1077 rr.qtype = "SOA";
1078 rr.domain_id = di.id;
1079 rr.auth = 1;
1080 rr.ttl = sd.ttl;
a6448d95 1081 increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
1d6b70f9
CH
1082 // fixup dots after serializeSOAData/increaseSOARecord
1083 rr.content = makeBackendRecordContent(rr.qtype, rr.content);
d29d5db7
CH
1084
1085 if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
1086 throw ApiException("Hosting backend does not support editing records.");
1087 }
1088 }
1089
d708640f
CH
1090 } catch(...) {
1091 di.backend->abortTransaction();
1092 throw;
1093 }
1094 di.backend->commitTransaction();
b3905a3d 1095
be9d7339 1096 PC.purgeExact(zonename);
d1587ceb 1097
d708640f 1098 // now the PTRs
995473c8 1099 storeChangedPTRs(B, new_ptrs);
b3905a3d
CH
1100
1101 // success
d708640f 1102 fillZone(zonename, resp);
b3905a3d
CH
1103}
1104
b1902fab
CH
1105static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
1106 if(req->method != "GET")
1107 throw HttpMethodNotAllowedException();
1108
583ea80d 1109 string q = req->getvars["q"];
720ed2bd
AT
1110 string sMax = req->getvars["max"];
1111 int maxEnts = 100;
1112 int ents = 0;
1113
b1902fab
CH
1114 if (q.empty())
1115 throw ApiException("Query q can't be blank");
720ed2bd 1116 if (sMax.empty() == false)
335da0ba 1117 maxEnts = std::stoi(sMax);
720ed2bd
AT
1118 if (maxEnts < 1)
1119 throw ApiException("Maximum entries must be larger than 0");
b1902fab 1120
720ed2bd 1121 SimpleMatch sm(q,true);
b1902fab 1122 UeberBackend B;
b1902fab 1123 vector<DomainInfo> domains;
720ed2bd
AT
1124 vector<DNSResourceRecord> result_rr;
1125 vector<Comment> result_c;
1d6b70f9
CH
1126 map<int,DomainInfo> zoneIdZone;
1127 map<int,DomainInfo>::iterator val;
00963dea 1128 Json::array doc;
b1902fab 1129
720ed2bd 1130 B.getAllDomains(&domains, true);
d2d194a9 1131
720ed2bd 1132 for(const DomainInfo di: domains)
1d6b70f9 1133 {
720ed2bd 1134 if (ents < maxEnts && sm.match(di.zone)) {
00963dea
CH
1135 doc.push_back(Json::object {
1136 { "object_type", "zone" },
1137 { "zone_id", apiZoneNameToId(di.zone) },
1138 { "name", di.zone.toString() }
1139 });
720ed2bd 1140 ents++;
b1902fab 1141 }
1d6b70f9 1142 zoneIdZone[di.id] = di; // populate cache
720ed2bd 1143 }
b1902fab 1144
720ed2bd
AT
1145 if (B.searchRecords(q, maxEnts, result_rr))
1146 {
1147 for(const DNSResourceRecord& rr: result_rr)
1148 {
00963dea
CH
1149 auto object = Json::object {
1150 { "object_type", "record" },
1151 { "name", rr.qname.toString() },
1152 { "type", rr.qtype.getName() },
1153 { "ttl", (double)rr.ttl },
1154 { "disabled", rr.disabled },
1155 { "content", makeApiRecordContent(rr.qtype, rr.content) }
1156 };
720ed2bd 1157 if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) {
00963dea
CH
1158 object["zone_id"] = apiZoneNameToId(val->second.zone);
1159 object["zone"] = val->second.zone.toString();
720ed2bd 1160 }
00963dea 1161 doc.push_back(object);
b1902fab 1162 }
720ed2bd 1163 }
b1902fab 1164
720ed2bd
AT
1165 if (B.searchComments(q, maxEnts, result_c))
1166 {
1167 for(const Comment &c: result_c)
1168 {
00963dea
CH
1169 auto object = Json::object {
1170 { "object_type", "comment" },
25dcc05f 1171 { "name", c.qname.toString() },
00963dea
CH
1172 { "content", c.content }
1173 };
720ed2bd 1174 if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) {
00963dea
CH
1175 object["zone_id"] = apiZoneNameToId(val->second.zone);
1176 object["zone"] = val->second.zone.toString();
720ed2bd 1177 }
00963dea 1178 doc.push_back(object);
b1902fab
CH
1179 }
1180 }
4bd3d119 1181
b1902fab
CH
1182 resp->setBody(doc);
1183}
1184
c0f6a1da 1185void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
a426cb89
CH
1186 if(req->method != "PUT")
1187 throw HttpMethodNotAllowedException();
80d59cd1 1188
c0f6a1da
CH
1189 DNSName canon = apiNameToDNSName(req->getvars["domain"]);
1190
c0f6a1da 1191 int count = PC.purgeExact(canon);
f682752a
CH
1192 resp->setBody(Json::object {
1193 { "count", count },
1194 { "result", "Flushed cache." }
1195 });
ddc84d12
CH
1196}
1197
dea47634 1198void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 1199{
80d59cd1
CH
1200 resp->headers["Cache-Control"] = "max-age=86400";
1201 resp->headers["Content-Type"] = "text/css";
c67bf8c5 1202
1071abdd 1203 ostringstream ret;
1071abdd
CH
1204 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
1205 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
1206 ret<<"a { color: #0959c2; }"<<endl;
1207 ret<<"a:hover { color: #3B8EC8; }"<<endl;
1208 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
1209 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
1210 ret<<".row:after { clear: both; }"<<endl;
1211 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
1212 ret<<".all { width: 100%; }"<<endl;
1213 ret<<".headl { width: 60%; }"<<endl;
1214 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
1215 ret<<"background-image: url(data:image/png;base64,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);";
1216 ret<<" width: 154px; height: 20px; }"<<endl;
1217 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
1218 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
1219 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
1220 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
1221 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
1222 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
1223 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
1224 ret<<"table.data tr:hover { background: white; }"<<endl;
1225 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
1226 ret<<".resetring {float: right; }"<<endl;
1227 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;
1228 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
1229 ret<<".resizering {float: right;}"<<endl;
80d59cd1 1230 resp->body = ret.str();
c146576d 1231 resp->status = 200;
1071abdd
CH
1232}
1233
dea47634 1234void AuthWebServer::webThread()
12c86877
BH
1235{
1236 try {
479e0976 1237 if(::arg().mustDo("api")) {
c0f6a1da 1238 d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush);
46d06a12 1239 d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig);
46d06a12
PL
1240 d_ws->registerApiHandler("/api/v1/servers/localhost/search-log", &apiServerSearchLog);
1241 d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServerSearchData);
1242 d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics);
1243 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", &apiServerZoneAxfrRetrieve);
1244 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", &apiZoneCryptokeys);
1245 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", &apiZoneCryptokeys);
1246 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", &apiServerZoneExport);
1247 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", &apiServerZoneNotify);
1248 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServerZoneDetail);
1249 d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZones);
1250 d_ws->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail);
1251 d_ws->registerApiHandler("/api/v1/servers", &apiServer);
9e6d2033 1252 d_ws->registerApiHandler("/api", &apiDiscovery);
c67bf8c5 1253 }
bbef8f04
CH
1254 d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
1255 d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
96d299db 1256 d_ws->go();
12c86877
BH
1257 }
1258 catch(...) {
dea47634 1259 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
12c86877
BH
1260 exit(1);
1261 }
1262}