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