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