]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
Merge pull request #1766 from rubenk/fixup-static-binaries-check
[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")) {
c81c2ea8 64 d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port"),arg()["webserver-password"]);
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());
342 object.AddMember("priority", rr.priority, doc.GetAllocator());
cea26350 343 object.AddMember("disabled", rr.disabled, doc.GetAllocator());
1abb81f4
CH
344 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
345 object.AddMember("content", jcontent, doc.GetAllocator());
346 records.PushBack(object, doc.GetAllocator());
347 }
e2dba705 348 doc.AddMember("records", records, doc.GetAllocator());
1abb81f4 349
6cc98ddf
CH
350 // fill comments
351 Comment comment;
352 Value comments;
353 comments.SetArray();
354 di.backend->listComments(di.id);
355 while(di.backend->getComment(comment)) {
356 Value object;
357 object.SetObject();
358 Value jname(comment.qname.c_str(), doc.GetAllocator()); // copy
359 object.AddMember("name", jname, doc.GetAllocator());
360 Value jtype(comment.qtype.getName().c_str(), doc.GetAllocator()); // copy
361 object.AddMember("type", jtype, doc.GetAllocator());
55f12d58 362 object.AddMember("modified_at", (unsigned int) comment.modified_at, doc.GetAllocator());
6cc98ddf
CH
363 Value jaccount(comment.account.c_str(), doc.GetAllocator()); // copy
364 object.AddMember("account", jaccount, doc.GetAllocator());
365 Value jcontent(comment.content.c_str(), doc.GetAllocator()); // copy
366 object.AddMember("content", jcontent, doc.GetAllocator());
367 comments.PushBack(object, doc.GetAllocator());
368 }
369 doc.AddMember("comments", comments, doc.GetAllocator());
370
669822d0 371 resp->setBody(doc);
1abb81f4
CH
372}
373
6ec5e728
CH
374void productServerStatisticsFetch(map<string,string>& out)
375{
a45303b8 376 vector<string> items = S.getEntries();
a45303b8 377 BOOST_FOREACH(const string& item, items) {
6ec5e728 378 out[item] = lexical_cast<string>(S.read(item));
a45303b8
CH
379 }
380
381 // add uptime
6ec5e728 382 out["uptime"] = lexical_cast<string>(time(0) - s_starttime);
c67bf8c5
CH
383}
384
f63168e6
CH
385static void gatherRecords(const Value& container, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
386 UeberBackend B;
387 DNSResourceRecord rr;
388 const Value& records = container["records"];
389 if (records.IsArray()) {
390 for (SizeType idx = 0; idx < records.Size(); ++idx) {
391 const Value& record = records[idx];
392 rr.qname = stringFromJson(record, "name");
393 rr.qtype = stringFromJson(record, "type");
394 rr.content = stringFromJson(record, "content");
395 rr.auth = 1;
396 rr.ttl = intFromJson(record, "ttl");
397 rr.priority = intFromJson(record, "priority");
398 rr.disabled = boolFromJson(record, "disabled");
399
400 string temp_content = rr.content;
401 if (rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV)
402 temp_content = lexical_cast<string>(rr.priority)+" "+rr.content;
403
404 try {
405 shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, temp_content));
406 string tmp = drc->serialize(rr.qname);
407 }
408 catch(std::exception& e)
409 {
410 throw ApiException("Record "+rr.qname+"/"+rr.qtype.getName()+" "+rr.content+": "+e.what());
411 }
412
413 if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
414 boolFromJson(record, "set-ptr", false) == true) {
415 DNSResourceRecord ptr;
416 makePtr(rr, &ptr);
417
418 // verify that there's a zone for the PTR
419 DNSPacket fakePacket;
420 SOAData sd;
421 fakePacket.qtype = QType::PTR;
422 if (!B.getAuth(&fakePacket, &sd, ptr.qname, 0))
423 throw ApiException("Could not find domain for PTR '"+ptr.qname+"' requested for '"+ptr.content+"'");
424
425 ptr.domain_id = sd.domain_id;
426 new_ptrs.push_back(ptr);
427 }
428
429 new_records.push_back(rr);
430 }
431 }
432}
433
434static void gatherComments(const Value& container, vector<Comment>& new_comments, bool use_name_type_from_container) {
435 Comment c;
436 if (use_name_type_from_container) {
437 c.qname = stringFromJson(container, "name");
438 c.qtype = stringFromJson(container, "type");
439 }
440
441 time_t now = time(0);
442 const Value& comments = container["comments"];
443 if (comments.IsArray()) {
444 for(SizeType idx = 0; idx < comments.Size(); ++idx) {
445 const Value& comment = comments[idx];
446 if (!use_name_type_from_container) {
447 c.qname = stringFromJson(comment, "name");
448 c.qtype = stringFromJson(comment, "type");
449 }
450 c.modified_at = intFromJson(comment, "modified_at", now);
451 c.content = stringFromJson(comment, "content");
452 c.account = stringFromJson(comment, "account");
453 new_comments.push_back(c);
454 }
455 }
456}
6cc98ddf 457
bb9fd223
CH
458static void updateDomainSettingsFromDocument(const DomainInfo& di, const string& zonename, Document& document) {
459 string master;
460 const Value &masters = document["masters"];
461 if (masters.IsArray()) {
462 for (SizeType i = 0; i < masters.Size(); ++i) {
463 master += masters[i].GetString();
464 master += " ";
465 }
466 }
467
468 di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
469 di.backend->setMaster(zonename, master);
d29d5db7
CH
470
471 if (document["soa_edit_api"].IsString()) {
472 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].GetString());
473 }
6bb25159
MS
474 if (document["soa_edit"].IsString()) {
475 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].GetString());
476 }
bb9fd223
CH
477}
478
4b7f120a
MS
479static void apiZoneCryptokeys(HttpRequest* req, HttpResponse* resp) {
480 if(req->method != "GET")
481 throw ApiException("Only GET is implemented");
482
583ea80d 483 string zonename = apiZoneIdToName(req->parameters["id"]);
4b7f120a
MS
484
485 UeberBackend B;
486 DomainInfo di;
487 DNSSECKeeper dk;
488
489 if(!B.getDomainInfo(zonename, di))
490 throw ApiException("Could not find domain '"+zonename+"'");
491
492 if(!dk.isSecuredZone(zonename))
493 throw ApiException("Zone '"+zonename+"' is not secured");
494
495 DNSSECKeeper::keyset_t keyset=dk.getKeys(zonename);
496
497 if (keyset.empty())
498 throw ApiException("No keys for zone '"+zonename+"'");
499
500 Document doc;
501 doc.SetArray();
502
503 BOOST_FOREACH(DNSSECKeeper::keyset_t::value_type value, keyset) {
583ea80d
CH
504 if (req->parameters.count("key_id")) {
505 int keyid = lexical_cast<int>(req->parameters["key_id"]);
38809e97
MS
506 int curid = lexical_cast<int>(value.second.id);
507 if (keyid != curid)
508 continue;
509 }
4b7f120a
MS
510 Value key;
511 key.SetObject();
512 key.AddMember("type", "Cryptokey", doc.GetAllocator());
513 key.AddMember("id", value.second.id, doc.GetAllocator());
514 key.AddMember("active", value.second.active, doc.GetAllocator());
515 key.AddMember("keytype", (value.second.keyOrZone ? "ksk" : "zsk"), doc.GetAllocator());
cc016934
MS
516 Value dnskey(value.first.getDNSKEY().getZoneRepresentation().c_str(), doc.GetAllocator());
517 key.AddMember("dnskey", dnskey, doc.GetAllocator());
583ea80d
CH
518 if (req->parameters.count("key_id")) {
519 DNSSECPrivateKey dpk=dk.getKeyById(zonename, lexical_cast<int>(req->parameters["key_id"]));
cc016934 520 Value content(dpk.getKey()->convertToISC().c_str(), doc.GetAllocator());
38809e97
MS
521 key.AddMember("content", content, doc.GetAllocator());
522 }
4b7f120a
MS
523
524 if (value.second.keyOrZone) {
525 Value dses;
526 dses.SetArray();
38809e97 527 Value ds(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), 1).getZoneRepresentation().c_str(), doc.GetAllocator());
4b7f120a 528 dses.PushBack(ds, doc.GetAllocator());
38809e97 529 Value ds2(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), 2).getZoneRepresentation().c_str(), doc.GetAllocator());
4b7f120a
MS
530 dses.PushBack(ds2, doc.GetAllocator());
531
532 try {
38809e97
MS
533 Value ds3(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), 3).getZoneRepresentation().c_str(), doc.GetAllocator());
534 dses.PushBack(ds3, doc.GetAllocator());
4b7f120a
MS
535 }
536 catch(...)
537 {
538 }
539 try {
38809e97
MS
540 Value ds4(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), 4).getZoneRepresentation().c_str(), doc.GetAllocator());
541 dses.PushBack(ds4, doc.GetAllocator());
4b7f120a
MS
542 }
543 catch(...)
544 {
545 }
4b7f120a
MS
546 key.AddMember("ds", dses, doc.GetAllocator());
547 }
548
549 doc.PushBack(key, doc.GetAllocator());
550 }
551
552 resp->setBody(doc);
553}
554
0f0e73fe
MS
555static void gatherRecordsFromZone(const Value &container, vector<DNSResourceRecord>& new_records, string zonename) {
556 DNSResourceRecord rr;
557 vector<string> zonedata;
558 stringtok(zonedata, stringFromJson(container, "zone"), "\r\n");
559
560 ZoneParserTNG zpt(zonedata, zonename);
561
562 bool seenSOA=false;
563
564 string comment = "Imported via the API";
565
566 try {
567 while(zpt.get(rr, &comment)) {
568 if(seenSOA && rr.qtype.getCode() == QType::SOA)
569 continue;
570 if(rr.qtype.getCode() == QType::SOA)
571 seenSOA=true;
572
28d96d3f
MS
573 rr.priority = 0;
574
575 if (rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV) {
576 int prio;
577 prio=atoi(rr.content.c_str());
578
579 string::size_type pos = rr.content.find_first_not_of("0123456789");
580 if(pos != string::npos)
581 boost::erase_head(rr.content, pos);
582 trim_left(rr.content);
583 rr.priority = prio;
584 }
585
586
0f0e73fe
MS
587 rr.qname = stripDot(rr.qname);
588 new_records.push_back(rr);
589 }
590 }
591 catch(std::exception& ae) {
592 throw ApiException("An error occured while parsing the zonedata: "+string(ae.what()));
593 }
594}
595
80d59cd1 596static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
e2dba705 597 UeberBackend B;
559115f6 598 DNSSECKeeper dk;
18179c61 599 if (req->method == "POST" && !::arg().mustDo("experimental-api-readonly")) {
e2dba705
CH
600 DomainInfo di;
601 Document document;
6ec5e728 602 req->json(document);
e2dba705 603 string zonename = stringFromJson(document, "name");
f63168e6 604 string dotsuffix = "." + zonename;
0f0e73fe
MS
605 string zonestring = stringFromJson(document, "zone", "");
606
e2dba705
CH
607 // TODO: better validation of zonename
608 if(zonename.empty())
609 throw ApiException("Zone name empty");
610
4ebf78b1
CH
611 // strip any trailing dots
612 while (zonename.substr(zonename.size()-1) == ".") {
613 zonename.resize(zonename.size()-1);
614 }
615
e2dba705
CH
616 bool exists = B.getDomainInfo(zonename, di);
617 if(exists)
618 throw ApiException("Domain '"+zonename+"' already exists");
619
bb9fd223 620 // validate 'kind' is set
4bdff352 621 DomainInfo::DomainKind zonekind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
bb9fd223 622
0f0e73fe
MS
623 const Value &records = document["records"];
624 if (records.IsArray() && zonestring != "")
625 throw ApiException("You cannot give zonedata AND records");
626
e2dba705 627 const Value &nameservers = document["nameservers"];
4bdff352 628 if (!nameservers.IsArray() && zonekind != DomainInfo::Slave)
f63168e6 629 throw ApiException("Nameservers list must be given (but can be empty if NS records are supplied)");
e2dba705 630
f63168e6
CH
631 string soa_edit_api_kind;
632 if (document["soa_edit_api"].IsString())
633 soa_edit_api_kind = document["soa_edit_api"].GetString();
e90b4e38 634
f63168e6
CH
635 // if records/comments are given, load and check them
636 bool have_soa = false;
637 vector<DNSResourceRecord> new_records;
638 vector<Comment> new_comments;
639 vector<DNSResourceRecord> new_ptrs;
0f0e73fe
MS
640
641 if (records.IsArray()) {
642 gatherRecords(document, new_records, new_ptrs);
643 } else if (zonestring != "") {
644 gatherRecordsFromZone(document, new_records, zonename);
645 }
646
f63168e6 647 gatherComments(document, new_comments, false);
e2dba705 648
f63168e6 649 DNSResourceRecord rr;
e2dba705 650
f63168e6
CH
651 BOOST_FOREACH(rr, new_records) {
652 if (!iends_with(rr.qname, dotsuffix) && !pdns_iequals(rr.qname, zonename))
653 throw ApiException("RRset "+rr.qname+" IN "+rr.qtype.getName()+": Name is out of zone");
654
655 if (rr.qtype.getCode() == QType::SOA && pdns_iequals(rr.qname, zonename)) {
656 have_soa = true;
657 editSOARecord(rr, soa_edit_api_kind);
658 }
659 }
e2dba705 660
e2dba705 661 rr.qname = zonename;
f62559e1 662 rr.auth = 1;
f63168e6 663 rr.ttl = ::arg().asNum("default-ttl");
e2dba705 664 rr.priority = 0;
e2dba705 665
4de11a54 666 if (!have_soa && zonekind != DomainInfo::Slave) {
f63168e6
CH
667 // synthesize a SOA record so the zone "really" exists
668
669 SOAData sd;
670 sd.qname = zonename;
671 sd.nameserver = arg()["default-soa-name"];
672 if (!arg().isEmpty("default-soa-mail")) {
673 sd.hostmaster = arg()["default-soa-mail"];
674 attodot(sd.hostmaster);
675 } else {
676 sd.hostmaster = "hostmaster." + zonename;
677 }
678 sd.serial = intFromJson(document, "serial", 0);
679 sd.ttl = rr.ttl;
680 sd.refresh = ::arg().asNum("soa-refresh-default");
681 sd.retry = ::arg().asNum("soa-retry-default");
682 sd.expire = ::arg().asNum("soa-expire-default");
683 sd.default_ttl = ::arg().asNum("soa-minimum-ttl");
684
685 rr.content = serializeSOAData(sd);
686 rr.qtype = "SOA";
687 editSOARecord(rr, soa_edit_api_kind);
688 new_records.push_back(rr);
689 }
690
691 // create NS records if nameservers are given
4bdff352
CH
692 if (nameservers.IsArray()) {
693 for (SizeType i = 0; i < nameservers.Size(); ++i) {
694 if (!nameservers[i].IsString())
695 throw ApiException("Nameservers must be strings");
696 rr.content = nameservers[i].GetString();
697 rr.qtype = "NS";
698 new_records.push_back(rr);
699 }
e2dba705
CH
700 }
701
f63168e6
CH
702 // no going back after this
703 if(!B.createDomain(zonename))
704 throw ApiException("Creating domain '"+zonename+"' failed");
705
706 if(!B.getDomainInfo(zonename, di))
707 throw ApiException("Creating domain '"+zonename+"' failed: lookup of domain ID failed");
708
e2dba705 709 di.backend->startTransaction(zonename, di.id);
f63168e6
CH
710
711 BOOST_FOREACH(rr, new_records) {
712 rr.domain_id = di.id;
e2dba705
CH
713 di.backend->feedRecord(rr);
714 }
f63168e6
CH
715 BOOST_FOREACH(Comment& c, new_comments) {
716 c.domain_id = di.id;
717 di.backend->feedComment(c);
718 }
e2dba705 719
bb9fd223 720 updateDomainSettingsFromDocument(di, zonename, document);
e2dba705 721
f63168e6
CH
722 di.backend->commitTransaction();
723
669822d0 724 fillZone(zonename, resp);
64a36f0d 725 resp->status = 201;
e2dba705
CH
726 return;
727 }
728
c67bf8c5
CH
729 if(req->method != "GET")
730 throw HttpMethodNotAllowedException();
731
c67bf8c5 732 vector<DomainInfo> domains;
cea26350 733 B.getAllDomains(&domains, true); // incl. disabled
c67bf8c5
CH
734
735 Document doc;
45de6290 736 doc.SetArray();
c67bf8c5
CH
737
738 BOOST_FOREACH(const DomainInfo& di, domains) {
739 Value jdi;
740 jdi.SetObject();
418aa246 741 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
3c3c006b
CH
742 string zoneId = apiZoneNameToId(di.zone);
743 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
744 jdi.AddMember("id", jzoneId, doc.GetAllocator());
745 string url = "/servers/localhost/zones/" + zoneId;
418aa246
CH
746 Value jurl(url.c_str(), doc.GetAllocator()); // copy
747 jdi.AddMember("url", jurl, doc.GetAllocator());
c67bf8c5
CH
748 jdi.AddMember("name", di.zone.c_str(), doc.GetAllocator());
749 jdi.AddMember("kind", di.getKindString(), doc.GetAllocator());
c1ea40a0 750 jdi.AddMember("dnssec", dk.isSecuredZone(di.zone), doc.GetAllocator());
c67bf8c5
CH
751 Value masters;
752 masters.SetArray();
753 BOOST_FOREACH(const string& master, di.masters) {
754 Value value(master.c_str(), doc.GetAllocator());
755 masters.PushBack(value, doc.GetAllocator());
756 }
757 jdi.AddMember("masters", masters, doc.GetAllocator());
758 jdi.AddMember("serial", di.serial, doc.GetAllocator());
759 jdi.AddMember("notified_serial", di.notified_serial, doc.GetAllocator());
760 jdi.AddMember("last_check", (unsigned int) di.last_check, doc.GetAllocator());
45de6290 761 doc.PushBack(jdi, doc.GetAllocator());
c67bf8c5 762 }
669822d0 763 resp->setBody(doc);
c67bf8c5
CH
764}
765
05776d2f 766static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
583ea80d 767 string zonename = apiZoneIdToName(req->parameters["id"]);
05776d2f 768
18179c61 769 if(req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
7c0ba3d2
CH
770 // update domain settings
771 UeberBackend B;
772 DomainInfo di;
773 if(!B.getDomainInfo(zonename, di))
774 throw ApiException("Could not find domain '"+zonename+"'");
775
776 Document document;
6ec5e728 777 req->json(document);
7c0ba3d2 778
bb9fd223 779 updateDomainSettingsFromDocument(di, zonename, document);
7c0ba3d2 780
669822d0 781 fillZone(zonename, resp);
7c0ba3d2
CH
782 return;
783 }
18179c61 784 else if(req->method == "DELETE" && !::arg().mustDo("experimental-api-readonly")) {
a462a01d
CH
785 // delete domain
786 UeberBackend B;
787 DomainInfo di;
788 if(!B.getDomainInfo(zonename, di))
789 throw ApiException("Could not find domain '"+zonename+"'");
790
791 if(!di.backend->deleteDomain(zonename))
792 throw ApiException("Deleting domain '"+zonename+"' failed: backend delete failed/unsupported");
793
794 // empty body on success
795 resp->body = "";
37663c3b 796 resp->status = 204; // No Content: declare that the zone is gone now
a462a01d 797 return;
18179c61 798 } else if (req->method == "PATCH" && !::arg().mustDo("experimental-api-readonly")) {
d708640f 799 patchZone(req, resp);
6cc98ddf
CH
800 return;
801 } else if (req->method == "GET") {
802 fillZone(zonename, resp);
803 return;
a462a01d 804 }
7c0ba3d2 805
6cc98ddf 806 throw HttpMethodNotAllowedException();
05776d2f
CH
807}
808
a83004d3
CH
809static string makeDotted(string in) {
810 if (in.empty()) {
811 return ".";
812 }
813 if (in[in.size()-1] != '.') {
814 return in + ".";
815 }
816 return in;
817}
818
819static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
583ea80d 820 string zonename = apiZoneIdToName(req->parameters["id"]);
a83004d3
CH
821
822 if(req->method != "GET")
823 throw HttpMethodNotAllowedException();
824
825 ostringstream ss;
826
827 UeberBackend B;
828 DomainInfo di;
829 if(!B.getDomainInfo(zonename, di))
830 throw ApiException("Could not find domain '"+zonename+"'");
831
832 DNSResourceRecord rr;
833 SOAData sd;
834 di.backend->list(zonename, di.id);
835 while(di.backend->get(rr)) {
836 if (!rr.qtype.getCode())
837 continue; // skip empty non-terminals
838
839 string content = rr.content;
840
841 switch(rr.qtype.getCode()) {
842 case QType::SOA:
843 fillSOAData(rr.content, sd);
844 sd.nameserver = makeDotted(sd.nameserver);
845 sd.hostmaster = makeDotted(sd.hostmaster);
846 content = serializeSOAData(sd);
847 break;
848 case QType::MX:
849 case QType::SRV:
850 content = lexical_cast<string>(rr.priority) + "\t" + makeDotted(content);
851 break;
852 case QType::CNAME:
853 case QType::NS:
854 case QType::AFSDB:
855 content = makeDotted(rr.content);
856 break;
857 default:
858 break;
859 }
860
861 ss <<
862 makeDotted(rr.qname) << "\t" <<
863 rr.ttl << "\t" <<
864 rr.qtype.getName() << "\t" <<
865 content <<
866 endl;
867 }
868
869 if (req->accept_json) {
870 Document doc;
871 doc.SetObject();
872 Value val(ss.str().c_str(), doc.GetAllocator()); // copy
873 doc.AddMember("zone", val, doc.GetAllocator());
874 resp->body = makeStringFromDocument(doc);
875 } else {
876 resp->headers["Content-Type"] = "text/plain; charset=us-ascii";
877 resp->body = ss.str();
878 }
879}
880
d1587ceb
CH
881static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) {
882 if (rr.qtype.getCode() == QType::A) {
883 uint32_t ip;
884 if (!IpToU32(rr.content, &ip)) {
885 throw ApiException("PTR: Invalid IP address given");
886 }
887 ptr->qname = (boost::format("%u.%u.%u.%u.in-addr.arpa")
888 % ((ip >> 24) & 0xff)
889 % ((ip >> 16) & 0xff)
890 % ((ip >> 8) & 0xff)
891 % ((ip ) & 0xff)
892 ).str();
893 } else if (rr.qtype.getCode() == QType::AAAA) {
894 ComboAddress ca(rr.content);
5fb3aa58 895 char buf[3];
d1587ceb 896 ostringstream ss;
5fb3aa58
CH
897 for (int octet = 0; octet < 16; ++octet) {
898 if (snprintf(buf, sizeof(buf), "%02x", ca.sin6.sin6_addr.s6_addr[octet]) != (sizeof(buf)-1)) {
899 // this should be impossible: no byte should give more than two digits in hex format
900 throw PDNSException("Formatting IPv6 address failed");
901 }
902 ss << buf[0] << '.' << buf[1] << '.';
d1587ceb 903 }
5fb3aa58
CH
904 string tmp = ss.str();
905 tmp.resize(tmp.size()-1); // remove last dot
906 // reverse and append arpa domain
907 ptr->qname = string(tmp.rbegin(), tmp.rend()) + ".ip6.arpa";
d1587ceb
CH
908 } else {
909 throw ApiException("Unsupported PTR source '" + rr.qname + "' type '" + rr.qtype.getName() + "'");
910 }
911
912 ptr->qtype = "PTR";
913 ptr->ttl = rr.ttl;
914 ptr->disabled = rr.disabled;
915 ptr->priority = 0;
916 ptr->content = rr.qname;
917}
918
d708640f 919static void patchZone(HttpRequest* req, HttpResponse* resp) {
b3905a3d
CH
920 UeberBackend B;
921 DomainInfo di;
583ea80d 922 string zonename = apiZoneIdToName(req->parameters["id"]);
d708640f 923 if (!B.getDomainInfo(zonename, di))
b3905a3d
CH
924 throw ApiException("Could not find domain '"+zonename+"'");
925
d708640f 926 string dotsuffix = "." + zonename;
f63168e6
CH
927 vector<DNSResourceRecord> new_records;
928 vector<Comment> new_comments;
d708640f
CH
929 vector<DNSResourceRecord> new_ptrs;
930
b3905a3d 931 Document document;
6ec5e728 932 req->json(document);
b3905a3d 933
d708640f
CH
934 const Value& rrsets = document["rrsets"];
935 if (!rrsets.IsArray())
936 throw ApiException("No rrsets given in update request");
b3905a3d 937
d708640f 938 di.backend->startTransaction(zonename);
6cc98ddf 939
d708640f 940 try {
d29d5db7
CH
941 string soa_edit_api_kind;
942 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
943 bool soa_edit_done = false;
944
d708640f
CH
945 for(SizeType rrsetIdx = 0; rrsetIdx < rrsets.Size(); ++rrsetIdx) {
946 const Value& rrset = rrsets[rrsetIdx];
947 string qname, changetype;
948 QType qtype;
949 qname = stringFromJson(rrset, "name");
950 qtype = stringFromJson(rrset, "type");
951 changetype = toUpper(stringFromJson(rrset, "changetype"));
952
5e4b015b 953 if (!iends_with(qname, dotsuffix) && !pdns_iequals(qname, zonename))
d708640f
CH
954 throw ApiException("RRset "+qname+" IN "+qtype.getName()+": Name is out of zone");
955
956 if (changetype == "DELETE") {
957 // delete all matching qname/qtype RRs (and, implictly comments).
958 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
959 throw ApiException("Hosting backend does not support editing records.");
6cc98ddf 960 }
d708640f
CH
961 }
962 else if (changetype == "REPLACE") {
f63168e6
CH
963 new_records.clear();
964 new_comments.clear();
965 // new_ptrs is merged
966 gatherRecords(rrset, new_records, new_ptrs);
967 gatherComments(rrset, new_comments, true);
968
969 BOOST_FOREACH(DNSResourceRecord& rr, new_records) {
970 rr.domain_id = di.id;
971
972 if (rr.qname != qname || rr.qtype != qtype)
973 throw ApiException("Record "+rr.qname+"/"+rr.qtype.getName()+" "+rr.content+": Record wrongly bundled with RRset " + qname + "/" + qtype.getName());
974
975 if (rr.qtype.getCode() == QType::SOA && pdns_iequals(rr.qname, zonename)) {
976 soa_edit_done = editSOARecord(rr, soa_edit_api_kind);
d708640f 977 }
6cc98ddf
CH
978 }
979
f63168e6
CH
980 BOOST_FOREACH(Comment& c, new_comments) {
981 c.domain_id = di.id;
d1587ceb
CH
982 }
983
f63168e6
CH
984 bool replace_records = rrset["records"].IsArray();
985 bool replace_comments = rrset["comments"].IsArray();
986
d708640f
CH
987 if (!replace_records && !replace_comments) {
988 throw ApiException("No change for RRset " + qname + "/" + qtype.getName());
989 }
b3905a3d 990
d708640f
CH
991 if (replace_records) {
992 if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
993 throw ApiException("Hosting backend does not support editing records.");
994 }
995 }
996 if (replace_comments) {
997 if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
998 throw ApiException("Hosting backend does not support editing comments.");
999 }
1000 }
6cc98ddf 1001 }
d708640f
CH
1002 else
1003 throw ApiException("Changetype not understood");
6cc98ddf 1004 }
d29d5db7
CH
1005
1006 // edit SOA (if needed)
1007 if (!soa_edit_api_kind.empty() && !soa_edit_done) {
1008 SOAData sd;
1009 if (!B.getSOA(zonename, sd))
1010 throw ApiException("No SOA found for domain '"+zonename+"'");
1011
1012 DNSResourceRecord rr;
1013 rr.qname = zonename;
1014 rr.content = serializeSOAData(sd);
1015 rr.qtype = "SOA";
1016 rr.domain_id = di.id;
1017 rr.auth = 1;
1018 rr.ttl = sd.ttl;
1019 rr.priority = 0;
1020 editSOARecord(rr, soa_edit_api_kind);
1021
1022 if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
1023 throw ApiException("Hosting backend does not support editing records.");
1024 }
1025 }
1026
d708640f
CH
1027 } catch(...) {
1028 di.backend->abortTransaction();
1029 throw;
1030 }
1031 di.backend->commitTransaction();
b3905a3d 1032
d708640f
CH
1033 extern PacketCache PC;
1034 PC.purge(zonename);
d1587ceb 1035
d708640f
CH
1036 // now the PTRs
1037 BOOST_FOREACH(const DNSResourceRecord& rr, new_ptrs) {
1038 DNSPacket fakePacket;
1039 SOAData sd;
1040 sd.db = (DNSBackend *)-1;
1041 fakePacket.qtype = QType::PTR;
d1587ceb 1042
d708640f
CH
1043 if (!B.getAuth(&fakePacket, &sd, rr.qname, 0))
1044 throw ApiException("Could not find domain for PTR '"+rr.qname+"' requested for '"+rr.content+"' (while saving)");
d1587ceb 1045
d708640f
CH
1046 sd.db->startTransaction(rr.qname);
1047 if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
1048 throw ApiException("PTR-Hosting backend for "+rr.qname+"/"+rr.qtype.getName()+" does not support editing records.");
d1587ceb 1049 }
d708640f
CH
1050 sd.db->commitTransaction();
1051 PC.purge(rr.qname);
b3905a3d 1052 }
b3905a3d
CH
1053
1054 // success
d708640f 1055 fillZone(zonename, resp);
b3905a3d
CH
1056}
1057
b1902fab
CH
1058static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
1059 if(req->method != "GET")
1060 throw HttpMethodNotAllowedException();
1061
583ea80d 1062 string q = req->getvars["q"];
b1902fab
CH
1063 if (q.empty())
1064 throw ApiException("Query q can't be blank");
1065
1066 UeberBackend B;
1067
1068 vector<DomainInfo> domains;
1069 B.getAllDomains(&domains, true); // incl. disabled
1070
1071 Document doc;
1072 doc.SetArray();
1073
1074 DNSResourceRecord rr;
1075 Comment comment;
1076
1077 BOOST_FOREACH(const DomainInfo& di, domains) {
d2d194a9
CH
1078 string zoneId = apiZoneNameToId(di.zone);
1079
57cb86d8 1080 if (pdns_ci_find(di.zone, q) != string::npos) {
b1902fab
CH
1081 Value object;
1082 object.SetObject();
1083 object.AddMember("type", "zone", doc.GetAllocator());
d2d194a9
CH
1084 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
1085 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
1086 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
1087 object.AddMember("name", jzoneName, doc.GetAllocator());
b1902fab
CH
1088 doc.PushBack(object, doc.GetAllocator());
1089 }
1090
1091 // if zone name is an exact match, don't bother with returning all records/comments in it
1092 if (di.zone == q) {
1093 continue;
1094 }
4bd3d119 1095 // the code below is too slow
1096#if 0
b1902fab
CH
1097 di.backend->list(di.zone, di.id, true); // incl. disabled
1098 while(di.backend->get(rr)) {
1099 if (!rr.qtype.getCode())
1100 continue; // skip empty non-terminals
1101
57cb86d8 1102 if (pdns_ci_find(rr.qname, q) == string::npos && pdns_ci_find(rr.content, q) == string::npos)
b1902fab
CH
1103 continue;
1104
1105 Value object;
1106 object.SetObject();
1107 object.AddMember("type", "record", doc.GetAllocator());
d2d194a9
CH
1108 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
1109 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
1110 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
1111 object.AddMember("zone_name", jzoneName, doc.GetAllocator());
b1902fab
CH
1112 Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy
1113 object.AddMember("name", jname, doc.GetAllocator());
1114 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
1115 object.AddMember("content", jcontent, doc.GetAllocator());
1116 doc.PushBack(object, doc.GetAllocator());
1117 }
1118
1119 di.backend->listComments(di.id);
1120 while(di.backend->getComment(comment)) {
57cb86d8 1121 if (pdns_ci_find(comment.qname, q) == string::npos && pdns_ci_find(comment.content, q) == string::npos)
b1902fab
CH
1122 continue;
1123
1124 Value object;
1125 object.SetObject();
1126 object.AddMember("type", "comment", doc.GetAllocator());
d2d194a9
CH
1127 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
1128 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
1129 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
1130 object.AddMember("zone_name", jzoneName, doc.GetAllocator());
b1902fab
CH
1131 Value jname(comment.qname.c_str(), doc.GetAllocator()); // copy
1132 object.AddMember("name", jname, doc.GetAllocator());
1133 Value jcontent(comment.content.c_str(), doc.GetAllocator()); // copy
1134 object.AddMember("content", jcontent, doc.GetAllocator());
1135 doc.PushBack(object, doc.GetAllocator());
1136 }
4bd3d119 1137#endif
b1902fab 1138 }
4bd3d119 1139
b1902fab
CH
1140 resp->setBody(doc);
1141}
1142
dea47634 1143void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
80d59cd1
CH
1144{
1145 string command;
1146
583ea80d
CH
1147 if(req->getvars.count("command")) {
1148 command = req->getvars["command"];
1149 req->getvars.erase("command");
80d59cd1
CH
1150 }
1151
a45303b8 1152 if(command == "flush-cache") {
e611a06c
BH
1153 extern PacketCache PC;
1154 int number;
583ea80d 1155 if(req->getvars["domain"].empty())
e611a06c
BH
1156 number = PC.purge();
1157 else
583ea80d 1158 number = PC.purge(req->getvars["domain"]);
ac7ba905 1159
e611a06c
BH
1160 map<string, string> object;
1161 object["number"]=lexical_cast<string>(number);
80d59cd1 1162 //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
6ec5e728 1163 resp->body = returnJsonObject(object);
c146576d 1164 resp->status = 200;
80d59cd1 1165 return;
e611a06c 1166 }
2fe9c01c 1167 else if(command == "pdns-control") {
02c04144 1168 if(req->method!="POST")
33196945 1169 throw HttpMethodNotAllowedException();
d267d1bf
BH
1170 // cout<<"post: "<<post<<endl;
1171 rapidjson::Document document;
6ec5e728 1172 req->json(document);
d267d1bf
BH
1173 // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
1174 vector<string> parameters;
1175 stringtok(parameters, document["parameters"].GetString(), " \t");
1176
1177 DynListener::g_funk_t* ptr=0;
1178 if(!parameters.empty())
1179 ptr = DynListener::getFunc(toUpper(parameters[0]));
1180 map<string, string> m;
1181
1182 if(ptr) {
c146576d 1183 resp->status = 200;
d267d1bf
BH
1184 m["result"] = (*ptr)(parameters, 0);
1185 } else {
80d59cd1 1186 resp->status = 404;
d267d1bf
BH
1187 m["error"]="No such function "+toUpper(parameters[0]);
1188 }
6ec5e728 1189 resp->body = returnJsonObject(m);
80d59cd1 1190 return;
d267d1bf 1191 }
2fe9c01c 1192 else if(command=="log-grep") {
6ec5e728 1193 // legacy parameter name hack
583ea80d 1194 req->getvars["q"] = req->getvars["needle"];
6ec5e728 1195 apiServerSearchLog(req, resp);
80d59cd1 1196 return;
9ac4a7c6 1197 }
ddc84d12 1198
6ec5e728 1199 resp->body = returnJsonError("No or unknown command given");
80d59cd1
CH
1200 resp->status = 404;
1201 return;
ddc84d12
CH
1202}
1203
dea47634 1204void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 1205{
80d59cd1
CH
1206 resp->headers["Cache-Control"] = "max-age=86400";
1207 resp->headers["Content-Type"] = "text/css";
c67bf8c5 1208
1071abdd 1209 ostringstream ret;
1071abdd
CH
1210 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
1211 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
1212 ret<<"a { color: #0959c2; }"<<endl;
1213 ret<<"a:hover { color: #3B8EC8; }"<<endl;
1214 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
1215 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
1216 ret<<".row:after { clear: both; }"<<endl;
1217 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
1218 ret<<".all { width: 100%; }"<<endl;
1219 ret<<".headl { width: 60%; }"<<endl;
1220 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
1221 ret<<"background-image: url(data:image/png;base64,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);";
1222 ret<<" width: 154px; height: 20px; }"<<endl;
1223 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
1224 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
1225 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
1226 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
1227 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
1228 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
1229 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
1230 ret<<"table.data tr:hover { background: white; }"<<endl;
1231 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
1232 ret<<".resetring {float: right; }"<<endl;
1233 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;
1234 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
1235 ret<<".resizering {float: right;}"<<endl;
80d59cd1 1236 resp->body = ret.str();
c146576d 1237 resp->status = 200;
1071abdd
CH
1238}
1239
dea47634 1240void AuthWebServer::webThread()
12c86877
BH
1241{
1242 try {
c67bf8c5 1243 if(::arg().mustDo("experimental-json-interface")) {
3ae143b0
CH
1244 d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
1245 d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
b1902fab 1246 d_ws->registerApiHandler("/servers/localhost/search-data", &apiServerSearchData);
3ae143b0 1247 d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
4b7f120a
MS
1248 d_ws->registerApiHandler("/servers/localhost/zones/<id>/cryptokeys/<key_id>", &apiZoneCryptokeys);
1249 d_ws->registerApiHandler("/servers/localhost/zones/<id>/cryptokeys", &apiZoneCryptokeys);
a83004d3 1250 d_ws->registerApiHandler("/servers/localhost/zones/<id>/export", &apiServerZoneExport);
3ae143b0
CH
1251 d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail);
1252 d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones);
1253 d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
1254 d_ws->registerApiHandler("/servers", &apiServer);
c67bf8c5 1255 // legacy dispatch
dea47634 1256 d_ws->registerApiHandler("/jsonstat", boost::bind(&AuthWebServer::jsonstat, this, _1, _2));
c67bf8c5 1257 }
dea47634
CH
1258 d_ws->registerHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
1259 d_ws->registerHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
96d299db 1260 d_ws->go();
12c86877
BH
1261 }
1262 catch(...) {
dea47634 1263 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
12c86877
BH
1264 exit(1);
1265 }
1266}