]> git.ipfire.org Git - thirdparty/pdns.git/blame_incremental - pdns/ws-auth.cc
gsql: Remove stripDot where not needed
[thirdparty/pdns.git] / pdns / ws-auth.cc
... / ...
CommitLineData
1/*
2 Copyright (C) 2002 - 2016 PowerDNS.COM BV
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License version 2
6 as published by the Free Software Foundation
7
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
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
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20*/
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#endif
24#include "utility.hh"
25#include "dynlistener.hh"
26#include "ws-auth.hh"
27#include "json.hh"
28#include "webserver.hh"
29#include "logger.hh"
30#include "packetcache.hh"
31#include "statbag.hh"
32#include "misc.hh"
33#include "arguments.hh"
34#include "dns.hh"
35#include "comment.hh"
36#include "ueberbackend.hh"
37#include <boost/format.hpp>
38
39#include "namespaces.hh"
40#include "ws-api.hh"
41#include "version.hh"
42#include "dnsseckeeper.hh"
43#include <iomanip>
44#include "zoneparser-tng.hh"
45#include "common_startup.hh"
46
47
48using json11::Json;
49
50extern StatBag S;
51
52static void patchZone(HttpRequest* req, HttpResponse* resp);
53static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr);
54
55AuthWebServer::AuthWebServer()
56{
57 d_start=time(0);
58 d_min10=d_min5=d_min1=0;
59 d_ws = 0;
60 d_tid = 0;
61 if(arg().mustDo("webserver")) {
62 d_ws = new WebServer(arg()["webserver-address"], arg().asNum("webserver-port"));
63 d_ws->bind();
64 }
65}
66
67void AuthWebServer::go()
68{
69 if(arg().mustDo("webserver"))
70 {
71 S.doRings();
72 pthread_create(&d_tid, 0, webThreadHelper, this);
73 pthread_create(&d_tid, 0, statThreadHelper, this);
74 }
75}
76
77void AuthWebServer::statThread()
78{
79 try {
80 for(;;) {
81 d_queries.submit(S.read("udp-queries"));
82 d_cachehits.submit(S.read("packetcache-hit"));
83 d_cachemisses.submit(S.read("packetcache-miss"));
84 d_qcachehits.submit(S.read("query-cache-hit"));
85 d_qcachemisses.submit(S.read("query-cache-miss"));
86 Utility::sleep(1);
87 }
88 }
89 catch(...) {
90 L<<Logger::Error<<"Webserver statThread caught an exception, dying"<<endl;
91 exit(1);
92 }
93}
94
95void *AuthWebServer::statThreadHelper(void *p)
96{
97 AuthWebServer *self=static_cast<AuthWebServer *>(p);
98 self->statThread();
99 return 0; // never reached
100}
101
102void *AuthWebServer::webThreadHelper(void *p)
103{
104 AuthWebServer *self=static_cast<AuthWebServer *>(p);
105 self->webThread();
106 return 0; // never reached
107}
108
109static string htmlescape(const string &s) {
110 string result;
111 for(string::const_iterator it=s.begin(); it!=s.end(); ++it) {
112 switch (*it) {
113 case '&':
114 result += "&amp;";
115 break;
116 case '<':
117 result += "&lt;";
118 break;
119 case '>':
120 result += "&gt;";
121 break;
122 case '"':
123 result += "&quot;";
124 break;
125 default:
126 result += *it;
127 }
128 }
129 return result;
130}
131
132void printtable(ostringstream &ret, const string &ringname, const string &title, int limit=10)
133{
134 int tot=0;
135 int entries=0;
136 vector<pair <string,unsigned int> >ring=S.getRing(ringname);
137
138 for(vector<pair<string, unsigned int> >::const_iterator i=ring.begin(); i!=ring.end();++i) {
139 tot+=i->second;
140 entries++;
141 }
142
143 ret<<"<div class=\"panel\">";
144 ret<<"<span class=resetring><i></i><a href=\"?resetring="<<htmlescape(ringname)<<"\">Reset</a></span>"<<endl;
145 ret<<"<h2>"<<title<<"</h2>"<<endl;
146 ret<<"<div class=ringmeta>";
147 ret<<"<a class=topXofY href=\"?ring="<<htmlescape(ringname)<<"\">Showing: Top "<<limit<<" of "<<entries<<"</a>"<<endl;
148 ret<<"<span class=resizering>Resize: ";
149 unsigned int sizes[]={10,100,500,1000,10000,500000,0};
150 for(int i=0;sizes[i];++i) {
151 if(S.getRingSize(ringname)!=sizes[i])
152 ret<<"<a href=\"?resizering="<<htmlescape(ringname)<<"&amp;size="<<sizes[i]<<"\">"<<sizes[i]<<"</a> ";
153 else
154 ret<<"("<<sizes[i]<<") ";
155 }
156 ret<<"</span></div>";
157
158 ret<<"<table class=\"data\">";
159 int printed=0;
160 int total=max(1,tot);
161 for(vector<pair<string,unsigned int> >::const_iterator i=ring.begin();limit && i!=ring.end();++i,--limit) {
162 ret<<"<tr><td>"<<htmlescape(i->first)<<"</td><td>"<<i->second<<"</td><td align=right>"<< AuthWebServer::makePercentage(i->second*100.0/total)<<"</td>"<<endl;
163 printed+=i->second;
164 }
165 ret<<"<tr><td colspan=3></td></tr>"<<endl;
166 if(printed!=tot)
167 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;
168
169 ret<<"<tr><td><b>Total:</b></td><td><b>"<<tot<<"</b></td><td align=right><b>100%</b></td>";
170 ret<<"</table></div>"<<endl;
171}
172
173void AuthWebServer::printvars(ostringstream &ret)
174{
175 ret<<"<div class=panel><h2>Variables</h2><table class=\"data\">"<<endl;
176
177 vector<string>entries=S.getEntries();
178 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
179 ret<<"<tr><td>"<<*i<<"</td><td>"<<S.read(*i)<<"</td><td>"<<S.getDescrip(*i)<<"</td>"<<endl;
180 }
181
182 ret<<"</table></div>"<<endl;
183}
184
185void AuthWebServer::printargs(ostringstream &ret)
186{
187 ret<<"<table border=1><tr><td colspan=3 bgcolor=\"#0000ff\"><font color=\"#ffffff\">Arguments</font></td>"<<endl;
188
189 vector<string>entries=arg().list();
190 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i) {
191 ret<<"<tr><td>"<<*i<<"</td><td>"<<arg()[*i]<<"</td><td>"<<arg().getHelp(*i)<<"</td>"<<endl;
192 }
193}
194
195string AuthWebServer::makePercentage(const double& val)
196{
197 return (boost::format("%.01f%%") % val).str();
198}
199
200void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
201{
202 if(!req->getvars["resetring"].empty()) {
203 if (S.ringExists(req->getvars["resetring"]))
204 S.resetRing(req->getvars["resetring"]);
205 resp->status = 301;
206 resp->headers["Location"] = req->url.path;
207 return;
208 }
209 if(!req->getvars["resizering"].empty()){
210 int size=std::stoi(req->getvars["size"]);
211 if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000)
212 S.resizeRing(req->getvars["resizering"], std::stoi(req->getvars["size"]));
213 resp->status = 301;
214 resp->headers["Location"] = req->url.path;
215 return;
216 }
217
218 ostringstream ret;
219
220 ret<<"<!DOCTYPE html>"<<endl;
221 ret<<"<html><head>"<<endl;
222 ret<<"<title>PowerDNS Authoritative Server Monitor</title>"<<endl;
223 ret<<"<link rel=\"stylesheet\" href=\"style.css\"/>"<<endl;
224 ret<<"</head><body>"<<endl;
225
226 ret<<"<div class=\"row\">"<<endl;
227 ret<<"<div class=\"headl columns\">";
228 ret<<"<a href=\"/\" id=\"appname\">PowerDNS "<<htmlescape(VERSION);
229 if(!arg()["config-name"].empty()) {
230 ret<<" ["<<htmlescape(arg()["config-name"])<<"]";
231 }
232 ret<<"</a></div>"<<endl;
233 ret<<"<div class=\"headr columns\"></div></div>";
234 ret<<"<div class=\"row\"><div class=\"all columns\">";
235
236 time_t passed=time(0)-s_starttime;
237
238 ret<<"<p>Uptime: "<<
239 humanDuration(passed)<<
240 "<br>"<<endl;
241
242 ret<<"Queries/second, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
243 d_queries.get1()<<", "<<
244 d_queries.get5()<<", "<<
245 d_queries.get10()<<". Max queries/second: "<<d_queries.getMax()<<
246 "<br>"<<endl;
247
248 if(d_cachemisses.get10()+d_cachehits.get10()>0)
249 ret<<"Cache hitrate, 1, 5, 10 minute averages: "<<
250 makePercentage((d_cachehits.get1()*100.0)/((d_cachehits.get1())+(d_cachemisses.get1())))<<", "<<
251 makePercentage((d_cachehits.get5()*100.0)/((d_cachehits.get5())+(d_cachemisses.get5())))<<", "<<
252 makePercentage((d_cachehits.get10()*100.0)/((d_cachehits.get10())+(d_cachemisses.get10())))<<
253 "<br>"<<endl;
254
255 if(d_qcachemisses.get10()+d_qcachehits.get10()>0)
256 ret<<"Backend query cache hitrate, 1, 5, 10 minute averages: "<<std::setprecision(2)<<
257 makePercentage((d_qcachehits.get1()*100.0)/((d_qcachehits.get1())+(d_qcachemisses.get1())))<<", "<<
258 makePercentage((d_qcachehits.get5()*100.0)/((d_qcachehits.get5())+(d_qcachemisses.get5())))<<", "<<
259 makePercentage((d_qcachehits.get10()*100.0)/((d_qcachehits.get10())+(d_qcachemisses.get10())))<<
260 "<br>"<<endl;
261
262 ret<<"Backend query load, 1, 5, 10 minute averages: "<<std::setprecision(3)<<
263 d_qcachemisses.get1()<<", "<<
264 d_qcachemisses.get5()<<", "<<
265 d_qcachemisses.get10()<<". Max queries/second: "<<d_qcachemisses.getMax()<<
266 "<br>"<<endl;
267
268 ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
269 if(req->getvars["ring"].empty()) {
270 vector<string>entries=S.listRings();
271 for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i)
272 printtable(ret,*i,S.getRingTitle(*i));
273
274 printvars(ret);
275 if(arg().mustDo("webserver-print-arguments"))
276 printargs(ret);
277 }
278 else
279 printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100);
280
281 ret<<"</div></div>"<<endl;
282 ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 - 2016 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
283 ret<<"</body></html>"<<endl;
284
285 resp->body = ret.str();
286 resp->status = 200;
287}
288
289/** Helper to build a record content as needed. */
290static inline string makeRecordContent(const QType& qtype, const string& content, bool noDot) {
291 // noDot: for backend storage, pass true. for API users, pass false.
292 return DNSRecordContent::mastermake(qtype.getCode(), 1, content)->getZoneRepresentation(noDot);
293}
294
295/** "Normalize" record content for API consumers. */
296static inline string makeApiRecordContent(const QType& qtype, const string& content) {
297 return makeRecordContent(qtype, content, false);
298}
299
300/** "Normalize" record content for backend storage. */
301static inline string makeBackendRecordContent(const QType& qtype, const string& content) {
302 return makeRecordContent(qtype, content, true);
303}
304
305static Json::object getZoneInfo(const DomainInfo& di) {
306 DNSSECKeeper dk;
307 string zoneId = apiZoneNameToId(di.zone);
308 return Json::object {
309 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
310 { "id", zoneId },
311 { "url", "api/v1/servers/localhost/zones/" + zoneId },
312 { "name", di.zone.toString() },
313 { "kind", di.getKindString() },
314 { "dnssec", dk.isSecuredZone(di.zone) },
315 { "account", di.account },
316 { "masters", di.masters },
317 { "serial", (double)di.serial },
318 { "notified_serial", (double)di.notified_serial },
319 { "last_check", (double)di.last_check }
320 };
321}
322
323static void fillZone(const DNSName& zonename, HttpResponse* resp) {
324 UeberBackend B;
325 DomainInfo di;
326 if(!B.getDomainInfo(zonename, di))
327 throw ApiException("Could not find domain '"+zonename.toString()+"'");
328
329 Json::object doc = getZoneInfo(di);
330 // extra stuff getZoneInfo doesn't do for us (more expensive)
331 string soa_edit_api;
332 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api);
333 doc["soa_edit_api"] = soa_edit_api;
334 string soa_edit;
335 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit);
336 doc["soa_edit"] = soa_edit;
337
338 // fill records
339 DNSResourceRecord rr;
340 Json::array records;
341 di.backend->list(zonename, di.id, true); // incl. disabled
342 while(di.backend->get(rr)) {
343 if (!rr.qtype.getCode())
344 continue; // skip empty non-terminals
345
346 records.push_back(Json::object {
347 { "name", rr.qname.toString() },
348 { "type", rr.qtype.getName() },
349 { "ttl", (double)rr.ttl },
350 { "disabled", rr.disabled },
351 { "content", makeApiRecordContent(rr.qtype, rr.content) }
352 });
353 }
354 doc["records"] = records;
355
356 // fill comments
357 Comment comment;
358 Json::array comments;
359 di.backend->listComments(di.id);
360 while(di.backend->getComment(comment)) {
361 comments.push_back(Json::object {
362 { "name", comment.qname },
363 { "type", comment.qtype.getName() },
364 { "modified_at", (double)comment.modified_at },
365 { "account", comment.account },
366 { "content", comment.content }
367 });
368 }
369 doc["comments"] = comments;
370
371 resp->setBody(doc);
372}
373
374void productServerStatisticsFetch(map<string,string>& out)
375{
376 vector<string> items = S.getEntries();
377 for(const string& item : items) {
378 out[item] = std::to_string(S.read(item));
379 }
380
381 // add uptime
382 out["uptime"] = std::to_string(time(0) - s_starttime);
383}
384
385static void gatherRecords(const Json container, vector<DNSResourceRecord>& new_records, vector<DNSResourceRecord>& new_ptrs) {
386 UeberBackend B;
387 DNSResourceRecord rr;
388 for(auto record : container["records"].array_items()) {
389 rr.qname = apiNameToDNSName(stringFromJson(record, "name"));
390 rr.qtype = stringFromJson(record, "type");
391 string content = stringFromJson(record, "content");
392 rr.auth = 1;
393 rr.ttl = intFromJson(record, "ttl");
394 rr.disabled = boolFromJson(record, "disabled");
395
396 if (rr.qtype.getCode() == 0) {
397 throw ApiException("Record "+rr.qname.toString()+"/"+stringFromJson(record, "type")+" is of unknown type");
398 }
399
400 // validate that the client sent something we can actually parse, and require that data to be dotted.
401 try {
402 if (rr.qtype.getCode() != QType::AAAA) {
403 string tmp = makeApiRecordContent(rr.qtype, content);
404 if (!pdns_iequals(tmp, content)) {
405 throw std::runtime_error("Not in expected format (parsed as '"+tmp+"')");
406 }
407 } else {
408 struct in6_addr tmpbuf;
409 if (inet_pton(AF_INET6, content.c_str(), &tmpbuf) != 1 || content.find('.') != string::npos) {
410 throw std::runtime_error("Invalid IPv6 address");
411 }
412 }
413 rr.content = makeBackendRecordContent(rr.qtype, content);
414 }
415 catch(std::exception& e)
416 {
417 throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.getName()+" '"+content+"': "+e.what());
418 }
419
420 if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) &&
421 boolFromJson(record, "set-ptr", false) == true) {
422 DNSResourceRecord ptr;
423 makePtr(rr, &ptr);
424
425 // verify that there's a zone for the PTR
426 DNSPacket fakePacket;
427 SOAData sd;
428 fakePacket.qtype = QType::PTR;
429 if (!B.getAuth(&fakePacket, &sd, ptr.qname))
430 throw ApiException("Could not find domain for PTR '"+ptr.qname.toString()+"' requested for '"+ptr.content+"'");
431
432 ptr.domain_id = sd.domain_id;
433 new_ptrs.push_back(ptr);
434 }
435
436 new_records.push_back(rr);
437 }
438}
439
440static void gatherComments(const Json container, vector<Comment>& new_comments, bool use_name_type_from_container) {
441 Comment c;
442 if (use_name_type_from_container) {
443 c.qname = stringFromJson(container, "name");
444 c.qtype = stringFromJson(container, "type");
445 }
446
447 time_t now = time(0);
448 for (auto comment : container["comments"].array_items()) {
449 if (!use_name_type_from_container) {
450 c.qname = stringFromJson(comment, "name");
451 c.qtype = stringFromJson(comment, "type");
452 }
453 c.modified_at = intFromJson(comment, "modified_at", now);
454 c.content = stringFromJson(comment, "content");
455 c.account = stringFromJson(comment, "account");
456 new_comments.push_back(c);
457 }
458}
459
460static void updateDomainSettingsFromDocument(const DomainInfo& di, const DNSName& zonename, const Json document) {
461 string zonemaster;
462 for(auto value : document["masters"].array_items()) {
463 string master = value.string_value();
464 if (master.empty())
465 throw ApiException("Master can not be an empty string");
466 zonemaster += master + " ";
467 }
468
469 di.backend->setKind(zonename, DomainInfo::stringToKind(stringFromJson(document, "kind")));
470 di.backend->setMaster(zonename, zonemaster);
471
472 if (document["soa_edit_api"].is_string()) {
473 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT-API", document["soa_edit_api"].string_value());
474 }
475 if (document["soa_edit"].is_string()) {
476 di.backend->setDomainMetadataOne(zonename, "SOA-EDIT", document["soa_edit"].string_value());
477 }
478 if (document["account"].is_string()) {
479 di.backend->setAccount(zonename, document["account"].string_value());
480 }
481}
482
483static void apiZoneCryptokeys(HttpRequest* req, HttpResponse* resp) {
484 if(req->method != "GET")
485 throw ApiException("Only GET is implemented");
486
487 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
488
489 UeberBackend B;
490 DomainInfo di;
491 DNSSECKeeper dk;
492
493 if(!B.getDomainInfo(zonename, di))
494 throw ApiException("Could not find domain '"+zonename.toString()+"'");
495
496 DNSSECKeeper::keyset_t keyset=dk.getKeys(zonename, false);
497
498 if (keyset.empty())
499 throw ApiException("No keys for zone '"+zonename.toString()+"'");
500
501 Json::array doc;
502 for(const DNSSECKeeper::keyset_t::value_type value : keyset) {
503 if (req->parameters.count("key_id")) {
504 int keyid = std::stoi(req->parameters["key_id"]);
505 int curid = value.second.id;
506 if (keyid != curid)
507 continue;
508 }
509
510 string keyType;
511 switch(value.second.keyType){
512 case DNSSECKeeper::KSK: keyType="ksk"; break;
513 case DNSSECKeeper::ZSK: keyType="zsk"; break;
514 case DNSSECKeeper::CSK: keyType="csk"; break;
515 }
516
517 Json::object key {
518 { "type", "Cryptokey" },
519 { "id", (int)value.second.id },
520 { "active", value.second.active },
521 { "keytype", keyType },
522 { "flags", (uint16_t)value.first.d_flags },
523 { "dnskey", value.first.getDNSKEY().getZoneRepresentation() }
524 };
525
526 if (req->parameters.count("key_id")) {
527 DNSSECPrivateKey dpk=dk.getKeyById(zonename, std::stoi(req->parameters["key_id"]));
528 key["content"] = dpk.getKey()->convertToISC();
529 }
530
531 if (value.second.keyType == DNSSECKeeper::KSK || value.second.keyType == DNSSECKeeper::CSK) {
532 Json::array dses;
533 for(const int keyid : { 1, 2, 3, 4 })
534 try {
535 dses.push_back(makeDSFromDNSKey(zonename, value.first.getDNSKEY(), keyid).getZoneRepresentation());
536 } catch (...) {}
537 key["ds"] = dses;
538 }
539 doc.push_back(key);
540 }
541
542 resp->setBody(doc);
543}
544
545static void gatherRecordsFromZone(const std::string& zonestring, vector<DNSResourceRecord>& new_records, DNSName zonename) {
546 DNSResourceRecord rr;
547 vector<string> zonedata;
548 stringtok(zonedata, zonestring, "\r\n");
549
550 ZoneParserTNG zpt(zonedata, zonename);
551
552 bool seenSOA=false;
553
554 string comment = "Imported via the API";
555
556 try {
557 while(zpt.get(rr, &comment)) {
558 if(seenSOA && rr.qtype.getCode() == QType::SOA)
559 continue;
560 if(rr.qtype.getCode() == QType::SOA)
561 seenSOA=true;
562
563 new_records.push_back(rr);
564 }
565 }
566 catch(std::exception& ae) {
567 throw ApiException("An error occured while parsing the zonedata: "+string(ae.what()));
568 }
569}
570
571static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
572 UeberBackend B;
573 DNSSECKeeper dk;
574 if (req->method == "POST" && !::arg().mustDo("api-readonly")) {
575 DomainInfo di;
576 auto document = req->json();
577 DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
578 apiCheckNameAllowedCharacters(zonename.toString());
579
580 string zonestring = document["zone"].string_value();
581
582 bool exists = B.getDomainInfo(zonename, di);
583 if(exists)
584 throw ApiException("Domain '"+zonename.toString()+"' already exists");
585
586 // validate 'kind' is set
587 DomainInfo::DomainKind zonekind = DomainInfo::stringToKind(stringFromJson(document, "kind"));
588
589 auto records = document["records"];
590 if (records.is_array() && zonestring != "")
591 throw ApiException("You cannot give zonedata AND records");
592
593 auto nameservers = document["nameservers"];
594 if (!nameservers.is_array() && zonekind != DomainInfo::Slave)
595 throw ApiException("Nameservers list must be given (but can be empty if NS records are supplied)");
596
597 string soa_edit_api_kind;
598 if (document["soa_edit_api"].is_string()) {
599 soa_edit_api_kind = document["soa_edit_api"].string_value();
600 }
601 else {
602 soa_edit_api_kind = "DEFAULT";
603 }
604 string soa_edit_kind = document["soa_edit"].string_value();
605
606 // if records/comments are given, load and check them
607 bool have_soa = false;
608 vector<DNSResourceRecord> new_records;
609 vector<Comment> new_comments;
610 vector<DNSResourceRecord> new_ptrs;
611
612 if (records.is_array()) {
613 gatherRecords(document, new_records, new_ptrs);
614 } else if (zonestring != "") {
615 gatherRecordsFromZone(zonestring, new_records, zonename);
616 }
617
618 gatherComments(document, new_comments, false);
619
620 for(auto& rr : new_records) {
621 if (!rr.qname.isPartOf(zonename) && rr.qname != zonename)
622 throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": Name is out of zone");
623 apiCheckQNameAllowedCharacters(rr.qname.toString());
624
625 if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
626 have_soa = true;
627 increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
628 // fixup dots after serializeSOAData/increaseSOARecord
629 rr.content = makeBackendRecordContent(rr.qtype, rr.content);
630 }
631 }
632
633 // synthesize RRs as needed
634 DNSResourceRecord autorr;
635 autorr.qname = zonename;
636 autorr.auth = 1;
637 autorr.ttl = ::arg().asNum("default-ttl");
638
639 if (!have_soa && zonekind != DomainInfo::Slave) {
640 // synthesize a SOA record so the zone "really" exists
641 string soa = (boost::format("%s %s %lu")
642 % ::arg()["default-soa-name"]
643 % (::arg().isEmpty("default-soa-mail") ? (DNSName("hostmaster.") + zonename).toString() : ::arg()["default-soa-mail"])
644 % document["serial"].int_value()
645 ).str();
646 SOAData sd;
647 fillSOAData(soa, sd); // fills out default values for us
648 autorr.qtype = "SOA";
649 autorr.content = serializeSOAData(sd);
650 increaseSOARecord(autorr, soa_edit_api_kind, soa_edit_kind);
651 // fixup dots after serializeSOAData/increaseSOARecord
652 autorr.content = makeBackendRecordContent(autorr.qtype, autorr.content);
653 new_records.push_back(autorr);
654 }
655
656 // create NS records if nameservers are given
657 for (auto value : nameservers.array_items()) {
658 string nameserver = value.string_value();
659 if (nameserver.empty())
660 throw ApiException("Nameservers must be non-empty strings");
661 if (!isCanonical(nameserver))
662 throw ApiException("Nameserver is not canonical: '" + nameserver + "'");
663 try {
664 // ensure the name parses
665 autorr.content = DNSName(nameserver).toStringNoDot();
666 } catch (...) {
667 throw ApiException("Unable to parse DNS Name for NS '" + nameserver + "'");
668 }
669 autorr.qtype = "NS";
670 new_records.push_back(autorr);
671 }
672
673 // no going back after this
674 if(!B.createDomain(zonename))
675 throw ApiException("Creating domain '"+zonename.toString()+"' failed");
676
677 if(!B.getDomainInfo(zonename, di))
678 throw ApiException("Creating domain '"+zonename.toString()+"' failed: lookup of domain ID failed");
679
680 di.backend->startTransaction(zonename, di.id);
681
682 for(auto rr : new_records) {
683 rr.domain_id = di.id;
684 di.backend->feedRecord(rr);
685 }
686 for(Comment& c : new_comments) {
687 c.domain_id = di.id;
688 di.backend->feedComment(c);
689 }
690
691 updateDomainSettingsFromDocument(di, zonename, document);
692
693 di.backend->commitTransaction();
694
695 fillZone(zonename, resp);
696 resp->status = 201;
697 return;
698 }
699
700 if(req->method != "GET")
701 throw HttpMethodNotAllowedException();
702
703 vector<DomainInfo> domains;
704 B.getAllDomains(&domains, true); // incl. disabled
705
706 Json::array doc;
707 for(const DomainInfo& di : domains) {
708 doc.push_back(getZoneInfo(di));
709 }
710 resp->setBody(doc);
711}
712
713static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
714 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
715
716 if(req->method == "PUT" && !::arg().mustDo("api-readonly")) {
717 // update domain settings
718 UeberBackend B;
719 DomainInfo di;
720 if(!B.getDomainInfo(zonename, di))
721 throw ApiException("Could not find domain '"+zonename.toString()+"'");
722
723 updateDomainSettingsFromDocument(di, zonename, req->json());
724
725 fillZone(zonename, resp);
726 return;
727 }
728 else if(req->method == "DELETE" && !::arg().mustDo("api-readonly")) {
729 // delete domain
730 UeberBackend B;
731 DomainInfo di;
732 if(!B.getDomainInfo(zonename, di))
733 throw ApiException("Could not find domain '"+zonename.toString()+"'");
734
735 if(!di.backend->deleteDomain(zonename))
736 throw ApiException("Deleting domain '"+zonename.toString()+"' failed: backend delete failed/unsupported");
737
738 // empty body on success
739 resp->body = "";
740 resp->status = 204; // No Content: declare that the zone is gone now
741 return;
742 } else if (req->method == "PATCH" && !::arg().mustDo("api-readonly")) {
743 patchZone(req, resp);
744 return;
745 } else if (req->method == "GET") {
746 fillZone(zonename, resp);
747 return;
748 }
749
750 throw HttpMethodNotAllowedException();
751}
752
753static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
754 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
755
756 if(req->method != "GET")
757 throw HttpMethodNotAllowedException();
758
759 ostringstream ss;
760
761 UeberBackend B;
762 DomainInfo di;
763 if(!B.getDomainInfo(zonename, di))
764 throw ApiException("Could not find domain '"+zonename.toString()+"'");
765
766 DNSResourceRecord rr;
767 SOAData sd;
768 di.backend->list(zonename, di.id);
769 while(di.backend->get(rr)) {
770 if (!rr.qtype.getCode())
771 continue; // skip empty non-terminals
772
773 ss <<
774 rr.qname.toString() << "\t" <<
775 rr.ttl << "\t" <<
776 rr.qtype.getName() << "\t" <<
777 makeApiRecordContent(rr.qtype, rr.content) <<
778 endl;
779 }
780
781 if (req->accept_json) {
782 resp->setBody(Json::object { { "zone", ss.str() } });
783 } else {
784 resp->headers["Content-Type"] = "text/plain; charset=us-ascii";
785 resp->body = ss.str();
786 }
787}
788
789static void apiServerZoneAxfrRetrieve(HttpRequest* req, HttpResponse* resp) {
790 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
791
792 if(req->method != "PUT")
793 throw HttpMethodNotAllowedException();
794
795 UeberBackend B;
796 DomainInfo di;
797 if(!B.getDomainInfo(zonename, di))
798 throw ApiException("Could not find domain '"+zonename.toString()+"'");
799
800 if(di.masters.empty())
801 throw ApiException("Domain '"+zonename.toString()+"' is not a slave domain (or has no master defined)");
802
803 random_shuffle(di.masters.begin(), di.masters.end());
804 Communicator.addSuckRequest(zonename, di.masters.front());
805 resp->setSuccessResult("Added retrieval request for '"+zonename.toString()+"' from master "+di.masters.front());
806}
807
808static void apiServerZoneNotify(HttpRequest* req, HttpResponse* resp) {
809 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
810
811 if(req->method != "PUT")
812 throw HttpMethodNotAllowedException();
813
814 UeberBackend B;
815 DomainInfo di;
816 if(!B.getDomainInfo(zonename, di))
817 throw ApiException("Could not find domain '"+zonename.toString()+"'");
818
819 if(!Communicator.notifyDomain(zonename))
820 throw ApiException("Failed to add to the queue - see server log");
821
822 resp->setSuccessResult("Notification queued");
823}
824
825static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) {
826 if (rr.qtype.getCode() == QType::A) {
827 uint32_t ip;
828 if (!IpToU32(rr.content, &ip)) {
829 throw ApiException("PTR: Invalid IP address given");
830 }
831 ptr->qname = DNSName((boost::format("%u.%u.%u.%u.in-addr.arpa.")
832 % ((ip >> 24) & 0xff)
833 % ((ip >> 16) & 0xff)
834 % ((ip >> 8) & 0xff)
835 % ((ip ) & 0xff)
836 ).str());
837 } else if (rr.qtype.getCode() == QType::AAAA) {
838 ComboAddress ca(rr.content);
839 char buf[3];
840 ostringstream ss;
841 for (int octet = 0; octet < 16; ++octet) {
842 if (snprintf(buf, sizeof(buf), "%02x", ca.sin6.sin6_addr.s6_addr[octet]) != (sizeof(buf)-1)) {
843 // this should be impossible: no byte should give more than two digits in hex format
844 throw PDNSException("Formatting IPv6 address failed");
845 }
846 ss << buf[0] << '.' << buf[1] << '.';
847 }
848 string tmp = ss.str();
849 tmp.resize(tmp.size()-1); // remove last dot
850 // reverse and append arpa domain
851 ptr->qname = DNSName(string(tmp.rbegin(), tmp.rend())) + DNSName("ip6.arpa.");
852 } else {
853 throw ApiException("Unsupported PTR source '" + rr.qname.toString() + "' type '" + rr.qtype.getName() + "'");
854 }
855
856 ptr->qtype = "PTR";
857 ptr->ttl = rr.ttl;
858 ptr->disabled = rr.disabled;
859 ptr->content = rr.qname.toString();
860}
861
862static void patchZone(HttpRequest* req, HttpResponse* resp) {
863 UeberBackend B;
864 DomainInfo di;
865 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
866 if (!B.getDomainInfo(zonename, di))
867 throw ApiException("Could not find domain '"+zonename.toString()+"'");
868
869 vector<DNSResourceRecord> new_records;
870 vector<Comment> new_comments;
871 vector<DNSResourceRecord> new_ptrs;
872
873 Json document = req->json();
874
875 auto rrsets = document["rrsets"];
876 if (!rrsets.is_array())
877 throw ApiException("No rrsets given in update request");
878
879 di.backend->startTransaction(zonename);
880
881 try {
882 string soa_edit_api_kind;
883 string soa_edit_kind;
884 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT-API", soa_edit_api_kind);
885 di.backend->getDomainMetadataOne(zonename, "SOA-EDIT", soa_edit_kind);
886 bool soa_edit_done = false;
887
888 for (auto rrset : rrsets.array_items()) {
889 string changetype;
890 QType qtype;
891 DNSName qname = apiNameToDNSName(stringFromJson(rrset, "name"));
892 apiCheckQNameAllowedCharacters(qname.toString());
893 qtype = stringFromJson(rrset, "type");
894 changetype = toUpper(stringFromJson(rrset, "changetype"));
895
896 if (changetype == "DELETE") {
897 // delete all matching qname/qtype RRs (and, implictly comments).
898 if (!di.backend->replaceRRSet(di.id, qname, qtype, vector<DNSResourceRecord>())) {
899 throw ApiException("Hosting backend does not support editing records.");
900 }
901 }
902 else if (changetype == "REPLACE") {
903 // we only validate for REPLACE, as DELETE can be used to "fix" out of zone records.
904 if (!qname.isPartOf(zonename) && qname != zonename)
905 throw ApiException("RRset "+qname.toString()+" IN "+qtype.getName()+": Name is out of zone");
906
907 new_records.clear();
908 new_comments.clear();
909 // new_ptrs is merged
910 gatherRecords(rrset, new_records, new_ptrs);
911 gatherComments(rrset, new_comments, true);
912
913 for(DNSResourceRecord& rr : new_records) {
914 rr.domain_id = di.id;
915
916 if (rr.qname != qname || rr.qtype != qtype)
917 throw ApiException("Record "+rr.qname.toString()+"/"+rr.qtype.getName()+" "+rr.content+": Record wrongly bundled with RRset " + qname.toString() + "/" + qtype.getName());
918
919 if (rr.qtype.getCode() == QType::SOA && rr.qname==zonename) {
920 soa_edit_done = increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
921 rr.content = makeBackendRecordContent(rr.qtype, rr.content);
922 }
923 }
924
925 for(Comment& c : new_comments) {
926 c.domain_id = di.id;
927 }
928
929 bool replace_records = rrset["records"].is_array();
930 bool replace_comments = rrset["comments"].is_array();
931
932 if (!replace_records && !replace_comments) {
933 throw ApiException("No change for RRset " + qname.toString() + "/" + qtype.getName());
934 }
935
936 if (replace_records) {
937 if (!di.backend->replaceRRSet(di.id, qname, qtype, new_records)) {
938 throw ApiException("Hosting backend does not support editing records.");
939 }
940 }
941 if (replace_comments) {
942 if (!di.backend->replaceComments(di.id, qname, qtype, new_comments)) {
943 throw ApiException("Hosting backend does not support editing comments.");
944 }
945 }
946 }
947 else
948 throw ApiException("Changetype not understood");
949 }
950
951 // edit SOA (if needed)
952 if (!soa_edit_api_kind.empty() && !soa_edit_done) {
953 SOAData sd;
954 if (!B.getSOA(zonename, sd))
955 throw ApiException("No SOA found for domain '"+zonename.toString()+"'");
956
957 DNSResourceRecord rr;
958 rr.qname = zonename;
959 rr.content = serializeSOAData(sd);
960 rr.qtype = "SOA";
961 rr.domain_id = di.id;
962 rr.auth = 1;
963 rr.ttl = sd.ttl;
964 increaseSOARecord(rr, soa_edit_api_kind, soa_edit_kind);
965 // fixup dots after serializeSOAData/increaseSOARecord
966 rr.content = makeBackendRecordContent(rr.qtype, rr.content);
967
968 if (!di.backend->replaceRRSet(di.id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
969 throw ApiException("Hosting backend does not support editing records.");
970 }
971 }
972
973 } catch(...) {
974 di.backend->abortTransaction();
975 throw;
976 }
977 di.backend->commitTransaction();
978
979 extern PacketCache PC;
980 PC.purgeExact(zonename);
981
982 // now the PTRs
983 for(const DNSResourceRecord& rr : new_ptrs) {
984 DNSPacket fakePacket;
985 SOAData sd;
986 sd.db = (DNSBackend *)-1; // getAuth() cache bypass
987 fakePacket.qtype = QType::PTR;
988
989 if (!B.getAuth(&fakePacket, &sd, rr.qname))
990 throw ApiException("Could not find domain for PTR '"+rr.qname.toString()+"' requested for '"+rr.content+"' (while saving)");
991
992 sd.db->startTransaction(rr.qname);
993 if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector<DNSResourceRecord>(1, rr))) {
994 sd.db->abortTransaction();
995 throw ApiException("PTR-Hosting backend for "+rr.qname.toString()+"/"+rr.qtype.getName()+" does not support editing records.");
996 }
997 sd.db->commitTransaction();
998 PC.purgeExact(rr.qname);
999 }
1000
1001 // success
1002 fillZone(zonename, resp);
1003}
1004
1005static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
1006 if(req->method != "GET")
1007 throw HttpMethodNotAllowedException();
1008
1009 string q = req->getvars["q"];
1010 string sMax = req->getvars["max"];
1011 int maxEnts = 100;
1012 int ents = 0;
1013
1014 if (q.empty())
1015 throw ApiException("Query q can't be blank");
1016 if (sMax.empty() == false)
1017 maxEnts = std::stoi(sMax);
1018 if (maxEnts < 1)
1019 throw ApiException("Maximum entries must be larger than 0");
1020
1021 SimpleMatch sm(q,true);
1022 UeberBackend B;
1023 vector<DomainInfo> domains;
1024 vector<DNSResourceRecord> result_rr;
1025 vector<Comment> result_c;
1026 map<int,DomainInfo> zoneIdZone;
1027 map<int,DomainInfo>::iterator val;
1028 Json::array doc;
1029
1030 B.getAllDomains(&domains, true);
1031
1032 for(const DomainInfo di: domains)
1033 {
1034 if (ents < maxEnts && sm.match(di.zone)) {
1035 doc.push_back(Json::object {
1036 { "object_type", "zone" },
1037 { "zone_id", apiZoneNameToId(di.zone) },
1038 { "name", di.zone.toString() }
1039 });
1040 ents++;
1041 }
1042 zoneIdZone[di.id] = di; // populate cache
1043 }
1044
1045 if (B.searchRecords(q, maxEnts, result_rr))
1046 {
1047 for(const DNSResourceRecord& rr: result_rr)
1048 {
1049 auto object = Json::object {
1050 { "object_type", "record" },
1051 { "name", rr.qname.toString() },
1052 { "type", rr.qtype.getName() },
1053 { "ttl", (double)rr.ttl },
1054 { "disabled", rr.disabled },
1055 { "content", makeApiRecordContent(rr.qtype, rr.content) }
1056 };
1057 if ((val = zoneIdZone.find(rr.domain_id)) != zoneIdZone.end()) {
1058 object["zone_id"] = apiZoneNameToId(val->second.zone);
1059 object["zone"] = val->second.zone.toString();
1060 }
1061 doc.push_back(object);
1062 }
1063 }
1064
1065 if (B.searchComments(q, maxEnts, result_c))
1066 {
1067 for(const Comment &c: result_c)
1068 {
1069 auto object = Json::object {
1070 { "object_type", "comment" },
1071 { "name", c.qname },
1072 { "content", c.content }
1073 };
1074 if ((val = zoneIdZone.find(c.domain_id)) != zoneIdZone.end()) {
1075 object["zone_id"] = apiZoneNameToId(val->second.zone);
1076 object["zone"] = val->second.zone.toString();
1077 }
1078 doc.push_back(object);
1079 }
1080 }
1081
1082 resp->setBody(doc);
1083}
1084
1085void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
1086 if(req->method != "PUT")
1087 throw HttpMethodNotAllowedException();
1088
1089 DNSName canon = apiNameToDNSName(req->getvars["domain"]);
1090
1091 extern PacketCache PC;
1092 int count = PC.purgeExact(canon);
1093 resp->setBody(Json::object {
1094 { "count", count },
1095 { "result", "Flushed cache." }
1096 });
1097}
1098
1099void AuthWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
1100{
1101 resp->headers["Cache-Control"] = "max-age=86400";
1102 resp->headers["Content-Type"] = "text/css";
1103
1104 ostringstream ret;
1105 ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
1106 ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
1107 ret<<"a { color: #0959c2; }"<<endl;
1108 ret<<"a:hover { color: #3B8EC8; }"<<endl;
1109 ret<<".row { width: 940px; max-width: 100%; min-width: 768px; margin: 0 auto; }"<<endl;
1110 ret<<".row:before, .row:after { display: table; content:\" \"; }"<<endl;
1111 ret<<".row:after { clear: both; }"<<endl;
1112 ret<<".columns { position: relative; min-height: 1px; float: left; }"<<endl;
1113 ret<<".all { width: 100%; }"<<endl;
1114 ret<<".headl { width: 60%; }"<<endl;
1115 ret<<".headr { width: 39.5%; float: right; background-repeat: no-repeat; margin-top: 7px; ";
1116 ret<<"background-image: url(data:image/png;base64,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);";
1117 ret<<" width: 154px; height: 20px; }"<<endl;
1118 ret<<"a#appname { margin: 0; font-size: 27px; color: #666; text-decoration: none; font-weight: bold; display: block; }"<<endl;
1119 ret<<"footer { border-top: 1px solid #ddd; padding-top: 4px; font-size: 12px; }"<<endl;
1120 ret<<"footer.row { margin-top: 1em; margin-bottom: 1em; }"<<endl;
1121 ret<<".panel { background: #f2f2f2; border: 1px solid #e6e6e6; margin: 0 0 22px 0; padding: 20px; }"<<endl;
1122 ret<<"table.data { width: 100%; border-spacing: 0; border-top: 1px solid #333; }"<<endl;
1123 ret<<"table.data td { border-bottom: 1px solid #333; padding: 2px; }"<<endl;
1124 ret<<"table.data tr:nth-child(2n) { background: #e2e2e2; }"<<endl;
1125 ret<<"table.data tr:hover { background: white; }"<<endl;
1126 ret<<".ringmeta { margin-bottom: 5px; }"<<endl;
1127 ret<<".resetring {float: right; }"<<endl;
1128 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;
1129 ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
1130 ret<<".resizering {float: right;}"<<endl;
1131 resp->body = ret.str();
1132 resp->status = 200;
1133}
1134
1135void AuthWebServer::webThread()
1136{
1137 try {
1138 if(::arg().mustDo("api")) {
1139 d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush);
1140 d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig);
1141 d_ws->registerApiHandler("/api/v1/servers/localhost/search-log", &apiServerSearchLog);
1142 d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServerSearchData);
1143 d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics);
1144 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/axfr-retrieve", &apiServerZoneAxfrRetrieve);
1145 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys/<key_id>", &apiZoneCryptokeys);
1146 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/cryptokeys", &apiZoneCryptokeys);
1147 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/export", &apiServerZoneExport);
1148 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>/notify", &apiServerZoneNotify);
1149 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServerZoneDetail);
1150 d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZones);
1151 d_ws->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail);
1152 d_ws->registerApiHandler("/api/v1/servers", &apiServer);
1153 }
1154 d_ws->registerWebHandler("/style.css", boost::bind(&AuthWebServer::cssfunction, this, _1, _2));
1155 d_ws->registerWebHandler("/", boost::bind(&AuthWebServer::indexfunction, this, _1, _2));
1156 d_ws->go();
1157 }
1158 catch(...) {
1159 L<<Logger::Error<<"AuthWebServer thread caught an exception, dying"<<endl;
1160 exit(1);
1161 }
1162}