]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-recursor.cc
auth 4.1 build: Switch to devtoolset 7 for el6
[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"
fb4b38f1 42
6dfff36f 43extern thread_local FDMultiplexer* t_fdm;
825fa717 44
639a6287 45using json11::Json;
a7a902fb 46
6ec5e728
CH
47void productServerStatisticsFetch(map<string,string>& out)
48{
49 map<string,string> stats = getAllStatsMap();
50 out.swap(stats);
51}
52
c348c0c8
CH
53static 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
69static 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 116static 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 154static 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 226static 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
247static 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
293static 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
326static 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 369static 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 386static 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 415RecursorWebServer::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 442void 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
544void 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
565void 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
571void 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 577void AsyncWebServer::serveConnection(std::shared_ptr<Socket> client) const
5d7bd100 578try {
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
611catch(PDNSException &e) {
612 L<<Logger::Error<<"HTTP Exception: "<<e.reason<<endl;
613}
614catch(std::exception &e) {
615 if(strstr(e.what(), "timeout")==0)
616 L<<Logger::Error<<"HTTP STL Exception: "<<e.what()<<endl;
617}
618catch(...) {
619 L<<Logger::Error<<"HTTP: Unknown exception"<<endl;
620}
825fa717
CH
621
622void 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}