2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
26 #include "webserver.hh"
29 #include "threadname.hh"
36 #include <yahttp/router.hpp>
38 json11::Json
HttpRequest::json()
41 if(this->body
.empty()) {
42 g_log
<<Logger::Debug
<<"HTTP: JSON document expected in request body, but body was empty" << endl
;
43 throw HttpBadRequestException();
45 json11::Json doc
= json11::Json::parse(this->body
, err
);
47 g_log
<<Logger::Debug
<<"HTTP: parsing of JSON document failed:" << err
<< endl
;
48 throw HttpBadRequestException();
53 bool HttpRequest::compareAuthorization(const string
&expected_password
)
56 YaHTTP::strstr_map_t::iterator header
= headers
.find("authorization");
58 if (header
!= headers
.end() && toLower(header
->second
).find("basic ") == 0) {
59 string cookie
= header
->second
.substr(6);
62 B64Decode(cookie
, plain
);
64 vector
<string
> cparts
;
65 stringtok(cparts
, plain
, ":");
67 // this gets rid of terminating zeros
68 auth_ok
= (cparts
.size()==2 && (0==strcmp(cparts
[1].c_str(), expected_password
.c_str())));
73 bool HttpRequest::compareHeader(const string
&header_name
, const string
&expected_value
)
75 YaHTTP::strstr_map_t::iterator header
= headers
.find(header_name
);
76 if (header
== headers
.end())
79 // this gets rid of terminating zeros
80 return (0==strcmp(header
->second
.c_str(), expected_value
.c_str()));
84 void HttpResponse::setBody(const json11::Json
& document
)
86 document
.dump(this->body
);
89 void HttpResponse::setErrorResult(const std::string
& message
, const int status_
)
91 setBody(json11::Json::object
{ { "error", message
} });
92 this->status
= status_
;
95 void HttpResponse::setSuccessResult(const std::string
& message
, const int status_
)
97 setBody(json11::Json::object
{ { "result", message
} });
98 this->status
= status_
;
101 static void bareHandlerWrapper(WebServer::HandlerFunction handler
, YaHTTP::Request
* req
, YaHTTP::Response
* resp
)
103 // wrapper to convert from YaHTTP::* to our subclasses
104 handler(static_cast<HttpRequest
*>(req
), static_cast<HttpResponse
*>(resp
));
107 void WebServer::registerBareHandler(const string
& url
, HandlerFunction handler
)
109 YaHTTP::THandlerFunction f
= boost::bind(&bareHandlerWrapper
, handler
, _1
, _2
);
110 YaHTTP::Router::Any(url
, f
);
113 static 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";
120 resp
->headers
["content-type"]= "text/plain";
127 static void apiWrapper(WebServer::HandlerFunction handler
, HttpRequest
* req
, HttpResponse
* resp
, const string
&apikey
) {
128 if (optionsHandler(req
, resp
)) return;
130 resp
->headers
["access-control-allow-origin"] = "*";
132 if (apikey
.empty()) {
133 g_log
<<Logger::Error
<<"HTTP API Request \"" << req
->url
.path
<< "\": Authentication failed, API Key missing in config" << endl
;
134 throw HttpUnauthorizedException("X-API-Key");
136 bool auth_ok
= req
->compareHeader("x-api-key", apikey
) || req
->getvars
["api-key"] == apikey
;
139 g_log
<<Logger::Error
<<"HTTP Request \"" << req
->url
.path
<< "\": Authentication by API Key failed" << endl
;
140 throw HttpUnauthorizedException("X-API-Key");
143 resp
->headers
["Content-Type"] = "application/json";
146 resp
->headers
["X-Content-Type-Options"] = "nosniff";
147 resp
->headers
["X-Frame-Options"] = "deny";
148 resp
->headers
["X-Permitted-Cross-Domain-Policies"] = "none";
149 resp
->headers
["X-XSS-Protection"] = "1; mode=block";
150 resp
->headers
["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
152 req
->getvars
.erase("_"); // jQuery cache buster
157 } catch (ApiException
&e
) {
158 resp
->setErrorResult(e
.what(), 422);
160 } catch (JsonException
&e
) {
161 resp
->setErrorResult(e
.what(), 422);
165 if (resp
->status
== 204) {
166 // No Content -> no Content-Type.
167 resp
->headers
.erase("Content-Type");
171 void WebServer::registerApiHandler(const string
& url
, HandlerFunction handler
) {
172 HandlerFunction f
= boost::bind(&apiWrapper
, handler
, _1
, _2
, d_apikey
);
173 registerBareHandler(url
, f
);
174 d_registerApiHandlerCalled
= true;
177 static void webWrapper(WebServer::HandlerFunction handler
, HttpRequest
* req
, HttpResponse
* resp
, const string
&password
) {
178 if (!password
.empty()) {
179 bool auth_ok
= req
->compareAuthorization(password
);
181 g_log
<<Logger::Debug
<<"HTTP Request \"" << req
->url
.path
<< "\": Web Authentication failed" << endl
;
182 throw HttpUnauthorizedException("Basic");
189 void WebServer::registerWebHandler(const string
& url
, HandlerFunction handler
) {
190 HandlerFunction f
= boost::bind(&webWrapper
, handler
, _1
, _2
, d_webserverPassword
);
191 registerBareHandler(url
, f
);
194 static void *WebServerConnectionThreadStart(const WebServer
* webServer
, std::shared_ptr
<Socket
> client
) {
195 setThreadName("pdns-r/webhndlr");
196 webServer
->serveConnection(client
);
200 void WebServer::handleRequest(HttpRequest
& req
, HttpResponse
& resp
) const
202 // set default headers
203 resp
.headers
["Content-Type"] = "text/html; charset=utf-8";
207 g_log
<<Logger::Debug
<<"HTTP: Incomplete request" << endl
;
208 throw HttpBadRequestException();
211 g_log
<<Logger::Debug
<<"HTTP: Handling request \"" << req
.url
.path
<< "\"" << endl
;
213 YaHTTP::strstr_map_t::iterator header
;
215 if ((header
= req
.headers
.find("accept")) != req
.headers
.end()) {
216 // json wins over html
217 if (header
->second
.find("application/json") != std::string::npos
) {
218 req
.accept_json
= true;
219 } else if (header
->second
.find("text/html") != std::string::npos
) {
220 req
.accept_html
= true;
224 YaHTTP::THandlerFunction handler
;
225 if (!YaHTTP::Router::Route(&req
, handler
)) {
226 g_log
<<Logger::Debug
<<"HTTP: No route found for \"" << req
.url
.path
<< "\"" << endl
;
227 throw HttpNotFoundException();
231 handler(&req
, &resp
);
232 g_log
<<Logger::Debug
<<"HTTP: Result for \"" << req
.url
.path
<< "\": " << resp
.status
<< ", body length: " << resp
.body
.size() << endl
;
234 catch(HttpException
&) {
237 catch(PDNSException
&e
) {
238 g_log
<<Logger::Error
<<"HTTP ISE for \""<< req
.url
.path
<< "\": Exception: " << e
.reason
<< endl
;
239 throw HttpInternalServerErrorException();
241 catch(std::exception
&e
) {
242 g_log
<<Logger::Error
<<"HTTP ISE for \""<< req
.url
.path
<< "\": STL Exception: " << e
.what() << endl
;
243 throw HttpInternalServerErrorException();
246 g_log
<<Logger::Error
<<"HTTP ISE for \""<< req
.url
.path
<< "\": Unknown Exception" << endl
;
247 throw HttpInternalServerErrorException();
250 catch(HttpException
&e
) {
252 g_log
<<Logger::Debug
<<"HTTP: Error result for \"" << req
.url
.path
<< "\": " << resp
.status
<< endl
;
253 string what
= YaHTTP::Utility::status2text(resp
.status
);
254 if(req
.accept_html
) {
255 resp
.headers
["Content-Type"] = "text/html; charset=utf-8";
256 resp
.body
= "<!html><title>" + what
+ "</title><h1>" + what
+ "</h1>";
257 } else if (req
.accept_json
) {
258 resp
.headers
["Content-Type"] = "application/json";
259 if (resp
.body
.empty()) {
260 resp
.setErrorResult(what
, resp
.status
);
263 resp
.headers
["Content-Type"] = "text/plain; charset=utf-8";
268 // always set these headers
269 resp
.headers
["Server"] = "PowerDNS/" VERSION
;
270 resp
.headers
["Connection"] = "close";
272 if (req
.method
== "HEAD") {
275 resp
.headers
["Content-Length"] = std::to_string(resp
.body
.size());
279 void WebServer::serveConnection(std::shared_ptr
<Socket
> client
) const
282 YaHTTP::AsyncRequestLoader yarl
;
283 yarl
.initialize(&req
);
285 client
->setNonBlocking();
288 while(!req
.complete
) {
291 bytes
= client
->readWithTimeout(buf
, sizeof(buf
), timeout
);
293 string data
= string(buf
, bytes
);
294 req
.complete
= yarl
.feed(data
);
301 } catch (YaHTTP::ParseError
&e
) {
302 // request stays incomplete
306 WebServer::handleRequest(req
, resp
);
309 string reply
= ss
.str();
311 client
->writenWithTimeout(reply
.c_str(), reply
.size(), timeout
);
313 catch(PDNSException
&e
) {
314 g_log
<<Logger::Error
<<"HTTP Exception: "<<e
.reason
<<endl
;
316 catch(std::exception
&e
) {
317 if(strstr(e
.what(), "timeout")==0)
318 g_log
<<Logger::Error
<<"HTTP STL Exception: "<<e
.what()<<endl
;
321 g_log
<<Logger::Error
<<"HTTP: Unknown exception"<<endl
;
324 WebServer::WebServer(const string
&listenaddress
, int port
) :
325 d_listenaddress(listenaddress
),
331 void WebServer::bind()
334 d_server
= createServer();
335 g_log
<<Logger::Warning
<<"Listening for HTTP requests on "<<d_server
->d_local
.toStringWithPort()<<endl
;
337 catch(NetworkError
&e
) {
338 g_log
<<Logger::Error
<<"Listening on HTTP socket failed: "<<e
.what()<<endl
;
350 auto client
= d_server
->accept();
354 if (client
->acl(d_acl
)) {
355 std::thread
webHandler(WebServerConnectionThreadStart
, this, client
);
359 if (client
->getRemote(remote
))
360 g_log
<<Logger::Error
<<"Webserver closing socket: remote ("<< remote
.toString() <<") does not match the set ACL("<<d_acl
.toString()<<")"<<endl
;
363 catch(PDNSException
&e
) {
364 g_log
<<Logger::Error
<<"PDNSException while accepting a connection in main webserver thread: "<<e
.reason
<<endl
;
366 catch(std::exception
&e
) {
367 g_log
<<Logger::Error
<<"STL Exception while accepting a connection in main webserver thread: "<<e
.what()<<endl
;
370 g_log
<<Logger::Error
<<"Unknown exception while accepting a connection in main webserver thread"<<endl
;
374 catch(PDNSException
&e
) {
375 g_log
<<Logger::Error
<<"PDNSException in main webserver thread: "<<e
.reason
<<endl
;
377 catch(std::exception
&e
) {
378 g_log
<<Logger::Error
<<"STL Exception in main webserver thread: "<<e
.what()<<endl
;
381 g_log
<<Logger::Error
<<"Unknown exception in main webserver thread"<<endl
;