]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
Merge branch 'master' of github.com:PowerDNS/pdns
[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"
3c3c006b
CH
42#include <iomanip>
43
44#ifdef HAVE_CONFIG_H
45# include <config.h>
46#endif // HAVE_CONFIG_H
8537b9f0
BH
47
48using namespace rapidjson;
12c86877
BH
49
50extern StatBag S;
51
dea47634 52AuthWebServer::AuthWebServer()
12c86877
BH
53{
54 d_start=time(0);
96d299db 55 d_min10=d_min5=d_min1=0;
c81c2ea8 56 d_ws = 0;
f17c93b4 57 d_tid = 0;
825fa717 58 if(arg().mustDo("webserver")) {
c81c2ea8 59 d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port"),arg()["webserver-password"]);
825fa717
CH
60 d_ws->bind();
61 }
12c86877
BH
62}
63
dea47634 64void AuthWebServer::go()
12c86877 65{
c81c2ea8
PD
66 if(arg().mustDo("webserver"))
67 {
68 S.doRings();
dea47634 69 pthread_create(&d_tid, 0, webThreadHelper, this);
c81c2ea8
PD
70 pthread_create(&d_tid, 0, statThreadHelper, this);
71 }
12c86877
BH
72}
73
dea47634 74void AuthWebServer::statThread()
12c86877
BH
75{
76 try {
77 for(;;) {
78 d_queries.submit(S.read("udp-queries"));
79 d_cachehits.submit(S.read("packetcache-hit"));
80 d_cachemisses.submit(S.read("packetcache-miss"));
81 d_qcachehits.submit(S.read("query-cache-hit"));
82 d_qcachemisses.submit(S.read("query-cache-miss"));
83 Utility::sleep(1);
84 }
85 }
86 catch(...) {
87 L<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl;
88 exit(1);
89 }
90}
91
dea47634 92void *AuthWebServer::statThreadHelper(void *p)
12c86877 93{
dea47634
CH
94 AuthWebServer *self=static_cast<AuthWebServer *>(p);
95 self->statThread();
12c86877
BH
96 return 0; // never reached
97}
98
dea47634 99void *AuthWebServer::webThreadHelper(void *p)
12c86877 100{
dea47634
CH
101 AuthWebServer *self=static_cast<AuthWebServer *>(p);
102 self->webThread();
12c86877
BH
103 return 0; // never reached
104}
105
9f3fdaa0
CH
106static string htmlescape(const string &s) {
107 string result;
108 for(string::const_iterator it=s.begin(); it!=s.end(); ++it) {
109 switch (*it) {
110 case '&':
c86a96f9 111 result += "&amp;";
9f3fdaa0
CH
112 break;
113 case '<':
114 result += "&lt;";
115 break;
116 case '>':
117 result += "&gt;";
118 break;
119 default:
120 result += *it;
121 }
122 }
123 return result;
124}
125
12c86877
BH
126void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
127{
128 int tot=0;
129 int entries=0;
101b5d5d 130 vector<pair <string,unsigned int> >ring=S.getRing(ringname);
12c86877 131
1071abdd 132 for(vector<pair<string, unsigned int> >::const_iterator i=ring.begin(); i!=ring.end();++i) {
12c86877
BH
133 tot+=i->second;
134 entries++;
135 }
136
1071abdd
CH
137 ret<<"<div class=\"panel\">";
138 ret<<"<span class=resetring><i></i><a href=\"?resetring="<<ringname<<"\">Reset</a></span>"<<endl;
139 ret<<"<h2>"<<title<<"</h2>"<<endl;
140 ret<<"<div class=ringmeta>";
141 ret<<"<a class=topXofY href=\"?ring="<<ringname<<"\">Showing: Top "<<limit<<" of "<<entries<<"</a>"<<endl;
142 ret<<"<span class=resizering>Resize: ";
bb3c3f50 143 unsigned int sizes[]={10,100,500,1000,10000,500000,0};
12c86877
BH
144 for(int i=0;sizes[i];++i) {
145 if(S.getRingSize(ringname)!=sizes[i])
e2a77e08 146 ret<<"<a href=\"?resizering="<<ringname<<"&amp;size="<<sizes[i]<<"\">"<<sizes[i]<<"</a> ";
12c86877
BH
147 else
148 ret<<"("<<sizes[i]<<") ";
149 }
1071abdd 150 ret<<"</span></div>";
12c86877 151
1071abdd 152 ret<<"<table class=\"data\">";
12c86877 153 int printed=0;
f5cb7e61 154 int total=max(1,tot);
bb3c3f50 155 for(vector<pair<string,unsigned int> >::const_iterator i=ring.begin();limit && i!=ring.end();++i,--limit) {
dea47634 156 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
157 printed+=i->second;
158 }
159 ret<<"<tr><td colspan=3></td></tr>"<<endl;
160 if(printed!=tot)
dea47634 161 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 162
e2a77e08 163 ret<<"<tr><td><b>Total:</b></td><td><b>"<<tot<<"</b></td><td align=right><b>100%</b></td>";
1071abdd 164 ret<<"</table></div>"<<endl;
12c86877
BH
165}
166
dea47634 167void AuthWebServer::printvars(ostringstream &ret)
12c86877 168{
1071abdd 169 ret<<"<div class=panel><h2>Variables</h2><table class=\"data\">"<<endl;
12c86877
BH
170
171 vector<string>entries=S.getEntries();
172 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
173 ret<<"<tr><td>"<<*i<<"</td><td>"<<S.read(*i)<<"</td><td>"<<S.getDescrip(*i)<<"</td>"<<endl;
174 }
e2a77e08 175
1071abdd 176 ret<<"</table></div>"<<endl;
12c86877
BH
177}
178
dea47634 179void AuthWebServer::printargs(ostringstream &ret)
12c86877 180{
e2a77e08 181 ret<<"<table border=1><tr><td colspan=3 bgcolor=\"#0000ff\"><font color=\"#ffffff\">Arguments</font></td>"<<endl;
12c86877
BH
182
183 vector<string>entries=arg().list();
184 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
185 ret<<"<tr><td>"<<*i<<"</td><td>"<<arg()[*i]<<"</td><td>"<<arg().getHelp(*i)<<"</td>"<<endl;
186 }
187}
188
dea47634 189string AuthWebServer::makePercentage(const double& val)
b6f57093
BH
190{
191 return (boost::format("%.01f%%") % val).str();
192}
193
dea47634 194void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
12c86877 195{
ef1439ff
KM
196 if(!req->parameters["resetring"].empty()) {
197 if (S.ringExists(req->parameters["resetring"]))
198 S.resetRing(req->parameters["resetring"]);
80d59cd1
CH
199 resp->status = 301;
200 resp->headers["Location"] = "/";
201 return;
12c86877 202 }
80d59cd1 203 if(!req->parameters["resizering"].empty()){
ef1439ff
KM
204 int size=atoi(req->parameters["size"].c_str());
205 if (S.ringExists(req->parameters["resizering"]) && size > 0 && size <= 500000)
206 S.resizeRing(req->parameters["resizering"], atoi(req->parameters["size"].c_str()));
80d59cd1
CH
207 resp->status = 301;
208 resp->headers["Location"] = "/";
209 return;
12c86877
BH
210 }
211
212 ostringstream ret;
213
1071abdd
CH
214 ret<<"<!DOCTYPE html>"<<endl;
215 ret<<"<html><head>"<<endl;
216 ret<<"<title>PowerDNS Authoritative Server Monitor</title>"<<endl;
217 ret<<"<link rel=\"stylesheet\" href=\"style.css\"/>"<<endl;
218 ret<<"</head><body>"<<endl;
219
220 ret<<"<div class=\"row\">"<<endl;
221 ret<<"<div class=\"headl columns\">";
222 ret<<"<a href=\"/\" id=\"appname\">PowerDNS "VERSION;
223 if(!arg()["config-name"].empty()) {
224 ret<<" ["<<arg()["config-name"]<<"]";
225 }
226 ret<<"</a></div>"<<endl;
227 ret<<"<div class=\"headr columns\"></div></div>";
228 ret<<"<div class=\"row\"><div class=\"all columns\">";
12c86877
BH
229
230 time_t passed=time(0)-s_starttime;
231
e2a77e08
KM
232 ret<<"<p>Uptime: "<<
233 humanDuration(passed)<<
234 "<br>"<<endl;
12c86877 235
395b07ea 236 ret<<"Queries/second, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
f6154a3b
CH
237 d_queries.get1()<<", "<<
238 d_queries.get5()<<", "<<
239 d_queries.get10()<<". Max queries/second: "<<d_queries.getMax()<<
12c86877
BH
240 "<br>"<<endl;
241
f6154a3b 242 if(d_cachemisses.get10()+d_cachehits.get10()>0)
b6f57093 243 ret<<"Cache hitrate, 1, 5, 10 minute averages: "<<
f6154a3b
CH
244 makePercentage((d_cachehits.get1()*100.0)/((d_cachehits.get1())+(d_cachemisses.get1())))<<", "<<
245 makePercentage((d_cachehits.get5()*100.0)/((d_cachehits.get5())+(d_cachemisses.get5())))<<", "<<
246 makePercentage((d_cachehits.get10()*100.0)/((d_cachehits.get10())+(d_cachemisses.get10())))<<
b6f57093 247 "<br>"<<endl;
12c86877 248
f6154a3b 249 if(d_qcachemisses.get10()+d_qcachehits.get10()>0)
395b07ea 250 ret<<"Backend query cache hitrate, 1, 5, 10 minute averages: "<<std::setprecision(2)<<
f6154a3b
CH
251 makePercentage((d_qcachehits.get1()*100.0)/((d_qcachehits.get1())+(d_qcachemisses.get1())))<<", "<<
252 makePercentage((d_qcachehits.get5()*100.0)/((d_qcachehits.get5())+(d_qcachemisses.get5())))<<", "<<
253 makePercentage((d_qcachehits.get10()*100.0)/((d_qcachehits.get10())+(d_qcachemisses.get10())))<<
b6f57093 254 "<br>"<<endl;
12c86877 255
395b07ea 256 ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
f6154a3b
CH
257 d_qcachemisses.get1()<<", "<<
258 d_qcachemisses.get5()<<", "<<
259 d_qcachemisses.get10()<<". Max queries/second: "<<d_qcachemisses.getMax()<<
12c86877
BH
260 "<br>"<<endl;
261
1071abdd 262 ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
80d59cd1 263 if(req->parameters["ring"].empty()) {
12c86877
BH
264 vector<string>entries=S.listRings();
265 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i)
266 printtable(ret,*i,S.getRingTitle(*i));
267
f6154a3b 268 printvars(ret);
12c86877 269 if(arg().mustDo("webserver-print-arguments"))
f6154a3b 270 printargs(ret);
12c86877
BH
271 }
272 else
80d59cd1 273 printtable(ret,req->parameters["ring"],S.getRingTitle(req->parameters["ring"]),100);
12c86877 274
1071abdd 275 ret<<"</div></div>"<<endl;
ba1a571d 276 ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
12c86877
BH
277 ret<<"</body></html>"<<endl;
278
80d59cd1 279 resp->body = ret.str();
12c86877
BH
280}
281
669822d0 282static void fillZone(const string& zonename, HttpResponse* resp) {
1abb81f4 283 UeberBackend B;
1abb81f4 284 DomainInfo di;
73301d73 285 if(!B.getDomainInfo(zonename, di))
669822d0 286 throw ApiException("Could not find domain '"+zonename+"'");
1abb81f4
CH
287
288 Document doc;
289 doc.SetObject();
290
418aa246 291 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
3c3c006b
CH
292 string zoneId = apiZoneNameToId(di.zone);
293 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
294 doc.AddMember("id", jzoneId, doc.GetAllocator());
295 string url = "/servers/localhost/zones/" + zoneId;
418aa246
CH
296 Value jurl(url.c_str(), doc.GetAllocator()); // copy
297 doc.AddMember("url", jurl, doc.GetAllocator());
298 doc.AddMember("name", di.zone.c_str(), doc.GetAllocator());
e2dba705
CH
299 doc.AddMember("type", "Zone", doc.GetAllocator());
300 doc.AddMember("kind", di.getKindString(), doc.GetAllocator());
1abb81f4
CH
301 Value masters;
302 masters.SetArray();
303 BOOST_FOREACH(const string& master, di.masters) {
304 Value value(master.c_str(), doc.GetAllocator());
305 masters.PushBack(value, doc.GetAllocator());
306 }
e2dba705
CH
307 doc.AddMember("masters", masters, doc.GetAllocator());
308 doc.AddMember("serial", di.serial, doc.GetAllocator());
309 doc.AddMember("notified_serial", di.notified_serial, doc.GetAllocator());
310 doc.AddMember("last_check", (unsigned int) di.last_check, doc.GetAllocator());
1abb81f4 311
6cc98ddf 312 // fill records
1abb81f4
CH
313 DNSResourceRecord rr;
314 Value records;
315 records.SetArray();
cea26350 316 di.backend->list(zonename, di.id, true); // incl. disabled
3d89fc28 317 while(di.backend->get(rr)) {
1abb81f4
CH
318 if (!rr.qtype.getCode())
319 continue; // skip empty non-terminals
320
321 Value object;
322 object.SetObject();
323 Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy
324 object.AddMember("name", jname, doc.GetAllocator());
325 Value jtype(rr.qtype.getName().c_str(), doc.GetAllocator()); // copy
326 object.AddMember("type", jtype, doc.GetAllocator());
327 object.AddMember("ttl", rr.ttl, doc.GetAllocator());
328 object.AddMember("priority", rr.priority, doc.GetAllocator());
cea26350 329 object.AddMember("disabled", rr.disabled, doc.GetAllocator());
1abb81f4
CH
330 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
331 object.AddMember("content", jcontent, doc.GetAllocator());
332 records.PushBack(object, doc.GetAllocator());
333 }
e2dba705 334 doc.AddMember("records", records, doc.GetAllocator());
1abb81f4 335
6cc98ddf
CH
336 // fill comments
337 Comment comment;
338 Value comments;
339 comments.SetArray();
340 di.backend->listComments(di.id);
341 while(di.backend->getComment(comment)) {
342 Value object;
343 object.SetObject();
344 Value jname(comment.qname.c_str(), doc.GetAllocator()); // copy
345 object.AddMember("name", jname, doc.GetAllocator());
346 Value jtype(comment.qtype.getName().c_str(), doc.GetAllocator()); // copy
347 object.AddMember("type", jtype, doc.GetAllocator());
55f12d58 348 object.AddMember("modified_at", (unsigned int) comment.modified_at, doc.GetAllocator());
6cc98ddf
CH
349 Value jaccount(comment.account.c_str(), doc.GetAllocator()); // copy
350 object.AddMember("account", jaccount, doc.GetAllocator());
351 Value jcontent(comment.content.c_str(), doc.GetAllocator()); // copy
352 object.AddMember("content", jcontent, doc.GetAllocator());
353 comments.PushBack(object, doc.GetAllocator());
354 }
355 doc.AddMember("comments", comments, doc.GetAllocator());
356
669822d0 357 resp->setBody(doc);
1abb81f4
CH
358}
359
6ec5e728
CH
360void productServerStatisticsFetch(map<string,string>& out)
361{
a45303b8 362 vector<string> items = S.getEntries();
a45303b8 363 BOOST_FOREACH(const string& item, items) {
6ec5e728 364 out[item] = lexical_cast<string>(S.read(item));
a45303b8
CH
365 }
366
367 // add uptime
6ec5e728 368 out["uptime"] = lexical_cast<string>(time(0) - s_starttime);
c67bf8c5
CH
369}
370
6cc98ddf
CH
371static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp);
372
80d59cd1 373static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
e2dba705 374 UeberBackend B;
18179c61 375 if (req->method == "POST" && !::arg().mustDo("experimental-api-readonly")) {
e2dba705
CH
376 DomainInfo di;
377 Document document;
6ec5e728 378 req->json(document);
e2dba705
CH
379 string zonename = stringFromJson(document, "name");
380 // TODO: better validation of zonename
381 if(zonename.empty())
382 throw ApiException("Zone name empty");
383
4ebf78b1
CH
384 // strip any trailing dots
385 while (zonename.substr(zonename.size()-1) == ".") {
386 zonename.resize(zonename.size()-1);
387 }
388
e2dba705 389 string kind = stringFromJson(document, "kind");
e2dba705
CH
390
391 bool exists = B.getDomainInfo(zonename, di);
392 if(exists)
393 throw ApiException("Domain '"+zonename+"' already exists");
394
395 const Value &nameservers = document["nameservers"];
396 if (!nameservers.IsArray() || nameservers.Size() == 0)
397 throw ApiException("Need at least one nameserver");
398
7c0ba3d2
CH
399 string master;
400 const Value &masters = document["masters"];
401 if (masters.IsArray()) {
402 for (SizeType i = 0; i < masters.Size(); ++i) {
403 master += masters[i].GetString();
404 master += " ";
405 }
406 }
407
e2dba705
CH
408 // no going back after this
409 if(!B.createDomain(zonename))
410 throw ApiException("Creating domain '"+zonename+"' failed");
411
412 if(!B.getDomainInfo(zonename, di))
413 throw ApiException("Creating domain '"+zonename+"' failed: lookup of domain ID failed");
414
415 vector<DNSResourceRecord> rrset;
416
417 // create SOA record so zone "really" exists
418 DNSResourceRecord rr;
419 rr.qname = zonename;
39cb2a0d 420 rr.content = (boost::format("%s hostmaster@%s %d")
03f39ef9
CH
421 % nameservers[SizeType(0)].GetString()
422 % zonename
423 % intFromJson(document, "serial", 1)
e2dba705 424 ).str();
39cb2a0d
CH
425 SOAData sd;
426 fillSOAData(rr.content, sd);
427 rr.content = serializeSOAData(sd);
e2dba705
CH
428 rr.qtype = "SOA";
429 rr.domain_id = di.id;
f62559e1 430 rr.auth = 1;
e2dba705
CH
431 rr.ttl = ::arg().asNum( "default-ttl" );
432 rr.priority = 0;
433 rrset.push_back(rr);
434
7c0ba3d2 435 for (SizeType i = 0; i < nameservers.Size(); ++i) {
e2dba705
CH
436 rr.content = nameservers[i].GetString();
437 rr.qtype = "NS";
438 rrset.push_back(rr);
439 }
440
441 di.backend->startTransaction(zonename, di.id);
442 BOOST_FOREACH(rr, rrset) {
443 di.backend->feedRecord(rr);
444 }
445 di.backend->commitTransaction();
446
447 di.backend->setKind(zonename, DomainInfo::stringToKind(kind));
7c0ba3d2 448 di.backend->setMaster(zonename, master);
e2dba705 449
669822d0 450 fillZone(zonename, resp);
e2dba705
CH
451 return;
452 }
453
c67bf8c5
CH
454 if(req->method != "GET")
455 throw HttpMethodNotAllowedException();
456
c67bf8c5 457 vector<DomainInfo> domains;
cea26350 458 B.getAllDomains(&domains, true); // incl. disabled
c67bf8c5
CH
459
460 Document doc;
45de6290 461 doc.SetArray();
c67bf8c5
CH
462
463 BOOST_FOREACH(const DomainInfo& di, domains) {
464 Value jdi;
465 jdi.SetObject();
418aa246 466 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
3c3c006b
CH
467 string zoneId = apiZoneNameToId(di.zone);
468 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
469 jdi.AddMember("id", jzoneId, doc.GetAllocator());
470 string url = "/servers/localhost/zones/" + zoneId;
418aa246
CH
471 Value jurl(url.c_str(), doc.GetAllocator()); // copy
472 jdi.AddMember("url", jurl, doc.GetAllocator());
c67bf8c5
CH
473 jdi.AddMember("name", di.zone.c_str(), doc.GetAllocator());
474 jdi.AddMember("kind", di.getKindString(), doc.GetAllocator());
475 Value masters;
476 masters.SetArray();
477 BOOST_FOREACH(const string& master, di.masters) {
478 Value value(master.c_str(), doc.GetAllocator());
479 masters.PushBack(value, doc.GetAllocator());
480 }
481 jdi.AddMember("masters", masters, doc.GetAllocator());
482 jdi.AddMember("serial", di.serial, doc.GetAllocator());
483 jdi.AddMember("notified_serial", di.notified_serial, doc.GetAllocator());
484 jdi.AddMember("last_check", (unsigned int) di.last_check, doc.GetAllocator());
45de6290 485 doc.PushBack(jdi, doc.GetAllocator());
c67bf8c5 486 }
669822d0 487 resp->setBody(doc);
c67bf8c5
CH
488}
489
05776d2f 490static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
3c3c006b 491 string zonename = apiZoneIdToName(req->path_parameters["id"]);
05776d2f 492
18179c61 493 if(req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
7c0ba3d2
CH
494 // update domain settings
495 UeberBackend B;
496 DomainInfo di;
497 if(!B.getDomainInfo(zonename, di))
498 throw ApiException("Could not find domain '"+zonename+"'");
499
500 Document document;
6ec5e728 501 req->json(document);
7c0ba3d2
CH
502
503 string master;
504 const Value &masters = document["masters"];
505 if (masters.IsArray()) {
506 for(SizeType i = 0; i < masters.Size(); ++i) {
507 master += masters[i].GetString();
508 master += " ";
509 }
510 }
511
512 di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
513 di.backend->setMaster(zonename, master);
669822d0 514 fillZone(zonename, resp);
7c0ba3d2
CH
515 return;
516 }
18179c61 517 else if(req->method == "DELETE" && !::arg().mustDo("experimental-api-readonly")) {
a462a01d
CH
518 // delete domain
519 UeberBackend B;
520 DomainInfo di;
521 if(!B.getDomainInfo(zonename, di))
522 throw ApiException("Could not find domain '"+zonename+"'");
523
524 if(!di.backend->deleteDomain(zonename))
525 throw ApiException("Deleting domain '"+zonename+"' failed: backend delete failed/unsupported");
526
527 // empty body on success
528 resp->body = "";
529 return;
18179c61 530 } else if (req->method == "PATCH" && !::arg().mustDo("experimental-api-readonly")) {
6cc98ddf
CH
531 apiServerZoneRRset(req, resp);
532 return;
533 } else if (req->method == "GET") {
534 fillZone(zonename, resp);
535 return;
a462a01d 536 }
7c0ba3d2 537
6cc98ddf 538 throw HttpMethodNotAllowedException();
05776d2f
CH
539}
540
d1587ceb
CH
541static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) {
542 if (rr.qtype.getCode() == QType::A) {
543 uint32_t ip;
544 if (!IpToU32(rr.content, &ip)) {
545 throw ApiException("PTR: Invalid IP address given");
546 }
547 ptr->qname = (boost::format("%u.%u.%u.%u.in-addr.arpa")
548 % ((ip >> 24) & 0xff)
549 % ((ip >> 16) & 0xff)
550 % ((ip >> 8) & 0xff)
551 % ((ip ) & 0xff)
552 ).str();
553 } else if (rr.qtype.getCode() == QType::AAAA) {
554 ComboAddress ca(rr.content);
5fb3aa58 555 char buf[3];
d1587ceb 556 ostringstream ss;
5fb3aa58
CH
557 for (int octet = 0; octet < 16; ++octet) {
558 if (snprintf(buf, sizeof(buf), "%02x", ca.sin6.sin6_addr.s6_addr[octet]) != (sizeof(buf)-1)) {
559 // this should be impossible: no byte should give more than two digits in hex format
560 throw PDNSException("Formatting IPv6 address failed");
561 }
562 ss << buf[0] << '.' << buf[1] << '.';
d1587ceb 563 }
5fb3aa58
CH
564 string tmp = ss.str();
565 tmp.resize(tmp.size()-1); // remove last dot
566 // reverse and append arpa domain
567 ptr->qname = string(tmp.rbegin(), tmp.rend()) + ".ip6.arpa";
d1587ceb
CH
568 } else {
569 throw ApiException("Unsupported PTR source '" + rr.qname + "' type '" + rr.qtype.getName() + "'");
570 }
571
572 ptr->qtype = "PTR";
573 ptr->ttl = rr.ttl;
574 ptr->disabled = rr.disabled;
575 ptr->priority = 0;
576 ptr->content = rr.qname;
577}
578
b3905a3d 579static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) {
18179c61 580 if(req->method != "PATCH" || ::arg().mustDo("experimental-api-readonly"))
b3905a3d
CH
581 throw HttpMethodNotAllowedException();
582
583 UeberBackend B;
584 DomainInfo di;
3c3c006b 585 string zonename = apiZoneIdToName(req->path_parameters["id"]);
b3905a3d
CH
586 if(!B.getDomainInfo(zonename, di))
587 throw ApiException("Could not find domain '"+zonename+"'");
588
b3905a3d 589 Document document;
6ec5e728 590 req->json(document);
b3905a3d
CH
591
592 string qname, changetype;
593 QType qtype;
594 qname = stringFromJson(document, "name");
595 qtype = stringFromJson(document, "type");
596 changetype = toUpper(stringFromJson(document, "changetype"));
597
35f26cc5
CH
598 string dotsuffix = "." + zonename;
599 if(!iends_with(qname, dotsuffix) && qname != zonename)
600 throw ApiException("RRset "+qname+" IN "+qtype.getName()+": Name is out of zone");
601
b3905a3d 602 if (changetype == "DELETE") {
6cc98ddf
CH
603 // delete all matching qname/qtype RRs (and, implictly comments).
604 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
605 throw ApiException("Hosting backend does not support editing records.");
606 }
b3905a3d
CH
607 }
608 else if (changetype == "REPLACE") {
6cc98ddf
CH
609 vector<DNSResourceRecord> new_records;
610 vector<Comment> new_comments;
d1587ceb 611 vector<DNSResourceRecord> new_ptrs;
6cc98ddf
CH
612 bool replace_records = false;
613 bool replace_comments = false;
614
615 // gather records
b3905a3d 616 DNSResourceRecord rr;
b3905a3d 617 const Value& records = document["records"];
6cc98ddf
CH
618 if (records.IsArray()) {
619 replace_records = true;
620 for(SizeType idx = 0; idx < records.Size(); ++idx) {
621 const Value& record = records[idx];
622 rr.qname = stringFromJson(record, "name");
623 rr.content = stringFromJson(record, "content");
624 rr.qtype = stringFromJson(record, "type");
625 rr.domain_id = di.id;
626 rr.auth = 1;
627 rr.ttl = intFromJson(record, "ttl");
628 rr.priority = intFromJson(record, "priority");
629 rr.disabled = boolFromJson(record, "disabled");
630
6cc98ddf
CH
631 if(rr.qname != qname || rr.qtype != qtype)
632 throw ApiException("Record "+rr.qname+" IN "+rr.qtype.getName()+" "+rr.content+": Record bundled with wrong RRset");
633
41e3b10e
CH
634 string temp_content = rr.content;
635 if(rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV)
636 temp_content = lexical_cast<string>(rr.priority)+" "+rr.content;
637
6cc98ddf 638 try {
41e3b10e 639 shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, temp_content));
6cc98ddf
CH
640 string tmp = drc->serialize(rr.qname);
641 }
642 catch(std::exception& e)
643 {
644 throw ApiException("Record "+rr.qname+" IN "+rr.qtype.getName()+" "+rr.content+": "+e.what());
645 }
646
d1587ceb
CH
647 if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
648 boolFromJson(record, "set-ptr", false) == true) {
649 DNSResourceRecord ptr;
650 makePtr(rr, &ptr);
651
652 // verify that there's a zone for the PTR
653 DNSPacket fakePacket;
654 SOAData sd;
655 fakePacket.qtype = QType::PTR;
656 if (!B.getAuth(&fakePacket, &sd, ptr.qname, 0))
657 throw ApiException("Could not find domain for PTR '"+ptr.qname+"' requested for '"+ptr.content+"'");
658
659 ptr.domain_id = sd.domain_id;
660 new_ptrs.push_back(ptr);
661 }
662
6cc98ddf
CH
663 new_records.push_back(rr);
664 }
665 }
b3905a3d 666
6cc98ddf
CH
667 // gather comments
668 Comment c;
669 c.domain_id = di.id;
670 c.qname = qname;
671 c.qtype = qtype;
672 time_t now = time(0);
673 const Value& comments = document["comments"];
674 if (comments.IsArray()) {
675 replace_comments = true;
676 for(SizeType idx = 0; idx < comments.Size(); ++idx) {
677 const Value& comment = comments[idx];
678 c.modified_at = intFromJson(comment, "modified_at", now);
679 c.content = stringFromJson(comment, "content");
680 c.account = stringFromJson(comment, "account");
681 new_comments.push_back(c);
682 }
683 }
b3905a3d 684
6cc98ddf
CH
685 if (!replace_records && !replace_comments) {
686 throw ApiException("No change");
687 }
35f26cc5 688
6cc98ddf
CH
689 // Actually store the change(s).
690 di.backend->startTransaction(qname);
691 if (replace_records) {
692 if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
693 throw ApiException("Hosting backend does not support editing records.");
b3905a3d 694 }
6cc98ddf
CH
695 }
696 if (replace_comments) {
697 if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
698 throw ApiException("Hosting backend does not support editing comments.");
b3905a3d
CH
699 }
700 }
cea26350 701 di.backend->commitTransaction();
d1587ceb
CH
702
703 // now the PTRs
704 BOOST_FOREACH(const DNSResourceRecord& rr, new_ptrs) {
705 DNSPacket fakePacket;
706 SOAData sd;
707 sd.db = (DNSBackend *)-1;
708 fakePacket.qtype = QType::PTR;
709
710 if (!B.getAuth(&fakePacket, &sd, rr.qname, 0))
711 throw ApiException("Could not find domain for PTR '"+rr.qname+"' requested for '"+rr.content+"' (while saving)");
712
713 sd.db->startTransaction(rr.qname);
714 if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
715 throw ApiException("PTR-Hosting backend does not support editing records.");
716 }
717 sd.db->commitTransaction();
718 }
719
b3905a3d
CH
720 }
721 else
722 throw ApiException("Changetype not understood");
723
724 extern PacketCache PC;
725 PC.purge(qname);
726
727 // success
728 resp->body = "{}";
729}
730
b1902fab
CH
731static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
732 if(req->method != "GET")
733 throw HttpMethodNotAllowedException();
734
735 string q = req->parameters["q"];
736 if (q.empty())
737 throw ApiException("Query q can't be blank");
738
739 UeberBackend B;
740
741 vector<DomainInfo> domains;
742 B.getAllDomains(&domains, true); // incl. disabled
743
744 Document doc;
745 doc.SetArray();
746
747 DNSResourceRecord rr;
748 Comment comment;
749
750 BOOST_FOREACH(const DomainInfo& di, domains) {
d2d194a9
CH
751 string zoneId = apiZoneNameToId(di.zone);
752
57cb86d8 753 if (pdns_ci_find(di.zone, q) != string::npos) {
b1902fab
CH
754 Value object;
755 object.SetObject();
756 object.AddMember("type", "zone", doc.GetAllocator());
d2d194a9
CH
757 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
758 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
759 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
760 object.AddMember("name", jzoneName, doc.GetAllocator());
b1902fab
CH
761 doc.PushBack(object, doc.GetAllocator());
762 }
763
764 // if zone name is an exact match, don't bother with returning all records/comments in it
765 if (di.zone == q) {
766 continue;
767 }
768
769 di.backend->list(di.zone, di.id, true); // incl. disabled
770 while(di.backend->get(rr)) {
771 if (!rr.qtype.getCode())
772 continue; // skip empty non-terminals
773
57cb86d8 774 if (pdns_ci_find(rr.qname, q) == string::npos && pdns_ci_find(rr.content, q) == string::npos)
b1902fab
CH
775 continue;
776
777 Value object;
778 object.SetObject();
779 object.AddMember("type", "record", doc.GetAllocator());
d2d194a9
CH
780 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
781 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
782 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
783 object.AddMember("zone_name", jzoneName, doc.GetAllocator());
b1902fab
CH
784 Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy
785 object.AddMember("name", jname, doc.GetAllocator());
786 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
787 object.AddMember("content", jcontent, doc.GetAllocator());
788 doc.PushBack(object, doc.GetAllocator());
789 }
790
791 di.backend->listComments(di.id);
792 while(di.backend->getComment(comment)) {
57cb86d8 793 if (pdns_ci_find(comment.qname, q) == string::npos && pdns_ci_find(comment.content, q) == string::npos)
b1902fab
CH
794 continue;
795
796 Value object;
797 object.SetObject();
798 object.AddMember("type", "comment", doc.GetAllocator());
d2d194a9
CH
799 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
800 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
801 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
802 object.AddMember("zone_name", jzoneName, doc.GetAllocator());
b1902fab
CH
803 Value jname(comment.qname.c_str(), doc.GetAllocator()); // copy
804 object.AddMember("name", jname, doc.GetAllocator());
805 Value jcontent(comment.content.c_str(), doc.GetAllocator()); // copy
806 object.AddMember("content", jcontent, doc.GetAllocator());
807 doc.PushBack(object, doc.GetAllocator());
808 }
809 }
810 resp->setBody(doc);
811}
812
dea47634 813void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
80d59cd1
CH
814{
815 string command;
816
817 if(req->parameters.count("command")) {
818 command = req->parameters["command"];
819 req->parameters.erase("command");
820 }
821
a45303b8 822 if(command == "flush-cache") {
e611a06c
BH
823 extern PacketCache PC;
824 int number;
80d59cd1 825 if(req->parameters["domain"].empty())
e611a06c
BH
826 number = PC.purge();
827 else
80d59cd1 828 number = PC.purge(req->parameters["domain"]);
ac7ba905 829
e611a06c
BH
830 map<string, string> object;
831 object["number"]=lexical_cast<string>(number);
80d59cd1 832 //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
6ec5e728 833 resp->body = returnJsonObject(object);
80d59cd1 834 return;
e611a06c 835 }
2fe9c01c 836 else if(command == "pdns-control") {
02c04144 837 if(req->method!="POST")
33196945 838 throw HttpMethodNotAllowedException();
d267d1bf
BH
839 // cout<<"post: "<<post<<endl;
840 rapidjson::Document document;
6ec5e728 841 req->json(document);
d267d1bf
BH
842 // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
843 vector<string> parameters;
844 stringtok(parameters, document["parameters"].GetString(), " \t");
845
846 DynListener::g_funk_t* ptr=0;
847 if(!parameters.empty())
848 ptr = DynListener::getFunc(toUpper(parameters[0]));
849 map<string, string> m;
850
851 if(ptr) {
852 m["result"] = (*ptr)(parameters, 0);
853 } else {
80d59cd1 854 resp->status = 404;
d267d1bf
BH
855 m["error"]="No such function "+toUpper(parameters[0]);
856 }
6ec5e728 857 resp->body = returnJsonObject(m);
80d59cd1 858 return;
d267d1bf 859 }
2fe9c01c 860 else if(command=="log-grep") {
6ec5e728
CH
861 // legacy parameter name hack
862 req->parameters["q"] = req->parameters["needle"];
863 apiServerSearchLog(req, resp);
80d59cd1 864 return;
9ac4a7c6 865 }
ddc84d12 866
6ec5e728 867 resp->body = returnJsonError("No or unknown command given");
80d59cd1
CH
868 resp->status = 404;
869 return;
ddc84d12
CH
870}
871
dea47634 872void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 873{
80d59cd1
CH
874 resp->headers["Cache-Control"] = "max-age=86400";
875 resp->headers["Content-Type"] = "text/css";
c67bf8c5 876
1071abdd 877 ostringstream ret;
1071abdd
CH
878 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
879 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
880 ret<<"a { color: #0959c2; }"<<endl;
881 ret<<"a:hover { color: #3B8EC8; }"<<endl;
882 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
883 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
884 ret<<".row:after { clear: both; }"<<endl;
885 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
886 ret<<".all { width: 100%; }"<<endl;
887 ret<<".headl { width: 60%; }"<<endl;
888 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
889 ret<<"background-image: url(data:image/png;base64,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);";
890 ret<<" width: 154px; height: 20px; }"<<endl;
891 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
892 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
893 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
894 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
895 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
896 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
897 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
898 ret<<"table.data tr:hover { background: white; }"<<endl;
899 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
900 ret<<".resetring {float: right; }"<<endl;
901 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;
902 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
903 ret<<".resizering {float: right;}"<<endl;
80d59cd1 904 resp->body = ret.str();
1071abdd
CH
905}
906
dea47634 907void AuthWebServer::webThread()
12c86877
BH
908{
909 try {
c67bf8c5 910 if(::arg().mustDo("experimental-json-interface")) {
3ae143b0
CH
911 d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
912 d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
b1902fab 913 d_ws->registerApiHandler("/servers/localhost/search-data", &apiServerSearchData);
3ae143b0
CH
914 d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
915 d_ws->registerApiHandler("/servers/localhost/zones/<id>/rrset", &apiServerZoneRRset);
916 d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail);
917 d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones);
918 d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
919 d_ws->registerApiHandler("/servers", &apiServer);
c67bf8c5 920 // legacy dispatch
dea47634 921 d_ws->registerApiHandler("/jsonstat", boost::bind(&AuthWebServer::jsonstat, this, _1, _2));
c67bf8c5 922 }
dea47634
CH
923 d_ws->registerHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
924 d_ws->registerHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
96d299db 925 d_ws->go();
12c86877
BH
926 }
927 catch(...) {
dea47634 928 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
12c86877
BH
929 exit(1);
930 }
931}