]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
Merge pull request #3957 from cmouse/fix-fwd-parsing
[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 532 bool inquireSingleKey = false;
a388f0af 533 unsigned int inquireKeyId = 0;
29704f66
CH
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
3fe7c7d6
CH
762 storeChangedPTRs(B, new_ptrs);
763
1d6b70f9 764 fillZone(zonename, resp);
64a36f0d 765 resp->status = 201;
e2dba705
CH
766 return;
767 }
768
c67bf8c5
CH
769 if(req->method != "GET")
770 throw HttpMethodNotAllowedException();
771
c67bf8c5 772 vector<DomainInfo> domains;
cea26350 773 B.getAllDomains(&domains, true); // incl. disabled
c67bf8c5 774
62a9a74c
CH
775 Json::array doc;
776 for(const DomainInfo& di : domains) {
777 doc.push_back(getZoneInfo(di));
c67bf8c5 778 }
669822d0 779 resp->setBody(doc);
c67bf8c5
CH
780}
781
05776d2f 782static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
290a083d 783 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
05776d2f 784
d07bf7ff 785 if(req->method == "PUT" && !::arg().mustDo("api-readonly")) {
7c0ba3d2
CH
786 // update domain settings
787 UeberBackend B;
788 DomainInfo di;
789 if(!B.getDomainInfo(zonename, di))
290a083d 790 throw ApiException("Could not find domain '"+zonename.toString()+"'");
7c0ba3d2 791
1f68b185 792 updateDomainSettingsFromDocument(di, zonename, req->json());
7c0ba3d2 793
669822d0 794 fillZone(zonename, resp);
7c0ba3d2
CH
795 return;
796 }
d07bf7ff 797 else if(req->method == "DELETE" && !::arg().mustDo("api-readonly")) {
a462a01d
CH
798 // delete domain
799 UeberBackend B;
800 DomainInfo di;
801 if(!B.getDomainInfo(zonename, di))
290a083d 802 throw ApiException("Could not find domain '"+zonename.toString()+"'");
a462a01d
CH
803
804 if(!di.backend->deleteDomain(zonename))
290a083d 805 throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
a462a01d
CH
806
807 // empty body on success
808 resp->body = "";
37663c3b 809 resp->status = 204; // No Content: declare that the zone is gone now
a462a01d 810 return;
d07bf7ff 811 } else if (req->method == "PATCH" && !::arg().mustDo("api-readonly")) {
d708640f 812 patchZone(req, resp);
6cc98ddf
CH
813 return;
814 } else if (req->method == "GET") {
815 fillZone(zonename, resp);
816 return;
a462a01d 817 }
7c0ba3d2 818
6cc98ddf 819 throw HttpMethodNotAllowedException();
05776d2f
CH
820}
821
a83004d3 822static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
290a083d 823 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a83004d3
CH
824
825 if(req->method != "GET")
826 throw HttpMethodNotAllowedException();
827
828 ostringstream ss;
829
830 UeberBackend B;
831 DomainInfo di;
832 if(!B.getDomainInfo(zonename, di))
290a083d 833 throw ApiException("Could not find domain '"+zonename.toString()+"'");
a83004d3
CH
834
835 DNSResourceRecord rr;
836 SOAData sd;
837 di.backend->list(zonename, di.id);
838 while(di.backend->get(rr)) {
839 if (!rr.qtype.getCode())
840 continue; // skip empty non-terminals
841
a83004d3 842 ss <<
675fa24c 843 rr.qname.toString() << "\t" <<
a83004d3
CH
844 rr.ttl << "\t" <<
845 rr.qtype.getName() << "\t" <<
1d6b70f9 846 makeApiRecordContent(rr.qtype, rr.content) <<
a83004d3
CH
847 endl;
848 }
849
850 if (req->accept_json) {
41873e7c 851 resp->setBody(Json::object { { "zone", ss.str() } });
a83004d3
CH
852 } else {
853 resp->headers["Content-Type"] = "text/plain; charset=us-ascii";
854 resp->body = ss.str();
855 }
856}
857
a426cb89 858static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
290a083d 859 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a426cb89
CH
860
861 if(req->method != "PUT")
862 throw HttpMethodNotAllowedException();
863
864 UeberBackend B;
865 DomainInfo di;
866 if(!B.getDomainInfo(zonename, di))
290a083d 867 throw ApiException("Could not find domain '"+zonename.toString()+"'");
a426cb89
CH
868
869 if(di.masters.empty())
290a083d 870 throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain (or has no master defined)");
a426cb89
CH
871
872 random_shuffle(di.masters.begin(), di.masters.end());
873 Communicator.addSuckRequest(zonename, di.masters.front());
692829aa 874 resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front());
a426cb89
CH
875}
876
877static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) {
290a083d 878 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
a426cb89
CH
879
880 if(req->method != "PUT")
881 throw HttpMethodNotAllowedException();
882
883 UeberBackend B;
884 DomainInfo di;
885 if(!B.getDomainInfo(zonename, di))
290a083d 886 throw ApiException("Could not find domain '"+zonename.toString()+"'");
a426cb89
CH
887
888 if(!Communicator.notifyDomain(zonename))
889 throw ApiException("Failed to add to the queue - see server log");
890
692829aa 891 resp->setSuccessResult("Notification queued");
a426cb89
CH
892}
893
d1587ceb
CH
894static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) {
895 if (rr.qtype.getCode() == QType::A) {
896 uint32_t ip;
897 if (!IpToU32(rr.content, &ip)) {
898 throw ApiException("PTR: Invalid IP address given");
899 }
1d6b70f9 900 ptr->qname = DNSName((boost::format("%u.%u.%u.%u.in-addr.arpa.")
d1587ceb
CH
901 % ((ip >> 24) & 0xff)
902 % ((ip >> 16) & 0xff)
903 % ((ip >> 8) & 0xff)
904 % ((ip ) & 0xff)
1d6b70f9 905 ).str());
d1587ceb
CH
906 } else if (rr.qtype.getCode() == QType::AAAA) {
907 ComboAddress ca(rr.content);
5fb3aa58 908 char buf[3];
d1587ceb 909 ostringstream ss;
5fb3aa58
CH
910 for (int octet = 0; octet < 16; ++octet) {
911 if (snprintf(buf, sizeof(buf), "%02x", ca.sin6.sin6_addr.s6_addr[octet]) != (sizeof(buf)-1)) {
912 // this should be impossible: no byte should give more than two digits in hex format
913 throw PDNSException("Formatting IPv6 address failed");
914 }
915 ss << buf[0] << '.' << buf[1] << '.';
d1587ceb 916 }
5fb3aa58
CH
917 string tmp = ss.str();
918 tmp.resize(tmp.size()-1); // remove last dot
919 // reverse and append arpa domain
1d6b70f9 920 ptr->qname = DNSName(string(tmp.rbegin(), tmp.rend())) + DNSName("ip6.arpa.");
d1587ceb 921 } else {
675fa24c 922 throw ApiException("Unsupported PTR source '" + rr.qname.toString() + "' type '" + rr.qtype.getName() + "'");
d1587ceb
CH
923 }
924
925 ptr->qtype = "PTR";
926 ptr->ttl = rr.ttl;
927 ptr->disabled = rr.disabled;
675fa24c 928 ptr->content = rr.qname.toString();
d1587ceb
CH
929}
930
995473c8
CH
931static void storeChangedPTRs(UeberBackend& B, vector<DNSResourceRecord>& new_ptrs) {
932 for(const DNSResourceRecord& rr : new_ptrs) {
933 DNSPacket fakePacket;
934 SOAData sd;
935 sd.db = (DNSBackend *)-1; // getAuth() cache bypass
936 fakePacket.qtype = QType::PTR;
937
938 if (!B.getAuth(&fakePacket, &sd, rr.qname))
939 throw ApiException("Could not find domain for PTR '"+rr.qname.toString()+"' requested for '"+rr.content+"' (while saving)");
940
941 string soa_edit_api_kind;
942 string soa_edit_kind;
943 bool soa_changed = false;
944 DNSResourceRecord soarr;
945 sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT-API", soa_edit_api_kind);
946 sd.db->getDomainMetadataOne(sd.qname, "SOA-EDIT", soa_edit_kind);
947 if (!soa_edit_api_kind.empty()) {
948 soarr.qname = sd.qname;
949 soarr.content = serializeSOAData(sd);
950 soarr.qtype = "SOA";
951 soarr.domain_id = sd.domain_id;
952 soarr.auth = 1;
953 soarr.ttl = sd.ttl;
954 increaseSOARecord(soarr, soa_edit_api_kind, soa_edit_kind);
955 // fixup dots after serializeSOAData/increaseSOARecord
956 soarr.content = makeBackendRecordContent(soarr.qtype, soarr.content);
957 soa_changed = true;
958 }
959
960 sd.db->startTransaction(sd.qname);
961 if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
962 sd.db->abortTransaction();
963 throw ApiException("PTR-Hosting backend for "+rr.qname.toString()+"/"+rr.qtype.getName()+" does not support editing records.");
964 }
965
966 if (soa_changed) {
967 sd.db->replaceRRSet(sd.domain_id, soarr.qname, soarr.qtype, vector<DNSResourceRecord>(1, soarr));
968 }
969
970 sd.db->commitTransaction();
971 PC.purgeExact(rr.qname);
972 }
973}
974
d708640f 975static void patchZone(HttpRequest* req, HttpResponse* resp) {
b3905a3d
CH
976 UeberBackend B;
977 DomainInfo di;
290a083d 978 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
d708640f 979 if (!B.getDomainInfo(zonename, di))
290a083d 980 throw ApiException("Could not find domain '"+zonename.toString()+"'");
b3905a3d 981
f63168e6
CH
982 vector<DNSResourceRecord> new_records;
983 vector<Comment> new_comments;
d708640f
CH
984 vector<DNSResourceRecord> new_ptrs;
985
1f68b185 986 Json document = req->json();
b3905a3d 987
1f68b185
CH
988 auto rrsets = document["rrsets"];
989 if (!rrsets.is_array())
d708640f 990 throw ApiException("No rrsets given in update request");
b3905a3d 991
d708640f 992 di.backend->startTransaction(zonename);
6cc98ddf 993
d708640f 994 try {
d29d5db7 995 string soa_edit_api_kind;
a6448d95 996 string soa_edit_kind;
d29d5db7 997 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
a6448d95 998 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
d29d5db7
CH
999 bool soa_edit_done = false;
1000
6754ef71
CH
1001 for (const auto& rrset : rrsets.array_items()) {
1002 string changetype = toUpper(stringFromJson(rrset, "changetype"));
c576d0c5 1003 DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
cb9b5901 1004 apiCheckQNameAllowedCharacters(qname.toString());
6754ef71 1005 QType qtype;
d708640f 1006 qtype = stringFromJson(rrset, "type");
6754ef71
CH
1007 if (qtype.getCode() == 0) {
1008 throw ApiException("RRset "+qname.toString()+" IN "+stringFromJson(rrset, "type")+": unknown type given");
1009 }
d708640f 1010
d708640f
CH
1011 if (changetype == "DELETE") {
1012 // delete all matching qname/qtype RRs (and, implictly comments).
1013 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
1014 throw ApiException("Hosting backend does not support editing records.");
6cc98ddf 1015 }
d708640f
CH
1016 }
1017 else if (changetype == "REPLACE") {
1d6b70f9 1018 // we only validate for REPLACE, as DELETE can be used to "fix" out of zone records.
e325f20c 1019 if (!qname.isPartOf(zonename) && qname != zonename)
edda67a2 1020 throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Name is out of zone");
34df6ecc 1021
6754ef71
CH
1022 bool replace_records = rrset["records"].is_array();
1023 bool replace_comments = rrset["comments"].is_array();
f63168e6 1024
6754ef71
CH
1025 if (!replace_records && !replace_comments) {
1026 throw ApiException("No change for RRset " + qname.toString() + " IN " + qtype.getName());
1027 }
f63168e6 1028
6754ef71
CH
1029 new_records.clear();
1030 new_comments.clear();
f63168e6 1031
6754ef71
CH
1032 if (replace_records) {
1033 // ttl shouldn't be part of DELETE, and it shouldn't be required if we don't get new records.
1034 int ttl = intFromJson(rrset, "ttl");
1035 // new_ptrs is merged.
1036 gatherRecords(rrset, qname, qtype, ttl, new_records, new_ptrs);
1037
1038 for(DNSResourceRecord& rr : new_records) {
1039 rr.domain_id = di.id;
1040 if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
1041 soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
1042 rr.content = makeBackendRecordContent(rr.qtype, rr.content);
1043 }
d708640f 1044 }
6cc98ddf
CH
1045 }
1046
6754ef71
CH
1047 if (replace_comments) {
1048 gatherComments(rrset, qname, qtype, new_comments);
f63168e6 1049
6754ef71
CH
1050 for(Comment& c : new_comments) {
1051 c.domain_id = di.id;
1052 }
d708640f 1053 }
b3905a3d 1054
d708640f
CH
1055 if (replace_records) {
1056 if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
1057 throw ApiException("Hosting backend does not support editing records.");
1058 }
1059 }
1060 if (replace_comments) {
1061 if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
1062 throw ApiException("Hosting backend does not support editing comments.");
1063 }
1064 }
6cc98ddf 1065 }
d708640f
CH
1066 else
1067 throw ApiException("Changetype not understood");
6cc98ddf 1068 }
d29d5db7
CH
1069
1070 // edit SOA (if needed)
1071 if (!soa_edit_api_kind.empty() && !soa_edit_done) {
1072 SOAData sd;
1073 if (!B.getSOA(zonename, sd))
290a083d 1074 throw ApiException("No SOA found for domain '"+zonename.toString()+"'");
d29d5db7
CH
1075
1076 DNSResourceRecord rr;
1077 rr.qname = zonename;
1078 rr.content = serializeSOAData(sd);
1079 rr.qtype = "SOA";
1080 rr.domain_id = di.id;
1081 rr.auth = 1;
1082 rr.ttl = sd.ttl;
a6448d95 1083 increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
1d6b70f9
CH
1084 // fixup dots after serializeSOAData/increaseSOARecord
1085 rr.content = makeBackendRecordContent(rr.qtype, rr.content);
d29d5db7
CH
1086
1087 if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
1088 throw ApiException("Hosting backend does not support editing records.");
1089 }
1090 }
1091
d708640f
CH
1092 } catch(...) {
1093 di.backend->abortTransaction();
1094 throw;
1095 }
1096 di.backend->commitTransaction();
b3905a3d 1097
be9d7339 1098 PC.purgeExact(zonename);
d1587ceb 1099
d708640f 1100 // now the PTRs
995473c8 1101 storeChangedPTRs(B, new_ptrs);
b3905a3d
CH
1102
1103 // success
d708640f 1104 fillZone(zonename, resp);
b3905a3d
CH
1105}
1106
b1902fab
CH
1107static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
1108 if(req->method != "GET")
1109 throw HttpMethodNotAllowedException();
1110
583ea80d 1111 string q = req->getvars["q"];
720ed2bd
AT
1112 string sMax = req->getvars["max"];
1113 int maxEnts = 100;
1114 int ents = 0;
1115
b1902fab
CH
1116 if (q.empty())
1117 throw ApiException("Query q can't be blank");
720ed2bd 1118 if (sMax.empty() == false)
335da0ba 1119 maxEnts = std::stoi(sMax);
720ed2bd
AT
1120 if (maxEnts < 1)
1121 throw ApiException("Maximum entries must be larger than 0");
b1902fab 1122
720ed2bd 1123 SimpleMatch sm(q,true);
b1902fab 1124 UeberBackend B;
b1902fab 1125 vector<DomainInfo> domains;
720ed2bd
AT
1126 vector<DNSResourceRecord> result_rr;
1127 vector<Comment> result_c;
1d6b70f9
CH
1128 map<int,DomainInfo> zoneIdZone;
1129 map<int,DomainInfo>::iterator val;
00963dea 1130 Json::array doc;
b1902fab 1131
720ed2bd 1132 B.getAllDomains(&domains, true);
d2d194a9 1133
720ed2bd 1134 for(const DomainInfo di: domains)
1d6b70f9 1135 {
720ed2bd 1136 if (ents < maxEnts && sm.match(di.zone)) {
00963dea
CH
1137 doc.push_back(Json::object {
1138 { "object_type", "zone" },
1139 { "zone_id", apiZoneNameToId(di.zone) },
1140 { "name", di.zone.toString() }
1141 });
720ed2bd 1142 ents++;
b1902fab 1143 }
1d6b70f9 1144 zoneIdZone[di.id] = di; // populate cache
720ed2bd 1145 }
b1902fab 1146
720ed2bd
AT
1147 if (B.searchRecords(q, maxEnts, result_rr))
1148 {
1149 for(const DNSResourceRecord& rr: result_rr)
1150 {
00963dea
CH
1151 auto object = Json::object {
1152 { "object_type", "record" },
1153 { "name", rr.qname.toString() },
1154 { "type", rr.qtype.getName() },
1155 { "ttl", (double)rr.ttl },
1156 { "disabled", rr.disabled },
1157 { "content", makeApiRecordContent(rr.qtype, rr.content) }
1158 };
720ed2bd 1159 if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) {
00963dea
CH
1160 object["zone_id"] = apiZoneNameToId(val->second.zone);
1161 object["zone"] = val->second.zone.toString();
720ed2bd 1162 }
00963dea 1163 doc.push_back(object);
b1902fab 1164 }
720ed2bd 1165 }
b1902fab 1166
720ed2bd
AT
1167 if (B.searchComments(q, maxEnts, result_c))
1168 {
1169 for(const Comment &c: result_c)
1170 {
00963dea
CH
1171 auto object = Json::object {
1172 { "object_type", "comment" },
25dcc05f 1173 { "name", c.qname.toString() },
00963dea
CH
1174 { "content", c.content }
1175 };
720ed2bd 1176 if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) {
00963dea
CH
1177 object["zone_id"] = apiZoneNameToId(val->second.zone);
1178 object["zone"] = val->second.zone.toString();
720ed2bd 1179 }
00963dea 1180 doc.push_back(object);
b1902fab
CH
1181 }
1182 }
4bd3d119 1183
b1902fab
CH
1184 resp->setBody(doc);
1185}
1186
c0f6a1da 1187void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
a426cb89
CH
1188 if(req->method != "PUT")
1189 throw HttpMethodNotAllowedException();
80d59cd1 1190
c0f6a1da
CH
1191 DNSName canon = apiNameToDNSName(req->getvars["domain"]);
1192
c0f6a1da 1193 int count = PC.purgeExact(canon);
f682752a
CH
1194 resp->setBody(Json::object {
1195 { "count", count },
1196 { "result", "Flushed cache." }
1197 });
ddc84d12
CH
1198}
1199
dea47634 1200void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 1201{
80d59cd1
CH
1202 resp->headers["Cache-Control"] = "max-age=86400";
1203 resp->headers["Content-Type"] = "text/css";
c67bf8c5 1204
1071abdd 1205 ostringstream ret;
1071abdd
CH
1206 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
1207 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
1208 ret<<"a { color: #0959c2; }"<<endl;
1209 ret<<"a:hover { color: #3B8EC8; }"<<endl;
1210 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
1211 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
1212 ret<<".row:after { clear: both; }"<<endl;
1213 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
1214 ret<<".all { width: 100%; }"<<endl;
1215 ret<<".headl { width: 60%; }"<<endl;
1216 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
1217 ret<<"background-image: url(data:image/png;base64,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);";
1218 ret<<" width: 154px; height: 20px; }"<<endl;
1219 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
1220 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
1221 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
1222 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
1223 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
1224 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
1225 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
1226 ret<<"table.data tr:hover { background: white; }"<<endl;
1227 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
1228 ret<<".resetring {float: right; }"<<endl;
1229 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;
1230 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
1231 ret<<".resizering {float: right;}"<<endl;
80d59cd1 1232 resp->body = ret.str();
c146576d 1233 resp->status = 200;
1071abdd
CH
1234}
1235
dea47634 1236void AuthWebServer::webThread()
12c86877
BH
1237{
1238 try {
479e0976 1239 if(::arg().mustDo("api")) {
c0f6a1da 1240 d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush);
46d06a12 1241 d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig);
46d06a12
PL
1242 d_ws->registerApiHandler("/api/v1/servers/localhost/search-log", &apiServerSearchLog);
1243 d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServerSearchData);
1244 d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics);
1245 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", &apiServerZoneAxfrRetrieve);
1246 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", &apiZoneCryptokeys);
1247 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", &apiZoneCryptokeys);
1248 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", &apiServerZoneExport);
1249 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", &apiServerZoneNotify);
1250 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServerZoneDetail);
1251 d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZones);
1252 d_ws->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail);
1253 d_ws->registerApiHandler("/api/v1/servers", &apiServer);
9e6d2033 1254 d_ws->registerApiHandler("/api", &apiDiscovery);
c67bf8c5 1255 }
bbef8f04
CH
1256 d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
1257 d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
96d299db 1258 d_ws->go();
12c86877
BH
1259 }
1260 catch(...) {
dea47634 1261 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
12c86877
BH
1262 exit(1);
1263 }
1264}