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