]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-recursor.cc
Merge pull request #6842 from zeha/mysql8
[thirdparty/pdns.git] / pdns / ws-recursor.cc
CommitLineData
a7a902fb 1/*
6edbf68a
PL
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
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 Street, 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"
639a6287 37#include "json11.hpp"
3ae143b0 38#include "webserver.hh"
6ec5e728 39#include "ws-api.hh"
41942bb3 40#include "logger.hh"
d579380d 41#include "ext/incbin/incbin.h"
2278ffda
PL
42#include "rec-lua-conf.hh"
43#include "rpzloader.hh"
fb4b38f1 44
6dfff36f 45extern thread_local FDMultiplexer* t_fdm;
825fa717 46
639a6287 47using json11::Json;
a7a902fb 48
6ec5e728
CH
49void productServerStatisticsFetch(map<string,string>& out)
50{
51 map<string,string> stats = getAllStatsMap();
52 out.swap(stats);
53}
54
c348c0c8
CH
55static void apiWriteConfigFile(const string& filebasename, const string& content)
56{
d07bf7ff
PL
57 if (::arg()["api-config-dir"].empty()) {
58 throw ApiException("Config Option \"api-config-dir\" must be set");
c348c0c8
CH
59 }
60
d07bf7ff 61 string filename = ::arg()["api-config-dir"] + "/" + filebasename + ".conf";
c348c0c8
CH
62 ofstream ofconf(filename.c_str());
63 if (!ofconf) {
64 throw ApiException("Could not open config fragment file '"+filename+"' for writing: "+stringerror());
65 }
66 ofconf << "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
67 ofconf << content << endl;
68 ofconf.close();
69}
70
41942bb3
CH
71static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp)
72{
d07bf7ff 73 if (req->method == "PUT" && !::arg().mustDo("api-readonly")) {
639a6287 74 Json document = req->json();
41942bb3 75
639a6287
CH
76 auto jlist = document["value"];
77 if (!jlist.is_array()) {
bd0320fe 78 throw ApiException("'value' must be an array");
41942bb3
CH
79 }
80
5f8108e9 81 NetmaskGroup nmg;
639a6287 82 for (auto value : jlist.array_items()) {
faa0f891 83 try {
5f8108e9
RG
84 nmg.addMask(value.string_value());
85 } catch (const NetmaskException &e) {
faa0f891
CH
86 throw ApiException(e.reason);
87 }
88 }
89
41942bb3
CH
90 ostringstream ss;
91
92 // Clear allow-from-file if set, so our changes take effect
93 ss << "allow-from-file=" << endl;
94
95 // Clear allow-from, and provide a "parent" value
96 ss << "allow-from=" << endl;
5f8108e9 97 ss << "allow-from+=" << nmg.toString() << endl;
41942bb3
CH
98
99 apiWriteConfigFile("allow-from", ss.str());
100
101 parseACLs();
102
103 // fall through to GET
104 } else if (req->method != "GET") {
105 throw HttpMethodNotAllowedException();
106 }
107
108 // Return currently configured ACLs
41942bb3
CH
109 vector<string> entries;
110 t_allowFrom->toStringVector(&entries);
111
639a6287
CH
112 resp->setBody(Json::object {
113 { "name", "allow-from" },
114 { "value", entries },
115 });
41942bb3
CH
116}
117
8171ab83 118static void fillZone(const DNSName& zonename, HttpResponse* resp)
02945d9a 119{
a712cb56
RG
120 auto iter = SyncRes::t_sstorage.domainmap->find(zonename);
121 if (iter == SyncRes::t_sstorage.domainmap->end())
86f1af1c 122 throw ApiException("Could not find domain '"+zonename.toLogString()+"'");
02945d9a 123
02945d9a
CH
124 const SyncRes::AuthDomain& zone = iter->second;
125
15cb63ca
CH
126 Json::array servers;
127 for(const ComboAddress& server : zone.d_servers) {
128 servers.push_back(server.toStringWithPort());
02945d9a 129 }
15cb63ca
CH
130
131 Json::array records;
132 for(const SyncRes::AuthDomain::records_t::value_type& dr : zone.d_records) {
133 records.push_back(Json::object {
134 { "name", dr.d_name.toString() },
135 { "type", DNSRecordContent::NumberToType(dr.d_type) },
136 { "ttl", (double)dr.d_ttl },
137 { "content", dr.d_content->getZoneRepresentation() }
138 });
02945d9a 139 }
15cb63ca
CH
140
141 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
142 string zoneId = apiZoneNameToId(iter->first);
143 Json::object doc = {
144 { "id", zoneId },
145 { "url", "/api/v1/servers/localhost/zones/" + zoneId },
146 { "name", iter->first.toString() },
147 { "kind", zone.d_servers.empty() ? "Native" : "Forwarded" },
148 { "servers", servers },
149 { "recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward },
150 { "records", records }
151 };
02945d9a
CH
152
153 resp->setBody(doc);
154}
155
15cb63ca 156static void doCreateZone(const Json document)
02945d9a 157{
d07bf7ff
PL
158 if (::arg()["api-config-dir"].empty()) {
159 throw ApiException("Config Option \"api-config-dir\" must be set");
02945d9a
CH
160 }
161
c576d0c5 162 DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
1d6b70f9 163 apiCheckNameAllowedCharacters(zonename.toString());
02945d9a 164
15cb63ca 165 string singleIPTarget = document["single_target_ip"].string_value();
02945d9a
CH
166 string kind = toUpper(stringFromJson(document, "kind"));
167 bool rd = boolFromJson(document, "recursion_desired");
168 string confbasename = "zone-" + apiZoneNameToId(zonename);
169
170 if (kind == "NATIVE") {
171 if (rd)
172 throw ApiException("kind=Native and recursion_desired are mutually exclusive");
10e69330 173 if(!singleIPTarget.empty()) {
174 try {
175 ComboAddress rem(singleIPTarget);
176 if(rem.sin4.sin_family != AF_INET)
177 throw ApiException("");
178 singleIPTarget = rem.toString();
179 }
180 catch(...) {
181 throw ApiException("Single IP target '"+singleIPTarget+"' is invalid");
182 }
183 }
d07bf7ff 184 string zonefilename = ::arg()["api-config-dir"] + "/" + confbasename + ".zone";
02945d9a
CH
185 ofstream ofzone(zonefilename.c_str());
186 if (!ofzone) {
187 throw ApiException("Could not open '"+zonefilename+"' for writing: "+stringerror());
188 }
189 ofzone << "; Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
190 ofzone << zonename << "\tIN\tSOA\tlocal.zone.\thostmaster."<<zonename<<" 1 1 1 1 1" << endl;
7f643957 191 if(!singleIPTarget.empty()) {
10e69330 192 ofzone <<zonename << "\t3600\tIN\tA\t"<<singleIPTarget<<endl;
193 ofzone <<"*."<<zonename << "\t3600\tIN\tA\t"<<singleIPTarget<<endl;
7f643957 194 }
02945d9a
CH
195 ofzone.close();
196
8171ab83 197 apiWriteConfigFile(confbasename, "auth-zones+=" + zonename.toString() + "=" + zonefilename);
02945d9a 198 } else if (kind == "FORWARDED") {
02945d9a 199 string serverlist;
15cb63ca
CH
200 for (auto value : document["servers"].array_items()) {
201 string server = value.string_value();
202 if (server == "") {
203 throw ApiException("Forwarded-to server must not be an empty string");
02945d9a 204 }
5f8108e9
RG
205 try {
206 ComboAddress ca = parseIPAndPort(server, 53);
207 if (!serverlist.empty()) {
208 serverlist += ";";
209 }
210 serverlist += ca.toStringWithPort();
211 } catch (const PDNSException &e) {
212 throw ApiException(e.reason);
15cb63ca 213 }
02945d9a 214 }
15cb63ca
CH
215 if (serverlist == "")
216 throw ApiException("Need at least one upstream server when forwarding");
02945d9a
CH
217
218 if (rd) {
8171ab83 219 apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename.toString() + "=" + serverlist);
02945d9a 220 } else {
8171ab83 221 apiWriteConfigFile(confbasename, "forward-zones+=" + zonename.toString() + "=" + serverlist);
02945d9a
CH
222 }
223 } else {
224 throw ApiException("invalid kind");
225 }
226}
227
8171ab83 228static bool doDeleteZone(const DNSName& zonename)
02945d9a 229{
d07bf7ff
PL
230 if (::arg()["api-config-dir"].empty()) {
231 throw ApiException("Config Option \"api-config-dir\" must be set");
02945d9a
CH
232 }
233
234 string filename;
235
236 // this one must exist
d07bf7ff 237 filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
02945d9a
CH
238 if (unlink(filename.c_str()) != 0) {
239 return false;
240 }
241
242 // .zone file is optional
d07bf7ff 243 filename = ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".zone";
02945d9a
CH
244 unlink(filename.c_str());
245
246 return true;
247}
248
249static void apiServerZones(HttpRequest* req, HttpResponse* resp)
250{
d07bf7ff
PL
251 if (req->method == "POST" && !::arg().mustDo("api-readonly")) {
252 if (::arg()["api-config-dir"].empty()) {
253 throw ApiException("Config Option \"api-config-dir\" must be set");
02945d9a
CH
254 }
255
15cb63ca 256 Json document = req->json();
02945d9a 257
c576d0c5 258 DNSName zonename = apiNameToDNSName(stringFromJson(document, "name"));
02945d9a 259
a712cb56
RG
260 auto iter = SyncRes::t_sstorage.domainmap->find(zonename);
261 if (iter != SyncRes::t_sstorage.domainmap->end())
02945d9a
CH
262 throw ApiException("Zone already exists");
263
264 doCreateZone(document);
265 reloadAuthAndForwards();
266 fillZone(zonename, resp);
64a36f0d 267 resp->status = 201;
02945d9a
CH
268 return;
269 }
270
271 if(req->method != "GET")
272 throw HttpMethodNotAllowedException();
273
15cb63ca 274 Json::array doc;
a712cb56 275 for(const SyncRes::domainmap_t::value_type& val : *SyncRes::t_sstorage.domainmap) {
02945d9a 276 const SyncRes::AuthDomain& zone = val.second;
15cb63ca
CH
277 Json::array servers;
278 for(const ComboAddress& server : zone.d_servers) {
279 servers.push_back(server.toStringWithPort());
280 }
02945d9a 281 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
8171ab83 282 string zoneId = apiZoneNameToId(val.first);
15cb63ca
CH
283 doc.push_back(Json::object {
284 { "id", zoneId },
285 { "url", "/api/v1/servers/localhost/zones/" + zoneId },
286 { "name", val.first.toString() },
287 { "kind", zone.d_servers.empty() ? "Native" : "Forwarded" },
288 { "servers", servers },
289 { "recursion_desired", zone.d_servers.empty() ? false : zone.d_rdForward }
290 });
02945d9a
CH
291 }
292 resp->setBody(doc);
293}
294
295static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
296{
8171ab83 297 DNSName zonename = apiZoneIdToName(req->parameters["id"]);
02945d9a 298
a712cb56
RG
299 SyncRes::domainmap_t::const_iterator iter = SyncRes::t_sstorage.domainmap->find(zonename);
300 if (iter == SyncRes::t_sstorage.domainmap->end())
86f1af1c 301 throw ApiException("Could not find domain '"+zonename.toLogString()+"'");
02945d9a 302
d07bf7ff 303 if(req->method == "PUT" && !::arg().mustDo("api-readonly")) {
15cb63ca 304 Json document = req->json();
02945d9a
CH
305
306 doDeleteZone(zonename);
307 doCreateZone(document);
308 reloadAuthAndForwards();
f0e76cee
CH
309 resp->body = "";
310 resp->status = 204; // No Content, but indicate success
02945d9a 311 }
d07bf7ff 312 else if(req->method == "DELETE" && !::arg().mustDo("api-readonly")) {
02945d9a
CH
313 if (!doDeleteZone(zonename)) {
314 throw ApiException("Deleting domain failed");
315 }
316
317 reloadAuthAndForwards();
318 // empty body on success
319 resp->body = "";
37663c3b 320 resp->status = 204; // No Content: declare that the zone is gone now
02945d9a
CH
321 } else if(req->method == "GET") {
322 fillZone(zonename, resp);
323 } else {
324 throw HttpMethodNotAllowedException();
325 }
326}
327
37bc3d01
CH
328static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
329 if(req->method != "GET")
330 throw HttpMethodNotAllowedException();
331
583ea80d 332 string q = req->getvars["q"];
37bc3d01
CH
333 if (q.empty())
334 throw ApiException("Query q can't be blank");
335
565a3e28 336 Json::array doc;
a712cb56 337 for(const SyncRes::domainmap_t::value_type& val : *SyncRes::t_sstorage.domainmap) {
8171ab83 338 string zoneId = apiZoneNameToId(val.first);
565a3e28
CH
339 string zoneName = val.first.toString();
340 if (pdns_ci_find(zoneName, q) != string::npos) {
341 doc.push_back(Json::object {
342 { "type", "zone" },
343 { "zone_id", zoneId },
344 { "name", zoneName }
345 });
37bc3d01
CH
346 }
347
348 // if zone name is an exact match, don't bother with returning all records/comments in it
8171ab83 349 if (val.first == DNSName(q)) {
37bc3d01
CH
350 continue;
351 }
352
353 const SyncRes::AuthDomain& zone = val.second;
354
565a3e28 355 for(const SyncRes::AuthDomain::records_t::value_type& rr : zone.d_records) {
e325f20c 356 if (pdns_ci_find(rr.d_name.toString(), q) == string::npos && pdns_ci_find(rr.d_content->getZoneRepresentation(), q) == string::npos)
37bc3d01
CH
357 continue;
358
565a3e28
CH
359 doc.push_back(Json::object {
360 { "type", "record" },
361 { "zone_id", zoneId },
362 { "zone_name", zoneName },
363 { "name", rr.d_name.toString() },
364 { "content", rr.d_content->getZoneRepresentation() }
365 });
37bc3d01
CH
366 }
367 }
368 resp->setBody(doc);
369}
370
c0f6a1da 371static void apiServerCacheFlush(HttpRequest* req, HttpResponse* resp) {
a426cb89
CH
372 if(req->method != "PUT")
373 throw HttpMethodNotAllowedException();
374
c0f6a1da 375 DNSName canon = apiNameToDNSName(req->getvars["domain"]);
46d03ec1 376 bool subtree = (req->getvars.count("subtree") > 0 && req->getvars["subtree"].compare("true") == 0);
c0f6a1da 377
d19c22a1
CHB
378 int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon, subtree));
379 count += broadcastAccFunction<uint64_t>(boost::bind(pleaseWipePacketCache, canon, subtree));
380 count += broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon, subtree));
f682752a
CH
381 resp->setBody(Json::object {
382 { "count", count },
383 { "result", "Flushed cache." }
384 });
a426cb89
CH
385}
386
64c9da8c 387static void apiServerRPZStats(HttpRequest* req, HttpResponse* resp) {
2278ffda
PL
388 if(req->method != "GET")
389 throw HttpMethodNotAllowedException();
390
391 auto luaconf = g_luaconfs.getLocal();
392 auto numZones = luaconf->dfe.size();
393
394 Json::object ret;
395
396 for (size_t i=0; i < numZones; i++) {
397 auto zone = luaconf->dfe.getZone(i);
398 if (zone == nullptr)
399 continue;
400 auto name = zone->getName();
20c37dec
PL
401 auto stats = getRPZZoneStats(*name);
402 if (stats == nullptr)
403 continue;
2278ffda 404 Json::object zoneInfo = {
20c37dec
PL
405 {"transfers_failed", (double)stats->d_failedTransfers},
406 {"transfers_success", (double)stats->d_successfulTransfers},
407 {"transfers_full", (double)stats->d_fullTransfers},
408 {"records", (double)stats->d_numberOfRecords},
409 {"last_update", (double)stats->d_lastUpdate},
410 {"serial", (double)stats->d_serial},
2278ffda
PL
411 };
412 ret[*name] = zoneInfo;
413 }
414 resp->setBody(ret);
415}
416
642339d8 417#include "htmlfiles.h"
d579380d 418
b184a9dc 419static void serveStuff(HttpRequest* req, HttpResponse* resp)
d579380d 420{
421 resp->headers["Cache-Control"] = "max-age=86400";
d579380d 422
d887229a 423 if(req->url.path == "/")
424 req->url.path = "/index.html";
425
642339d8 426 const string charset = "; charset=utf-8";
427 if(boost::ends_with(req->url.path, ".html"))
428 resp->headers["Content-Type"] = "text/html" + charset;
429 else if(boost::ends_with(req->url.path, ".css"))
430 resp->headers["Content-Type"] = "text/css" + charset;
431 else if(boost::ends_with(req->url.path,".js"))
432 resp->headers["Content-Type"] = "application/javascript" + charset;
433 else if(boost::ends_with(req->url.path, ".png"))
434 resp->headers["Content-Type"] = "image/png";
435
436 resp->headers["X-Content-Type-Options"] = "nosniff";
437 resp->headers["X-Frame-Options"] = "deny";
438 resp->headers["X-Permitted-Cross-Domain-Policies"] = "none";
439
440 resp->headers["X-XSS-Protection"] = "1; mode=block";
441 // resp->headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
442
443 resp->body = g_urlmap[req->url.path.c_str()+1];
d579380d 444 resp->status = 200;
445}
446
447
1ce57618 448RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
a7a902fb 449{
b0b37121 450 registerAllStats();
a7a902fb 451
d07bf7ff 452 d_ws = new AsyncWebServer(fdm, arg()["webserver-address"], arg().asNum("webserver-port"));
825fa717 453 d_ws->bind();
867f6abc 454
3ae143b0 455 // legacy dispatch
1ce57618 456 d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2));
c0f6a1da 457 d_ws->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush);
46d06a12
PL
458 d_ws->registerApiHandler("/api/v1/servers/localhost/config/allow-from", &apiServerConfigAllowFrom);
459 d_ws->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig);
64c9da8c 460 d_ws->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", &apiServerRPZStats);
46d06a12
PL
461 d_ws->registerApiHandler("/api/v1/servers/localhost/search-log", &apiServerSearchLog);
462 d_ws->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServerSearchData);
463 d_ws->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics);
464 d_ws->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServerZoneDetail);
465 d_ws->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZones);
466 d_ws->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail);
467 d_ws->registerApiHandler("/api/v1/servers", &apiServer);
9e6d2033 468 d_ws->registerApiHandler("/api", &apiDiscovery);
867f6abc 469
642339d8 470 for(const auto& u : g_urlmap)
471 d_ws->registerWebHandler("/"+u.first, serveStuff);
d579380d 472 d_ws->registerWebHandler("/", serveStuff);
3ae143b0 473 d_ws->go();
867f6abc
CH
474}
475
1ce57618 476void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
867f6abc 477{
e2897a7d 478 string command;
a7a902fb 479
583ea80d
CH
480 if(req->getvars.count("command")) {
481 command = req->getvars["command"];
482 req->getvars.erase("command");
3ae143b0 483 }
a7a902fb 484
3ddb9247 485 map<string, string> stats;
a426cb89 486 if(command == "get-query-ring") {
3ddb9247 487 typedef pair<DNSName,uint16_t> query_t;
c89f8cd0 488 vector<query_t> queries;
509196af 489 bool filter=!req->getvars["public-filtered"].empty();
3ddb9247 490
c89f8cd0 491 if(req->getvars["name"]=="servfail-queries")
492 queries=broadcastAccFunction<vector<query_t> >(pleaseGetServfailQueryRing);
66f2e6ad
KM
493 if(req->getvars["name"]=="bogus-queries")
494 queries=broadcastAccFunction<vector<query_t> >(pleaseGetBogusQueryRing);
c89f8cd0 495 else if(req->getvars["name"]=="queries")
496 queries=broadcastAccFunction<vector<query_t> >(pleaseGetQueryRing);
3ddb9247 497
c89f8cd0 498 typedef map<query_t,unsigned int> counts_t;
499 counts_t counts;
500 unsigned int total=0;
ef7cd021 501 for(const query_t& q : queries) {
c89f8cd0 502 total++;
509196af 503 if(filter)
3ddb9247
PD
504 counts[make_pair(getRegisteredName(q.first), q.second)]++;
505 else
506 counts[make_pair(q.first, q.second)]++;
c89f8cd0 507 }
3ddb9247 508
c89f8cd0 509 typedef std::multimap<int, query_t> rcounts_t;
510 rcounts_t rcounts;
3ddb9247 511
c89f8cd0 512 for(counts_t::const_iterator i=counts.begin(); i != counts.end(); ++i)
513 rcounts.insert(make_pair(-i->second, i->first));
514
e0e741df 515 Json::array entries;
509196af 516 unsigned int tot=0, totIncluded=0;
ef7cd021 517 for(const rcounts_t::value_type& q : rcounts) {
509196af 518 totIncluded-=q.first;
e0e741df
CH
519 entries.push_back(Json::array {
520 -q.first, q.second.first.toString(), DNSRecordContent::NumberToType(q.second.second)
521 });
c89f8cd0 522 if(tot++>=100)
523 break;
524 }
509196af 525 if(queries.size() != totIncluded) {
e0e741df
CH
526 entries.push_back(Json::array {
527 (int)(queries.size() - totIncluded), "", ""
528 });
509196af 529 }
e0e741df 530 resp->setBody(Json::object { { "entries", entries } });
509196af 531 return;
532 }
533 else if(command == "get-remote-ring") {
534 vector<ComboAddress> queries;
535 if(req->getvars["name"]=="remotes")
536 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetRemotes);
537 else if(req->getvars["name"]=="servfail-remotes")
538 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetServfailRemotes);
66f2e6ad
KM
539 else if(req->getvars["name"]=="bogus-remotes")
540 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetBogusRemotes);
509196af 541 else if(req->getvars["name"]=="large-answer-remotes")
542 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetLargeAnswerRemotes);
3ddb9247 543
509196af 544 typedef map<ComboAddress,unsigned int,ComboAddress::addressOnlyLessThan> counts_t;
545 counts_t counts;
546 unsigned int total=0;
ef7cd021 547 for(const ComboAddress& q : queries) {
509196af 548 total++;
549 counts[q]++;
550 }
3ddb9247 551
509196af 552 typedef std::multimap<int, ComboAddress> rcounts_t;
553 rcounts_t rcounts;
3ddb9247 554
509196af 555 for(counts_t::const_iterator i=counts.begin(); i != counts.end(); ++i)
556 rcounts.insert(make_pair(-i->second, i->first));
557
e0e741df 558 Json::array entries;
509196af 559 unsigned int tot=0, totIncluded=0;
ef7cd021 560 for(const rcounts_t::value_type& q : rcounts) {
509196af 561 totIncluded-=q.first;
e0e741df
CH
562 entries.push_back(Json::array {
563 -q.first, q.second.toString()
564 });
509196af 565 if(tot++>=100)
566 break;
567 }
568 if(queries.size() != totIncluded) {
e0e741df 569 entries.push_back(Json::array {
d6dcfe36 570 (int)(queries.size() - totIncluded), ""
e0e741df 571 });
509196af 572 }
573
e0e741df 574 resp->setBody(Json::object { { "entries", entries } });
c89f8cd0 575 return;
3ae143b0 576 } else {
692829aa 577 resp->setErrorResult("Command '"+command+"' not found", 404);
3ae143b0 578 }
a7a902fb 579}
825fa717
CH
580
581
582void AsyncServerNewConnectionMT(void *p) {
583 AsyncServer *server = (AsyncServer*)p;
2cfc20bd 584
825fa717 585 try {
2cfc20bd 586 auto socket = server->accept(); // this is actually a shared_ptr
8a781bb5
RG
587 if (socket) {
588 server->d_asyncNewConnectionCallback(socket);
589 }
825fa717
CH
590 } catch (NetworkError &e) {
591 // we're running in a shared process/thread, so can't just terminate/abort.
e6a9dde5 592 g_log<<Logger::Warning<<"Network error in web thread: "<<e.what()<<endl;
825fa717
CH
593 return;
594 }
2cfc20bd 595 catch (...) {
e6a9dde5 596 g_log<<Logger::Warning<<"Unknown error in web thread"<<endl;
2cfc20bd 597
598 return;
599 }
600
825fa717
CH
601}
602
603void AsyncServer::asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback)
604{
605 d_asyncNewConnectionCallback = callback;
606 fdm->addReadFD(d_server_socket.getHandle(), boost::bind(&AsyncServer::newConnection, this));
607}
608
609void AsyncServer::newConnection()
610{
f165a1f4 611 getMT()->makeThread(&AsyncServerNewConnectionMT, this);
825fa717
CH
612}
613
5d7bd100 614// This is an entry point from FDM, so it needs to catch everything.
b184a9dc 615void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const
5d7bd100 616try {
825fa717 617 HttpRequest req;
583ea80d
CH
618 YaHTTP::AsyncRequestLoader yarl;
619 yarl.initialize(&req);
825fa717 620 client->setNonBlocking();
3ddb9247 621
825fa717
CH
622 string data;
623 try {
624 while(!req.complete) {
d4c53d8c 625 int bytes = arecvtcp(data, 16384, client.get(), true);
825fa717
CH
626 if (bytes > 0) {
627 req.complete = yarl.feed(data);
628 } else {
629 // read error OR EOF
630 break;
631 }
632 }
583ea80d 633 yarl.finalize();
825fa717
CH
634 } catch (YaHTTP::ParseError &e) {
635 // request stays incomplete
636 }
637
34c513f9
RG
638 HttpResponse resp;
639 handleRequest(req, resp);
825fa717
CH
640 ostringstream ss;
641 resp.write(ss);
642 data = ss.str();
643
644 // now send the reply
d4c53d8c 645 if (asendtcp(data, client.get()) == -1 || data.empty()) {
e6a9dde5 646 g_log<<Logger::Error<<"Failed sending reply to HTTP client"<<endl;
825fa717
CH
647 }
648}
5d7bd100 649catch(PDNSException &e) {
e6a9dde5 650 g_log<<Logger::Error<<"HTTP Exception: "<<e.reason<<endl;
5d7bd100
CH
651}
652catch(std::exception &e) {
653 if(strstr(e.what(), "timeout")==0)
e6a9dde5 654 g_log<<Logger::Error<<"HTTP STL Exception: "<<e.what()<<endl;
5d7bd100
CH
655}
656catch(...) {
e6a9dde5 657 g_log<<Logger::Error<<"HTTP: Unknown exception"<<endl;
5d7bd100 658}
825fa717
CH
659
660void AsyncWebServer::go() {
661 if (!d_server)
662 return;
690984d4
RG
663 auto server = std::dynamic_pointer_cast<AsyncServer>(d_server);
664 if (!server)
665 return;
666 server->asyncWaitForConnections(d_fdm, boost::bind(&AsyncWebServer::serveConnection, this, _1));
825fa717 667}