]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-auth.cc
regression-tests.api: allow extra args in create_zone()
[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
d708640f 371static void patchZone(HttpRequest* req, HttpResponse* resp);
6cc98ddf 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")) {
d708640f 531 patchZone(req, resp);
6cc98ddf
CH
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
d708640f 579static void patchZone(HttpRequest* req, HttpResponse* resp) {
b3905a3d
CH
580 UeberBackend B;
581 DomainInfo di;
3c3c006b 582 string zonename = apiZoneIdToName(req->path_parameters["id"]);
d708640f 583 if (!B.getDomainInfo(zonename, di))
b3905a3d
CH
584 throw ApiException("Could not find domain '"+zonename+"'");
585
d708640f
CH
586 string dotsuffix = "." + zonename;
587 vector<DNSResourceRecord> new_ptrs;
588
b3905a3d 589 Document document;
6ec5e728 590 req->json(document);
b3905a3d 591
d708640f
CH
592 const Value& rrsets = document["rrsets"];
593 if (!rrsets.IsArray())
594 throw ApiException("No rrsets given in update request");
b3905a3d 595
d708640f 596 di.backend->startTransaction(zonename);
6cc98ddf 597
d708640f
CH
598 try {
599 for(SizeType rrsetIdx = 0; rrsetIdx < rrsets.Size(); ++rrsetIdx) {
600 const Value& rrset = rrsets[rrsetIdx];
601 string qname, changetype;
602 QType qtype;
603 qname = stringFromJson(rrset, "name");
604 qtype = stringFromJson(rrset, "type");
605 changetype = toUpper(stringFromJson(rrset, "changetype"));
606
607 if (!iends_with(qname, dotsuffix) && qname != zonename)
608 throw ApiException("RRset "+qname+" IN "+qtype.getName()+": Name is out of zone");
609
610 if (changetype == "DELETE") {
611 // delete all matching qname/qtype RRs (and, implictly comments).
612 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
613 throw ApiException("Hosting backend does not support editing records.");
6cc98ddf 614 }
d708640f
CH
615 }
616 else if (changetype == "REPLACE") {
617 vector<DNSResourceRecord> new_records;
618 vector<Comment> new_comments;
619 bool replace_records = false;
620 bool replace_comments = false;
621
622 // gather records
623 DNSResourceRecord rr;
624 const Value& records = rrset["records"];
625 if (records.IsArray()) {
626 replace_records = true;
627 for (SizeType idx = 0; idx < records.Size(); ++idx) {
628 const Value& record = records[idx];
629 rr.qname = stringFromJson(record, "name");
630 rr.content = stringFromJson(record, "content");
631 rr.qtype = stringFromJson(record, "type");
632 rr.domain_id = di.id;
633 rr.auth = 1;
634 rr.ttl = intFromJson(record, "ttl");
635 rr.priority = intFromJson(record, "priority");
636 rr.disabled = boolFromJson(record, "disabled");
637
638 if (rr.qname != qname || rr.qtype != qtype)
639 throw ApiException("Record "+rr.qname+"/"+rr.qtype.getName()+" "+rr.content+": Record wrongly bundled with RRset " + qname + "/" + qtype.getName());
640
641 string temp_content = rr.content;
642 if (rr.qtype.getCode() == QType::MX || rr.qtype.getCode() == QType::SRV)
643 temp_content = lexical_cast<string>(rr.priority)+" "+rr.content;
644
645 try {
646 shared_ptr<DNSRecordContent> drc(DNSRecordContent::mastermake(rr.qtype.getCode(), 1, temp_content));
647 string tmp = drc->serialize(rr.qname);
648 }
649 catch(std::exception& e)
650 {
651 throw ApiException("Record "+rr.qname+"/"+rr.qtype.getName()+" "+rr.content+": "+e.what());
652 }
653
654 if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
655 boolFromJson(record, "set-ptr", false) == true) {
656 DNSResourceRecord ptr;
657 makePtr(rr, &ptr);
658
659 // verify that there's a zone for the PTR
660 DNSPacket fakePacket;
661 SOAData sd;
662 fakePacket.qtype = QType::PTR;
663 if (!B.getAuth(&fakePacket, &sd, ptr.qname, 0))
664 throw ApiException("Could not find domain for PTR '"+ptr.qname+"' requested for '"+ptr.content+"'");
665
666 ptr.domain_id = sd.domain_id;
667 new_ptrs.push_back(ptr);
668 }
669
670 new_records.push_back(rr);
671 }
6cc98ddf
CH
672 }
673
d708640f
CH
674 // gather comments
675 Comment c;
676 c.domain_id = di.id;
677 c.qname = qname;
678 c.qtype = qtype;
679 time_t now = time(0);
680 const Value& comments = rrset["comments"];
681 if (comments.IsArray()) {
682 replace_comments = true;
683 for(SizeType idx = 0; idx < comments.Size(); ++idx) {
684 const Value& comment = comments[idx];
685 c.modified_at = intFromJson(comment, "modified_at", now);
686 c.content = stringFromJson(comment, "content");
687 c.account = stringFromJson(comment, "account");
688 new_comments.push_back(c);
689 }
d1587ceb
CH
690 }
691
d708640f
CH
692 if (!replace_records && !replace_comments) {
693 throw ApiException("No change for RRset " + qname + "/" + qtype.getName());
694 }
b3905a3d 695
d708640f
CH
696 if (replace_records) {
697 if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
698 throw ApiException("Hosting backend does not support editing records.");
699 }
700 }
701 if (replace_comments) {
702 if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
703 throw ApiException("Hosting backend does not support editing comments.");
704 }
705 }
6cc98ddf 706 }
d708640f
CH
707 else
708 throw ApiException("Changetype not understood");
6cc98ddf 709 }
d708640f
CH
710 } catch(...) {
711 di.backend->abortTransaction();
712 throw;
713 }
714 di.backend->commitTransaction();
b3905a3d 715
d708640f
CH
716 extern PacketCache PC;
717 PC.purge(zonename);
d1587ceb 718
d708640f
CH
719 // now the PTRs
720 BOOST_FOREACH(const DNSResourceRecord& rr, new_ptrs) {
721 DNSPacket fakePacket;
722 SOAData sd;
723 sd.db = (DNSBackend *)-1;
724 fakePacket.qtype = QType::PTR;
d1587ceb 725
d708640f
CH
726 if (!B.getAuth(&fakePacket, &sd, rr.qname, 0))
727 throw ApiException("Could not find domain for PTR '"+rr.qname+"' requested for '"+rr.content+"' (while saving)");
d1587ceb 728
d708640f
CH
729 sd.db->startTransaction(rr.qname);
730 if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
731 throw ApiException("PTR-Hosting backend for "+rr.qname+"/"+rr.qtype.getName()+" does not support editing records.");
d1587ceb 732 }
d708640f
CH
733 sd.db->commitTransaction();
734 PC.purge(rr.qname);
b3905a3d 735 }
b3905a3d
CH
736
737 // success
d708640f 738 fillZone(zonename, resp);
b3905a3d
CH
739}
740
b1902fab
CH
741static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
742 if(req->method != "GET")
743 throw HttpMethodNotAllowedException();
744
745 string q = req->parameters["q"];
746 if (q.empty())
747 throw ApiException("Query q can't be blank");
748
749 UeberBackend B;
750
751 vector<DomainInfo> domains;
752 B.getAllDomains(&domains, true); // incl. disabled
753
754 Document doc;
755 doc.SetArray();
756
757 DNSResourceRecord rr;
758 Comment comment;
759
760 BOOST_FOREACH(const DomainInfo& di, domains) {
d2d194a9
CH
761 string zoneId = apiZoneNameToId(di.zone);
762
57cb86d8 763 if (pdns_ci_find(di.zone, q) != string::npos) {
b1902fab
CH
764 Value object;
765 object.SetObject();
766 object.AddMember("type", "zone", doc.GetAllocator());
d2d194a9
CH
767 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
768 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
769 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
770 object.AddMember("name", jzoneName, doc.GetAllocator());
b1902fab
CH
771 doc.PushBack(object, doc.GetAllocator());
772 }
773
774 // if zone name is an exact match, don't bother with returning all records/comments in it
775 if (di.zone == q) {
776 continue;
777 }
778
779 di.backend->list(di.zone, di.id, true); // incl. disabled
780 while(di.backend->get(rr)) {
781 if (!rr.qtype.getCode())
782 continue; // skip empty non-terminals
783
57cb86d8 784 if (pdns_ci_find(rr.qname, q) == string::npos && pdns_ci_find(rr.content, q) == string::npos)
b1902fab
CH
785 continue;
786
787 Value object;
788 object.SetObject();
789 object.AddMember("type", "record", doc.GetAllocator());
d2d194a9
CH
790 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
791 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
792 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
793 object.AddMember("zone_name", jzoneName, doc.GetAllocator());
b1902fab
CH
794 Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy
795 object.AddMember("name", jname, doc.GetAllocator());
796 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
797 object.AddMember("content", jcontent, doc.GetAllocator());
798 doc.PushBack(object, doc.GetAllocator());
799 }
800
801 di.backend->listComments(di.id);
802 while(di.backend->getComment(comment)) {
57cb86d8 803 if (pdns_ci_find(comment.qname, q) == string::npos && pdns_ci_find(comment.content, q) == string::npos)
b1902fab
CH
804 continue;
805
806 Value object;
807 object.SetObject();
808 object.AddMember("type", "comment", doc.GetAllocator());
d2d194a9
CH
809 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
810 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
811 Value jzoneName(di.zone.c_str(), doc.GetAllocator()); // copy
812 object.AddMember("zone_name", jzoneName, doc.GetAllocator());
b1902fab
CH
813 Value jname(comment.qname.c_str(), doc.GetAllocator()); // copy
814 object.AddMember("name", jname, doc.GetAllocator());
815 Value jcontent(comment.content.c_str(), doc.GetAllocator()); // copy
816 object.AddMember("content", jcontent, doc.GetAllocator());
817 doc.PushBack(object, doc.GetAllocator());
818 }
819 }
820 resp->setBody(doc);
821}
822
dea47634 823void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
80d59cd1
CH
824{
825 string command;
826
827 if(req->parameters.count("command")) {
828 command = req->parameters["command"];
829 req->parameters.erase("command");
830 }
831
a45303b8 832 if(command == "flush-cache") {
e611a06c
BH
833 extern PacketCache PC;
834 int number;
80d59cd1 835 if(req->parameters["domain"].empty())
e611a06c
BH
836 number = PC.purge();
837 else
80d59cd1 838 number = PC.purge(req->parameters["domain"]);
ac7ba905 839
e611a06c
BH
840 map<string, string> object;
841 object["number"]=lexical_cast<string>(number);
80d59cd1 842 //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
6ec5e728 843 resp->body = returnJsonObject(object);
80d59cd1 844 return;
e611a06c 845 }
2fe9c01c 846 else if(command == "pdns-control") {
02c04144 847 if(req->method!="POST")
33196945 848 throw HttpMethodNotAllowedException();
d267d1bf
BH
849 // cout<<"post: "<<post<<endl;
850 rapidjson::Document document;
6ec5e728 851 req->json(document);
d267d1bf
BH
852 // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
853 vector<string> parameters;
854 stringtok(parameters, document["parameters"].GetString(), " \t");
855
856 DynListener::g_funk_t* ptr=0;
857 if(!parameters.empty())
858 ptr = DynListener::getFunc(toUpper(parameters[0]));
859 map<string, string> m;
860
861 if(ptr) {
862 m["result"] = (*ptr)(parameters, 0);
863 } else {
80d59cd1 864 resp->status = 404;
d267d1bf
BH
865 m["error"]="No such function "+toUpper(parameters[0]);
866 }
6ec5e728 867 resp->body = returnJsonObject(m);
80d59cd1 868 return;
d267d1bf 869 }
2fe9c01c 870 else if(command=="log-grep") {
6ec5e728
CH
871 // legacy parameter name hack
872 req->parameters["q"] = req->parameters["needle"];
873 apiServerSearchLog(req, resp);
80d59cd1 874 return;
9ac4a7c6 875 }
ddc84d12 876
6ec5e728 877 resp->body = returnJsonError("No or unknown command given");
80d59cd1
CH
878 resp->status = 404;
879 return;
ddc84d12
CH
880}
881
dea47634 882void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
c67bf8c5 883{
80d59cd1
CH
884 resp->headers["Cache-Control"] = "max-age=86400";
885 resp->headers["Content-Type"] = "text/css";
c67bf8c5 886
1071abdd 887 ostringstream ret;
1071abdd
CH
888 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
889 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
890 ret<<"a { color: #0959c2; }"<<endl;
891 ret<<"a:hover { color: #3B8EC8; }"<<endl;
892 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
893 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
894 ret<<".row:after { clear: both; }"<<endl;
895 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
896 ret<<".all { width: 100%; }"<<endl;
897 ret<<".headl { width: 60%; }"<<endl;
898 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
899 ret<<"background-image: url(data:image/png;base64,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);";
900 ret<<" width: 154px; height: 20px; }"<<endl;
901 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
902 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
903 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
904 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
905 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
906 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
907 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
908 ret<<"table.data tr:hover { background: white; }"<<endl;
909 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
910 ret<<".resetring {float: right; }"<<endl;
911 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;
912 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
913 ret<<".resizering {float: right;}"<<endl;
80d59cd1 914 resp->body = ret.str();
1071abdd
CH
915}
916
dea47634 917void AuthWebServer::webThread()
12c86877
BH
918{
919 try {
c67bf8c5 920 if(::arg().mustDo("experimental-json-interface")) {
3ae143b0
CH
921 d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
922 d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
b1902fab 923 d_ws->registerApiHandler("/servers/localhost/search-data", &apiServerSearchData);
3ae143b0 924 d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
3ae143b0
CH
925 d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail);
926 d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones);
927 d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
928 d_ws->registerApiHandler("/servers", &apiServer);
c67bf8c5 929 // legacy dispatch
dea47634 930 d_ws->registerApiHandler("/jsonstat", boost::bind(&AuthWebServer::jsonstat, this, _1, _2));
c67bf8c5 931 }
dea47634
CH
932 d_ws->registerHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
933 d_ws->registerHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
96d299db 934 d_ws->go();
12c86877
BH
935 }
936 catch(...) {
dea47634 937 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
12c86877
BH
938 exit(1);
939 }
940}