]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
Merge pull request #1325 from mind04/query
[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);
5fb3aa58 553 char buf[3];
d1587ceb 554 ostringstream ss;
5fb3aa58
CH
555 for (int octet = 0; octet < 16; ++octet) {
556 if (snprintf(buf, sizeof(buf), "%02x", ca.sin6.sin6_addr.s6_addr[octet]) != (sizeof(buf)-1)) {
557 // this should be impossible: no byte should give more than two digits in hex format
558 throw PDNSException("Formatting IPv6 address failed");
559 }
560 ss << buf[0] << '.' << buf[1] << '.';
d1587ceb 561 }
5fb3aa58
CH
562 string tmp = ss.str();
563 tmp.resize(tmp.size()-1); // remove last dot
564 // reverse and append arpa domain
565 ptr->qname = string(tmp.rbegin(), tmp.rend()) + ".ip6.arpa";
d1587ceb
CH
566 } else {
567 throw ApiException("Unsupported PTR source '" + rr.qname + "' type '" + rr.qtype.getName() + "'");
568 }
569
570 ptr->qtype = "PTR";
571 ptr->ttl = rr.ttl;
572 ptr->disabled = rr.disabled;
573 ptr->priority = 0;
574 ptr->content = rr.qname;
575}
576
b3905a3d 577static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) {
18179c61 578 if(req->method != "PATCH" || ::arg().mustDo("experimental-api-readonly"))
b3905a3d
CH
579 throw HttpMethodNotAllowedException();
580
581 UeberBackend B;
582 DomainInfo di;
3c3c006b 583 string zonename = apiZoneIdToName(req->path_parameters["id"]);
b3905a3d
CH
584 if(!B.getDomainInfo(zonename, di))
585 throw ApiException("Could not find domain '"+zonename+"'");
586
b3905a3d 587 Document document;
6ec5e728 588 req->json(document);
b3905a3d
CH
589
590 string qname, changetype;
591 QType qtype;
592 qname = stringFromJson(document, "name");
593 qtype = stringFromJson(document, "type");
594 changetype = toUpper(stringFromJson(document, "changetype"));
595
35f26cc5
CH
596 string dotsuffix = "." + zonename;
597 if(!iends_with(qname, dotsuffix) && qname != zonename)
598 throw ApiException("RRset "+qname+" IN "+qtype.getName()+": Name is out of zone");
599
b3905a3d 600 if (changetype == "DELETE") {
6cc98ddf
CH
601 // delete all matching qname/qtype RRs (and, implictly comments).
602 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
603 throw ApiException("Hosting backend does not support editing records.");
604 }
b3905a3d
CH
605 }
606 else if (changetype == "REPLACE") {
6cc98ddf
CH
607 vector<DNSResourceRecord> new_records;
608 vector<Comment> new_comments;
d1587ceb 609 vector<DNSResourceRecord> new_ptrs;
6cc98ddf
CH
610 bool replace_records = false;
611 bool replace_comments = false;
612
613 // gather records
b3905a3d 614 DNSResourceRecord rr;
b3905a3d 615 const Value& records = document["records"];
6cc98ddf
CH
616 if (records.IsArray()) {
617 replace_records = true;
618 for(SizeType idx = 0; idx < records.Size(); ++idx) {
619 const Value& record = records[idx];
620 rr.qname = stringFromJson(record, "name");
621 rr.content = stringFromJson(record, "content");
622 rr.qtype = stringFromJson(record, "type");
623 rr.domain_id = di.id;
624 rr.auth = 1;
625 rr.ttl = intFromJson(record, "ttl");
626 rr.priority = intFromJson(record, "priority");
627 rr.disabled = boolFromJson(record, "disabled");
628
629 if(rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV)
630 rr.content = lexical_cast<string>(rr.priority)+" "+rr.content;
631
632 if(rr.qname != qname || rr.qtype != qtype)
633 throw ApiException("Record "+rr.qname+" IN "+rr.qtype.getName()+" "+rr.content+": Record bundled with wrong RRset");
634
635 try {
636 shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, rr.content));
637 string tmp = drc->serialize(rr.qname);
638 }
639 catch(std::exception& e)
640 {
641 throw ApiException("Record "+rr.qname+" IN "+rr.qtype.getName()+" "+rr.content+": "+e.what());
642 }
643
d1587ceb
CH
644 if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
645 boolFromJson(record, "set-ptr", false) == true) {
646 DNSResourceRecord ptr;
647 makePtr(rr, &ptr);
648
649 // verify that there's a zone for the PTR
650 DNSPacket fakePacket;
651 SOAData sd;
652 fakePacket.qtype = QType::PTR;
653 if (!B.getAuth(&fakePacket, &sd, ptr.qname, 0))
654 throw ApiException("Could not find domain for PTR '"+ptr.qname+"' requested for '"+ptr.content+"'");
655
656 ptr.domain_id = sd.domain_id;
657 new_ptrs.push_back(ptr);
658 }
659
6cc98ddf
CH
660 new_records.push_back(rr);
661 }
662 }
b3905a3d 663
6cc98ddf
CH
664 // gather comments
665 Comment c;
666 c.domain_id = di.id;
667 c.qname = qname;
668 c.qtype = qtype;
669 time_t now = time(0);
670 const Value& comments = document["comments"];
671 if (comments.IsArray()) {
672 replace_comments = true;
673 for(SizeType idx = 0; idx < comments.Size(); ++idx) {
674 const Value& comment = comments[idx];
675 c.modified_at = intFromJson(comment, "modified_at", now);
676 c.content = stringFromJson(comment, "content");
677 c.account = stringFromJson(comment, "account");
678 new_comments.push_back(c);
679 }
680 }
b3905a3d 681
6cc98ddf
CH
682 if (!replace_records && !replace_comments) {
683 throw ApiException("No change");
684 }
35f26cc5 685
6cc98ddf
CH
686 // Actually store the change(s).
687 di.backend->startTransaction(qname);
688 if (replace_records) {
689 if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
690 throw ApiException("Hosting backend does not support editing records.");
b3905a3d 691 }
6cc98ddf
CH
692 }
693 if (replace_comments) {
694 if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
695 throw ApiException("Hosting backend does not support editing comments.");
b3905a3d
CH
696 }
697 }
cea26350 698 di.backend->commitTransaction();
d1587ceb
CH
699
700 // now the PTRs
701 BOOST_FOREACH(const DNSResourceRecord& rr, new_ptrs) {
702 DNSPacket fakePacket;
703 SOAData sd;
704 sd.db = (DNSBackend *)-1;
705 fakePacket.qtype = QType::PTR;
706
707 if (!B.getAuth(&fakePacket, &sd, rr.qname, 0))
708 throw ApiException("Could not find domain for PTR '"+rr.qname+"' requested for '"+rr.content+"' (while saving)");
709
710 sd.db->startTransaction(rr.qname);
711 if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
712 throw ApiException("PTR-Hosting backend does not support editing records.");
713 }
714 sd.db->commitTransaction();
715 }
716
b3905a3d
CH
717 }
718 else
719 throw ApiException("Changetype not understood");
720
721 extern PacketCache PC;
722 PC.purge(qname);
723
724 // success
725 resp->body = "{}";
726}
727
dea47634 728void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
80d59cd1
CH
729{
730 string command;
731
732 if(req->parameters.count("command")) {
733 command = req->parameters["command"];
734 req->parameters.erase("command");
735 }
736
a45303b8 737 if(command == "flush-cache") {
e611a06c
BH
738 extern PacketCache PC;
739 int number;
80d59cd1 740 if(req->parameters["domain"].empty())
e611a06c
BH
741 number = PC.purge();
742 else
80d59cd1 743 number = PC.purge(req->parameters["domain"]);
ac7ba905 744
e611a06c
BH
745 map<string, string> object;
746 object["number"]=lexical_cast<string>(number);
80d59cd1 747 //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
6ec5e728 748 resp->body = returnJsonObject(object);
80d59cd1 749 return;
e611a06c 750 }
2fe9c01c 751 else if(command == "pdns-control") {
02c04144 752 if(req->method!="POST")
33196945 753 throw HttpMethodNotAllowedException();
d267d1bf
BH
754 // cout<<"post: "<<post<<endl;
755 rapidjson::Document document;
6ec5e728 756 req->json(document);
d267d1bf
BH
757 // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
758 vector<string> parameters;
759 stringtok(parameters, document["parameters"].GetString(), " \t");
760
761 DynListener::g_funk_t* ptr=0;
762 if(!parameters.empty())
763 ptr = DynListener::getFunc(toUpper(parameters[0]));
764 map<string, string> m;
765
766 if(ptr) {
767 m["result"] = (*ptr)(parameters, 0);
768 } else {
80d59cd1 769 resp->status = 404;
d267d1bf
BH
770 m["error"]="No such function "+toUpper(parameters[0]);
771 }
6ec5e728 772 resp->body = returnJsonObject(m);
80d59cd1 773 return;
d267d1bf 774 }
2fe9c01c 775 else if(command=="log-grep") {
6ec5e728
CH
776 // legacy parameter name hack
777 req->parameters["q"] = req->parameters["needle"];
778 apiServerSearchLog(req, resp);
80d59cd1 779 return;
9ac4a7c6 780 }
ddc84d12 781
6ec5e728 782 resp->body = returnJsonError("No or unknown command given");
80d59cd1
CH
783 resp->status = 404;
784 return;
ddc84d12
CH
785}
786
dea47634 787void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 788{
80d59cd1
CH
789 resp->headers["Cache-Control"] = "max-age=86400";
790 resp->headers["Content-Type"] = "text/css";
c67bf8c5 791
1071abdd 792 ostringstream ret;
1071abdd
CH
793 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
794 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
795 ret<<"a { color: #0959c2; }"<<endl;
796 ret<<"a:hover { color: #3B8EC8; }"<<endl;
797 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
798 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
799 ret<<".row:after { clear: both; }"<<endl;
800 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
801 ret<<".all { width: 100%; }"<<endl;
802 ret<<".headl { width: 60%; }"<<endl;
803 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
804 ret<<"background-image: url(data:image/png;base64,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);";
805 ret<<" width: 154px; height: 20px; }"<<endl;
806 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
807 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
808 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
809 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
810 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
811 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
812 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
813 ret<<"table.data tr:hover { background: white; }"<<endl;
814 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
815 ret<<".resetring {float: right; }"<<endl;
816 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;
817 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
818 ret<<".resizering {float: right;}"<<endl;
80d59cd1 819 resp->body = ret.str();
1071abdd
CH
820}
821
dea47634 822void AuthWebServer::webThread()
12c86877
BH
823{
824 try {
c67bf8c5 825 if(::arg().mustDo("experimental-json-interface")) {
3ae143b0
CH
826 d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
827 d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
828 d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
829 d_ws->registerApiHandler("/servers/localhost/zones/<id>/rrset", &apiServerZoneRRset);
830 d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail);
831 d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones);
832 d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
833 d_ws->registerApiHandler("/servers", &apiServer);
c67bf8c5 834 // legacy dispatch
dea47634 835 d_ws->registerApiHandler("/jsonstat", boost::bind(&AuthWebServer::jsonstat, this, _1, _2));
c67bf8c5 836 }
dea47634
CH
837 d_ws->registerHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
838 d_ws->registerHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
96d299db 839 d_ws->go();
12c86877
BH
840 }
841 catch(...) {
dea47634 842 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
12c86877
BH
843 exit(1);
844 }
845}