]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
split up auth latency stats between v4 and v6 since you need to be able to see them...
[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)
7abbc40f 293 string zoneId = apiZoneNameToId(di.zone.toString());
c04b5870
CH
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());
a528c61d
CH
299 Value jname(di.zone.toString().c_str(), doc.GetAllocator()); // copy
300 jdi.AddMember("name", jname, doc.GetAllocator());
c04b5870
CH
301 jdi.AddMember("kind", di.getKindString(), doc.GetAllocator());
302 jdi.AddMember("dnssec", dk.isSecuredZone(di.zone), doc.GetAllocator());
303 jdi.AddMember("account", di.account.c_str(), doc.GetAllocator());
304 Value masters;
305 masters.SetArray();
306 BOOST_FOREACH(const string& master, di.masters) {
307 Value value(master.c_str(), doc.GetAllocator());
308 masters.PushBack(value, doc.GetAllocator());
309 }
310 jdi.AddMember("masters", masters, doc.GetAllocator());
311 jdi.AddMember("serial", di.serial, doc.GetAllocator());
312 jdi.AddMember("notified_serial", di.notified_serial, doc.GetAllocator());
313 jdi.AddMember("last_check", (unsigned int) di.last_check, doc.GetAllocator());
314}
315
669822d0 316static void fillZone(const string& zonename, HttpResponse* resp) {
1abb81f4 317 UeberBackend B;
1abb81f4 318 DomainInfo di;
73301d73 319 if(!B.getDomainInfo(zonename, di))
669822d0 320 throw ApiException("Could not find domain '"+zonename+"'");
1abb81f4
CH
321
322 Document doc;
c04b5870
CH
323 fillZoneInfo(di, doc, doc);
324 // extra stuff fillZoneInfo doesn't do for us (more expensive)
d29d5db7
CH
325 string soa_edit_api;
326 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
327 doc.AddMember("soa_edit_api", soa_edit_api.c_str(), doc.GetAllocator());
6bb25159
MS
328 string soa_edit;
329 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
330 doc.AddMember("soa_edit", soa_edit.c_str(), doc.GetAllocator());
1abb81f4 331
6cc98ddf 332 // fill records
1abb81f4
CH
333 DNSResourceRecord rr;
334 Value records;
335 records.SetArray();
cea26350 336 di.backend->list(zonename, di.id, true); // incl. disabled
3d89fc28 337 while(di.backend->get(rr)) {
1abb81f4
CH
338 if (!rr.qtype.getCode())
339 continue; // skip empty non-terminals
340
341 Value object;
342 object.SetObject();
7abbc40f 343 Value jname(rr.qname.toString().c_str(), doc.GetAllocator()); // copy
1abb81f4
CH
344 object.AddMember("name", jname, doc.GetAllocator());
345 Value jtype(rr.qtype.getName().c_str(), doc.GetAllocator()); // copy
346 object.AddMember("type", jtype, doc.GetAllocator());
347 object.AddMember("ttl", rr.ttl, doc.GetAllocator());
cea26350 348 object.AddMember("disabled", rr.disabled, doc.GetAllocator());
1abb81f4
CH
349 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
350 object.AddMember("content", jcontent, doc.GetAllocator());
351 records.PushBack(object, doc.GetAllocator());
352 }
e2dba705 353 doc.AddMember("records", records, doc.GetAllocator());
1abb81f4 354
6cc98ddf
CH
355 // fill comments
356 Comment comment;
357 Value comments;
358 comments.SetArray();
359 di.backend->listComments(di.id);
360 while(di.backend->getComment(comment)) {
361 Value object;
362 object.SetObject();
363 Value jname(comment.qname.c_str(), doc.GetAllocator()); // copy
364 object.AddMember("name", jname, doc.GetAllocator());
365 Value jtype(comment.qtype.getName().c_str(), doc.GetAllocator()); // copy
366 object.AddMember("type", jtype, doc.GetAllocator());
55f12d58 367 object.AddMember("modified_at", (unsigned int) comment.modified_at, doc.GetAllocator());
6cc98ddf
CH
368 Value jaccount(comment.account.c_str(), doc.GetAllocator()); // copy
369 object.AddMember("account", jaccount, doc.GetAllocator());
370 Value jcontent(comment.content.c_str(), doc.GetAllocator()); // copy
371 object.AddMember("content", jcontent, doc.GetAllocator());
372 comments.PushBack(object, doc.GetAllocator());
373 }
374 doc.AddMember("comments", comments, doc.GetAllocator());
375
669822d0 376 resp->setBody(doc);
1abb81f4
CH
377}
378
6ec5e728
CH
379void productServerStatisticsFetch(map<string,string>& out)
380{
a45303b8 381 vector<string> items = S.getEntries();
a45303b8 382 BOOST_FOREACH(const string& item, items) {
6ec5e728 383 out[item] = lexical_cast<string>(S.read(item));
a45303b8
CH
384 }
385
386 // add uptime
6ec5e728 387 out["uptime"] = lexical_cast<string>(time(0) - s_starttime);
c67bf8c5
CH
388}
389
f63168e6
CH
390static void gatherRecords(const Value& container, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
391 UeberBackend B;
392 DNSResourceRecord rr;
393 const Value& records = container["records"];
394 if (records.IsArray()) {
395 for (SizeType idx = 0; idx < records.Size(); ++idx) {
396 const Value& record = records[idx];
397 rr.qname = stringFromJson(record, "name");
398 rr.qtype = stringFromJson(record, "type");
399 rr.content = stringFromJson(record, "content");
400 rr.auth = 1;
401 rr.ttl = intFromJson(record, "ttl");
f63168e6
CH
402 rr.disabled = boolFromJson(record, "disabled");
403
24cd86ca 404 if (rr.qtype.getCode() == 0) {
561434a6 405 throw ApiException("Record "+rr.qname.toString()+"/"+stringFromJson(record, "type")+" is of unknown type");
24cd86ca
CH
406 }
407
f63168e6 408 try {
b9bafae0 409 shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content));
f63168e6 410 string tmp = drc->serialize(rr.qname);
1e5b9ab9
CH
411 if (rr.qtype.getCode() != QType::AAAA) {
412 tmp = drc->getZoneRepresentation();
413 if (!pdns_iequals(tmp, rr.content)) {
414 throw std::runtime_error("Not in expected format (parsed as '"+tmp+"')");
415 }
416 } else {
417 struct in6_addr tmpbuf;
418 if (inet_pton(AF_INET6, rr.content.c_str(), &tmpbuf) != 1 || rr.content.find('.') != string::npos) {
419 throw std::runtime_error("Invalid IPv6 address");
420 }
421 }
f63168e6
CH
422 }
423 catch(std::exception& e)
424 {
561434a6 425 throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.getName()+" '"+rr.content+"': "+e.what());
f63168e6
CH
426 }
427
428 if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
429 boolFromJson(record, "set-ptr", false) == true) {
430 DNSResourceRecord ptr;
431 makePtr(rr, &ptr);
432
433 // verify that there's a zone for the PTR
434 DNSPacket fakePacket;
435 SOAData sd;
436 fakePacket.qtype = QType::PTR;
81c486ad 437 if (!B.getAuth(&fakePacket, &sd, ptr.qname))
561434a6 438 throw ApiException("Could not find domain for PTR '"+ptr.qname.toString()+"' requested for '"+ptr.content+"'");
f63168e6
CH
439
440 ptr.domain_id = sd.domain_id;
441 new_ptrs.push_back(ptr);
442 }
443
444 new_records.push_back(rr);
445 }
446 }
447}
448
449static void gatherComments(const Value& container, vector<Comment>& new_comments, bool use_name_type_from_container) {
450 Comment c;
451 if (use_name_type_from_container) {
452 c.qname = stringFromJson(container, "name");
453 c.qtype = stringFromJson(container, "type");
454 }
455
456 time_t now = time(0);
457 const Value& comments = container["comments"];
458 if (comments.IsArray()) {
459 for(SizeType idx = 0; idx < comments.Size(); ++idx) {
460 const Value& comment = comments[idx];
461 if (!use_name_type_from_container) {
462 c.qname = stringFromJson(comment, "name");
463 c.qtype = stringFromJson(comment, "type");
464 }
465 c.modified_at = intFromJson(comment, "modified_at", now);
466 c.content = stringFromJson(comment, "content");
467 c.account = stringFromJson(comment, "account");
468 new_comments.push_back(c);
469 }
470 }
471}
6cc98ddf 472
bb9fd223
CH
473static void updateDomainSettingsFromDocument(const DomainInfo& di, const string& zonename, Document& document) {
474 string master;
475 const Value &masters = document["masters"];
476 if (masters.IsArray()) {
477 for (SizeType i = 0; i < masters.Size(); ++i) {
478 master += masters[i].GetString();
479 master += " ";
480 }
481 }
482
483 di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
484 di.backend->setMaster(zonename, master);
d29d5db7
CH
485
486 if (document["soa_edit_api"].IsString()) {
487 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].GetString());
488 }
6bb25159
MS
489 if (document["soa_edit"].IsString()) {
490 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].GetString());
491 }
79532aa7
CH
492 if (document["account"].IsString()) {
493 di.backend->setAccount(zonename, document["account"].GetString());
494 }
bb9fd223
CH
495}
496
4b7f120a
MS
497static void apiZoneCryptokeys(HttpRequest* req, HttpResponse* resp) {
498 if(req->method != "GET")
499 throw ApiException("Only GET is implemented");
500
583ea80d 501 string zonename = apiZoneIdToName(req->parameters["id"]);
4b7f120a
MS
502
503 UeberBackend B;
504 DomainInfo di;
505 DNSSECKeeper dk;
506
507 if(!B.getDomainInfo(zonename, di))
508 throw ApiException("Could not find domain '"+zonename+"'");
509
d8455c78 510 DNSSECKeeper::keyset_t keyset=dk.getKeys(zonename, boost::indeterminate, false);
4b7f120a
MS
511
512 if (keyset.empty())
513 throw ApiException("No keys for zone '"+zonename+"'");
514
515 Document doc;
516 doc.SetArray();
517
518 BOOST_FOREACH(DNSSECKeeper::keyset_t::value_type value, keyset) {
583ea80d
CH
519 if (req->parameters.count("key_id")) {
520 int keyid = lexical_cast<int>(req->parameters["key_id"]);
38809e97
MS
521 int curid = lexical_cast<int>(value.second.id);
522 if (keyid != curid)
523 continue;
524 }
4b7f120a
MS
525 Value key;
526 key.SetObject();
527 key.AddMember("type", "Cryptokey", doc.GetAllocator());
528 key.AddMember("id", value.second.id, doc.GetAllocator());
529 key.AddMember("active", value.second.active, doc.GetAllocator());
530 key.AddMember("keytype", (value.second.keyOrZone ? "ksk" : "zsk"), doc.GetAllocator());
cc016934
MS
531 Value dnskey(value.first.getDNSKEY().getZoneRepresentation().c_str(), doc.GetAllocator());
532 key.AddMember("dnskey", dnskey, doc.GetAllocator());
583ea80d
CH
533 if (req->parameters.count("key_id")) {
534 DNSSECPrivateKey dpk=dk.getKeyById(zonename, lexical_cast<int>(req->parameters["key_id"]));
cc016934 535 Value content(dpk.getKey()->convertToISC().c_str(), doc.GetAllocator());
38809e97
MS
536 key.AddMember("content", content, doc.GetAllocator());
537 }
4b7f120a
MS
538
539 if (value.second.keyOrZone) {
540 Value dses;
541 dses.SetArray();
38809e97 542 Value ds(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), 1).getZoneRepresentation().c_str(), doc.GetAllocator());
4b7f120a 543 dses.PushBack(ds, doc.GetAllocator());
38809e97 544 Value ds2(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), 2).getZoneRepresentation().c_str(), doc.GetAllocator());
4b7f120a
MS
545 dses.PushBack(ds2, doc.GetAllocator());
546
547 try {
38809e97
MS
548 Value ds3(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), 3).getZoneRepresentation().c_str(), doc.GetAllocator());
549 dses.PushBack(ds3, doc.GetAllocator());
4b7f120a
MS
550 }
551 catch(...)
552 {
553 }
554 try {
38809e97
MS
555 Value ds4(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), 4).getZoneRepresentation().c_str(), doc.GetAllocator());
556 dses.PushBack(ds4, doc.GetAllocator());
4b7f120a
MS
557 }
558 catch(...)
559 {
560 }
4b7f120a
MS
561 key.AddMember("ds", dses, doc.GetAllocator());
562 }
563
564 doc.PushBack(key, doc.GetAllocator());
565 }
566
567 resp->setBody(doc);
568}
569
0f0e73fe
MS
570static void gatherRecordsFromZone(const Value &container, vector<DNSResourceRecord>& new_records, string zonename) {
571 DNSResourceRecord rr;
572 vector<string> zonedata;
573 stringtok(zonedata, stringFromJson(container, "zone"), "\r\n");
574
575 ZoneParserTNG zpt(zonedata, zonename);
576
577 bool seenSOA=false;
578
579 string comment = "Imported via the API";
580
581 try {
582 while(zpt.get(rr, &comment)) {
583 if(seenSOA && rr.qtype.getCode() == QType::SOA)
584 continue;
585 if(rr.qtype.getCode() == QType::SOA)
586 seenSOA=true;
587
7abbc40f 588 // rr.qname = stripDot(rr.qname);
0f0e73fe
MS
589 new_records.push_back(rr);
590 }
591 }
592 catch(std::exception& ae) {
593 throw ApiException("An error occured while parsing the zonedata: "+string(ae.what()));
594 }
595}
596
80d59cd1 597static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
e2dba705 598 UeberBackend B;
559115f6 599 DNSSECKeeper dk;
18179c61 600 if (req->method == "POST" && !::arg().mustDo("experimental-api-readonly")) {
e2dba705
CH
601 DomainInfo di;
602 Document document;
6ec5e728 603 req->json(document);
e2dba705 604 string zonename = stringFromJson(document, "name");
e2dba705 605
406497f5
CH
606 // strip trailing dot (from spec PoV this is wrong, but be nice to clients)
607 if (zonename.size() > 0 && zonename.substr(zonename.size()-1) == ".") {
4ebf78b1
CH
608 zonename.resize(zonename.size()-1);
609 }
610
406497f5
CH
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 655 BOOST_FOREACH(rr, new_records) {
edda67a2 656 if (!rr.qname.isPartOf(zonename) && !pdns_iequals(rr.qname, zonename))
561434a6 657 throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": Name is out of zone");
f63168e6
CH
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"];
3343ad1f 677 // attodot(sd.hostmaster); FIXME400
f63168e6
CH
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
675fa24c
PD
792// static 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// }
a83004d3
CH
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);
675fa24c
PD
827 sd.nameserver = sd.nameserver.toString();
828 sd.hostmaster = sd.hostmaster.toString();
a83004d3
CH
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:
7abbc40f 836 content = rr.content;
a83004d3
CH
837 break;
838 default:
839 break;
840 }
841
842 ss <<
675fa24c 843 rr.qname.toString() << "\t" <<
a83004d3
CH
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 925 } else {
675fa24c 926 throw ApiException("Unsupported PTR source '" + rr.qname.toString() + "' type '" + rr.qtype.getName() + "'");
d1587ceb
CH
927 }
928
929 ptr->qtype = "PTR";
930 ptr->ttl = rr.ttl;
931 ptr->disabled = rr.disabled;
675fa24c 932 ptr->content = rr.qname.toString();
d1587ceb
CH
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
f63168e6
CH
942 vector<DNSResourceRecord> new_records;
943 vector<Comment> new_comments;
d708640f
CH
944 vector<DNSResourceRecord> new_ptrs;
945
b3905a3d 946 Document document;
6ec5e728 947 req->json(document);
b3905a3d 948
d708640f
CH
949 const Value& rrsets = document["rrsets"];
950 if (!rrsets.IsArray())
951 throw ApiException("No rrsets given in update request");
b3905a3d 952
d708640f 953 di.backend->startTransaction(zonename);
6cc98ddf 954
d708640f 955 try {
d29d5db7 956 string soa_edit_api_kind;
a6448d95 957 string soa_edit_kind;
d29d5db7 958 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
a6448d95 959 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
d29d5db7
CH
960 bool soa_edit_done = false;
961
d708640f
CH
962 for(SizeType rrsetIdx = 0; rrsetIdx < rrsets.Size(); ++rrsetIdx) {
963 const Value& rrset = rrsets[rrsetIdx];
edda67a2 964 string changetype;
d708640f 965 QType qtype;
edda67a2 966 DNSName qname = stringFromJson(rrset, "name");
d708640f
CH
967 qtype = stringFromJson(rrset, "type");
968 changetype = toUpper(stringFromJson(rrset, "changetype"));
969
d708640f
CH
970 if (changetype == "DELETE") {
971 // delete all matching qname/qtype RRs (and, implictly comments).
972 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
973 throw ApiException("Hosting backend does not support editing records.");
6cc98ddf 974 }
d708640f
CH
975 }
976 else if (changetype == "REPLACE") {
34df6ecc 977 // we only validate for REPLACE, as DELETE can be used to "fix" out of zone records.
edda67a2
CH
978 if (!qname.isPartOf(zonename) && !pdns_iequals(qname, zonename))
979 throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Name is out of zone");
34df6ecc
CH
980
981 new_records.clear();
f63168e6
CH
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)
edda67a2 991 throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.getName()+" "+rr.content+": Record wrongly bundled with RRset " + qname.toString() + "/" + qtype.getName());
f63168e6
CH
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 1005 if (!replace_records && !replace_comments) {
edda67a2 1006 throw ApiException("No change for RRset " + qname.toString() + "/" + qtype.getName());
d708640f 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))
561434a6 1061 throw ApiException("Could not find domain for PTR '"+rr.qname.toString()+"' 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();
561434a6 1066 throw ApiException("PTR-Hosting backend for "+rr.qname.toString()+"/"+rr.qtype.getName()+" does not support editing records.");
d1587ceb 1067 }
d708640f 1068 sd.db->commitTransaction();
7abbc40f 1069 PC.purge(rr.qname.toString());
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"];
720ed2bd
AT
1081 string sMax = req->getvars["max"];
1082 int maxEnts = 100;
1083 int ents = 0;
1084
b1902fab
CH
1085 if (q.empty())
1086 throw ApiException("Query q can't be blank");
720ed2bd
AT
1087 if (sMax.empty() == false)
1088 maxEnts = boost::lexical_cast<int>(sMax);
1089 if (maxEnts < 1)
1090 throw ApiException("Maximum entries must be larger than 0");
b1902fab 1091
720ed2bd 1092 SimpleMatch sm(q,true);
b1902fab 1093 UeberBackend B;
b1902fab 1094 vector<DomainInfo> domains;
720ed2bd
AT
1095 vector<DNSResourceRecord> result_rr;
1096 vector<Comment> result_c;
1097 map<int,DNSName> zoneIdZone;
1098 map<int,DNSName>::iterator val;
b1902fab 1099 Document doc;
b1902fab 1100
720ed2bd 1101 doc.SetArray();
b1902fab 1102
720ed2bd 1103 B.getAllDomains(&domains, true);
d2d194a9 1104
720ed2bd
AT
1105 for(const DomainInfo di: domains)
1106 {
1107 if (ents < maxEnts && sm.match(di.zone)) {
b1902fab
CH
1108 Value object;
1109 object.SetObject();
720ed2bd
AT
1110 object.AddMember("object_type", "zone", doc.GetAllocator());
1111 object.AddMember("zone_id", di.id, doc.GetAllocator());
561434a6 1112 Value jzoneName(di.zone.toString().c_str(), doc.GetAllocator()); // copy
d2d194a9 1113 object.AddMember("name", jzoneName, doc.GetAllocator());
b1902fab 1114 doc.PushBack(object, doc.GetAllocator());
720ed2bd 1115 ents++;
b1902fab 1116 }
720ed2bd
AT
1117 zoneIdZone[di.id] = di.zone; // populate cache
1118 }
b1902fab 1119
720ed2bd
AT
1120 if (B.searchRecords(q, maxEnts, result_rr))
1121 {
1122 for(const DNSResourceRecord& rr: result_rr)
1123 {
b1902fab
CH
1124 Value object;
1125 object.SetObject();
720ed2bd
AT
1126 object.AddMember("object_type", "record", doc.GetAllocator());
1127 object.AddMember("zone_id", rr.domain_id, doc.GetAllocator());
1128 if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) {
1129 Value zname(val->second.toString().c_str(), doc.GetAllocator()); // copy
1130 object.AddMember("zone", zname, doc.GetAllocator()); // copy
1131 }
1132 Value jname(rr.qname.toString().c_str(), doc.GetAllocator()); // copy
b1902fab 1133 object.AddMember("name", jname, doc.GetAllocator());
720ed2bd
AT
1134 Value jtype(rr.qtype.getName().c_str(), doc.GetAllocator()); // copy
1135 object.AddMember("type", jtype, doc.GetAllocator());
1136 object.AddMember("ttl", rr.ttl, doc.GetAllocator());
1137 object.AddMember("disabled", rr.disabled, doc.GetAllocator());
b1902fab
CH
1138 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
1139 object.AddMember("content", jcontent, doc.GetAllocator());
1140 doc.PushBack(object, doc.GetAllocator());
1141 }
720ed2bd 1142 }
b1902fab 1143
720ed2bd
AT
1144 if (B.searchComments(q, maxEnts, result_c))
1145 {
1146 for(const Comment &c: result_c)
1147 {
b1902fab
CH
1148
1149 Value object;
1150 object.SetObject();
720ed2bd
AT
1151 object.AddMember("object_type", "comment", doc.GetAllocator());
1152 object.AddMember("zone_id", c.domain_id, doc.GetAllocator());
1153 if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) {
1154 Value zname(val->second.toString().c_str(), doc.GetAllocator()); // copy
1155 object.AddMember("zone", zname, doc.GetAllocator()); // copy
1156 }
1157 Value jname(c.qname.c_str(), doc.GetAllocator()); // copy
b1902fab 1158 object.AddMember("name", jname, doc.GetAllocator());
720ed2bd 1159 Value jcontent(c.content.c_str(), doc.GetAllocator()); // copy
b1902fab
CH
1160 object.AddMember("content", jcontent, doc.GetAllocator());
1161 doc.PushBack(object, doc.GetAllocator());
1162 }
1163 }
4bd3d119 1164
b1902fab
CH
1165 resp->setBody(doc);
1166}
1167
a426cb89
CH
1168void apiServerFlushCache(HttpRequest* req, HttpResponse* resp) {
1169 if(req->method != "PUT")
1170 throw HttpMethodNotAllowedException();
80d59cd1 1171
a426cb89
CH
1172 extern PacketCache PC;
1173 int count;
1174 if(req->getvars["domain"].empty())
1175 count = PC.purge();
1176 else
1177 count = PC.purge(req->getvars["domain"]);
ddc84d12 1178
a426cb89
CH
1179 map<string, string> object;
1180 object["count"] = lexical_cast<string>(count);
1181 object["result"] = "Flushed cache.";
1182 resp->body = returnJsonObject(object);
ddc84d12
CH
1183}
1184
dea47634 1185void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 1186{
80d59cd1
CH
1187 resp->headers["Cache-Control"] = "max-age=86400";
1188 resp->headers["Content-Type"] = "text/css";
c67bf8c5 1189
1071abdd 1190 ostringstream ret;
1071abdd
CH
1191 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
1192 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
1193 ret<<"a { color: #0959c2; }"<<endl;
1194 ret<<"a:hover { color: #3B8EC8; }"<<endl;
1195 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
1196 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
1197 ret<<".row:after { clear: both; }"<<endl;
1198 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
1199 ret<<".all { width: 100%; }"<<endl;
1200 ret<<".headl { width: 60%; }"<<endl;
1201 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
1202 ret<<"background-image: url(data:image/png;base64,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);";
1203 ret<<" width: 154px; height: 20px; }"<<endl;
1204 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
1205 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
1206 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
1207 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
1208 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
1209 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
1210 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
1211 ret<<"table.data tr:hover { background: white; }"<<endl;
1212 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
1213 ret<<".resetring {float: right; }"<<endl;
1214 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;
1215 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
1216 ret<<".resizering {float: right;}"<<endl;
80d59cd1 1217 resp->body = ret.str();
c146576d 1218 resp->status = 200;
1071abdd
CH
1219}
1220
dea47634 1221void AuthWebServer::webThread()
12c86877
BH
1222{
1223 try {
c67bf8c5 1224 if(::arg().mustDo("experimental-json-interface")) {
3ae143b0 1225 d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
a426cb89 1226 d_ws->registerApiHandler("/servers/localhost/flush-cache", &apiServerFlushCache);
3ae143b0 1227 d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
b1902fab 1228 d_ws->registerApiHandler("/servers/localhost/search-data", &apiServerSearchData);
3ae143b0 1229 d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
a426cb89 1230 d_ws->registerApiHandler("/servers/localhost/zones/<id>/axfr-retrieve", &apiServerZoneAxfrRetrieve);
4b7f120a
MS
1231 d_ws->registerApiHandler("/servers/localhost/zones/<id>/cryptokeys/<key_id>", &apiZoneCryptokeys);
1232 d_ws->registerApiHandler("/servers/localhost/zones/<id>/cryptokeys", &apiZoneCryptokeys);
a83004d3 1233 d_ws->registerApiHandler("/servers/localhost/zones/<id>/export", &apiServerZoneExport);
a426cb89 1234 d_ws->registerApiHandler("/servers/localhost/zones/<id>/notify", &apiServerZoneNotify);
3ae143b0
CH
1235 d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail);
1236 d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones);
1237 d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
1238 d_ws->registerApiHandler("/servers", &apiServer);
c67bf8c5 1239 }
bbef8f04
CH
1240 d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
1241 d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
96d299db 1242 d_ws->go();
12c86877
BH
1243 }
1244 catch(...) {
dea47634 1245 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
12c86877
BH
1246 exit(1);
1247 }
1248}