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