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