]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/webserver.cc
JSON-API: Send 401 on bad API-Key
[thirdparty/pdns.git] / pdns / webserver.cc
CommitLineData
12c86877
BH
1/*
2 PowerDNS Versatile Database Driven Nameserver
ac7ba905 3 Copyright (C) 2002-2012 PowerDNS.COM BV
12c86877
BH
4
5 This program is free software; you can redistribute it and/or modify
22dc646a
BH
6 it under the terms of the GNU General Public License version 2
7 as published by the Free Software Foundation
f782fe38
MH
8
9 Additionally, the license of this program contains a special
10 exception which allows to distribute the program in binary form when
11 it is linked against OpenSSL.
12c86877
BH
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
06bd9ccf 20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
12c86877 21*/
731f58b8 22#include "utility.hh"
12c86877 23#include "webserver.hh"
12c86877
BH
24#include "misc.hh"
25#include <vector>
26#include "logger.hh"
27#include <stdio.h>
28#include "dns.hh"
2db9c30e 29#include "base64.hh"
33196945 30#include "json.hh"
69e7f117 31#include "arguments.hh"
583ea80d 32#include <yahttp/router.hpp>
12c86877 33
232f0877
CH
34struct connectionThreadData {
35 WebServer* webServer;
825fa717 36 Socket* client;
232f0877 37};
12c86877 38
6ec5e728
CH
39void HttpRequest::json(rapidjson::Document& document)
40{
583ea80d
CH
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 }
6ec5e728 45 if(document.Parse<0>(this->body.c_str()).HasParseError()) {
583ea80d 46 L<<Logger::Debug<<"HTTP: parsing of JSON document failed" << endl;
6ec5e728
CH
47 throw HttpBadRequestException();
48 }
49}
50
bbef8f04
CH
51bool HttpRequest::compareAuthorization(const string &expected_password)
52{
53 // validate password
54 YaHTTP::strstr_map_t::iterator header = headers.find("authorization");
55 bool auth_ok = false;
56 if (header != headers.end() && toLower(header->second).find("basic ") == 0) {
57 string cookie = header->second.substr(6);
58
59 string plain;
60 B64Decode(cookie, plain);
61
62 vector<string> cparts;
63 stringtok(cparts, plain, ":");
64
65 // this gets rid of terminating zeros
66 auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), expected_password.c_str())));
67 }
68 return auth_ok;
69}
70
71bool HttpRequest::compareHeader(const string &header_name, const string &expected_value)
72{
73 YaHTTP::strstr_map_t::iterator header = headers.find(header_name);
74 if (header == headers.end())
75 return false;
76
77 // this gets rid of terminating zeros
78 return (0==strcmp(header->second.c_str(), expected_value.c_str()));
79}
80
81
669822d0
CH
82void HttpResponse::setBody(rapidjson::Document& document)
83{
84 this->body = makeStringFromDocument(document);
85}
86
bbef8f04 87static void bareHandlerWrapper(WebServer::HandlerFunction handler, YaHTTP::Request* req, YaHTTP::Response* resp)
12c86877 88{
583ea80d
CH
89 // wrapper to convert from YaHTTP::* to our subclasses
90 handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
91}
232f0877 92
bbef8f04 93void WebServer::registerBareHandler(const string& url, HandlerFunction handler)
583ea80d 94{
bbef8f04 95 YaHTTP::THandlerFunction f = boost::bind(&bareHandlerWrapper, handler, _1, _2);
583ea80d 96 YaHTTP::Router::Any(url, f);
232f0877
CH
97}
98
7f7481be
AT
99static bool optionsHandler(HttpRequest* req, HttpResponse* resp) {
100 if (req->method == "OPTIONS") {
101 resp->headers["access-control-allow-origin"] = "*";
102 resp->headers["access-control-allow-headers"] = "Content-Type, X-API-Key";
103 resp->headers["access-control-allow-methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS";
104 resp->headers["access-control-max-age"] = "3600";
105 resp->status = 200;
106 resp->headers["content-type"]= "text/plain";
107 resp->body = "";
108 return true;
109 }
110 return false;
111}
112
583ea80d 113static void apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp) {
bbef8f04 114 const string& api_key = arg()["experimental-api-key"];
7f7481be
AT
115
116 if (optionsHandler(req, resp)) return;
117
118 resp->headers["access-control-allow-origin"] = "*";
119
bbef8f04 120 if (api_key.empty()) {
53255086
PL
121 L<<Logger::Error<<"HTTP API Request \"" << req->url.path << "\": Authentication failed, API Key missing in config" << endl;
122 throw HttpUnauthorizedException("X-API-Key");
bbef8f04 123 }
c89f8cd0 124 bool auth_ok = req->compareHeader("x-api-key", api_key) || req->getvars["api-key"]==api_key;
125
bbef8f04 126 if (!auth_ok) {
53255086
PL
127 L<<Logger::Error<<"HTTP Request \"" << req->url.path << "\": Authentication by API Key failed" << endl;
128 throw HttpUnauthorizedException("X-API-Key");
bbef8f04
CH
129 }
130
3ae143b0
CH
131 resp->headers["Content-Type"] = "application/json";
132
133 string callback;
134
583ea80d
CH
135 if(req->getvars.count("callback")) {
136 callback=req->getvars["callback"];
137 req->getvars.erase("callback");
3ae143b0
CH
138 }
139
583ea80d 140 req->getvars.erase("_"); // jQuery cache buster
3ae143b0
CH
141
142 try {
583ea80d 143 resp->status = 200;
3ae143b0
CH
144 handler(req, resp);
145 } catch (ApiException &e) {
6ec5e728
CH
146 resp->body = returnJsonError(e.what());
147 resp->status = 422;
148 return;
149 } catch (JsonException &e) {
150 resp->body = returnJsonError(e.what());
3ae143b0
CH
151 resp->status = 422;
152 return;
153 }
154
37663c3b
CH
155 if (resp->status == 204) {
156 // No Content -> no Content-Type.
157 resp->headers.erase("Content-Type");
158 }
159
3ae143b0
CH
160 if(!callback.empty()) {
161 resp->body = callback + "(" + resp->body + ");";
162 }
163}
164
165void WebServer::registerApiHandler(const string& url, HandlerFunction handler) {
166 HandlerFunction f = boost::bind(&apiWrapper, handler, _1, _2);
bbef8f04
CH
167 registerBareHandler(url, f);
168}
169
170static void webWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp) {
171 const string& web_password = arg()["webserver-password"];
7f7481be 172
bbef8f04
CH
173 if (!web_password.empty()) {
174 bool auth_ok = req->compareAuthorization(web_password);
175 if (!auth_ok) {
176 L<<Logger::Debug<<"HTTP Request \"" << req->url.path << "\": Web Authentication failed" << endl;
53255086 177 throw HttpUnauthorizedException("Basic");
bbef8f04
CH
178 }
179 }
180
181 handler(req, resp);
182}
183
184void WebServer::registerWebHandler(const string& url, HandlerFunction handler) {
185 HandlerFunction f = boost::bind(&webWrapper, handler, _1, _2);
186 registerBareHandler(url, f);
3ae143b0
CH
187}
188
232f0877
CH
189static void *WebServerConnectionThreadStart(void *p) {
190 connectionThreadData* data = static_cast<connectionThreadData*>(p);
6b70b8c7 191 pthread_detach(pthread_self());
232f0877 192 data->webServer->serveConnection(data->client);
80d59cd1 193
825fa717 194 delete data->client; // close socket
232f0877 195 delete data;
80d59cd1 196
232f0877
CH
197 return NULL;
198}
199
80d59cd1
CH
200HttpResponse WebServer::handleRequest(HttpRequest req)
201{
583ea80d 202 HttpResponse resp;
825fa717 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 }
583ea80d
CH
236 catch(HttpException) {
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";
6ec5e728 261 resp.body = returnJsonError(what);
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 {
275 resp.headers["Content-Length"] = lexical_cast<string>(resp.body.size());
276 }
277
80d59cd1
CH
278 return resp;
279}
280
825fa717 281void WebServer::serveConnection(Socket *client)
80d59cd1
CH
282try {
283 HttpRequest req;
583ea80d
CH
284 YaHTTP::AsyncRequestLoader yarl;
285 yarl.initialize(&req);
825fa717
CH
286 int timeout = 5;
287 client->setNonBlocking();
80d59cd1 288
80d59cd1 289 try {
825fa717 290 while(!req.complete) {
80d59cd1
CH
291 int bytes;
292 char buf[1024];
825fa717
CH
293 bytes = client->readWithTimeout(buf, sizeof(buf), timeout);
294 if (bytes > 0) {
80d59cd1 295 string data = string(buf, bytes);
825fa717
CH
296 req.complete = yarl.feed(data);
297 } else {
298 // read error OR EOF
299 break;
80d59cd1
CH
300 }
301 }
583ea80d 302 yarl.finalize();
80d59cd1 303 } catch (YaHTTP::ParseError &e) {
825fa717 304 // request stays incomplete
80d59cd1
CH
305 }
306
307 HttpResponse resp = WebServer::handleRequest(req);
308 ostringstream ss;
309 resp.write(ss);
825fa717
CH
310 string reply = ss.str();
311
312 client->writenWithTimeout(reply.c_str(), reply.size(), timeout);
33196945
CH
313}
314catch(PDNSException &e) {
825fa717 315 L<<Logger::Error<<"HTTP Exception: "<<e.reason<<endl;
33196945
CH
316}
317catch(std::exception &e) {
7d7d776e 318 if(strstr(e.what(), "timeout")==0)
319 L<<Logger::Error<<"HTTP STL Exception: "<<e.what()<<endl;
33196945
CH
320}
321catch(...) {
825fa717 322 L<<Logger::Error<<"HTTP: Unknown exception"<<endl;
33196945
CH
323}
324
bbef8f04 325WebServer::WebServer(const string &listenaddress, int port) : d_server(NULL)
12c86877
BH
326{
327 d_listenaddress=listenaddress;
328 d_port=port;
825fa717
CH
329}
330
331void WebServer::bind()
332{
96d299db 333 try {
825fa717
CH
334 d_server = createServer();
335 L<<Logger::Warning<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl;
96d299db 336 }
825fa717
CH
337 catch(NetworkError &e) {
338 L<<Logger::Error<<"Listening on HTTP socket failed: "<<e.what()<<endl;
8b6348d6 339 d_server = NULL;
96d299db 340 }
12c86877
BH
341}
342
343void WebServer::go()
344{
96d299db
BH
345 if(!d_server)
346 return;
12c86877 347 try {
12c86877 348 pthread_t tid;
12c86877 349
69e7f117
KM
350 NetmaskGroup acl;
351 acl.toMasks(::arg()["webserver-allow-from"]);
352
3ae143b0 353 while(true) {
825fa717 354 // data and data->client will be freed by thread
232f0877
CH
355 connectionThreadData *data = new connectionThreadData;
356 data->webServer = this;
3ae143b0 357 data->client = d_server->accept();
69e7f117
KM
358 if (data->client->acl(acl)) {
359 pthread_create(&tid, 0, &WebServerConnectionThreadStart, (void *)data);
360 } else {
f0e5e114
KM
361 ComboAddress remote;
362 if (data->client->getRemote(remote))
363 L<<Logger::Error<<"Webserver closing socket: remote ("<< remote.toString() <<") does not match 'webserver-allow-from'"<<endl;
69e7f117
KM
364 delete data->client; // close socket
365 delete data;
366 }
12c86877
BH
367 }
368 }
69e7f117
KM
369 catch(PDNSException &e) {
370 L<<Logger::Error<<"PDNSException in main webserver thread: "<<e.reason<<endl;
371 }
adc10f99 372 catch(std::exception &e) {
12c86877
BH
373 L<<Logger::Error<<"STL Exception in main webserver thread: "<<e.what()<<endl;
374 }
375 catch(...) {
376 L<<Logger::Error<<"Unknown exception in main webserver thread"<<endl;
377 }
378 exit(1);
3ae143b0 379}