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