]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/webserver.cc
webserver: rename 'common' to normal wrt loglevels
[thirdparty/pdns.git] / pdns / webserver.cc
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 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "utility.hh"
26 #include "webserver.hh"
27 #include "misc.hh"
28 #include <thread>
29 #include "threadname.hh"
30 #include <vector>
31 #include "logger.hh"
32 #include <stdio.h>
33 #include "dns.hh"
34 #include "base64.hh"
35 #include "json.hh"
36 #include "uuid-utils.hh"
37 #include <yahttp/router.hpp>
38
39 json11::Json HttpRequest::json()
40 {
41 string err;
42 if(this->body.empty()) {
43 g_log<<Logger::Debug<<logprefix<<"JSON document expected in request body, but body was empty" << endl;
44 throw HttpBadRequestException();
45 }
46 json11::Json doc = json11::Json::parse(this->body, err);
47 if (doc.is_null()) {
48 g_log<<Logger::Debug<<logprefix<<"parsing of JSON document failed:" << err << endl;
49 throw HttpBadRequestException();
50 }
51 return doc;
52 }
53
54 bool 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
74 bool 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
85 void HttpResponse::setBody(const json11::Json& document)
86 {
87 document.dump(this->body);
88 }
89
90 void HttpResponse::setErrorResult(const std::string& message, const int status_)
91 {
92 setBody(json11::Json::object { { "error", message } });
93 this->status = status_;
94 }
95
96 void HttpResponse::setSuccessResult(const std::string& message, const int status_)
97 {
98 setBody(json11::Json::object { { "result", message } });
99 this->status = status_;
100 }
101
102 static void bareHandlerWrapper(WebServer::HandlerFunction handler, YaHTTP::Request* req, YaHTTP::Response* resp)
103 {
104 // wrapper to convert from YaHTTP::* to our subclasses
105 handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
106 }
107
108 void WebServer::registerBareHandler(const string& url, HandlerFunction handler)
109 {
110 YaHTTP::THandlerFunction f = boost::bind(&bareHandlerWrapper, handler, _1, _2);
111 YaHTTP::Router::Any(url, f);
112 }
113
114 static 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
128 static void apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp, const string &apikey) {
129 if (optionsHandler(req, resp)) return;
130
131 resp->headers["access-control-allow-origin"] = "*";
132
133 if (apikey.empty()) {
134 g_log<<Logger::Error<<req->logprefix<<"HTTP API Request \"" << req->url.path << "\": Authentication failed, API Key missing in config" << endl;
135 throw HttpUnauthorizedException("X-API-Key");
136 }
137 bool auth_ok = req->compareHeader("x-api-key", apikey) || req->getvars["api-key"] == apikey;
138
139 if (!auth_ok) {
140 g_log<<Logger::Error<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Authentication by API Key failed" << endl;
141 throw HttpUnauthorizedException("X-API-Key");
142 }
143
144 resp->headers["Content-Type"] = "application/json";
145
146 // security headers
147 resp->headers["X-Content-Type-Options"] = "nosniff";
148 resp->headers["X-Frame-Options"] = "deny";
149 resp->headers["X-Permitted-Cross-Domain-Policies"] = "none";
150 resp->headers["X-XSS-Protection"] = "1; mode=block";
151 resp->headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
152
153 req->getvars.erase("_"); // jQuery cache buster
154
155 try {
156 resp->status = 200;
157 handler(req, resp);
158 } catch (ApiException &e) {
159 resp->setErrorResult(e.what(), 422);
160 return;
161 } catch (JsonException &e) {
162 resp->setErrorResult(e.what(), 422);
163 return;
164 }
165
166 if (resp->status == 204) {
167 // No Content -> no Content-Type.
168 resp->headers.erase("Content-Type");
169 }
170 }
171
172 void WebServer::registerApiHandler(const string& url, HandlerFunction handler) {
173 HandlerFunction f = boost::bind(&apiWrapper, handler, _1, _2, d_apikey);
174 registerBareHandler(url, f);
175 d_registerApiHandlerCalled = true;
176 }
177
178 static void webWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp, const string &password) {
179 if (!password.empty()) {
180 bool auth_ok = req->compareAuthorization(password);
181 if (!auth_ok) {
182 g_log<<Logger::Debug<<req->logprefix<<"HTTP Request \"" << req->url.path << "\": Web Authentication failed" << endl;
183 throw HttpUnauthorizedException("Basic");
184 }
185 }
186
187 handler(req, resp);
188 }
189
190 void WebServer::registerWebHandler(const string& url, HandlerFunction handler) {
191 HandlerFunction f = boost::bind(&webWrapper, handler, _1, _2, d_webserverPassword);
192 registerBareHandler(url, f);
193 }
194
195 static void *WebServerConnectionThreadStart(const WebServer* webServer, std::shared_ptr<Socket> client) {
196 setThreadName("pdns-r/webhndlr");
197 webServer->serveConnection(client);
198 return nullptr;
199 }
200
201 void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
202 {
203 // set default headers
204 resp.headers["Content-Type"] = "text/html; charset=utf-8";
205
206 try {
207 if (!req.complete) {
208 g_log<<Logger::Debug<<req.logprefix<<"Incomplete request" << endl;
209 throw HttpBadRequestException();
210 }
211
212 g_log<<Logger::Debug<<req.logprefix<<"Handling request \"" << req.url.path << "\"" << endl;
213
214 YaHTTP::strstr_map_t::iterator header;
215
216 if ((header = req.headers.find("accept")) != req.headers.end()) {
217 // json wins over html
218 if (header->second.find("application/json") != std::string::npos) {
219 req.accept_json = true;
220 } else if (header->second.find("text/html") != std::string::npos) {
221 req.accept_html = true;
222 }
223 }
224
225 YaHTTP::THandlerFunction handler;
226 if (!YaHTTP::Router::Route(&req, handler)) {
227 g_log<<Logger::Debug<<req.logprefix<<"No route found for \"" << req.url.path << "\"" << endl;
228 throw HttpNotFoundException();
229 }
230
231 try {
232 handler(&req, &resp);
233 g_log<<Logger::Debug<<req.logprefix<<"Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl;
234 }
235 catch(HttpException&) {
236 throw;
237 }
238 catch(PDNSException &e) {
239 g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl;
240 throw HttpInternalServerErrorException();
241 }
242 catch(std::exception &e) {
243 g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": STL Exception: " << e.what() << endl;
244 throw HttpInternalServerErrorException();
245 }
246 catch(...) {
247 g_log<<Logger::Error<<req.logprefix<<"HTTP ISE for \""<< req.url.path << "\": Unknown Exception" << endl;
248 throw HttpInternalServerErrorException();
249 }
250 }
251 catch(HttpException &e) {
252 resp = e.response();
253 // TODO rm this logline?
254 g_log<<Logger::Debug<<req.logprefix<<"Error result for \"" << req.url.path << "\": " << resp.status << endl;
255 string what = YaHTTP::Utility::status2text(resp.status);
256 if(req.accept_html) {
257 resp.headers["Content-Type"] = "text/html; charset=utf-8";
258 resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
259 } else if (req.accept_json) {
260 resp.headers["Content-Type"] = "application/json";
261 if (resp.body.empty()) {
262 resp.setErrorResult(what, resp.status);
263 }
264 } else {
265 resp.headers["Content-Type"] = "text/plain; charset=utf-8";
266 resp.body = what;
267 }
268 }
269
270 // always set these headers
271 resp.headers["Server"] = "PowerDNS/" VERSION;
272 resp.headers["Connection"] = "close";
273
274 if (req.method == "HEAD") {
275 resp.body = "";
276 } else {
277 resp.headers["Content-Length"] = std::to_string(resp.body.size());
278 }
279 }
280
281 void WebServer::serveConnection(std::shared_ptr<Socket> client) const {
282 const string logprefix = d_logprefix + "<" + to_string(getUniqueID()) + "> ";
283
284 HttpRequest req(logprefix);
285 HttpResponse resp;
286 ComboAddress remote;
287 string reply;
288
289 try {
290 YaHTTP::AsyncRequestLoader yarl;
291 yarl.initialize(&req);
292 int timeout = 5;
293 client->setNonBlocking();
294
295 try {
296 while(!req.complete) {
297 int bytes;
298 char buf[1024];
299 bytes = client->readWithTimeout(buf, sizeof(buf), timeout);
300 if (bytes > 0) {
301 string data = string(buf, bytes);
302 req.complete = yarl.feed(data);
303 } else {
304 // read error OR EOF
305 break;
306 }
307 }
308 yarl.finalize();
309 } catch (YaHTTP::ParseError &e) {
310 // request stays incomplete
311 }
312
313 if (d_loglevel >= WebServer::LogLevel::None) {
314 client->getRemote(remote);
315 }
316
317 if (d_loglevel >= WebServer::LogLevel::Detailed) {
318 g_log<<Logger::Info<<logprefix<<"Request Details:"<<endl;
319
320 bool first = true;
321 for (const auto& r : req.getvars) {
322 if (first) {
323 first = false;
324 g_log<<Logger::Info<<logprefix<<" GET params:"<<endl;
325 }
326 g_log<<Logger::Info<<logprefix<<" "<<r.first<<": "<<r.second<<endl;
327 }
328
329 first = true;
330 for (const auto& r : req.postvars) {
331 if (first) {
332 first = false;
333 g_log<<Logger::Info<<logprefix<<" POST params:"<<endl;
334 }
335 g_log<<Logger::Info<<logprefix<<" "<<r.first<<": "<<r.second<<endl;
336 }
337 first = true;
338
339 for (const auto& h : req.headers) {
340 if (first) {
341 first = false;
342 g_log<<Logger::Info<<logprefix<<" Headers:"<<endl;
343 }
344 g_log<<Logger::Info<<logprefix<<" "<<h.first<<": "<<h.second<<endl;
345 }
346
347 if (req.body.empty()) {
348 g_log<<Logger::Info<<logprefix<<" No body"<<endl;
349 } else {
350 g_log<<Logger::Info<<logprefix<<" Full body: "<<endl;
351 g_log<<Logger::Info<<logprefix<<" "<<req.body<<endl;
352 }
353 }
354
355 WebServer::handleRequest(req, resp);
356 ostringstream ss;
357 resp.write(ss);
358 reply = ss.str();
359
360 if (d_loglevel >= WebServer::LogLevel::Detailed) {
361 g_log<<Logger::Info<<logprefix<<"Response details:"<<endl;
362 bool first = true;
363 for (const auto& h : resp.headers) {
364 if (first) {
365 first = false;
366 g_log<<Logger::Info<<logprefix<<" Headers:"<<endl;
367 }
368 g_log<<Logger::Info<<logprefix<<" "<<h.first<<": "<<h.second<<endl;
369 }
370 if (resp.body.empty()) {
371 g_log<<Logger::Info<<logprefix<<" No body"<<endl;
372 } else {
373 g_log<<Logger::Info<<logprefix<<" Full body: "<<endl;
374 g_log<<Logger::Info<<logprefix<<" "<<resp.body<<endl;
375 }
376 }
377
378 client->writenWithTimeout(reply.c_str(), reply.size(), timeout);
379 }
380 catch(PDNSException &e) {
381 g_log<<Logger::Error<<logprefix<<"HTTP Exception: "<<e.reason<<endl;
382 }
383 catch(std::exception &e) {
384 if(strstr(e.what(), "timeout")==0)
385 g_log<<Logger::Error<<logprefix<<"HTTP STL Exception: "<<e.what()<<endl;
386 }
387 catch(...) {
388 g_log<<Logger::Error<<logprefix<<"Unknown exception"<<endl;
389 }
390
391 if (d_loglevel >= WebServer::LogLevel::Normal) {
392 g_log<<Logger::Info<<logprefix<<remote<<" \""<<req.method<<" "<<req.url.path<<" HTTP/"<<req.versionStr(req.version)<<"\" "<<resp.status<<" "<<reply.size()<<endl;
393 }
394 }
395
396 WebServer::WebServer(const string &listenaddress, int port) :
397 d_listenaddress(listenaddress),
398 d_port(port),
399 d_server(nullptr)
400 {
401 }
402
403 void WebServer::bind()
404 {
405 try {
406 d_server = createServer();
407 g_log<<Logger::Warning<<d_logprefix<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl;
408 }
409 catch(NetworkError &e) {
410 g_log<<Logger::Error<<d_logprefix<<"Listening on HTTP socket failed: "<<e.what()<<endl;
411 d_server = nullptr;
412 }
413 }
414
415 void WebServer::go()
416 {
417 if(!d_server)
418 return;
419 try {
420 while(true) {
421 try {
422 auto client = d_server->accept();
423 if (!client) {
424 continue;
425 }
426 if (client->acl(d_acl)) {
427 std::thread webHandler(WebServerConnectionThreadStart, this, client);
428 webHandler.detach();
429 } else {
430 ComboAddress remote;
431 if (client->getRemote(remote))
432 g_log<<Logger::Error<<d_logprefix<<"Webserver closing socket: remote ("<< remote.toString() <<") does not match the set ACL("<<d_acl.toString()<<")"<<endl;
433 }
434 }
435 catch(PDNSException &e) {
436 g_log<<Logger::Error<<d_logprefix<<"PDNSException while accepting a connection in main webserver thread: "<<e.reason<<endl;
437 }
438 catch(std::exception &e) {
439 g_log<<Logger::Error<<d_logprefix<<"STL Exception while accepting a connection in main webserver thread: "<<e.what()<<endl;
440 }
441 catch(...) {
442 g_log<<Logger::Error<<d_logprefix<<"Unknown exception while accepting a connection in main webserver thread"<<endl;
443 }
444 }
445 }
446 catch(PDNSException &e) {
447 g_log<<Logger::Error<<d_logprefix<<"PDNSException in main webserver thread: "<<e.reason<<endl;
448 }
449 catch(std::exception &e) {
450 g_log<<Logger::Error<<d_logprefix<<"STL Exception in main webserver thread: "<<e.what()<<endl;
451 }
452 catch(...) {
453 g_log<<Logger::Error<<d_logprefix<<"Unknown exception in main webserver thread"<<endl;
454 }
455 _exit(1);
456 }