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