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