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