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