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