]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-recursor.cc
API: port auth /zones, /zone to json11
[thirdparty/pdns.git] / pdns / ws-recursor.cc
CommitLineData
a7a902fb
BH
1/*
2 PowerDNS Versatile Database Driven Nameserver
825fa717 3 Copyright (C) 2003 - 2014 PowerDNS.COM BV
a7a902fb
BH
4
5 This program is free software; you can redistribute it and/or modify
6ec5e728 6 it under the terms of the GNU General Public License version 2
a7a902fb
BH
7 as published by the Free Software Foundation
8
f782fe38
MH
9 Additionally, the license of this program contains a special
10 exception which allows to distribute the program in binary form when
11 it is linked against OpenSSL.
12
a7a902fb
BH
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21*/
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
2470b36e 25#include "ws-recursor.hh"
a7a902fb 26#include "json.hh"
fa8fd4d2 27
a7a902fb
BH
28#include <string>
29#include "namespaces.hh"
30#include <iostream>
31#include "iputils.hh"
32#include "rec_channel.hh"
33#include "arguments.hh"
34#include "misc.hh"
8465487d 35#include "syncres.hh"
c89f8cd0 36#include "dnsparser.hh"
fb4b38f1
CH
37#include "rapidjson/document.h"
38#include "rapidjson/stringbuffer.h"
39#include "rapidjson/writer.h"
3ae143b0 40#include "webserver.hh"
6ec5e728 41#include "ws-api.hh"
41942bb3 42#include "logger.hh"
fb4b38f1 43
825fa717
CH
44extern __thread FDMultiplexer* t_fdm;
45
fb4b38f1 46using namespace rapidjson;
a7a902fb 47
6ec5e728
CH
48void productServerStatisticsFetch(map<string,string>& out)
49{
50 map<string,string> stats = getAllStatsMap();
51 out.swap(stats);
52}
53
c348c0c8
CH
54static void apiWriteConfigFile(const string& filebasename, const string& content)
55{
d07bf7ff
PL
56 if (::arg()["api-config-dir"].empty()) {
57 throw ApiException("Config Option \"api-config-dir\" must be set");
c348c0c8
CH
58 }
59
d07bf7ff 60 string filename = ::arg()["api-config-dir"] + "/" + filebasename + ".conf";
c348c0c8
CH
61 ofstream ofconf(filename.c_str());
62 if (!ofconf) {
63 throw ApiException("Could not open config fragment file '"+filename+"' for writing: "+stringerror());
64 }
65 ofconf << "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
66 ofconf << content << endl;
67 ofconf.close();
68}
69
41942bb3
CH
70static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp)
71{
d07bf7ff 72 if (req->method == "PUT" && !::arg().mustDo("api-readonly")) {
41942bb3
CH
73 Document document;
74 req->json(document);
bd0320fe 75 const Value &jlist = document["value"];
41942bb3 76
bd0320fe
CH
77 if (!document.IsObject()) {
78 throw ApiException("Supplied JSON must be an object");
79 }
80
81 if (!jlist.IsArray()) {
82 throw ApiException("'value' must be an array");
41942bb3
CH
83 }
84
faa0f891
CH
85 for (SizeType i = 0; i < jlist.Size(); ++i) {
86 try {
87 Netmask(jlist[i].GetString());
88 } catch (NetmaskException &e) {
89 throw ApiException(e.reason);
90 }
91 }
92
41942bb3
CH
93 ostringstream ss;
94
95 // Clear allow-from-file if set, so our changes take effect
96 ss << "allow-from-file=" << endl;
97
98 // Clear allow-from, and provide a "parent" value
99 ss << "allow-from=" << endl;
bd0320fe
CH
100 for (SizeType i = 0; i < jlist.Size(); ++i) {
101 ss << "allow-from+=" << jlist[i].GetString() << endl;
41942bb3
CH
102 }
103
104 apiWriteConfigFile("allow-from", ss.str());
105
106 parseACLs();
107
108 // fall through to GET
109 } else if (req->method != "GET") {
110 throw HttpMethodNotAllowedException();
111 }
112
113 // Return currently configured ACLs
114 Document document;
bd0320fe
CH
115 document.SetObject();
116
117 Value jlist;
118 jlist.SetArray();
41942bb3
CH
119
120 vector<string> entries;
121 t_allowFrom->toStringVector(&entries);
122
ef7cd021 123 for(const string& entry : entries) {
41942bb3 124 Value jentry(entry.c_str(), document.GetAllocator()); // copy
bd0320fe 125 jlist.PushBack(jentry, document.GetAllocator());
41942bb3
CH
126 }
127
bd0320fe
CH
128 document.AddMember("name", "allow-from", document.GetAllocator());
129 document.AddMember("value", jlist, document.GetAllocator());
130
41942bb3
CH
131 resp->setBody(document);
132}
133
8171ab83 134static void fillZone(const DNSName& zonename, HttpResponse* resp)
02945d9a 135{
8171ab83 136 auto iter = t_sstorage->domainmap->find(zonename);
02945d9a 137 if (iter == t_sstorage->domainmap->end())
8171ab83 138 throw ApiException("Could not find domain '"+zonename.toString()+"'");
02945d9a
CH
139
140 Document doc;
141 doc.SetObject();
142
143 const SyncRes::AuthDomain& zone = iter->second;
144
145 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
8171ab83 146 string zoneId = apiZoneNameToId(iter->first);
7bdd945a
CH
147 Value jzoneid(zoneId.c_str(), doc.GetAllocator()); // copy
148 doc.AddMember("id", jzoneid, doc.GetAllocator());
46d06a12 149 string url = "/api/v1/servers/localhost/zones/" + zoneId;
02945d9a
CH
150 Value jurl(url.c_str(), doc.GetAllocator()); // copy
151 doc.AddMember("url", jurl, doc.GetAllocator());
3fa5806e
CH
152 Value jzonename(iter->first.toString().c_str(), doc.GetAllocator()); // copy
153 doc.AddMember("name", jzonename, doc.GetAllocator());
02945d9a
CH
154 doc.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator());
155 Value servers;
156 servers.SetArray();
ef7cd021 157 for(const ComboAddress& server : zone.d_servers) {
02945d9a
CH
158 Value value(server.toStringWithPort().c_str(), doc.GetAllocator());
159 servers.PushBack(value, doc.GetAllocator());
160 }
161 doc.AddMember("servers", servers, doc.GetAllocator());
162 bool rd = zone.d_servers.empty() ? false : zone.d_rdForward;
163 doc.AddMember("recursion_desired", rd, doc.GetAllocator());
164
165 Value records;
166 records.SetArray();
ef7cd021 167 for(const SyncRes::AuthDomain::records_t::value_type& dr : zone.d_records) {
02945d9a
CH
168 Value object;
169 object.SetObject();
e325f20c 170 Value jname(dr.d_name.toString().c_str(), doc.GetAllocator()); // copy
02945d9a 171 object.AddMember("name", jname, doc.GetAllocator());
e325f20c 172 Value jtype(DNSRecordContent::NumberToType(dr.d_type).c_str(), doc.GetAllocator()); // copy
02945d9a 173 object.AddMember("type", jtype, doc.GetAllocator());
e325f20c 174 object.AddMember("ttl", dr.d_ttl, doc.GetAllocator());
175 Value jcontent(dr.d_content->getZoneRepresentation().c_str(), doc.GetAllocator()); // copy
02945d9a
CH
176 object.AddMember("content", jcontent, doc.GetAllocator());
177 records.PushBack(object, doc.GetAllocator());
178 }
179 doc.AddMember("records", records, doc.GetAllocator());
180
181 resp->setBody(doc);
182}
183
184static void doCreateZone(const Value& document)
185{
d07bf7ff
PL
186 if (::arg()["api-config-dir"].empty()) {
187 throw ApiException("Config Option \"api-config-dir\" must be set");
02945d9a
CH
188 }
189
c576d0c5 190 DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
1d6b70f9 191 apiCheckNameAllowedCharacters(zonename.toString());
02945d9a 192
d9081fac 193 string singleIPTarget = stringFromJson(document, "single_target_ip", "");
02945d9a
CH
194 string kind = toUpper(stringFromJson(document, "kind"));
195 bool rd = boolFromJson(document, "recursion_desired");
196 string confbasename = "zone-" + apiZoneNameToId(zonename);
197
198 if (kind == "NATIVE") {
199 if (rd)
200 throw ApiException("kind=Native and recursion_desired are mutually exclusive");
10e69330 201 if(!singleIPTarget.empty()) {
202 try {
203 ComboAddress rem(singleIPTarget);
204 if(rem.sin4.sin_family != AF_INET)
205 throw ApiException("");
206 singleIPTarget = rem.toString();
207 }
208 catch(...) {
209 throw ApiException("Single IP target '"+singleIPTarget+"' is invalid");
210 }
211 }
d07bf7ff 212 string zonefilename = ::arg()["api-config-dir"] + "/" + confbasename + ".zone";
02945d9a
CH
213 ofstream ofzone(zonefilename.c_str());
214 if (!ofzone) {
215 throw ApiException("Could not open '"+zonefilename+"' for writing: "+stringerror());
216 }
217 ofzone << "; Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
218 ofzone << zonename << "\tIN\tSOA\tlocal.zone.\thostmaster."<<zonename<<" 1 1 1 1 1" << endl;
7f643957 219 if(!singleIPTarget.empty()) {
10e69330 220 ofzone <<zonename << "\t3600\tIN\tA\t"<<singleIPTarget<<endl;
221 ofzone <<"*."<<zonename << "\t3600\tIN\tA\t"<<singleIPTarget<<endl;
7f643957 222 }
02945d9a
CH
223 ofzone.close();
224
8171ab83 225 apiWriteConfigFile(confbasename, "auth-zones+=" + zonename.toString() + "=" + zonefilename);
02945d9a
CH
226 } else if (kind == "FORWARDED") {
227 const Value &servers = document["servers"];
228 if (kind == "FORWARDED" && (!servers.IsArray() || servers.Size() == 0))
229 throw ApiException("Need at least one upstream server when forwarding");
230
231 string serverlist;
232 if (servers.IsArray()) {
233 for (SizeType i = 0; i < servers.Size(); ++i) {
234 if (!serverlist.empty()) {
235 serverlist += ";";
236 }
237 serverlist += servers[i].GetString();
238 }
239 }
240
241 if (rd) {
8171ab83 242 apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename.toString() + "=" + serverlist);
02945d9a 243 } else {
8171ab83 244 apiWriteConfigFile(confbasename, "forward-zones+=" + zonename.toString() + "=" + serverlist);
02945d9a
CH
245 }
246 } else {
247 throw ApiException("invalid kind");
248 }
249}
250
8171ab83 251static bool doDeleteZone(const DNSName& zonename)
02945d9a 252{
d07bf7ff
PL
253 if (::arg()["api-config-dir"].empty()) {
254 throw ApiException("Config Option \"api-config-dir\" must be set");
02945d9a
CH
255 }
256
257 string filename;
258
259 // this one must exist
d07bf7ff 260 filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
02945d9a
CH
261 if (unlink(filename.c_str()) != 0) {
262 return false;
263 }
264
265 // .zone file is optional
d07bf7ff 266 filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".zone";
02945d9a
CH
267 unlink(filename.c_str());
268
269 return true;
270}
271
272static void apiServerZones(HttpRequest* req, HttpResponse* resp)
273{
d07bf7ff
PL
274 if (req->method == "POST" && !::arg().mustDo("api-readonly")) {
275 if (::arg()["api-config-dir"].empty()) {
276 throw ApiException("Config Option \"api-config-dir\" must be set");
02945d9a
CH
277 }
278
279 Document document;
280 req->json(document);
281
c576d0c5 282 DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
02945d9a 283
8171ab83 284 auto iter = t_sstorage->domainmap->find(zonename);
02945d9a
CH
285 if (iter != t_sstorage->domainmap->end())
286 throw ApiException("Zone already exists");
287
288 doCreateZone(document);
289 reloadAuthAndForwards();
290 fillZone(zonename, resp);
64a36f0d 291 resp->status = 201;
02945d9a
CH
292 return;
293 }
294
295 if(req->method != "GET")
296 throw HttpMethodNotAllowedException();
297
298 Document doc;
299 doc.SetArray();
300
ef7cd021 301 for(const SyncRes::domainmap_t::value_type& val : *t_sstorage->domainmap) {
02945d9a
CH
302 const SyncRes::AuthDomain& zone = val.second;
303 Value jdi;
304 jdi.SetObject();
305 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
8171ab83 306 string zoneId = apiZoneNameToId(val.first);
7bdd945a
CH
307 Value jzoneid(zoneId.c_str(), doc.GetAllocator()); // copy
308 jdi.AddMember("id", jzoneid, doc.GetAllocator());
46d06a12 309 string url = "/api/v1/servers/localhost/zones/" + zoneId;
02945d9a
CH
310 Value jurl(url.c_str(), doc.GetAllocator()); // copy
311 jdi.AddMember("url", jurl, doc.GetAllocator());
3fa5806e
CH
312 Value jzonename(val.first.toString().c_str(), doc.GetAllocator()); // copy
313 jdi.AddMember("name", jzonename, doc.GetAllocator());
02945d9a
CH
314 jdi.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator());
315 Value servers;
316 servers.SetArray();
ef7cd021 317 for(const ComboAddress& server : zone.d_servers) {
02945d9a
CH
318 Value value(server.toStringWithPort().c_str(), doc.GetAllocator());
319 servers.PushBack(value, doc.GetAllocator());
320 }
321 jdi.AddMember("servers", servers, doc.GetAllocator());
322 bool rd = zone.d_servers.empty() ? false : zone.d_rdForward;
323 jdi.AddMember("recursion_desired", rd, doc.GetAllocator());
324 doc.PushBack(jdi, doc.GetAllocator());
325 }
326 resp->setBody(doc);
327}
328
329static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
330{
8171ab83 331 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
02945d9a
CH
332
333 SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename);
334 if (iter == t_sstorage->domainmap->end())
8171ab83 335 throw ApiException("Could not find domain '"+zonename.toString()+"'");
02945d9a 336
d07bf7ff 337 if(req->method == "PUT" && !::arg().mustDo("api-readonly")) {
02945d9a
CH
338 Document document;
339 req->json(document);
340
341 doDeleteZone(zonename);
342 doCreateZone(document);
343 reloadAuthAndForwards();
c576d0c5 344 fillZone(apiNameToDNSName(stringFromJson(document, "name")), resp);
02945d9a 345 }
d07bf7ff 346 else if(req->method == "DELETE" && !::arg().mustDo("api-readonly")) {
02945d9a
CH
347 if (!doDeleteZone(zonename)) {
348 throw ApiException("Deleting domain failed");
349 }
350
351 reloadAuthAndForwards();
352 // empty body on success
353 resp->body = "";
37663c3b 354 resp->status = 204; // No Content: declare that the zone is gone now
02945d9a
CH
355 } else if(req->method == "GET") {
356 fillZone(zonename, resp);
357 } else {
358 throw HttpMethodNotAllowedException();
359 }
360}
361
37bc3d01
CH
362static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
363 if(req->method != "GET")
364 throw HttpMethodNotAllowedException();
365
583ea80d 366 string q = req->getvars["q"];
37bc3d01
CH
367 if (q.empty())
368 throw ApiException("Query q can't be blank");
369
370 Document doc;
371 doc.SetArray();
372
ef7cd021 373 for(const SyncRes::domainmap_t::value_type& val : *t_sstorage->domainmap) {
8171ab83 374 string zoneId = apiZoneNameToId(val.first);
3ddb9247 375 if (pdns_ci_find(val.first.toString(), q) != string::npos) {
37bc3d01
CH
376 Value object;
377 object.SetObject();
378 object.AddMember("type", "zone", doc.GetAllocator());
379 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
380 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
3ddb9247 381 Value jzoneName(val.first.toString().c_str(), doc.GetAllocator()); // copy
37bc3d01
CH
382 object.AddMember("name", jzoneName, doc.GetAllocator());
383 doc.PushBack(object, doc.GetAllocator());
384 }
385
386 // if zone name is an exact match, don't bother with returning all records/comments in it
8171ab83 387 if (val.first == DNSName(q)) {
37bc3d01
CH
388 continue;
389 }
390
391 const SyncRes::AuthDomain& zone = val.second;
392
ef7cd021 393 for(const SyncRes::AuthDomain::records_t::value_type& rr : zone.d_records) {
e325f20c 394 if (pdns_ci_find(rr.d_name.toString(), q) == string::npos && pdns_ci_find(rr.d_content->getZoneRepresentation(), q) == string::npos)
37bc3d01
CH
395 continue;
396
397 Value object;
398 object.SetObject();
399 object.AddMember("type", "record", doc.GetAllocator());
400 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
401 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
3ddb9247 402 Value jzoneName(val.first.toString().c_str(), doc.GetAllocator()); // copy
37bc3d01 403 object.AddMember("zone_name", jzoneName, doc.GetAllocator());
e325f20c 404 Value jname(rr.d_name.toString().c_str(), doc.GetAllocator()); // copy
37bc3d01 405 object.AddMember("name", jname, doc.GetAllocator());
e325f20c 406 Value jcontent(rr.d_content->getZoneRepresentation().c_str(), doc.GetAllocator()); // copy
37bc3d01
CH
407 object.AddMember("content", jcontent, doc.GetAllocator());
408
409 doc.PushBack(object, doc.GetAllocator());
410 }
411 }
412 resp->setBody(doc);
413}
414
c0f6a1da 415static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
a426cb89
CH
416 if(req->method != "PUT")
417 throw HttpMethodNotAllowedException();
418
c0f6a1da
CH
419 DNSName canon = apiNameToDNSName(req->getvars["domain"]);
420
65a60c2c 421 int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon, false));
c0f6a1da 422 count += broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, canon, false));
65a60c2c 423 count += broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon, false));
a426cb89 424 map<string, string> object;
335da0ba 425 object["count"] = std::to_string(count);
a426cb89
CH
426 object["result"] = "Flushed cache.";
427 resp->body = returnJsonObject(object);
428}
429
1ce57618 430RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
a7a902fb
BH
431{
432 RecursorControlParser rcp; // inits
a7a902fb 433
d07bf7ff 434 d_ws = new AsyncWebServer(fdm, arg()["webserver-address"], arg().asNum("webserver-port"));
825fa717 435 d_ws->bind();
867f6abc 436
3ae143b0 437 // legacy dispatch
1ce57618 438 d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2));
c0f6a1da 439 d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush);
46d06a12
PL
440 d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", &apiServerConfigAllowFrom);
441 d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig);
442 d_ws->registerApiHandler("/api/v1/servers/localhost/search-log", &apiServerSearchLog);
443 d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServerSearchData);
444 d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics);
445 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServerZoneDetail);
446 d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZones);
447 d_ws->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail);
448 d_ws->registerApiHandler("/api/v1/servers", &apiServer);
867f6abc 449
3ae143b0 450 d_ws->go();
867f6abc
CH
451}
452
1ce57618 453void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
867f6abc 454{
e2897a7d 455 string command;
a7a902fb 456
583ea80d
CH
457 if(req->getvars.count("command")) {
458 command = req->getvars["command"];
459 req->getvars.erase("command");
3ae143b0 460 }
a7a902fb 461
3ddb9247 462 map<string, string> stats;
a426cb89 463 if(command == "get-query-ring") {
3ddb9247 464 typedef pair<DNSName,uint16_t> query_t;
c89f8cd0 465 vector<query_t> queries;
509196af 466 bool filter=!req->getvars["public-filtered"].empty();
3ddb9247 467
c89f8cd0 468 if(req->getvars["name"]=="servfail-queries")
469 queries=broadcastAccFunction<vector<query_t> >(pleaseGetServfailQueryRing);
470 else if(req->getvars["name"]=="queries")
471 queries=broadcastAccFunction<vector<query_t> >(pleaseGetQueryRing);
3ddb9247 472
c89f8cd0 473 typedef map<query_t,unsigned int> counts_t;
474 counts_t counts;
475 unsigned int total=0;
ef7cd021 476 for(const query_t& q : queries) {
c89f8cd0 477 total++;
509196af 478 if(filter)
3ddb9247
PD
479 counts[make_pair(getRegisteredName(q.first), q.second)]++;
480 else
481 counts[make_pair(q.first, q.second)]++;
c89f8cd0 482 }
3ddb9247 483
c89f8cd0 484 typedef std::multimap<int, query_t> rcounts_t;
485 rcounts_t rcounts;
3ddb9247 486
c89f8cd0 487 for(counts_t::const_iterator i=counts.begin(); i != counts.end(); ++i)
488 rcounts.insert(make_pair(-i->second, i->first));
489
c89f8cd0 490 Document doc;
491 doc.SetObject();
492 Value entries;
493 entries.SetArray();
509196af 494 unsigned int tot=0, totIncluded=0;
ef7cd021 495 for(const rcounts_t::value_type& q : rcounts) {
c89f8cd0 496 Value arr;
3ddb9247 497
c89f8cd0 498 arr.SetArray();
509196af 499 totIncluded-=q.first;
c89f8cd0 500 arr.PushBack(-q.first, doc.GetAllocator());
3ddb9247 501 arr.PushBack(q.second.first.toString().c_str(), doc.GetAllocator());
c89f8cd0 502 arr.PushBack(DNSRecordContent::NumberToType(q.second.second).c_str(), doc.GetAllocator());
503 entries.PushBack(arr, doc.GetAllocator());
504 if(tot++>=100)
505 break;
506 }
509196af 507 if(queries.size() != totIncluded) {
508 Value arr;
509 arr.SetArray();
e258fd11 510 arr.PushBack((unsigned int)(queries.size()-totIncluded), doc.GetAllocator());
509196af 511 arr.PushBack("", doc.GetAllocator());
512 arr.PushBack("", doc.GetAllocator());
513 entries.PushBack(arr, doc.GetAllocator());
514 }
3ddb9247 515 doc.AddMember("entries", entries, doc.GetAllocator());
509196af 516 resp->setBody(doc);
517 return;
518 }
519 else if(command == "get-remote-ring") {
520 vector<ComboAddress> queries;
521 if(req->getvars["name"]=="remotes")
522 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetRemotes);
523 else if(req->getvars["name"]=="servfail-remotes")
524 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetServfailRemotes);
525 else if(req->getvars["name"]=="large-answer-remotes")
526 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetLargeAnswerRemotes);
3ddb9247 527
509196af 528 typedef map<ComboAddress,unsigned int,ComboAddress::addressOnlyLessThan> counts_t;
529 counts_t counts;
530 unsigned int total=0;
ef7cd021 531 for(const ComboAddress& q : queries) {
509196af 532 total++;
533 counts[q]++;
534 }
3ddb9247 535
509196af 536 typedef std::multimap<int, ComboAddress> rcounts_t;
537 rcounts_t rcounts;
3ddb9247 538
509196af 539 for(counts_t::const_iterator i=counts.begin(); i != counts.end(); ++i)
540 rcounts.insert(make_pair(-i->second, i->first));
541
3ddb9247 542
509196af 543 Document doc;
544 doc.SetObject();
545 Value entries;
546 entries.SetArray();
547 unsigned int tot=0, totIncluded=0;
ef7cd021 548 for(const rcounts_t::value_type& q : rcounts) {
509196af 549 totIncluded-=q.first;
550 Value arr;
3ddb9247 551
509196af 552 arr.SetArray();
553 arr.PushBack(-q.first, doc.GetAllocator());
3ddb9247 554 Value jname(q.second.toString().c_str(), doc.GetAllocator()); // copy
509196af 555 arr.PushBack(jname, doc.GetAllocator());
556 entries.PushBack(arr, doc.GetAllocator());
557 if(tot++>=100)
558 break;
559 }
560 if(queries.size() != totIncluded) {
561 Value arr;
562 arr.SetArray();
59a24ed2 563 arr.PushBack((unsigned int)(queries.size()-totIncluded), doc.GetAllocator());
509196af 564 arr.PushBack("", doc.GetAllocator());
565 entries.PushBack(arr, doc.GetAllocator());
566 }
567
3ddb9247 568 doc.AddMember("entries", entries, doc.GetAllocator());
c89f8cd0 569 resp->setBody(doc);
570 return;
3ae143b0
CH
571 } else {
572 resp->status = 404;
c89f8cd0 573 resp->body = returnJsonError("Command '"+command+"' not found");
3ae143b0 574 }
a7a902fb 575}
825fa717
CH
576
577
578void AsyncServerNewConnectionMT(void *p) {
579 AsyncServer *server = (AsyncServer*)p;
580 try {
581 Socket* socket = server->accept();
582 server->d_asyncNewConnectionCallback(socket);
583 delete socket;
584 } catch (NetworkError &e) {
585 // we're running in a shared process/thread, so can't just terminate/abort.
586 return;
587 }
588}
589
590void AsyncServer::asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback)
591{
592 d_asyncNewConnectionCallback = callback;
593 fdm->addReadFD(d_server_socket.getHandle(), boost::bind(&AsyncServer::newConnection, this));
594}
595
596void AsyncServer::newConnection()
597{
598 MT->makeThread(&AsyncServerNewConnectionMT, this);
599}
600
601
602void AsyncWebServer::serveConnection(Socket *client)
603{
604 HttpRequest req;
583ea80d
CH
605 YaHTTP::AsyncRequestLoader yarl;
606 yarl.initialize(&req);
825fa717 607 client->setNonBlocking();
3ddb9247 608
825fa717
CH
609 string data;
610 try {
611 while(!req.complete) {
f0b2bab0 612 data.clear();
825fa717
CH
613 int bytes = arecvtcp(data, 16384, client, true);
614 if (bytes > 0) {
615 req.complete = yarl.feed(data);
616 } else {
617 // read error OR EOF
618 break;
619 }
620 }
583ea80d 621 yarl.finalize();
825fa717
CH
622 } catch (YaHTTP::ParseError &e) {
623 // request stays incomplete
624 }
625
626 HttpResponse resp = handleRequest(req);
627 ostringstream ss;
628 resp.write(ss);
629 data = ss.str();
630
631 // now send the reply
632 if (asendtcp(data, client) == -1 || data.empty()) {
633 L<<Logger::Error<<"Failed sending reply to HTTP client"<<endl;
634 }
635}
636
637void AsyncWebServer::go() {
638 if (!d_server)
639 return;
640 ((AsyncServer*)d_server)->asyncWaitForConnections(d_fdm, boost::bind(&AsyncWebServer::serveConnection, this, _1));
641}