]>
Commit | Line | Data |
---|---|---|
12471842 PL |
1 | /* |
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 | */ | |
50bed881 | 22 | #include "dnsdist.hh" |
23 | #include "sstuff.hh" | |
24 | #include "ext/json11/json11.hpp" | |
25 | #include "ext/incbin/incbin.h" | |
26 | #include "dolog.hh" | |
27 | #include <thread> | |
28 | #include <sstream> | |
29 | #include <yahttp/yahttp.hpp> | |
30 | #include "namespaces.hh" | |
31 | #include <sys/time.h> | |
32 | #include <sys/resource.h> | |
33 | #include "ext/incbin/incbin.h" | |
34 | #include "htmlfiles.h" | |
35 | #include "base64.hh" | |
85c7ca75 | 36 | #include "gettime.hh" |
50bed881 | 37 | |
56d68fad RG |
38 | bool g_apiReadWrite{false}; |
39 | std::string g_apiConfigDirectory; | |
40 | ||
41 | static bool apiWriteConfigFile(const string& filebasename, const string& content) | |
42 | { | |
43 | if (!g_apiReadWrite) { | |
44 | errlog("Not writing content to %s since the API is read-only", filebasename); | |
45 | return false; | |
46 | } | |
47 | ||
48 | if (g_apiConfigDirectory.empty()) { | |
49 | vinfolog("Not writing content to %s since the API configuration directory is not set", filebasename); | |
50 | return false; | |
51 | } | |
52 | ||
53 | string filename = g_apiConfigDirectory + "/" + filebasename + ".conf"; | |
54 | ofstream ofconf(filename.c_str()); | |
55 | if (!ofconf) { | |
56 | errlog("Could not open configuration fragment file '%s' for writing: %s", filename, stringerror()); | |
57 | return false; | |
58 | } | |
59 | ofconf << "-- Generated by the REST API, DO NOT EDIT" << endl; | |
60 | ofconf << content << endl; | |
61 | ofconf.close(); | |
62 | return true; | |
63 | } | |
64 | ||
65 | static void apiSaveACL(const NetmaskGroup& nmg) | |
66 | { | |
67 | vector<string> vec; | |
68 | g_ACL.getCopy().toStringVector(&vec); | |
69 | ||
70 | string acl; | |
71 | for(const auto& s : vec) { | |
72 | if (!acl.empty()) { | |
73 | acl += ", "; | |
74 | } | |
75 | acl += "\"" + s + "\""; | |
76 | } | |
77 | ||
78 | string content = "setACL({" + acl + "})"; | |
79 | apiWriteConfigFile("acl", content); | |
80 | } | |
50bed881 | 81 | |
87893e08 | 82 | static bool compareAuthorization(YaHTTP::Request& req, const string &expected_password, const string& expectedApiKey) |
50bed881 | 83 | { |
84 | // validate password | |
85 | YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization"); | |
86 | bool auth_ok = false; | |
87 | if (header != req.headers.end() && toLower(header->second).find("basic ") == 0) { | |
88 | string cookie = header->second.substr(6); | |
89 | ||
90 | string plain; | |
91 | B64Decode(cookie, plain); | |
92 | ||
93 | vector<string> cparts; | |
94 | stringtok(cparts, plain, ":"); | |
95 | ||
96 | // this gets rid of terminating zeros | |
97 | auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), expected_password.c_str()))); | |
98 | } | |
87893e08 RG |
99 | if (!auth_ok && !expectedApiKey.empty()) { |
100 | /* if this is a request for the API, | |
101 | check if the API key is correct */ | |
102 | if (req.url.path=="/jsonstat" || | |
c8adc34c RG |
103 | req.url.path=="/api/v1/servers/localhost" || |
104 | req.url.path=="/api/v1/servers/localhost/config" || | |
56d68fad | 105 | req.url.path=="/api/v1/servers/localhost/config/allow-from" || |
c8adc34c | 106 | req.url.path=="/api/v1/servers/localhost/statistics") { |
87893e08 RG |
107 | header = req.headers.find("x-api-key"); |
108 | if (header != req.headers.end()) { | |
109 | auth_ok = (0==strcmp(header->second.c_str(), expectedApiKey.c_str())); | |
110 | } | |
111 | } | |
112 | } | |
50bed881 | 113 | return auth_ok; |
114 | } | |
115 | ||
56d68fad RG |
116 | static bool isMethodAllowed(const YaHTTP::Request& req) |
117 | { | |
118 | if (req.method == "GET") { | |
119 | return true; | |
120 | } | |
121 | if (req.method == "PUT" && g_apiReadWrite) { | |
122 | if (req.url.path == "/api/v1/servers/localhost/config/allow-from") { | |
123 | return true; | |
124 | } | |
125 | } | |
126 | return false; | |
127 | } | |
128 | ||
5387cc78 RG |
129 | static void handleCORS(YaHTTP::Request& req, YaHTTP::Response& resp) |
130 | { | |
131 | YaHTTP::strstr_map_t::iterator origin = req.headers.find("Origin"); | |
132 | if (origin != req.headers.end()) { | |
133 | if (req.method == "OPTIONS") { | |
134 | /* Pre-flight request */ | |
56d68fad RG |
135 | if (g_apiReadWrite) { |
136 | resp.headers["Access-Control-Allow-Methods"] = "GET, PUT"; | |
137 | } | |
138 | else { | |
139 | resp.headers["Access-Control-Allow-Methods"] = "GET"; | |
140 | } | |
87893e08 | 141 | resp.headers["Access-Control-Allow-Headers"] = "Authorization, X-API-Key"; |
5387cc78 RG |
142 | } |
143 | ||
144 | resp.headers["Access-Control-Allow-Origin"] = origin->second; | |
145 | resp.headers["Access-Control-Allow-Credentials"] = "true"; | |
146 | } | |
147 | } | |
50bed881 | 148 | |
002decab RG |
149 | static void addSecurityHeaders(YaHTTP::Response& resp, const boost::optional<std::map<std::string, std::string> >& customHeaders) |
150 | { | |
151 | static const std::vector<std::pair<std::string, std::string> > headers = { | |
152 | { "X-Content-Type-Options", "nosniff" }, | |
153 | { "X-Frame-Options", "deny" }, | |
154 | { "X-Permitted-Cross-Domain-Policies", "none" }, | |
155 | { "X-XSS-Protection", "1; mode=block" }, | |
156 | { "Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'" }, | |
157 | }; | |
158 | ||
159 | for (const auto& h : headers) { | |
160 | if (customHeaders) { | |
161 | const auto& custom = customHeaders->find(h.first); | |
162 | if (custom != customHeaders->end()) { | |
163 | continue; | |
164 | } | |
165 | } | |
166 | resp.headers[h.first] = h.second; | |
167 | } | |
168 | } | |
169 | ||
170 | static void addCustomHeaders(YaHTTP::Response& resp, const boost::optional<std::map<std::string, std::string> >& customHeaders) | |
171 | { | |
172 | if (!customHeaders) | |
173 | return; | |
174 | ||
175 | for (const auto& c : *customHeaders) { | |
176 | if (!c.second.empty()) { | |
177 | resp.headers[c.first] = c.second; | |
178 | } | |
179 | } | |
180 | } | |
181 | ||
182 | static void connectionThread(int sock, ComboAddress remote, string password, string apiKey, const boost::optional<std::map<std::string, std::string> >& customHeaders) | |
50bed881 | 183 | { |
184 | using namespace json11; | |
fb7f8ec3 | 185 | vinfolog("Webserver handling connection from %s", remote.toStringWithPort()); |
56d68fad | 186 | |
438a189b | 187 | try { |
56d68fad | 188 | YaHTTP::AsyncRequestLoader yarl; |
438a189b | 189 | YaHTTP::Request req; |
56d68fad RG |
190 | bool finished = false; |
191 | ||
192 | yarl.initialize(&req); | |
193 | while(!finished) { | |
194 | int bytes; | |
195 | char buf[1024]; | |
196 | bytes = read(sock, buf, sizeof(buf)); | |
197 | if (bytes > 0) { | |
198 | string data = string(buf, bytes); | |
199 | finished = yarl.feed(data); | |
200 | } else { | |
201 | // read error OR EOF | |
202 | break; | |
203 | } | |
204 | } | |
205 | yarl.finalize(); | |
50bed881 | 206 | |
438a189b | 207 | string command=req.getvars["command"]; |
50bed881 | 208 | |
438a189b | 209 | req.getvars.erase("_"); // jQuery cache buster |
50bed881 | 210 | |
e3d76be2 RG |
211 | YaHTTP::Response resp; |
212 | resp.version = req.version; | |
c8c1a4fc | 213 | const string charset = "; charset=utf-8"; |
002decab RG |
214 | |
215 | addCustomHeaders(resp, customHeaders); | |
216 | addSecurityHeaders(resp, customHeaders); | |
217 | ||
87893e08 RG |
218 | /* no need to send back the API key if any */ |
219 | resp.headers.erase("X-API-Key"); | |
50bed881 | 220 | |
5387cc78 RG |
221 | if(req.method == "OPTIONS") { |
222 | /* the OPTIONS method should not require auth, otherwise it breaks CORS */ | |
223 | handleCORS(req, resp); | |
224 | resp.status=200; | |
225 | } | |
87893e08 | 226 | else if (!compareAuthorization(req, password, apiKey)) { |
cc55a8f2 JB |
227 | YaHTTP::strstr_map_t::iterator header = req.headers.find("authorization"); |
228 | if (header != req.headers.end()) | |
229 | errlog("HTTP Request \"%s\" from %s: Web Authentication failed", req.url.path, remote.toStringWithPort()); | |
438a189b | 230 | resp.status=401; |
231 | resp.body="<h1>Unauthorized</h1>"; | |
232 | resp.headers["WWW-Authenticate"] = "basic realm=\"PowerDNS\""; | |
50bed881 | 233 | |
50bed881 | 234 | } |
56d68fad | 235 | else if(!isMethodAllowed(req)) { |
5387cc78 RG |
236 | resp.status=405; |
237 | } | |
238 | else if(req.url.path=="/jsonstat") { | |
239 | handleCORS(req, resp); | |
438a189b | 240 | resp.status=200; |
d745cd6f | 241 | |
5387cc78 RG |
242 | if(command=="stats") { |
243 | auto obj=Json::object { | |
244 | { "packetcache-hits", 0}, | |
245 | { "packetcache-misses", 0}, | |
246 | { "over-capacity-drops", 0 }, | |
247 | { "too-old-drops", 0 }, | |
248 | { "server-policy", g_policy.getLocal()->name} | |
249 | }; | |
250 | ||
251 | for(const auto& e : g_stats.entries) { | |
252 | if(const auto& val = boost::get<DNSDistStats::stat_t*>(&e.second)) | |
ca12836d | 253 | obj.insert({e.first, (double)(*val)->load()}); |
5387cc78 RG |
254 | else if (const auto& val = boost::get<double*>(&e.second)) |
255 | obj.insert({e.first, (**val)}); | |
256 | else | |
257 | obj.insert({e.first, (int)(*boost::get<DNSDistStats::statfunction_t>(&e.second))(e.first)}); | |
258 | } | |
259 | Json my_json = obj; | |
260 | resp.body=my_json.dump(); | |
5d1c98df | 261 | resp.headers["Content-Type"] = "application/json"; |
d745cd6f | 262 | } |
5387cc78 RG |
263 | else if(command=="dynblocklist") { |
264 | Json::object obj; | |
265 | auto slow = g_dynblockNMG.getCopy(); | |
266 | struct timespec now; | |
85c7ca75 | 267 | gettime(&now); |
5387cc78 RG |
268 | for(const auto& e: slow) { |
269 | if(now < e->second.until ) { | |
8429ad04 RG |
270 | Json::object thing{ |
271 | {"reason", e->second.reason}, | |
272 | {"seconds", (double)(e->second.until.tv_sec - now.tv_sec)}, | |
273 | {"blocks", (double)e->second.blocks} | |
274 | }; | |
5387cc78 RG |
275 | obj.insert({e->first.toString(), thing}); |
276 | } | |
277 | } | |
0b71b874 | 278 | |
279 | auto slow2 = g_dynblockSMT.getCopy(); | |
280 | slow2.visit([&now,&obj](const SuffixMatchTree<DynBlock>& node) { | |
281 | if(now <node.d_value.until) { | |
282 | string dom("empty"); | |
283 | if(!node.d_value.domain.empty()) | |
284 | dom = node.d_value.domain.toString(); | |
285 | Json::object thing{{"reason", node.d_value.reason}, {"seconds", (double)(node.d_value.until.tv_sec - now.tv_sec)}, | |
286 | {"blocks", (double)node.d_value.blocks} }; | |
287 | ||
288 | obj.insert({dom, thing}); | |
289 | } | |
290 | }); | |
291 | ||
292 | ||
293 | ||
8429ad04 RG |
294 | Json my_json = obj; |
295 | resp.body=my_json.dump(); | |
296 | resp.headers["Content-Type"] = "application/json"; | |
297 | } | |
298 | else if(command=="ebpfblocklist") { | |
299 | Json::object obj; | |
300 | #ifdef HAVE_EBPF | |
301 | struct timespec now; | |
302 | gettime(&now); | |
303 | for (const auto& dynbpf : g_dynBPFFilters) { | |
304 | std::vector<std::tuple<ComboAddress, uint64_t, struct timespec> > addrStats = dynbpf->getAddrStats(); | |
305 | for (const auto& entry : addrStats) { | |
306 | Json::object thing | |
307 | { | |
308 | {"seconds", (double)(std::get<2>(entry).tv_sec - now.tv_sec)}, | |
309 | {"blocks", (double)(std::get<1>(entry))} | |
310 | }; | |
311 | obj.insert({std::get<0>(entry).toString(), thing }); | |
312 | } | |
313 | } | |
314 | #endif /* HAVE_EBPF */ | |
5387cc78 RG |
315 | Json my_json = obj; |
316 | resp.body=my_json.dump(); | |
5d1c98df | 317 | resp.headers["Content-Type"] = "application/json"; |
5387cc78 RG |
318 | } |
319 | else { | |
320 | resp.status=404; | |
7be71139 | 321 | } |
7be71139 | 322 | } |
46d06a12 | 323 | else if(req.url.path=="/api/v1/servers/localhost") { |
5387cc78 | 324 | handleCORS(req, resp); |
438a189b | 325 | resp.status=200; |
326 | ||
327 | Json::array servers; | |
328 | auto localServers = g_dstates.getCopy(); | |
329 | int num=0; | |
330 | for(const auto& a : localServers) { | |
331 | string status; | |
332 | if(a->availability == DownstreamState::Availability::Up) | |
333 | status = "UP"; | |
334 | else if(a->availability == DownstreamState::Availability::Down) | |
335 | status = "DOWN"; | |
336 | else | |
337 | status = (a->upStatus ? "up" : "down"); | |
357c22dd | 338 | Json::array pools; |
438a189b | 339 | for(const auto& p: a->pools) |
357c22dd RG |
340 | pools.push_back(p); |
341 | ||
438a189b | 342 | Json::object server{ |
357c22dd | 343 | {"id", num++}, |
18eeccc9 | 344 | {"name", a->name}, |
357c22dd RG |
345 | {"address", a->remote.toStringWithPort()}, |
346 | {"state", status}, | |
347 | {"qps", (int)a->queryLoad}, | |
348 | {"qpsLimit", (int)a->qps.getRate()}, | |
349 | {"outstanding", (int)a->outstanding}, | |
350 | {"reuseds", (int)a->reuseds}, | |
351 | {"weight", (int)a->weight}, | |
352 | {"order", (int)a->order}, | |
353 | {"pools", pools}, | |
354 | {"latency", (int)(a->latencyUsec/1000.0)}, | |
355 | {"queries", (int)a->queries}}; | |
356 | ||
36927800 RG |
357 | /* sending a latency for a DOWN server doesn't make sense */ |
358 | if (a->availability == DownstreamState::Availability::Down) { | |
359 | server["latency"] = nullptr; | |
360 | } | |
361 | ||
438a189b | 362 | servers.push_back(server); |
363 | } | |
364 | ||
65dec2a3 RG |
365 | Json::array frontends; |
366 | num=0; | |
367 | for(const auto& front : g_frontends) { | |
368 | if (front->udpFD == -1 && front->tcpFD == -1) | |
369 | continue; | |
370 | Json::object frontend{ | |
371 | { "id", num++ }, | |
372 | { "address", front->local.toStringWithPort() }, | |
373 | { "udp", front->udpFD >= 0 }, | |
374 | { "tcp", front->tcpFD >= 0 }, | |
375 | { "queries", (double) front->queries.load() } | |
376 | }; | |
377 | frontends.push_back(frontend); | |
378 | } | |
379 | ||
438a189b | 380 | Json::array rules; |
381 | auto localRules = g_rulactions.getCopy(); | |
382 | num=0; | |
383 | for(const auto& a : localRules) { | |
384 | Json::object rule{ | |
385 | {"id", num++}, | |
1f4059be | 386 | {"matches", (int)a.first->d_matches}, |
387 | {"rule", a.first->toString()}, | |
388 | {"action", a.second->toString()}, | |
389 | {"action-stats", a.second->getStats()} | |
390 | }; | |
438a189b | 391 | rules.push_back(rule); |
392 | } | |
da5d4999 | 393 | |
46e8b49e RG |
394 | Json::array responseRules; |
395 | auto localResponseRules = g_resprulactions.getCopy(); | |
396 | num=0; | |
397 | for(const auto& a : localResponseRules) { | |
398 | Json::object rule{ | |
399 | {"id", num++}, | |
400 | {"matches", (int)a.first->d_matches}, | |
401 | {"rule", a.first->toString()}, | |
402 | {"action", a.second->toString()}, | |
403 | }; | |
404 | responseRules.push_back(rule); | |
405 | } | |
da5d4999 | 406 | |
407 | string acl; | |
408 | ||
409 | vector<string> vec; | |
da5d4999 | 410 | g_ACL.getCopy().toStringVector(&vec); |
411 | ||
412 | for(const auto& s : vec) { | |
413 | if(!acl.empty()) acl += ", "; | |
414 | acl+=s; | |
415 | } | |
416 | string localaddresses; | |
417 | for(const auto& loc : g_locals) { | |
418 | if(!localaddresses.empty()) localaddresses += ", "; | |
4c34246d | 419 | localaddresses += std::get<0>(loc).toStringWithPort(); |
da5d4999 | 420 | } |
50bed881 | 421 | |
438a189b | 422 | Json my_json = Json::object { |
423 | { "daemon_type", "dnsdist" }, | |
52f40d27 | 424 | { "version", VERSION}, |
438a189b | 425 | { "servers", servers}, |
65dec2a3 | 426 | { "frontends", frontends }, |
438a189b | 427 | { "rules", rules}, |
46e8b49e | 428 | { "response-rules", responseRules}, |
da5d4999 | 429 | { "acl", acl}, |
430 | { "local", localaddresses} | |
438a189b | 431 | }; |
5d1c98df | 432 | resp.headers["Content-Type"] = "application/json"; |
438a189b | 433 | resp.body=my_json.dump(); |
c8adc34c RG |
434 | } |
435 | else if(req.url.path=="/api/v1/servers/localhost/statistics") { | |
436 | handleCORS(req, resp); | |
437 | resp.status=200; | |
438 | ||
439 | Json::array doc; | |
440 | for(const auto& item : g_stats.entries) { | |
441 | if(const auto& val = boost::get<DNSDistStats::stat_t*>(&item.second)) { | |
442 | doc.push_back(Json::object { | |
443 | { "type", "StatisticItem" }, | |
444 | { "name", item.first }, | |
445 | { "value", (double)(*val)->load() } | |
446 | }); | |
447 | } | |
448 | else if (const auto& val = boost::get<double*>(&item.second)) { | |
449 | doc.push_back(Json::object { | |
450 | { "type", "StatisticItem" }, | |
451 | { "name", item.first }, | |
452 | { "value", (**val) } | |
453 | }); | |
454 | } | |
455 | else { | |
456 | doc.push_back(Json::object { | |
457 | { "type", "StatisticItem" }, | |
458 | { "name", item.first }, | |
459 | { "value", (int)(*boost::get<DNSDistStats::statfunction_t>(&item.second))(item.first) } | |
460 | }); | |
461 | } | |
462 | } | |
463 | Json my_json = doc; | |
464 | resp.body=my_json.dump(); | |
465 | resp.headers["Content-Type"] = "application/json"; | |
466 | } | |
467 | else if(req.url.path=="/api/v1/servers/localhost/config") { | |
468 | handleCORS(req, resp); | |
469 | resp.status=200; | |
50bed881 | 470 | |
c8adc34c RG |
471 | Json::array doc; |
472 | typedef boost::variant<bool, double, std::string> configentry_t; | |
473 | std::vector<std::pair<std::string, configentry_t> > configEntries { | |
474 | { "acl", g_ACL.getCopy().toString() }, | |
475 | { "control-socket", g_serverControl.toStringWithPort() }, | |
476 | { "ecs-override", g_ECSOverride }, | |
477 | { "ecs-source-prefix-v4", (double) g_ECSSourcePrefixV4 }, | |
478 | { "ecs-source-prefix-v6", (double) g_ECSSourcePrefixV6 }, | |
479 | { "fixup-case", g_fixupCase }, | |
480 | { "max-outstanding", (double) g_maxOutstanding }, | |
481 | { "server-policy", g_policy.getLocal()->name }, | |
482 | { "stale-cache-entries-ttl", (double) g_staleCacheEntriesTTL }, | |
483 | { "tcp-recv-timeout", (double) g_tcpRecvTimeout }, | |
484 | { "tcp-send-timeout", (double) g_tcpSendTimeout }, | |
485 | { "truncate-tc", g_truncateTC }, | |
486 | { "verbose", g_verbose }, | |
487 | { "verbose-health-checks", g_verboseHealthChecks } | |
488 | }; | |
489 | for(const auto& item : configEntries) { | |
490 | if (const auto& val = boost::get<bool>(&item.second)) { | |
491 | doc.push_back(Json::object { | |
492 | { "type", "ConfigSetting" }, | |
493 | { "name", item.first }, | |
494 | { "value", *val } | |
495 | }); | |
496 | } | |
497 | else if (const auto& val = boost::get<string>(&item.second)) { | |
498 | doc.push_back(Json::object { | |
499 | { "type", "ConfigSetting" }, | |
500 | { "name", item.first }, | |
501 | { "value", *val } | |
502 | }); | |
503 | } | |
504 | else if (const auto& val = boost::get<double>(&item.second)) { | |
505 | doc.push_back(Json::object { | |
506 | { "type", "ConfigSetting" }, | |
507 | { "name", item.first }, | |
508 | { "value", *val } | |
509 | }); | |
510 | } | |
511 | } | |
512 | Json my_json = doc; | |
513 | resp.body=my_json.dump(); | |
514 | resp.headers["Content-Type"] = "application/json"; | |
438a189b | 515 | } |
56d68fad RG |
516 | else if(req.url.path=="/api/v1/servers/localhost/config/allow-from") { |
517 | handleCORS(req, resp); | |
518 | ||
519 | resp.headers["Content-Type"] = "application/json"; | |
520 | resp.status=200; | |
521 | ||
522 | if (req.method == "PUT") { | |
523 | std::string err; | |
524 | Json doc = Json::parse(req.body, err); | |
525 | ||
526 | if (!doc.is_null()) { | |
527 | NetmaskGroup nmg; | |
528 | auto aclList = doc["value"]; | |
529 | if (aclList.is_array()) { | |
530 | ||
531 | for (auto value : aclList.array_items()) { | |
532 | try { | |
533 | nmg.addMask(value.string_value()); | |
534 | } catch (NetmaskException &e) { | |
535 | resp.status = 400; | |
536 | break; | |
537 | } | |
538 | } | |
539 | ||
540 | if (resp.status == 200) { | |
541 | infolog("Updating the ACL via the API to %s", nmg.toString()); | |
542 | g_ACL.setState(nmg); | |
543 | apiSaveACL(nmg); | |
544 | } | |
545 | } | |
546 | else { | |
547 | resp.status = 400; | |
548 | } | |
549 | } | |
550 | else { | |
551 | resp.status = 400; | |
552 | } | |
553 | } | |
554 | if (resp.status == 200) { | |
555 | Json::array acl; | |
556 | vector<string> vec; | |
557 | g_ACL.getCopy().toStringVector(&vec); | |
558 | ||
559 | for(const auto& s : vec) { | |
560 | acl.push_back(s); | |
561 | } | |
562 | ||
563 | Json::object obj{ | |
564 | { "type", "ConfigSetting" }, | |
565 | { "name", "allow-from" }, | |
566 | { "value", acl } | |
567 | }; | |
568 | Json my_json = obj; | |
569 | resp.body=my_json.dump(); | |
570 | } | |
571 | } | |
e3d76be2 RG |
572 | else if(!req.url.path.empty() && g_urlmap.count(req.url.path.c_str()+1)) { |
573 | resp.body.assign(g_urlmap[req.url.path.c_str()+1]); | |
fe20a1c1 | 574 | vector<string> parts; |
e3d76be2 | 575 | stringtok(parts, req.url.path, "."); |
fe20a1c1 | 576 | if(parts.back() == "html") |
c8c1a4fc | 577 | resp.headers["Content-Type"] = "text/html" + charset; |
fe20a1c1 | 578 | else if(parts.back() == "css") |
c8c1a4fc | 579 | resp.headers["Content-Type"] = "text/css" + charset; |
fe20a1c1 | 580 | else if(parts.back() == "js") |
c8c1a4fc | 581 | resp.headers["Content-Type"] = "application/javascript" + charset; |
864a6641 RG |
582 | else if(parts.back() == "png") |
583 | resp.headers["Content-Type"] = "image/png"; | |
438a189b | 584 | resp.status=200; |
585 | } | |
e3d76be2 | 586 | else if(req.url.path=="/") { |
438a189b | 587 | resp.body.assign(g_urlmap["index.html"]); |
c8c1a4fc | 588 | resp.headers["Content-Type"] = "text/html" + charset; |
438a189b | 589 | resp.status=200; |
590 | } | |
591 | else { | |
e3d76be2 | 592 | // cerr<<"404 for: "<<req.url.path<<endl; |
438a189b | 593 | resp.status=404; |
594 | } | |
50bed881 | 595 | |
438a189b | 596 | std::ostringstream ofs; |
597 | ofs << resp; | |
598 | string done; | |
599 | done=ofs.str(); | |
600 | writen2(sock, done.c_str(), done.size()); | |
50bed881 | 601 | |
56d68fad RG |
602 | close(sock); |
603 | sock = -1; | |
438a189b | 604 | } |
59e9504e RG |
605 | catch(const YaHTTP::ParseError& e) { |
606 | vinfolog("Webserver thread died with parse error exception while processing a request from %s: %s", remote.toStringWithPort(), e.what()); | |
56d68fad | 607 | close(sock); |
59e9504e | 608 | } |
34a6dd76 | 609 | catch(const std::exception& e) { |
59e9504e | 610 | errlog("Webserver thread died with exception while processing a request from %s: %s", remote.toStringWithPort(), e.what()); |
56d68fad | 611 | close(sock); |
34a6dd76 RG |
612 | } |
613 | catch(...) { | |
59e9504e | 614 | errlog("Webserver thread died with exception while processing a request from %s", remote.toStringWithPort()); |
56d68fad | 615 | close(sock); |
34a6dd76 | 616 | } |
50bed881 | 617 | } |
002decab | 618 | void dnsdistWebserverThread(int sock, const ComboAddress& local, const std::string& password, const std::string& apiKey, const boost::optional<std::map<std::string, std::string> >& customHeaders) |
50bed881 | 619 | { |
d745cd6f | 620 | warnlog("Webserver launched on %s", local.toStringWithPort()); |
50bed881 | 621 | for(;;) { |
622 | try { | |
623 | ComboAddress remote(local); | |
624 | int fd = SAccept(sock, remote); | |
625 | vinfolog("Got connection from %s", remote.toStringWithPort()); | |
002decab | 626 | std::thread t(connectionThread, fd, remote, password, apiKey, customHeaders); |
50bed881 | 627 | t.detach(); |
628 | } | |
629 | catch(std::exception& e) { | |
630 | errlog("Had an error accepting new webserver connection: %s", e.what()); | |
631 | } | |
632 | } | |
633 | } |