]>
Commit | Line | Data |
---|---|---|
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" |
583ea80d | 31 | #include <yahttp/router.hpp> |
12c86877 | 32 | |
232f0877 CH |
33 | struct connectionThreadData { |
34 | WebServer* webServer; | |
825fa717 | 35 | Socket* client; |
232f0877 | 36 | }; |
12c86877 | 37 | |
6ec5e728 CH |
38 | void HttpRequest::json(rapidjson::Document& document) |
39 | { | |
583ea80d CH |
40 | if(this->body.empty()) { |
41 | L<<Logger::Debug<<"HTTP: JSON document expected in request body, but body was empty" << endl; | |
42 | throw HttpBadRequestException(); | |
43 | } | |
6ec5e728 | 44 | if(document.Parse<0>(this->body.c_str()).HasParseError()) { |
583ea80d | 45 | L<<Logger::Debug<<"HTTP: parsing of JSON document failed" << endl; |
6ec5e728 CH |
46 | throw HttpBadRequestException(); |
47 | } | |
48 | } | |
49 | ||
669822d0 CH |
50 | void HttpResponse::setBody(rapidjson::Document& document) |
51 | { | |
52 | this->body = makeStringFromDocument(document); | |
53 | } | |
54 | ||
12c86877 BH |
55 | int WebServer::B64Decode(const std::string& strInput, std::string& strOutput) |
56 | { | |
2db9c30e | 57 | return ::B64Decode(strInput, strOutput); |
12c86877 BH |
58 | } |
59 | ||
583ea80d | 60 | static void handlerWrapper(WebServer::HandlerFunction handler, YaHTTP::Request* req, YaHTTP::Response* resp) |
12c86877 | 61 | { |
583ea80d CH |
62 | // wrapper to convert from YaHTTP::* to our subclasses |
63 | handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp)); | |
64 | } | |
232f0877 | 65 | |
583ea80d CH |
66 | void WebServer::registerHandler(const string& url, HandlerFunction handler) |
67 | { | |
68 | YaHTTP::THandlerFunction f = boost::bind(&handlerWrapper, handler, _1, _2); | |
69 | YaHTTP::Router::Any(url, f); | |
232f0877 CH |
70 | } |
71 | ||
583ea80d | 72 | static void apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp) { |
3ae143b0 CH |
73 | resp->headers["Access-Control-Allow-Origin"] = "*"; |
74 | resp->headers["Content-Type"] = "application/json"; | |
75 | ||
76 | string callback; | |
77 | ||
583ea80d CH |
78 | if(req->getvars.count("callback")) { |
79 | callback=req->getvars["callback"]; | |
80 | req->getvars.erase("callback"); | |
3ae143b0 CH |
81 | } |
82 | ||
583ea80d | 83 | req->getvars.erase("_"); // jQuery cache buster |
3ae143b0 CH |
84 | |
85 | try { | |
583ea80d | 86 | resp->status = 200; |
3ae143b0 CH |
87 | handler(req, resp); |
88 | } catch (ApiException &e) { | |
6ec5e728 CH |
89 | resp->body = returnJsonError(e.what()); |
90 | resp->status = 422; | |
91 | return; | |
92 | } catch (JsonException &e) { | |
93 | resp->body = returnJsonError(e.what()); | |
3ae143b0 CH |
94 | resp->status = 422; |
95 | return; | |
96 | } | |
97 | ||
37663c3b CH |
98 | if (resp->status == 204) { |
99 | // No Content -> no Content-Type. | |
100 | resp->headers.erase("Content-Type"); | |
101 | } | |
102 | ||
3ae143b0 CH |
103 | if(!callback.empty()) { |
104 | resp->body = callback + "(" + resp->body + ");"; | |
105 | } | |
106 | } | |
107 | ||
108 | void WebServer::registerApiHandler(const string& url, HandlerFunction handler) { | |
109 | HandlerFunction f = boost::bind(&apiWrapper, handler, _1, _2); | |
110 | registerHandler(url, f); | |
111 | } | |
112 | ||
232f0877 CH |
113 | static void *WebServerConnectionThreadStart(void *p) { |
114 | connectionThreadData* data = static_cast<connectionThreadData*>(p); | |
6b70b8c7 | 115 | pthread_detach(pthread_self()); |
232f0877 | 116 | data->webServer->serveConnection(data->client); |
80d59cd1 | 117 | |
825fa717 | 118 | delete data->client; // close socket |
232f0877 | 119 | delete data; |
80d59cd1 | 120 | |
232f0877 CH |
121 | return NULL; |
122 | } | |
123 | ||
80d59cd1 CH |
124 | HttpResponse WebServer::handleRequest(HttpRequest req) |
125 | { | |
583ea80d | 126 | HttpResponse resp; |
825fa717 | 127 | |
80d59cd1 CH |
128 | // set default headers |
129 | resp.headers["Content-Type"] = "text/html; charset=utf-8"; | |
33196945 | 130 | |
12c86877 | 131 | try { |
825fa717 | 132 | if (!req.complete) { |
583ea80d | 133 | L<<Logger::Debug<<"HTTP: Incomplete request" << endl; |
825fa717 CH |
134 | throw HttpBadRequestException(); |
135 | } | |
136 | ||
137 | L<<Logger::Debug<<"HTTP: Handling request \"" << req.url.path << "\"" << endl; | |
138 | ||
80d59cd1 CH |
139 | YaHTTP::strstr_map_t::iterator header; |
140 | ||
141 | if ((header = req.headers.find("accept")) != req.headers.end()) { | |
142 | // json wins over html | |
143 | if (header->second.find("application/json") != std::string::npos) { | |
144 | req.accept_json = true; | |
145 | } else if (header->second.find("text/html") != std::string::npos) { | |
146 | req.accept_html = true; | |
147 | } | |
12c86877 BH |
148 | } |
149 | ||
80d59cd1 CH |
150 | if (!d_password.empty()) { |
151 | // validate password | |
152 | header = req.headers.find("authorization"); | |
153 | bool auth_ok = false; | |
154 | if (header != req.headers.end() && toLower(header->second).find("basic ") == 0) { | |
155 | string cookie = header->second.substr(6); | |
33196945 | 156 | |
4957a608 | 157 | string plain; |
80d59cd1 | 158 | B64Decode(cookie, plain); |
4957a608 | 159 | |
80d59cd1 CH |
160 | vector<string> cparts; |
161 | stringtok(cparts, plain, ":"); | |
162 | ||
163 | // this gets rid of terminating zeros | |
164 | auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), d_password.c_str()))); | |
a2ce158c | 165 | } |
80d59cd1 | 166 | if (!auth_ok) { |
51bd0969 | 167 | L<<Logger::Debug<<"HTTP Request \"" << req.url.path << "\": Authentication failed" << endl; |
80d59cd1 | 168 | throw HttpUnauthorizedException(); |
33196945 | 169 | } |
80d59cd1 | 170 | } |
12c86877 | 171 | |
583ea80d CH |
172 | YaHTTP::THandlerFunction handler; |
173 | if (!YaHTTP::Router::Route(&req, handler)) { | |
51bd0969 | 174 | L<<Logger::Debug<<"HTTP: No route found for \"" << req.url.path << "\"" << endl; |
33196945 | 175 | throw HttpNotFoundException(); |
12c86877 | 176 | } |
12c86877 | 177 | |
0f67eeda | 178 | try { |
583ea80d | 179 | handler(&req, &resp); |
51bd0969 | 180 | L<<Logger::Debug<<"HTTP: Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl; |
0f67eeda | 181 | } |
583ea80d CH |
182 | catch(HttpException) { |
183 | throw; | |
184 | } | |
0f67eeda CH |
185 | catch(PDNSException &e) { |
186 | L<<Logger::Error<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl; | |
187 | throw HttpInternalServerErrorException(); | |
188 | } | |
189 | catch(std::exception &e) { | |
190 | L<<Logger::Error<<"HTTP ISE for \""<< req.url.path << "\": STL Exception: " << e.what() << endl; | |
191 | throw HttpInternalServerErrorException(); | |
192 | } | |
193 | catch(...) { | |
194 | L<<Logger::Error<<"HTTP ISE for \""<< req.url.path << "\": Unknown Exception" << endl; | |
195 | throw HttpInternalServerErrorException(); | |
196 | } | |
12c86877 | 197 | } |
33196945 | 198 | catch(HttpException &e) { |
80d59cd1 | 199 | resp = e.response(); |
51bd0969 | 200 | L<<Logger::Debug<<"HTTP: Error result for \"" << req.url.path << "\": " << resp.status << endl; |
80d59cd1 | 201 | string what = YaHTTP::Utility::status2text(resp.status); |
02c04144 | 202 | if(req.accept_html) { |
80d59cd1 CH |
203 | resp.headers["Content-Type"] = "text/html; charset=utf-8"; |
204 | resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>"; | |
02c04144 | 205 | } else if (req.accept_json) { |
80d59cd1 | 206 | resp.headers["Content-Type"] = "application/json"; |
6ec5e728 | 207 | resp.body = returnJsonError(what); |
33196945 | 208 | } else { |
80d59cd1 CH |
209 | resp.headers["Content-Type"] = "text/plain; charset=utf-8"; |
210 | resp.body = what; | |
33196945 | 211 | } |
12c86877 | 212 | } |
33196945 | 213 | |
80d59cd1 CH |
214 | // always set these headers |
215 | resp.headers["Server"] = "PowerDNS/"VERSION; | |
216 | resp.headers["Connection"] = "close"; | |
217 | ||
218 | return resp; | |
219 | } | |
220 | ||
825fa717 | 221 | void WebServer::serveConnection(Socket *client) |
80d59cd1 CH |
222 | try { |
223 | HttpRequest req; | |
583ea80d CH |
224 | YaHTTP::AsyncRequestLoader yarl; |
225 | yarl.initialize(&req); | |
825fa717 CH |
226 | int timeout = 5; |
227 | client->setNonBlocking(); | |
80d59cd1 | 228 | |
80d59cd1 | 229 | try { |
825fa717 | 230 | while(!req.complete) { |
80d59cd1 CH |
231 | int bytes; |
232 | char buf[1024]; | |
825fa717 CH |
233 | bytes = client->readWithTimeout(buf, sizeof(buf), timeout); |
234 | if (bytes > 0) { | |
80d59cd1 | 235 | string data = string(buf, bytes); |
825fa717 CH |
236 | req.complete = yarl.feed(data); |
237 | } else { | |
238 | // read error OR EOF | |
239 | break; | |
80d59cd1 CH |
240 | } |
241 | } | |
583ea80d | 242 | yarl.finalize(); |
80d59cd1 | 243 | } catch (YaHTTP::ParseError &e) { |
825fa717 | 244 | // request stays incomplete |
80d59cd1 CH |
245 | } |
246 | ||
247 | HttpResponse resp = WebServer::handleRequest(req); | |
248 | ostringstream ss; | |
249 | resp.write(ss); | |
825fa717 CH |
250 | string reply = ss.str(); |
251 | ||
252 | client->writenWithTimeout(reply.c_str(), reply.size(), timeout); | |
33196945 CH |
253 | } |
254 | catch(PDNSException &e) { | |
825fa717 | 255 | L<<Logger::Error<<"HTTP Exception: "<<e.reason<<endl; |
33196945 CH |
256 | } |
257 | catch(std::exception &e) { | |
825fa717 | 258 | L<<Logger::Error<<"HTTP STL Exception: "<<e.what()<<endl; |
33196945 CH |
259 | } |
260 | catch(...) { | |
825fa717 | 261 | L<<Logger::Error<<"HTTP: Unknown exception"<<endl; |
33196945 CH |
262 | } |
263 | ||
0f9f663d | 264 | WebServer::WebServer(const string &listenaddress, int port, const string &password) : d_server(NULL) |
12c86877 BH |
265 | { |
266 | d_listenaddress=listenaddress; | |
267 | d_port=port; | |
268 | d_password=password; | |
825fa717 CH |
269 | } |
270 | ||
271 | void WebServer::bind() | |
272 | { | |
96d299db | 273 | try { |
825fa717 CH |
274 | d_server = createServer(); |
275 | L<<Logger::Warning<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl; | |
96d299db | 276 | } |
825fa717 CH |
277 | catch(NetworkError &e) { |
278 | L<<Logger::Error<<"Listening on HTTP socket failed: "<<e.what()<<endl; | |
8b6348d6 | 279 | d_server = NULL; |
96d299db | 280 | } |
12c86877 BH |
281 | } |
282 | ||
283 | void WebServer::go() | |
284 | { | |
96d299db BH |
285 | if(!d_server) |
286 | return; | |
12c86877 | 287 | try { |
12c86877 | 288 | pthread_t tid; |
12c86877 | 289 | |
3ae143b0 | 290 | while(true) { |
825fa717 | 291 | // data and data->client will be freed by thread |
232f0877 CH |
292 | connectionThreadData *data = new connectionThreadData; |
293 | data->webServer = this; | |
3ae143b0 | 294 | data->client = d_server->accept(); |
232f0877 | 295 | pthread_create(&tid, 0, &WebServerConnectionThreadStart, (void *)data); |
12c86877 BH |
296 | } |
297 | } | |
adc10f99 | 298 | catch(std::exception &e) { |
12c86877 BH |
299 | L<<Logger::Error<<"STL Exception in main webserver thread: "<<e.what()<<endl; |
300 | } | |
301 | catch(...) { | |
302 | L<<Logger::Error<<"Unknown exception in main webserver thread"<<endl; | |
303 | } | |
304 | exit(1); | |
3ae143b0 | 305 | } |