]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/webserver.cc
limit compression pointers to 14 bits
[thirdparty/pdns.git] / pdns / webserver.cc
CommitLineData
12c86877 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
731f58b8 25#include "utility.hh"
12c86877 26#include "webserver.hh"
12c86877 27#include "misc.hh"
d4c53d8c 28#include <thread>
519f5484 29#include "threadname.hh"
12c86877
BH
30#include <vector>
31#include "logger.hh"
32#include <stdio.h>
33#include "dns.hh"
2db9c30e 34#include "base64.hh"
33196945 35#include "json.hh"
3f9a8002 36#include "uuid-utils.hh"
583ea80d 37#include <yahttp/router.hpp>
12c86877 38
5938c49f
CH
39json11::Json HttpRequest::json()
40{
41 string err;
42 if(this->body.empty()) {
dc0db7b8 43 g_log<<Logger::Debug<<logprefix<<"JSON document expected in request body, but body was empty" << endl;
5938c49f
CH
44 throw HttpBadRequestException();
45 }
46 json11::Json doc = json11::Json::parse(this->body, err);
47 if (doc.is_null()) {
dc0db7b8 48 g_log<<Logger::Debug<<logprefix<<"parsing of JSON document failed:" << err << endl;
5938c49f
CH
49 throw HttpBadRequestException();
50 }
51 return doc;
52}
53
bbef8f04
CH
54bool HttpRequest::compareAuthorization(const string &expected_password)
55{
56 // validate password
57 YaHTTP::strstr_map_t::iterator header = headers.find("authorization");
58 bool auth_ok = false;
59 if (header != headers.end() && toLower(header->second).find("basic ") == 0) {
60 string cookie = header->second.substr(6);
61
62 string plain;
63 B64Decode(cookie, plain);
64
65 vector<string> cparts;
66 stringtok(cparts, plain, ":");
67
68 // this gets rid of terminating zeros
69 auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), expected_password.c_str())));
70 }
71 return auth_ok;
72}
73
74bool HttpRequest::compareHeader(const string &header_name, const string &expected_value)
75{
76 YaHTTP::strstr_map_t::iterator header = headers.find(header_name);
77 if (header == headers.end())
78 return false;
79
80 // this gets rid of terminating zeros
81 return (0==strcmp(header->second.c_str(), expected_value.c_str()));
82}
83
84
5938c49f
CH
85void HttpResponse::setBody(const json11::Json& document)
86{
87 document.dump(this->body);
88}
89
dd079764 90void HttpResponse::setErrorResult(const std::string& message, const int status_)
692829aa
CH
91{
92 setBody(json11::Json::object { { "error", message } });
dd079764 93 this->status = status_;
692829aa
CH
94}
95
dd079764 96void HttpResponse::setSuccessResult(const std::string& message, const int status_)
692829aa
CH
97{
98 setBody(json11::Json::object { { "result", message } });
dd079764 99 this->status = status_;
692829aa
CH
100}
101
bbef8f04 102static void bareHandlerWrapper(WebServer::HandlerFunction handler, YaHTTP::Request* req, YaHTTP::Response* resp)
12c86877 103{
583ea80d
CH
104 // wrapper to convert from YaHTTP::* to our subclasses
105 handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
106}
232f0877 107
bbef8f04 108void WebServer::registerBareHandler(const string& url, HandlerFunction handler)
583ea80d 109{
bbef8f04 110 YaHTTP::THandlerFunction f = boost::bind(&bareHandlerWrapper, handler, _1, _2);
583ea80d 111 YaHTTP::Router::Any(url, f);
232f0877
CH
112}
113
7f7481be
AT
114static bool optionsHandler(HttpRequest* req, HttpResponse* resp) {
115 if (req->method == "OPTIONS") {
116 resp->headers["access-control-allow-origin"] = "*";
117 resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key";
118 resp->headers["access-control-allow-methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS";
119 resp->headers["access-control-max-age"] = "3600";
120 resp->status = 200;
121 resp->headers["content-type"]= "text/plain";
122 resp->body = "";
123 return true;
124 }
125 return false;
126}
127
c563cbe5 128void WebServer::apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp, bool allowPassword) {
7f7481be
AT
129 if (optionsHandler(req, resp)) return;
130
131 resp->headers["access-control-allow-origin"] = "*";
132
7579a7b9 133 if (d_apikey.empty()) {
a53cd863 134 g_log<<Logger::Error<<req->logprefix<<"HTTP API Request \"" << req->url.path << "\": Authentication failed, API Key missing in config" << endl;
53255086 135 throw HttpUnauthorizedException("X-API-Key");
bbef8f04 136 }
7579a7b9
CH
137
138 bool auth_ok = req->compareHeader("x-api-key", d_apikey) || req->getvars["api-key"] == d_apikey;
c563cbe5
CH
139
140 if (!auth_ok && allowPassword) {
141 if (!d_webserverPassword.empty()) {
142 auth_ok = req->compareAuthorization(d_webserverPassword);
143 } else {
144 auth_ok = true;
145 }
146 }
147
bbef8f04 148 if (!auth_ok) {
a53cd863 149 g_log<<Logger::Error<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Authentication by API Key failed" << endl;
53255086 150 throw HttpUnauthorizedException("X-API-Key");
bbef8f04
CH
151 }
152
3ae143b0
CH
153 resp->headers["Content-Type"] = "application/json";
154
7fe2a2dc
CH
155 // security headers
156 resp->headers["X-Content-Type-Options"] = "nosniff";
157 resp->headers["X-Frame-Options"] = "deny";
158 resp->headers["X-Permitted-Cross-Domain-Policies"] = "none";
159 resp->headers["X-XSS-Protection"] = "1; mode=block";
160 resp->headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
3ae143b0 161
583ea80d 162 req->getvars.erase("_"); // jQuery cache buster
3ae143b0
CH
163
164 try {
583ea80d 165 resp->status = 200;
3ae143b0
CH
166 handler(req, resp);
167 } catch (ApiException &e) {
692829aa 168 resp->setErrorResult(e.what(), 422);
6ec5e728
CH
169 return;
170 } catch (JsonException &e) {
692829aa 171 resp->setErrorResult(e.what(), 422);
3ae143b0
CH
172 return;
173 }
174
37663c3b
CH
175 if (resp->status == 204) {
176 // No Content -> no Content-Type.
177 resp->headers.erase("Content-Type");
178 }
3ae143b0
CH
179}
180
c563cbe5
CH
181void WebServer::registerApiHandler(const string& url, HandlerFunction handler, bool allowPassword) {
182 HandlerFunction f = boost::bind(&WebServer::apiWrapper, this, handler, _1, _2, allowPassword);
bbef8f04
CH
183 registerBareHandler(url, f);
184}
185
7579a7b9
CH
186void WebServer::webWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp) {
187 if (!d_webserverPassword.empty()) {
188 bool auth_ok = req->compareAuthorization(d_webserverPassword);
bbef8f04 189 if (!auth_ok) {
a53cd863 190 g_log<<Logger::Debug<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Web Authentication failed" << endl;
53255086 191 throw HttpUnauthorizedException("Basic");
bbef8f04
CH
192 }
193 }
194
195 handler(req, resp);
196}
197
198void WebServer::registerWebHandler(const string& url, HandlerFunction handler) {
7579a7b9 199 HandlerFunction f = boost::bind(&WebServer::webWrapper, this, handler, _1, _2);
bbef8f04 200 registerBareHandler(url, f);
3ae143b0
CH
201}
202
b184a9dc 203static void *WebServerConnectionThreadStart(const WebServer* webServer, std::shared_ptr<Socket> client) {
519f5484 204 setThreadName("pdns-r/webhndlr");
b184a9dc 205 webServer->serveConnection(client);
d4c53d8c 206 return nullptr;
232f0877
CH
207}
208
b184a9dc 209void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
80d59cd1 210{
80d59cd1
CH
211 // set default headers
212 resp.headers["Content-Type"] = "text/html; charset=utf-8";
33196945 213
12c86877 214 try {
825fa717 215 if (!req.complete) {
dc0db7b8 216 g_log<<Logger::Debug<<req.logprefix<<"Incomplete request" << endl;
825fa717
CH
217 throw HttpBadRequestException();
218 }
219
dc0db7b8 220 g_log<<Logger::Debug<<req.logprefix<<"Handling request \"" << req.url.path << "\"" << endl;
825fa717 221
80d59cd1
CH
222 YaHTTP::strstr_map_t::iterator header;
223
224 if ((header = req.headers.find("accept")) != req.headers.end()) {
225 // json wins over html
226 if (header->second.find("application/json") != std::string::npos) {
227 req.accept_json = true;
228 } else if (header->second.find("text/html") != std::string::npos) {
229 req.accept_html = true;
230 }
12c86877
BH
231 }
232
583ea80d
CH
233 YaHTTP::THandlerFunction handler;
234 if (!YaHTTP::Router::Route(&req, handler)) {
dc0db7b8 235 g_log<<Logger::Debug<<req.logprefix<<"No route found for \"" << req.url.path << "\"" << endl;
33196945 236 throw HttpNotFoundException();
12c86877 237 }
12c86877 238
0f67eeda 239 try {
583ea80d 240 handler(&req, &resp);
dc0db7b8 241 g_log<<Logger::Debug<<req.logprefix<<"Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl;
0f67eeda 242 }
4d706054 243 catch(HttpException&) {
583ea80d
CH
244 throw;
245 }
0f67eeda 246 catch(PDNSException &e) {
a53cd863 247 g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl;
0f67eeda
CH
248 throw HttpInternalServerErrorException();
249 }
250 catch(std::exception &e) {
a53cd863 251 g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": STL Exception: " << e.what() << endl;
0f67eeda
CH
252 throw HttpInternalServerErrorException();
253 }
254 catch(...) {
a53cd863 255 g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Unknown Exception" << endl;
0f67eeda
CH
256 throw HttpInternalServerErrorException();
257 }
12c86877 258 }
33196945 259 catch(HttpException &e) {
80d59cd1 260 resp = e.response();
a53cd863
PL
261 // TODO rm this logline?
262 g_log<<Logger::Debug<<req.logprefix<<"Error result for \"" << req.url.path << "\": " << resp.status << endl;
80d59cd1 263 string what = YaHTTP::Utility::status2text(resp.status);
02c04144 264 if(req.accept_html) {
80d59cd1
CH
265 resp.headers["Content-Type"] = "text/html; charset=utf-8";
266 resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
02c04144 267 } else if (req.accept_json) {
80d59cd1 268 resp.headers["Content-Type"] = "application/json";
8204102e
PL
269 if (resp.body.empty()) {
270 resp.setErrorResult(what, resp.status);
271 }
33196945 272 } else {
80d59cd1
CH
273 resp.headers["Content-Type"] = "text/plain; charset=utf-8";
274 resp.body = what;
33196945 275 }
12c86877 276 }
33196945 277
80d59cd1 278 // always set these headers
41ea0e50 279 resp.headers["Server"] = "PowerDNS/" VERSION;
80d59cd1
CH
280 resp.headers["Connection"] = "close";
281
ac9908c2
CH
282 if (req.method == "HEAD") {
283 resp.body = "";
284 } else {
335da0ba 285 resp.headers["Content-Length"] = std::to_string(resp.body.size());
ac9908c2 286 }
80d59cd1
CH
287}
288
612ad9ec
PL
289void WebServer::logRequest(const HttpRequest& req, const ComboAddress& remote) const {
290 if (d_loglevel >= WebServer::LogLevel::Detailed) {
291 auto logprefix = req.logprefix;
292 g_log<<Logger::Notice<<logprefix<<"Request details:"<<endl;
293
294 bool first = true;
295 for (const auto& r : req.getvars) {
296 if (first) {
297 first = false;
298 g_log<<Logger::Notice<<logprefix<<" GET params:"<<endl;
299 }
300 g_log<<Logger::Notice<<logprefix<<" "<<r.first<<": "<<r.second<<endl;
301 }
302
303 first = true;
304 for (const auto& r : req.postvars) {
305 if (first) {
306 first = false;
307 g_log<<Logger::Notice<<logprefix<<" POST params:"<<endl;
308 }
309 g_log<<Logger::Notice<<logprefix<<" "<<r.first<<": "<<r.second<<endl;
310 }
311
312 first = true;
313 for (const auto& h : req.headers) {
314 if (first) {
315 first = false;
316 g_log<<Logger::Notice<<logprefix<<" Headers:"<<endl;
317 }
318 g_log<<Logger::Notice<<logprefix<<" "<<h.first<<": "<<h.second<<endl;
319 }
320
321 if (req.body.empty()) {
322 g_log<<Logger::Notice<<logprefix<<" No body"<<endl;
323 } else {
324 g_log<<Logger::Notice<<logprefix<<" Full body: "<<endl;
325 g_log<<Logger::Notice<<logprefix<<" "<<req.body<<endl;
326 }
327 }
328}
329
330void WebServer::logResponse(const HttpResponse& resp, const ComboAddress& remote, const string& logprefix) const {
331 if (d_loglevel >= WebServer::LogLevel::Detailed) {
332 g_log<<Logger::Notice<<logprefix<<"Response details:"<<endl;
333 bool first = true;
334 for (const auto& h : resp.headers) {
335 if (first) {
336 first = false;
337 g_log<<Logger::Notice<<logprefix<<" Headers:"<<endl;
338 }
339 g_log<<Logger::Notice<<logprefix<<" "<<h.first<<": "<<h.second<<endl;
340 }
341 if (resp.body.empty()) {
342 g_log<<Logger::Notice<<logprefix<<" No body"<<endl;
343 } else {
344 g_log<<Logger::Notice<<logprefix<<" Full body: "<<endl;
345 g_log<<Logger::Notice<<logprefix<<" "<<resp.body<<endl;
346 }
347 }
348}
349
a53cd863 350void WebServer::serveConnection(std::shared_ptr<Socket> client) const {
f024c7bc 351 const string logprefix = d_logprefix + to_string(getUniqueID()) + " ";
80d59cd1 352
9b960272
PL
353 HttpRequest req(logprefix);
354 HttpResponse resp;
214b034e 355 resp.max_response_size=d_maxbodysize;
9b960272
PL
356 ComboAddress remote;
357 string reply;
358
80d59cd1 359 try {
a53cd863
PL
360 YaHTTP::AsyncRequestLoader yarl;
361 yarl.initialize(&req);
214b034e 362 req.max_request_size=d_maxbodysize;
a53cd863
PL
363 int timeout = 5;
364 client->setNonBlocking();
365
366 try {
367 while(!req.complete) {
368 int bytes;
bfde2d10 369 char buf[16000];
a53cd863
PL
370 bytes = client->readWithTimeout(buf, sizeof(buf), timeout);
371 if (bytes > 0) {
372 string data = string(buf, bytes);
373 req.complete = yarl.feed(data);
374 } else {
375 // read error OR EOF
376 break;
377 }
378 }
379 yarl.finalize();
380 } catch (YaHTTP::ParseError &e) {
381 // request stays incomplete
9fc86908 382 g_log<<Logger::Warning<<logprefix<<"Unable to parse request: "<<e.what()<<endl;
a53cd863
PL
383 }
384
a53cd863
PL
385 if (d_loglevel >= WebServer::LogLevel::None) {
386 client->getRemote(remote);
387 }
388
612ad9ec 389 logRequest(req, remote);
80d59cd1 390
a53cd863
PL
391 WebServer::handleRequest(req, resp);
392 ostringstream ss;
393 resp.write(ss);
9b960272 394 reply = ss.str();
a53cd863 395
612ad9ec 396 logResponse(resp, remote, logprefix);
a53cd863 397
a53cd863 398 client->writenWithTimeout(reply.c_str(), reply.size(), timeout);
b2cb8982 399 }
a53cd863
PL
400 catch(PDNSException &e) {
401 g_log<<Logger::Error<<logprefix<<"HTTP Exception: "<<e.reason<<endl;
b2cb8982 402 }
a53cd863
PL
403 catch(std::exception &e) {
404 if(strstr(e.what(), "timeout")==0)
405 g_log<<Logger::Error<<logprefix<<"HTTP STL Exception: "<<e.what()<<endl;
406 }
407 catch(...) {
408 g_log<<Logger::Error<<logprefix<<"Unknown exception"<<endl;
b2cb8982 409 }
9b960272 410
a35306f9 411 if (d_loglevel >= WebServer::LogLevel::Normal) {
64c08e25 412 g_log<<Logger::Notice<<logprefix<<remote<<" \""<<req.method<<" "<<req.url.path<<" HTTP/"<<req.versionStr(req.version)<<"\" "<<resp.status<<" "<<reply.size()<<endl;
9b960272 413 }
33196945
CH
414}
415
8a70e507
CHB
416WebServer::WebServer(const string &listenaddress, int port) :
417 d_listenaddress(listenaddress),
418 d_port(port),
214b034e
PD
419 d_server(nullptr),
420 d_maxbodysize(2*1024*1024)
12c86877 421{
825fa717
CH
422}
423
424void WebServer::bind()
425{
96d299db 426 try {
825fa717 427 d_server = createServer();
a53cd863 428 g_log<<Logger::Warning<<d_logprefix<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl;
96d299db 429 }
825fa717 430 catch(NetworkError &e) {
a53cd863 431 g_log<<Logger::Error<<d_logprefix<<"Listening on HTTP socket failed: "<<e.what()<<endl;
690984d4 432 d_server = nullptr;
96d299db 433 }
12c86877
BH
434}
435
436void WebServer::go()
437{
96d299db
BH
438 if(!d_server)
439 return;
12c86877 440 try {
3ae143b0 441 while(true) {
17d60ab0 442 try {
b184a9dc 443 auto client = d_server->accept();
8a781bb5
RG
444 if (!client) {
445 continue;
446 }
0010aefa 447 if (client->acl(d_acl)) {
b184a9dc 448 std::thread webHandler(WebServerConnectionThreadStart, this, client);
d4c53d8c 449 webHandler.detach();
17d60ab0
RG
450 } else {
451 ComboAddress remote;
b184a9dc 452 if (client->getRemote(remote))
a53cd863 453 g_log<<Logger::Error<<d_logprefix<<"Webserver closing socket: remote ("<< remote.toString() <<") does not match the set ACL("<<d_acl.toString()<<")"<<endl;
17d60ab0
RG
454 }
455 }
456 catch(PDNSException &e) {
a53cd863 457 g_log<<Logger::Error<<d_logprefix<<"PDNSException while accepting a connection in main webserver thread: "<<e.reason<<endl;
17d60ab0
RG
458 }
459 catch(std::exception &e) {
a53cd863 460 g_log<<Logger::Error<<d_logprefix<<"STL Exception while accepting a connection in main webserver thread: "<<e.what()<<endl;
17d60ab0
RG
461 }
462 catch(...) {
a53cd863 463 g_log<<Logger::Error<<d_logprefix<<"Unknown exception while accepting a connection in main webserver thread"<<endl;
69e7f117 464 }
12c86877
BH
465 }
466 }
69e7f117 467 catch(PDNSException &e) {
a53cd863 468 g_log<<Logger::Error<<d_logprefix<<"PDNSException in main webserver thread: "<<e.reason<<endl;
69e7f117 469 }
adc10f99 470 catch(std::exception &e) {
a53cd863 471 g_log<<Logger::Error<<d_logprefix<<"STL Exception in main webserver thread: "<<e.what()<<endl;
12c86877
BH
472 }
473 catch(...) {
a53cd863 474 g_log<<Logger::Error<<d_logprefix<<"Unknown exception in main webserver thread"<<endl;
12c86877 475 }
5bd2ea7b 476 _exit(1);
3ae143b0 477}