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