]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws.cc
webserver: add URL router with support for <vars>
[thirdparty/pdns.git] / pdns / ws.cc
CommitLineData
12c86877 1/*
a2ce158c 2 Copyright (C) 2002 - 2012 PowerDNS.COM BV
12c86877
BH
3
4 This program is free software; you can redistribute it and/or modify
9054d8a4
BH
5 it under the terms of the GNU General Public License version 2
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"
12c86877 23#include "ws.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"
ba1a571d 39#include "version.hh"
12a82d65 40#include "session.hh"
8537b9f0
BH
41
42using namespace rapidjson;
12c86877
BH
43
44extern StatBag S;
45
ddc84d12
CH
46typedef map<string,string> varmap_t;
47
12c86877
BH
48StatWebServer::StatWebServer()
49{
50 d_start=time(0);
96d299db 51 d_min10=d_min5=d_min1=0;
c81c2ea8 52 d_ws = 0;
f17c93b4 53 d_tid = 0;
c81c2ea8
PD
54 if(arg().mustDo("webserver"))
55 d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port"),arg()["webserver-password"]);
12c86877
BH
56}
57
58void StatWebServer::go()
59{
c81c2ea8
PD
60 if(arg().mustDo("webserver"))
61 {
62 S.doRings();
63 pthread_create(&d_tid, 0, threadHelper, this);
64 pthread_create(&d_tid, 0, statThreadHelper, this);
65 }
12c86877
BH
66}
67
12c86877
BH
68void StatWebServer::statThread()
69{
70 try {
71 for(;;) {
72 d_queries.submit(S.read("udp-queries"));
73 d_cachehits.submit(S.read("packetcache-hit"));
74 d_cachemisses.submit(S.read("packetcache-miss"));
75 d_qcachehits.submit(S.read("query-cache-hit"));
76 d_qcachemisses.submit(S.read("query-cache-miss"));
77 Utility::sleep(1);
78 }
79 }
80 catch(...) {
81 L<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl;
82 exit(1);
83 }
84}
85
86void *StatWebServer::statThreadHelper(void *p)
87{
88 StatWebServer *sws=static_cast<StatWebServer *>(p);
89 sws->statThread();
90 return 0; // never reached
91}
92
93
94void *StatWebServer::threadHelper(void *p)
95{
96 StatWebServer *sws=static_cast<StatWebServer *>(p);
97 sws->launch();
98 return 0; // never reached
99}
100
9f3fdaa0
CH
101static string htmlescape(const string &s) {
102 string result;
103 for(string::const_iterator it=s.begin(); it!=s.end(); ++it) {
104 switch (*it) {
105 case '&':
c86a96f9 106 result += "&amp;";
9f3fdaa0
CH
107 break;
108 case '<':
109 result += "&lt;";
110 break;
111 case '>':
112 result += "&gt;";
113 break;
114 default:
115 result += *it;
116 }
117 }
118 return result;
119}
120
12c86877
BH
121void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
122{
123 int tot=0;
124 int entries=0;
101b5d5d 125 vector<pair <string,unsigned int> >ring=S.getRing(ringname);
12c86877 126
1071abdd 127 for(vector<pair<string, unsigned int> >::const_iterator i=ring.begin(); i!=ring.end();++i) {
12c86877
BH
128 tot+=i->second;
129 entries++;
130 }
131
1071abdd
CH
132 ret<<"<div class=\"panel\">";
133 ret<<"<span class=resetring><i></i><a href=\"?resetring="<<ringname<<"\">Reset</a></span>"<<endl;
134 ret<<"<h2>"<<title<<"</h2>"<<endl;
135 ret<<"<div class=ringmeta>";
136 ret<<"<a class=topXofY href=\"?ring="<<ringname<<"\">Showing: Top "<<limit<<" of "<<entries<<"</a>"<<endl;
137 ret<<"<span class=resizering>Resize: ";
bb3c3f50 138 unsigned int sizes[]={10,100,500,1000,10000,500000,0};
12c86877
BH
139 for(int i=0;sizes[i];++i) {
140 if(S.getRingSize(ringname)!=sizes[i])
e2a77e08 141 ret<<"<a href=\"?resizering="<<ringname<<"&amp;size="<<sizes[i]<<"\">"<<sizes[i]<<"</a> ";
12c86877
BH
142 else
143 ret<<"("<<sizes[i]<<") ";
144 }
1071abdd 145 ret<<"</span></div>";
12c86877 146
1071abdd 147 ret<<"<table class=\"data\">";
12c86877 148 int printed=0;
f5cb7e61 149 int total=max(1,tot);
bb3c3f50 150 for(vector<pair<string,unsigned int> >::const_iterator i=ring.begin();limit && i!=ring.end();++i,--limit) {
9f3fdaa0 151 ret<<"<tr><td>"<<htmlescape(i->first)<<"</td><td>"<<i->second<<"</td><td align=right>"<< StatWebServer::makePercentage(i->second*100.0/total)<<"</td>"<<endl;
12c86877
BH
152 printed+=i->second;
153 }
154 ret<<"<tr><td colspan=3></td></tr>"<<endl;
155 if(printed!=tot)
f5cb7e61 156 ret<<"<tr><td><b>Rest:</b></td><td><b>"<<tot-printed<<"</b></td><td align=right><b>"<< StatWebServer::makePercentage((tot-printed)*100.0/total)<<"</b></td>"<<endl;
12c86877 157
e2a77e08 158 ret<<"<tr><td><b>Total:</b></td><td><b>"<<tot<<"</b></td><td align=right><b>100%</b></td>";
1071abdd 159 ret<<"</table></div>"<<endl;
12c86877
BH
160}
161
162void StatWebServer::printvars(ostringstream &ret)
163{
1071abdd 164 ret<<"<div class=panel><h2>Variables</h2><table class=\"data\">"<<endl;
12c86877
BH
165
166 vector<string>entries=S.getEntries();
167 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
168 ret<<"<tr><td>"<<*i<<"</td><td>"<<S.read(*i)<<"</td><td>"<<S.getDescrip(*i)<<"</td>"<<endl;
169 }
e2a77e08 170
1071abdd 171 ret<<"</table></div>"<<endl;
12c86877
BH
172}
173
174void StatWebServer::printargs(ostringstream &ret)
175{
e2a77e08 176 ret<<"<table border=1><tr><td colspan=3 bgcolor=\"#0000ff\"><font color=\"#ffffff\">Arguments</font></td>"<<endl;
12c86877
BH
177
178 vector<string>entries=arg().list();
179 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
180 ret<<"<tr><td>"<<*i<<"</td><td>"<<arg()[*i]<<"</td><td>"<<arg().getHelp(*i)<<"</td>"<<endl;
181 }
182}
183
b6f57093
BH
184string StatWebServer::makePercentage(const double& val)
185{
186 return (boost::format("%.01f%%") % val).str();
187}
188
a2ce158c 189string StatWebServer::indexfunction(const string& method, const string& post, const map<string,string> &varmap, void *ptr, bool *custom)
12c86877 190{
12c86877
BH
191 StatWebServer *sws=static_cast<StatWebServer *>(ptr);
192 map<string,string>rvarmap=varmap;
193 if(!rvarmap["resetring"].empty()){
194 *custom=true;
195 S.resetRing(rvarmap["resetring"]);
196 return "HTTP/1.1 301 Moved Permanently\nLocation: /\nConnection: close\n\n";
197 }
198 if(!rvarmap["resizering"].empty()){
199 *custom=true;
200 S.resizeRing(rvarmap["resizering"], atoi(rvarmap["size"].c_str()));
201 return "HTTP/1.1 301 Moved Permanently\nLocation: /\nConnection: close\n\n";
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)<<
12c86877
BH
229 sws->d_queries.get1()<<", "<<
230 sws->d_queries.get5()<<", "<<
231 sws->d_queries.get10()<<". Max queries/second: "<<sws->d_queries.getMax()<<
232 "<br>"<<endl;
233
234 if(sws->d_cachemisses.get10()+sws->d_cachehits.get10()>0)
b6f57093
BH
235 ret<<"Cache hitrate, 1, 5, 10 minute averages: "<<
236 makePercentage((sws->d_cachehits.get1()*100.0)/((sws->d_cachehits.get1())+(sws->d_cachemisses.get1())))<<", "<<
237 makePercentage((sws->d_cachehits.get5()*100.0)/((sws->d_cachehits.get5())+(sws->d_cachemisses.get5())))<<", "<<
238 makePercentage((sws->d_cachehits.get10()*100.0)/((sws->d_cachehits.get10())+(sws->d_cachemisses.get10())))<<
239 "<br>"<<endl;
12c86877
BH
240
241 if(sws->d_qcachemisses.get10()+sws->d_qcachehits.get10()>0)
395b07ea 242 ret<<"Backend query cache hitrate, 1, 5, 10 minute averages: "<<std::setprecision(2)<<
b6f57093
BH
243 makePercentage((sws->d_qcachehits.get1()*100.0)/((sws->d_qcachehits.get1())+(sws->d_qcachemisses.get1())))<<", "<<
244 makePercentage((sws->d_qcachehits.get5()*100.0)/((sws->d_qcachehits.get5())+(sws->d_qcachemisses.get5())))<<", "<<
245 makePercentage((sws->d_qcachehits.get10()*100.0)/((sws->d_qcachehits.get10())+(sws->d_qcachemisses.get10())))<<
246 "<br>"<<endl;
12c86877 247
395b07ea 248 ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
12c86877
BH
249 sws->d_qcachemisses.get1()<<", "<<
250 sws->d_qcachemisses.get5()<<", "<<
251 sws->d_qcachemisses.get10()<<". Max queries/second: "<<sws->d_qcachemisses.getMax()<<
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;
12c86877
BH
255 if(rvarmap["ring"].empty()) {
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
260 sws->printvars(ret);
261 if(arg().mustDo("webserver-print-arguments"))
262 sws->printargs(ret);
263 }
264 else
265 printtable(ret,rvarmap["ring"],S.getRingTitle(rvarmap["ring"]),100);
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
271 return ret.str();
272}
273
6ed9291d 274static int intFromJson(const Value& val) {
12a82d65
CH
275 if (val.IsInt()) {
276 return val.GetInt();
277 } else if (val.IsString()) {
278 return atoi(val.GetString());
279 } else {
33196945 280 throw PDNSException("Value not an Integer");
12a82d65
CH
281 }
282}
9ac4a7c6 283
1abb81f4
CH
284static string getZone(const string& zonename) {
285 UeberBackend B;
1abb81f4 286 DomainInfo di;
73301d73
CH
287 if(!B.getDomainInfo(zonename, di))
288 return returnJSONError("Could not find domain '"+zonename+"'");
1abb81f4
CH
289
290 Document doc;
291 doc.SetObject();
292
293 Value root;
294 root.SetObject();
295 root.AddMember("name", zonename.c_str(), doc.GetAllocator());
296 root.AddMember("type", "Zone", doc.GetAllocator());
297 root.AddMember("kind", di.getKindString(), doc.GetAllocator());
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 }
304 root.AddMember("masters", masters, doc.GetAllocator());
305 root.AddMember("serial", di.serial, doc.GetAllocator());
306 root.AddMember("notified_serial", di.notified_serial, doc.GetAllocator());
307 root.AddMember("last_check", (unsigned int) di.last_check, doc.GetAllocator());
308
309 DNSResourceRecord rr;
310 Value records;
311 records.SetArray();
3d89fc28
CH
312 di.backend->list(zonename, di.id);
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());
325 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
326 object.AddMember("content", jcontent, doc.GetAllocator());
327 records.PushBack(object, doc.GetAllocator());
328 }
329 root.AddMember("records", records, doc.GetAllocator());
330
331 doc.AddMember("zone", root, doc.GetAllocator());
332 return makeStringFromDocument(doc);
333}
334
838f3b98 335static string createOrUpdateZone(const string& zonename, bool onlyCreate, varmap_t& varmap) {
543843b3 336 UeberBackend B;
543843b3 337 DomainInfo di;
543843b3 338
3d89fc28 339 bool exists = B.getDomainInfo(zonename, di);
73301d73
CH
340 if(exists && onlyCreate)
341 return returnJSONError("Domain '"+zonename+"' already exists");
543843b3 342
3d89fc28 343 if(!exists) {
4d73ea84 344 if(!B.createDomain(zonename))
73301d73 345 return returnJSONError("Creating domain '"+zonename+"' failed");
543843b3 346
73301d73
CH
347 if(!B.getDomainInfo(zonename, di))
348 return returnJSONError("Creating domain '"+zonename+"' failed: lookup of domain ID failed");
543843b3 349
838f3b98
CH
350 // create SOA record so zone "really" exists
351 DNSResourceRecord soa;
352 soa.qname = zonename;
353 soa.content = "1";
354 soa.qtype = "SOA";
355 soa.domain_id = di.id;
356 soa.auth = 0;
357 soa.ttl = ::arg().asNum( "default-ttl" );
358 soa.priority = 0;
359
3d89fc28
CH
360 di.backend->startTransaction(zonename, di.id);
361 di.backend->feedRecord(soa);
362 di.backend->commitTransaction();
838f3b98 363 }
543843b3 364
3d89fc28
CH
365 di.backend->setKind(zonename, DomainInfo::stringToKind(varmap["kind"]));
366 di.backend->setMaster(zonename, varmap["master"]);
367
543843b3
CH
368 return getZone(zonename);
369}
370
6ed9291d 371static string jsonDispatch(const string& method, const string& post, varmap_t& varmap, const string& command) {
e611a06c 372 if(command=="get") {
ddc84d12 373 if(varmap.empty()) {
e611a06c
BH
374 vector<string> entries = S.getEntries();
375 BOOST_FOREACH(string& ent, entries) {
ddc84d12 376 varmap[ent];
e611a06c 377 }
ddc84d12
CH
378 varmap["version"];
379 varmap["uptime"];
e611a06c 380 }
7b39c040 381
e611a06c
BH
382 string variable, value;
383
8537b9f0
BH
384 Document doc;
385 doc.SetObject();
ddc84d12 386 for(varmap_t::const_iterator iter = varmap.begin(); iter != varmap.end() ; ++iter) {
e611a06c
BH
387 variable = iter->first;
388 if(variable == "version") {
ddc84d12 389 value = VERSION;
e611a06c
BH
390 }
391 else if(variable == "uptime") {
392 value = lexical_cast<string>(time(0) - s_starttime);
393 }
394 else
395 value = lexical_cast<string>(S.read(variable));
8537b9f0
BH
396 Value jval;
397 jval.SetString(value.c_str(), value.length(), doc.GetAllocator());
398 doc.AddMember(variable.c_str(), jval, doc.GetAllocator());
e611a06c 399 }
ddc84d12 400 return makeStringFromDocument(doc);
e611a06c 401 }
2fe9c01c 402 else if(command=="config") {
e611a06c 403 vector<string> items = ::arg().list();
8537b9f0
BH
404 Document doc;
405 doc.SetArray();
e611a06c 406 BOOST_FOREACH(const string& var, items) {
8537b9f0
BH
407 Value kv, key, value;
408 kv.SetArray();
409 key.SetString(var.c_str(), var.length());
410 kv.PushBack(key, doc.GetAllocator());
8465487d 411
8465487d 412 if(var.find("password") != string::npos)
8537b9f0 413 value="*****";
8465487d 414 else
8537b9f0
BH
415 value.SetString(::arg()[var].c_str(), ::arg()[var].length(), doc.GetAllocator());
416
417 kv.PushBack(value, doc.GetAllocator());
418 doc.PushBack(kv, doc.GetAllocator());
e611a06c 419 }
ddc84d12 420 return makeStringFromDocument(doc);
e611a06c 421 }
2fe9c01c 422 else if(command == "flush-cache") {
e611a06c
BH
423 extern PacketCache PC;
424 int number;
ddc84d12 425 if(varmap["domain"].empty())
e611a06c
BH
426 number = PC.purge();
427 else
ddc84d12 428 number = PC.purge(varmap["domain"]);
ac7ba905 429
e611a06c
BH
430 map<string, string> object;
431 object["number"]=lexical_cast<string>(number);
ddc84d12
CH
432 //cerr<<"Flushed cache for '"<<varmap["domain"]<<"', cleaned "<<number<<" records"<<endl;
433 return returnJSONObject(object);
e611a06c 434 }
2fe9c01c 435 else if(command == "pdns-control") {
73301d73 436 if(method!="POST")
33196945 437 throw HttpMethodNotAllowedException();
d267d1bf
BH
438 // cout<<"post: "<<post<<endl;
439 rapidjson::Document document;
73301d73
CH
440 if(document.Parse<0>(post.c_str()).HasParseError())
441 return returnJSONError("Unable to parse JSON");
d267d1bf
BH
442 // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
443 vector<string> parameters;
444 stringtok(parameters, document["parameters"].GetString(), " \t");
445
446 DynListener::g_funk_t* ptr=0;
447 if(!parameters.empty())
448 ptr = DynListener::getFunc(toUpper(parameters[0]));
449 map<string, string> m;
450
451 if(ptr) {
452 m["result"] = (*ptr)(parameters, 0);
453 } else {
454 m["error"]="No such function "+toUpper(parameters[0]);
455 }
ddc84d12 456 return returnJSONObject(m);
d267d1bf 457 }
2fe9c01c 458 else if(command == "zone-rest") { // http://jsonstat?command=zone-rest&rest=/powerdns.nl/www.powerdns.nl/a
a2ce158c 459 vector<string> parts;
ddc84d12 460 stringtok(parts, varmap["rest"], "/");
a2ce158c 461 if(parts.size() != 3)
73301d73 462 return returnJSONError("Could not parse rest parameter");
a2ce158c
BH
463 UeberBackend B;
464 SOAData sd;
465 sd.db = (DNSBackend*)-1;
73301d73
CH
466 if(!B.getSOA(parts[0], sd) || !sd.db)
467 return returnJSONError("Could not find domain '"+parts[0]+"'");
a2ce158c
BH
468
469 QType qtype;
470 qtype=parts[2];
471 string qname=parts[1];
472 extern PacketCache PC;
473 PC.purge(qname);
474 // cerr<<"domain id: "<<sd.domain_id<<", lookup name: '"<<parts[1]<<"', for type: '"<<qtype.getName()<<"'"<<endl;
475
476 if(method == "GET" ) {
477 B.lookup(qtype, parts[1], 0, sd.domain_id);
478
479 DNSResourceRecord rr;
ddc84d12 480 string ret = "{ \"records\": [";
a2ce158c
BH
481 map<string, string> object;
482 bool first=1;
483
484 while(B.get(rr)) {
2e76c05a
BH
485 if(!first) ret += ", ";
486 first=false;
487 object.clear();
488 object["name"] = rr.qname;
489 object["type"] = rr.qtype.getName();
490 object["ttl"] = lexical_cast<string>(rr.ttl);
491 object["priority"] = lexical_cast<string>(rr.priority);
492 object["content"] = rr.content;
493 ret+=returnJSONObject(object);
a2ce158c
BH
494 }
495 ret+="]}";
ddc84d12 496 return ret;
a2ce158c
BH
497 }
498 else if(method=="DELETE") {
499 sd.db->replaceRRSet(sd.domain_id, qname, qtype, vector<DNSResourceRecord>());
500
501 }
502 else if(method=="POST") {
ca9fc6a1 503 rapidjson::Document document;
73301d73
CH
504 if(document.Parse<0>(post.c_str()).HasParseError())
505 return returnJSONError("Unable to parse JSON");
a2ce158c 506
a2ce158c
BH
507 DNSResourceRecord rr;
508 vector<DNSResourceRecord> rrset;
ca9fc6a1
BH
509 const rapidjson::Value &records= document["records"];
510 for(rapidjson::SizeType i = 0; i < records.Size(); ++i) {
511 const rapidjson::Value& record = records[i];
512 rr.qname=record["name"].GetString();
513 rr.content=record["content"].GetString();
514 rr.qtype=record["type"].GetString();
2e76c05a
BH
515 rr.domain_id = sd.domain_id;
516 rr.auth=0;
6ed9291d
CH
517 rr.ttl=intFromJson(record["ttl"]);
518 rr.priority=intFromJson(record["priority"]);
2e76c05a
BH
519
520 rrset.push_back(rr);
521
522 if(rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV)
523 rr.content = lexical_cast<string>(rr.priority)+" "+rr.content;
524
525 try {
526 shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content));
527 string tmp=drc->serialize(rr.qname);
528 }
529 catch(std::exception& e)
530 {
232f0877 531 return returnJSONError("Following record had a problem: "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what());
2e76c05a 532 }
a2ce158c
BH
533 }
534 // but now what
535 sd.db->startTransaction(qname);
536 sd.db->replaceRRSet(sd.domain_id, qname, qtype, rrset);
537 sd.db->commitTransaction();
ddc84d12 538 return post;
a2ce158c
BH
539 }
540 }
ea12b64a 541 else if(command == "zone") {
ddc84d12 542 string zonename = varmap["zone"];
73301d73
CH
543 if (zonename.empty())
544 return returnJSONError("Must give zone parameter");
ea12b64a
CH
545
546 if(method == "GET") {
547 // get current zone
a8f16540
CH
548 return getZone(zonename);
549 } else if (method == "POST") {
550 // create
838f3b98
CH
551 return createOrUpdateZone(zonename, true, varmap);
552 } else if (method == "PUT") {
553 // update or create
554 return createOrUpdateZone(zonename, false, varmap);
9a889c55
CH
555 } else if (method == "DELETE") {
556 // delete
557 UeberBackend B;
9a889c55 558 DomainInfo di;
73301d73 559 if(!B.getDomainInfo(zonename, di))
232f0877 560 return returnJSONError("Deleting domain '"+zonename+"' failed: domain does not exist");
73301d73 561 if(!di.backend->deleteDomain(zonename))
232f0877 562 return returnJSONError("Deleting domain '"+zonename+"' failed: backend delete failed/unsupported");
9a889c55
CH
563 map<string, string> success; // empty success object
564 return returnJSONObject(success);
ea12b64a 565 } else {
33196945 566 throw HttpMethodNotAllowedException();
ea12b64a
CH
567 }
568 }
2fe9c01c 569 else if(command=="log-grep") {
ddc84d12 570 return makeLogGrepJSON(varmap, ::arg()["experimental-logfile"], " pdns[");
9ac4a7c6 571 }
2fe9c01c 572 else if(command=="domains") {
e611a06c
BH
573 UeberBackend B;
574 vector<DomainInfo> domains;
575 B.getAllDomains(&domains);
8537b9f0
BH
576
577 Document doc;
578 doc.SetObject();
579
580 Value jdomains;
581 jdomains.SetArray();
582
583 BOOST_FOREACH(const DomainInfo& di, domains) {
584 Value jdi;
585 jdi.SetObject();
586 jdi.AddMember("name", di.zone.c_str(), doc.GetAllocator());
ec10217f 587 jdi.AddMember("kind", di.getKindString(), doc.GetAllocator());
e958bd6c
CH
588 Value masters;
589 masters.SetArray();
590 BOOST_FOREACH(const string& master, di.masters) {
591 Value value(master.c_str(), doc.GetAllocator());
592 masters.PushBack(value, doc.GetAllocator());
593 }
594 jdi.AddMember("masters", masters, doc.GetAllocator());
8537b9f0
BH
595 jdi.AddMember("serial", di.serial, doc.GetAllocator());
596 jdi.AddMember("notified_serial", di.notified_serial, doc.GetAllocator());
934029f8 597 jdi.AddMember("last_check", (unsigned int) di.last_check, doc.GetAllocator());
8537b9f0 598 jdomains.PushBack(jdi, doc.GetAllocator());
e611a06c 599 }
8537b9f0 600 doc.AddMember("domains", jdomains, doc.GetAllocator());
ddc84d12
CH
601 return makeStringFromDocument(doc);
602 }
603
73301d73 604 return returnJSONError("No or unknown command given");
ddc84d12
CH
605}
606
607string StatWebServer::jsonstat(const string& method, const string& post, const map<string,string> &varmap, void *ptr, bool *custom)
608{
609 *custom=1; // indicates we build the response
610 string ret="HTTP/1.1 200 OK\r\n"
611 "Server: PowerDNS/"VERSION"\r\n"
612 "Connection: close\r\n"
613 "Access-Control-Allow-Origin: *\r\n"
614 "Content-Type: application/json\r\n"
615 "\r\n" ;
616
617 varmap_t ourvarmap=varmap;
618 string callback;
619 string command;
620
621 if(ourvarmap.count("callback")) {
622 callback=ourvarmap["callback"];
623 ourvarmap.erase("callback");
e611a06c
BH
624 }
625
ddc84d12
CH
626 if(ourvarmap.count("command")) {
627 command=ourvarmap["command"];
628 ourvarmap.erase("command");
629 }
630
631 ourvarmap.erase("_");
632 if(!callback.empty())
633 ret += callback+"(";
634
6ed9291d 635 ret += jsonDispatch(method, post, ourvarmap, command);
ddc84d12 636
e611a06c
BH
637 if(!callback.empty()) {
638 ret += ");";
ac7ba905 639 }
ac7ba905
BH
640 return ret;
641}
642
1071abdd
CH
643string StatWebServer::cssfunction(const string& method, const string& post, const map<string,string> &varmap, void *ptr, bool *custom)
644{
645 *custom=1; // indicates we build the response
646 ostringstream ret;
647 ret<<"HTTP/1.1 200 OK\r\n"
648 "Server: PowerDNS/"VERSION"\r\n"
649 "Connection: close\r\n"
650 "Cache-Control: max-age=86400\r\n"
651 "Content-Type: text/css\r\n"
652 "\r\n";
653
654 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
655 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
656 ret<<"a { color: #0959c2; }"<<endl;
657 ret<<"a:hover { color: #3B8EC8; }"<<endl;
658 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
659 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
660 ret<<".row:after { clear: both; }"<<endl;
661 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
662 ret<<".all { width: 100%; }"<<endl;
663 ret<<".headl { width: 60%; }"<<endl;
664 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
665 ret<<"background-image: url(data:image/png;base64,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);";
666 ret<<" width: 154px; height: 20px; }"<<endl;
667 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
668 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
669 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
670 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
671 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
672 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
673 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
674 ret<<"table.data tr:hover { background: white; }"<<endl;
675 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
676 ret<<".resetring {float: right; }"<<endl;
677 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;
678 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
679 ret<<".resizering {float: right;}"<<endl;
680 return ret.str();
681}
682
12c86877
BH
683void StatWebServer::launch()
684{
685 try {
96d299db 686 d_ws->setCaller(this);
232f0877
CH
687 d_ws->registerHandler("/",&indexfunction);
688 d_ws->registerHandler("/style.css",&cssfunction);
9097239c 689 if(::arg().mustDo("experimental-json-interface"))
232f0877 690 d_ws->registerHandler("/jsonstat", &jsonstat);
96d299db 691 d_ws->go();
12c86877
BH
692 }
693 catch(...) {
694 L<<Logger::Error<<"StatWebserver thread caught an exception, dying"<<endl;
695 exit(1);
696 }
697}