]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
api: share apiServer* code across auth, recursor
[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"
e611a06c 32#include "ueberbackend.hh"
dcc65f25 33#include <boost/format.hpp>
7b39c040 34#include <boost/foreach.hpp>
9ac4a7c6 35#include "namespaces.hh"
ca9fc6a1 36#include "rapidjson/document.h"
8537b9f0
BH
37#include "rapidjson/stringbuffer.h"
38#include "rapidjson/writer.h"
6ec5e728 39#include "ws-api.hh"
ba1a571d 40#include "version.hh"
8537b9f0
BH
41
42using namespace rapidjson;
12c86877
BH
43
44extern StatBag S;
45
dea47634 46AuthWebServer::AuthWebServer()
12c86877
BH
47{
48 d_start=time(0);
96d299db 49 d_min10=d_min5=d_min1=0;
c81c2ea8 50 d_ws = 0;
f17c93b4 51 d_tid = 0;
c81c2ea8
PD
52 if(arg().mustDo("webserver"))
53 d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port"),arg()["webserver-password"]);
12c86877
BH
54}
55
dea47634 56void AuthWebServer::go()
12c86877 57{
c81c2ea8
PD
58 if(arg().mustDo("webserver"))
59 {
60 S.doRings();
dea47634 61 pthread_create(&d_tid, 0, webThreadHelper, this);
c81c2ea8
PD
62 pthread_create(&d_tid, 0, statThreadHelper, this);
63 }
12c86877
BH
64}
65
dea47634 66void AuthWebServer::statThread()
12c86877
BH
67{
68 try {
69 for(;;) {
70 d_queries.submit(S.read("udp-queries"));
71 d_cachehits.submit(S.read("packetcache-hit"));
72 d_cachemisses.submit(S.read("packetcache-miss"));
73 d_qcachehits.submit(S.read("query-cache-hit"));
74 d_qcachemisses.submit(S.read("query-cache-miss"));
75 Utility::sleep(1);
76 }
77 }
78 catch(...) {
79 L<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl;
80 exit(1);
81 }
82}
83
dea47634 84void *AuthWebServer::statThreadHelper(void *p)
12c86877 85{
dea47634
CH
86 AuthWebServer *self=static_cast<AuthWebServer *>(p);
87 self->statThread();
12c86877
BH
88 return 0; // never reached
89}
90
dea47634 91void *AuthWebServer::webThreadHelper(void *p)
12c86877 92{
dea47634
CH
93 AuthWebServer *self=static_cast<AuthWebServer *>(p);
94 self->webThread();
12c86877
BH
95 return 0; // never reached
96}
97
9f3fdaa0
CH
98static string htmlescape(const string &s) {
99 string result;
100 for(string::const_iterator it=s.begin(); it!=s.end(); ++it) {
101 switch (*it) {
102 case '&':
c86a96f9 103 result += "&amp;";
9f3fdaa0
CH
104 break;
105 case '<':
106 result += "&lt;";
107 break;
108 case '>':
109 result += "&gt;";
110 break;
111 default:
112 result += *it;
113 }
114 }
115 return result;
116}
117
12c86877
BH
118void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
119{
120 int tot=0;
121 int entries=0;
101b5d5d 122 vector<pair <string,unsigned int> >ring=S.getRing(ringname);
12c86877 123
1071abdd 124 for(vector<pair<string, unsigned int> >::const_iterator i=ring.begin(); i!=ring.end();++i) {
12c86877
BH
125 tot+=i->second;
126 entries++;
127 }
128
1071abdd
CH
129 ret<<"<div class=\"panel\">";
130 ret<<"<span class=resetring><i></i><a href=\"?resetring="<<ringname<<"\">Reset</a></span>"<<endl;
131 ret<<"<h2>"<<title<<"</h2>"<<endl;
132 ret<<"<div class=ringmeta>";
133 ret<<"<a class=topXofY href=\"?ring="<<ringname<<"\">Showing: Top "<<limit<<" of "<<entries<<"</a>"<<endl;
134 ret<<"<span class=resizering>Resize: ";
bb3c3f50 135 unsigned int sizes[]={10,100,500,1000,10000,500000,0};
12c86877
BH
136 for(int i=0;sizes[i];++i) {
137 if(S.getRingSize(ringname)!=sizes[i])
e2a77e08 138 ret<<"<a href=\"?resizering="<<ringname<<"&amp;size="<<sizes[i]<<"\">"<<sizes[i]<<"</a> ";
12c86877
BH
139 else
140 ret<<"("<<sizes[i]<<") ";
141 }
1071abdd 142 ret<<"</span></div>";
12c86877 143
1071abdd 144 ret<<"<table class=\"data\">";
12c86877 145 int printed=0;
f5cb7e61 146 int total=max(1,tot);
bb3c3f50 147 for(vector<pair<string,unsigned int> >::const_iterator i=ring.begin();limit && i!=ring.end();++i,--limit) {
dea47634 148 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
149 printed+=i->second;
150 }
151 ret<<"<tr><td colspan=3></td></tr>"<<endl;
152 if(printed!=tot)
dea47634 153 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 154
e2a77e08 155 ret<<"<tr><td><b>Total:</b></td><td><b>"<<tot<<"</b></td><td align=right><b>100%</b></td>";
1071abdd 156 ret<<"</table></div>"<<endl;
12c86877
BH
157}
158
dea47634 159void AuthWebServer::printvars(ostringstream &ret)
12c86877 160{
1071abdd 161 ret<<"<div class=panel><h2>Variables</h2><table class=\"data\">"<<endl;
12c86877
BH
162
163 vector<string>entries=S.getEntries();
164 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
165 ret<<"<tr><td>"<<*i<<"</td><td>"<<S.read(*i)<<"</td><td>"<<S.getDescrip(*i)<<"</td>"<<endl;
166 }
e2a77e08 167
1071abdd 168 ret<<"</table></div>"<<endl;
12c86877
BH
169}
170
dea47634 171void AuthWebServer::printargs(ostringstream &ret)
12c86877 172{
e2a77e08 173 ret<<"<table border=1><tr><td colspan=3 bgcolor=\"#0000ff\"><font color=\"#ffffff\">Arguments</font></td>"<<endl;
12c86877
BH
174
175 vector<string>entries=arg().list();
176 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
177 ret<<"<tr><td>"<<*i<<"</td><td>"<<arg()[*i]<<"</td><td>"<<arg().getHelp(*i)<<"</td>"<<endl;
178 }
179}
180
dea47634 181string AuthWebServer::makePercentage(const double& val)
b6f57093
BH
182{
183 return (boost::format("%.01f%%") % val).str();
184}
185
dea47634 186void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
12c86877 187{
ef1439ff
KM
188 if(!req->parameters["resetring"].empty()) {
189 if (S.ringExists(req->parameters["resetring"]))
190 S.resetRing(req->parameters["resetring"]);
80d59cd1
CH
191 resp->status = 301;
192 resp->headers["Location"] = "/";
193 return;
12c86877 194 }
80d59cd1 195 if(!req->parameters["resizering"].empty()){
ef1439ff
KM
196 int size=atoi(req->parameters["size"].c_str());
197 if (S.ringExists(req->parameters["resizering"]) && size > 0 && size <= 500000)
198 S.resizeRing(req->parameters["resizering"], atoi(req->parameters["size"].c_str()));
80d59cd1
CH
199 resp->status = 301;
200 resp->headers["Location"] = "/";
201 return;
12c86877
BH
202 }
203
204 ostringstream ret;
205
1071abdd
CH
206 ret<<"<!DOCTYPE html>"<<endl;
207 ret<<"<html><head>"<<endl;
208 ret<<"<title>PowerDNS Authoritative Server Monitor</title>"<<endl;
209 ret<<"<link rel=\"stylesheet\" href=\"style.css\"/>"<<endl;
210 ret<<"</head><body>"<<endl;
211
212 ret<<"<div class=\"row\">"<<endl;
213 ret<<"<div class=\"headl columns\">";
214 ret<<"<a href=\"/\" id=\"appname\">PowerDNS "VERSION;
215 if(!arg()["config-name"].empty()) {
216 ret<<" ["<<arg()["config-name"]<<"]";
217 }
218 ret<<"</a></div>"<<endl;
219 ret<<"<div class=\"headr columns\"></div></div>";
220 ret<<"<div class=\"row\"><div class=\"all columns\">";
12c86877
BH
221
222 time_t passed=time(0)-s_starttime;
223
e2a77e08
KM
224 ret<<"<p>Uptime: "<<
225 humanDuration(passed)<<
226 "<br>"<<endl;
12c86877 227
395b07ea 228 ret<<"Queries/second, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
f6154a3b
CH
229 d_queries.get1()<<", "<<
230 d_queries.get5()<<", "<<
231 d_queries.get10()<<". Max queries/second: "<<d_queries.getMax()<<
12c86877
BH
232 "<br>"<<endl;
233
f6154a3b 234 if(d_cachemisses.get10()+d_cachehits.get10()>0)
b6f57093 235 ret<<"Cache hitrate, 1, 5, 10 minute averages: "<<
f6154a3b
CH
236 makePercentage((d_cachehits.get1()*100.0)/((d_cachehits.get1())+(d_cachemisses.get1())))<<", "<<
237 makePercentage((d_cachehits.get5()*100.0)/((d_cachehits.get5())+(d_cachemisses.get5())))<<", "<<
238 makePercentage((d_cachehits.get10()*100.0)/((d_cachehits.get10())+(d_cachemisses.get10())))<<
b6f57093 239 "<br>"<<endl;
12c86877 240
f6154a3b 241 if(d_qcachemisses.get10()+d_qcachehits.get10()>0)
395b07ea 242 ret<<"Backend query cache hitrate, 1, 5, 10 minute averages: "<<std::setprecision(2)<<
f6154a3b
CH
243 makePercentage((d_qcachehits.get1()*100.0)/((d_qcachehits.get1())+(d_qcachemisses.get1())))<<", "<<
244 makePercentage((d_qcachehits.get5()*100.0)/((d_qcachehits.get5())+(d_qcachemisses.get5())))<<", "<<
245 makePercentage((d_qcachehits.get10()*100.0)/((d_qcachehits.get10())+(d_qcachemisses.get10())))<<
b6f57093 246 "<br>"<<endl;
12c86877 247
395b07ea 248 ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
f6154a3b
CH
249 d_qcachemisses.get1()<<", "<<
250 d_qcachemisses.get5()<<", "<<
251 d_qcachemisses.get10()<<". Max queries/second: "<<d_qcachemisses.getMax()<<
12c86877
BH
252 "<br>"<<endl;
253
1071abdd 254 ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
80d59cd1 255 if(req->parameters["ring"].empty()) {
12c86877
BH
256 vector<string>entries=S.listRings();
257 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i)
258 printtable(ret,*i,S.getRingTitle(*i));
259
f6154a3b 260 printvars(ret);
12c86877 261 if(arg().mustDo("webserver-print-arguments"))
f6154a3b 262 printargs(ret);
12c86877
BH
263 }
264 else
80d59cd1 265 printtable(ret,req->parameters["ring"],S.getRingTitle(req->parameters["ring"]),100);
12c86877 266
1071abdd 267 ret<<"</div></div>"<<endl;
ba1a571d 268 ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
12c86877
BH
269 ret<<"</body></html>"<<endl;
270
80d59cd1 271 resp->body = ret.str();
12c86877
BH
272}
273
1abb81f4
CH
274static string getZone(const string& zonename) {
275 UeberBackend B;
1abb81f4 276 DomainInfo di;
73301d73 277 if(!B.getDomainInfo(zonename, di))
6ec5e728 278 return returnJsonError("Could not find domain '"+zonename+"'");
1abb81f4
CH
279
280 Document doc;
281 doc.SetObject();
282
418aa246
CH
283 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
284 doc.AddMember("id", di.zone.c_str(), doc.GetAllocator());
285 string url = (boost::format("/servers/localhost/zones/%s") % di.zone).str();
286 Value jurl(url.c_str(), doc.GetAllocator()); // copy
287 doc.AddMember("url", jurl, doc.GetAllocator());
288 doc.AddMember("name", di.zone.c_str(), doc.GetAllocator());
e2dba705
CH
289 doc.AddMember("type", "Zone", doc.GetAllocator());
290 doc.AddMember("kind", di.getKindString(), doc.GetAllocator());
1abb81f4
CH
291 Value masters;
292 masters.SetArray();
293 BOOST_FOREACH(const string& master, di.masters) {
294 Value value(master.c_str(), doc.GetAllocator());
295 masters.PushBack(value, doc.GetAllocator());
296 }
e2dba705
CH
297 doc.AddMember("masters", masters, doc.GetAllocator());
298 doc.AddMember("serial", di.serial, doc.GetAllocator());
299 doc.AddMember("notified_serial", di.notified_serial, doc.GetAllocator());
300 doc.AddMember("last_check", (unsigned int) di.last_check, doc.GetAllocator());
1abb81f4
CH
301
302 DNSResourceRecord rr;
303 Value records;
304 records.SetArray();
3d89fc28
CH
305 di.backend->list(zonename, di.id);
306 while(di.backend->get(rr)) {
1abb81f4
CH
307 if (!rr.qtype.getCode())
308 continue; // skip empty non-terminals
309
310 Value object;
311 object.SetObject();
312 Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy
313 object.AddMember("name", jname, doc.GetAllocator());
314 Value jtype(rr.qtype.getName().c_str(), doc.GetAllocator()); // copy
315 object.AddMember("type", jtype, doc.GetAllocator());
316 object.AddMember("ttl", rr.ttl, doc.GetAllocator());
317 object.AddMember("priority", rr.priority, doc.GetAllocator());
318 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
319 object.AddMember("content", jcontent, doc.GetAllocator());
320 records.PushBack(object, doc.GetAllocator());
321 }
e2dba705 322 doc.AddMember("records", records, doc.GetAllocator());
1abb81f4 323
1abb81f4
CH
324 return makeStringFromDocument(doc);
325}
326
6ec5e728
CH
327void productServerStatisticsFetch(map<string,string>& out)
328{
a45303b8 329 vector<string> items = S.getEntries();
a45303b8 330 BOOST_FOREACH(const string& item, items) {
6ec5e728 331 out[item] = lexical_cast<string>(S.read(item));
a45303b8
CH
332 }
333
334 // add uptime
6ec5e728 335 out["uptime"] = lexical_cast<string>(time(0) - s_starttime);
c67bf8c5
CH
336}
337
80d59cd1 338static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
e2dba705
CH
339 UeberBackend B;
340 if (req->method == "POST") {
341 DomainInfo di;
342 Document document;
6ec5e728 343 req->json(document);
e2dba705
CH
344 string zonename = stringFromJson(document, "name");
345 // TODO: better validation of zonename
346 if(zonename.empty())
347 throw ApiException("Zone name empty");
348
349 string kind = stringFromJson(document, "kind");
e2dba705
CH
350
351 bool exists = B.getDomainInfo(zonename, di);
352 if(exists)
353 throw ApiException("Domain '"+zonename+"' already exists");
354
355 const Value &nameservers = document["nameservers"];
356 if (!nameservers.IsArray() || nameservers.Size() == 0)
357 throw ApiException("Need at least one nameserver");
358
7c0ba3d2
CH
359 string master;
360 const Value &masters = document["masters"];
361 if (masters.IsArray()) {
362 for (SizeType i = 0; i < masters.Size(); ++i) {
363 master += masters[i].GetString();
364 master += " ";
365 }
366 }
367
e2dba705
CH
368 // no going back after this
369 if(!B.createDomain(zonename))
370 throw ApiException("Creating domain '"+zonename+"' failed");
371
372 if(!B.getDomainInfo(zonename, di))
373 throw ApiException("Creating domain '"+zonename+"' failed: lookup of domain ID failed");
374
375 vector<DNSResourceRecord> rrset;
376
377 // create SOA record so zone "really" exists
378 DNSResourceRecord rr;
379 rr.qname = zonename;
380 rr.content = (boost::format("%s hostmaster.%s %d")
03f39ef9
CH
381 % nameservers[SizeType(0)].GetString()
382 % zonename
383 % intFromJson(document, "serial", 1)
e2dba705
CH
384 ).str();
385 rr.qtype = "SOA";
386 rr.domain_id = di.id;
f62559e1 387 rr.auth = 1;
e2dba705
CH
388 rr.ttl = ::arg().asNum( "default-ttl" );
389 rr.priority = 0;
390 rrset.push_back(rr);
391
7c0ba3d2 392 for (SizeType i = 0; i < nameservers.Size(); ++i) {
e2dba705
CH
393 rr.content = nameservers[i].GetString();
394 rr.qtype = "NS";
395 rrset.push_back(rr);
396 }
397
398 di.backend->startTransaction(zonename, di.id);
399 BOOST_FOREACH(rr, rrset) {
400 di.backend->feedRecord(rr);
401 }
402 di.backend->commitTransaction();
403
404 di.backend->setKind(zonename, DomainInfo::stringToKind(kind));
7c0ba3d2 405 di.backend->setMaster(zonename, master);
e2dba705
CH
406
407 resp->body = getZone(zonename);
408 return;
409 }
410
c67bf8c5
CH
411 if(req->method != "GET")
412 throw HttpMethodNotAllowedException();
413
c67bf8c5
CH
414 vector<DomainInfo> domains;
415 B.getAllDomains(&domains);
416
417 Document doc;
45de6290 418 doc.SetArray();
c67bf8c5
CH
419
420 BOOST_FOREACH(const DomainInfo& di, domains) {
421 Value jdi;
422 jdi.SetObject();
418aa246
CH
423 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
424 jdi.AddMember("id", di.zone.c_str(), doc.GetAllocator());
425 string url = (boost::format("/servers/localhost/zones/%s") % di.zone).str();
426 Value jurl(url.c_str(), doc.GetAllocator()); // copy
427 jdi.AddMember("url", jurl, doc.GetAllocator());
c67bf8c5
CH
428 jdi.AddMember("name", di.zone.c_str(), doc.GetAllocator());
429 jdi.AddMember("kind", di.getKindString(), doc.GetAllocator());
430 Value masters;
431 masters.SetArray();
432 BOOST_FOREACH(const string& master, di.masters) {
433 Value value(master.c_str(), doc.GetAllocator());
434 masters.PushBack(value, doc.GetAllocator());
435 }
436 jdi.AddMember("masters", masters, doc.GetAllocator());
437 jdi.AddMember("serial", di.serial, doc.GetAllocator());
438 jdi.AddMember("notified_serial", di.notified_serial, doc.GetAllocator());
439 jdi.AddMember("last_check", (unsigned int) di.last_check, doc.GetAllocator());
45de6290 440 doc.PushBack(jdi, doc.GetAllocator());
c67bf8c5 441 }
80d59cd1 442 resp->body = makeStringFromDocument(doc);
c67bf8c5
CH
443}
444
05776d2f
CH
445static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
446 string zonename = req->path_parameters["id"];
447
7c0ba3d2
CH
448 if(req->method == "PUT") {
449 // update domain settings
450 UeberBackend B;
451 DomainInfo di;
452 if(!B.getDomainInfo(zonename, di))
453 throw ApiException("Could not find domain '"+zonename+"'");
454
455 Document document;
6ec5e728 456 req->json(document);
7c0ba3d2
CH
457
458 string master;
459 const Value &masters = document["masters"];
460 if (masters.IsArray()) {
461 for(SizeType i = 0; i < masters.Size(); ++i) {
462 master += masters[i].GetString();
463 master += " ";
464 }
465 }
466
467 di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
468 di.backend->setMaster(zonename, master);
469 resp->body = getZone(zonename);
470 return;
471 }
a462a01d
CH
472 else if(req->method == "DELETE") {
473 // delete domain
474 UeberBackend B;
475 DomainInfo di;
476 if(!B.getDomainInfo(zonename, di))
477 throw ApiException("Could not find domain '"+zonename+"'");
478
479 if(!di.backend->deleteDomain(zonename))
480 throw ApiException("Deleting domain '"+zonename+"' failed: backend delete failed/unsupported");
481
482 // empty body on success
483 resp->body = "";
484 return;
485 }
7c0ba3d2 486
05776d2f
CH
487 if(req->method != "GET")
488 throw HttpMethodNotAllowedException();
489
490 resp->body = getZone(zonename);
491}
492
b3905a3d
CH
493static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) {
494 if(req->method != "PATCH")
495 throw HttpMethodNotAllowedException();
496
497 UeberBackend B;
498 DomainInfo di;
499 string zonename = req->path_parameters["id"];
500 if(!B.getDomainInfo(zonename, di))
501 throw ApiException("Could not find domain '"+zonename+"'");
502
503 SOAData sd;
504 sd.db = (DNSBackend*)-1;
505 if(!B.getSOA(zonename, sd) || !sd.db)
506 throw ApiException("Could not find domain '"+zonename+"'");
507
508 Document document;
6ec5e728 509 req->json(document);
b3905a3d
CH
510
511 string qname, changetype;
512 QType qtype;
513 qname = stringFromJson(document, "name");
514 qtype = stringFromJson(document, "type");
515 changetype = toUpper(stringFromJson(document, "changetype"));
516
517 if (changetype == "DELETE") {
518 // delete all matching qname/qtype RRs
519 sd.db->replaceRRSet(sd.domain_id, qname, qtype, vector<DNSResourceRecord>());
520 }
521 else if (changetype == "REPLACE") {
522 DNSResourceRecord rr;
523 vector<DNSResourceRecord> rrset;
524 const Value& records = document["records"];
525 for(SizeType idx = 0; idx < records.Size(); ++idx) {
526 const Value& record = records[idx];
527 rr.qname = stringFromJson(record, "name");
528 rr.content = stringFromJson(record, "content");
529 rr.qtype = stringFromJson(record, "type");
530 rr.domain_id = sd.domain_id;
531 rr.auth = 1;
532 rr.ttl = intFromJson(record, "ttl");
533 rr.priority = intFromJson(record, "priority");
534
535 rrset.push_back(rr);
536
537 if(rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV)
538 rr.content = lexical_cast<string>(rr.priority)+" "+rr.content;
539
540 try {
541 shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content));
542 string tmp = drc->serialize(rr.qname);
543 }
544 catch(std::exception& e)
545 {
546 throw ApiException("Record "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what());
547 }
548 }
549 // Actually store the change.
550 sd.db->startTransaction(qname);
551 sd.db->replaceRRSet(sd.domain_id, qname, qtype, rrset);
552 sd.db->commitTransaction();
553 }
554 else
555 throw ApiException("Changetype not understood");
556
557 extern PacketCache PC;
558 PC.purge(qname);
559
560 // success
561 resp->body = "{}";
562}
563
dea47634 564void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
80d59cd1
CH
565{
566 string command;
567
568 if(req->parameters.count("command")) {
569 command = req->parameters["command"];
570 req->parameters.erase("command");
571 }
572
a45303b8 573 if(command == "flush-cache") {
e611a06c
BH
574 extern PacketCache PC;
575 int number;
80d59cd1 576 if(req->parameters["domain"].empty())
e611a06c
BH
577 number = PC.purge();
578 else
80d59cd1 579 number = PC.purge(req->parameters["domain"]);
ac7ba905 580
e611a06c
BH
581 map<string, string> object;
582 object["number"]=lexical_cast<string>(number);
80d59cd1 583 //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
6ec5e728 584 resp->body = returnJsonObject(object);
80d59cd1 585 return;
e611a06c 586 }
2fe9c01c 587 else if(command == "pdns-control") {
02c04144 588 if(req->method!="POST")
33196945 589 throw HttpMethodNotAllowedException();
d267d1bf
BH
590 // cout<<"post: "<<post<<endl;
591 rapidjson::Document document;
6ec5e728 592 req->json(document);
d267d1bf
BH
593 // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
594 vector<string> parameters;
595 stringtok(parameters, document["parameters"].GetString(), " \t");
596
597 DynListener::g_funk_t* ptr=0;
598 if(!parameters.empty())
599 ptr = DynListener::getFunc(toUpper(parameters[0]));
600 map<string, string> m;
601
602 if(ptr) {
603 m["result"] = (*ptr)(parameters, 0);
604 } else {
80d59cd1 605 resp->status = 404;
d267d1bf
BH
606 m["error"]="No such function "+toUpper(parameters[0]);
607 }
6ec5e728 608 resp->body = returnJsonObject(m);
80d59cd1 609 return;
d267d1bf 610 }
2fe9c01c 611 else if(command=="log-grep") {
6ec5e728
CH
612 // legacy parameter name hack
613 req->parameters["q"] = req->parameters["needle"];
614 apiServerSearchLog(req, resp);
80d59cd1 615 return;
9ac4a7c6 616 }
ddc84d12 617
6ec5e728 618 resp->body = returnJsonError("No or unknown command given");
80d59cd1
CH
619 resp->status = 404;
620 return;
ddc84d12
CH
621}
622
dea47634 623void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 624{
80d59cd1
CH
625 resp->headers["Cache-Control"] = "max-age=86400";
626 resp->headers["Content-Type"] = "text/css";
c67bf8c5 627
1071abdd 628 ostringstream ret;
1071abdd
CH
629 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
630 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
631 ret<<"a { color: #0959c2; }"<<endl;
632 ret<<"a:hover { color: #3B8EC8; }"<<endl;
633 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
634 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
635 ret<<".row:after { clear: both; }"<<endl;
636 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
637 ret<<".all { width: 100%; }"<<endl;
638 ret<<".headl { width: 60%; }"<<endl;
639 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
640 ret<<"background-image: url(data:image/png;base64,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);";
641 ret<<" width: 154px; height: 20px; }"<<endl;
642 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
643 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
644 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
645 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
646 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
647 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
648 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
649 ret<<"table.data tr:hover { background: white; }"<<endl;
650 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
651 ret<<".resetring {float: right; }"<<endl;
652 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;
653 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
654 ret<<".resizering {float: right;}"<<endl;
80d59cd1 655 resp->body = ret.str();
1071abdd
CH
656}
657
dea47634 658void AuthWebServer::webThread()
12c86877
BH
659{
660 try {
c67bf8c5 661 if(::arg().mustDo("experimental-json-interface")) {
3ae143b0
CH
662 d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
663 d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
664 d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
665 d_ws->registerApiHandler("/servers/localhost/zones/<id>/rrset", &apiServerZoneRRset);
666 d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail);
667 d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones);
668 d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
669 d_ws->registerApiHandler("/servers", &apiServer);
c67bf8c5 670 // legacy dispatch
dea47634 671 d_ws->registerApiHandler("/jsonstat", boost::bind(&AuthWebServer::jsonstat, this, _1, _2));
c67bf8c5 672 }
dea47634
CH
673 d_ws->registerHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
674 d_ws->registerHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
96d299db 675 d_ws->go();
12c86877
BH
676 }
677 catch(...) {
dea47634 678 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
12c86877
BH
679 exit(1);
680 }
681}