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