]>
Commit | Line | Data |
---|---|---|
a7a902fb BH |
1 | /* |
2 | PowerDNS Versatile Database Driven Nameserver | |
825fa717 | 3 | Copyright (C) 2003 - 2014 PowerDNS.COM BV |
a7a902fb BH |
4 | |
5 | This program is free software; you can redistribute it and/or modify | |
6ec5e728 | 6 | it under the terms of the GNU General Public License version 2 |
a7a902fb BH |
7 | as published by the Free Software Foundation |
8 | ||
f782fe38 MH |
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. | |
12 | ||
a7a902fb BH |
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 St, Fifth Floor, Boston, MA 02110-1301 USA | |
21 | */ | |
2470b36e | 22 | #include "ws-recursor.hh" |
a7a902fb BH |
23 | #include "json.hh" |
24 | #include <boost/foreach.hpp> | |
25 | #include <string> | |
26 | #include "namespaces.hh" | |
27 | #include <iostream> | |
28 | #include "iputils.hh" | |
29 | #include "rec_channel.hh" | |
30 | #include "arguments.hh" | |
31 | #include "misc.hh" | |
8465487d | 32 | #include "syncres.hh" |
fb4b38f1 CH |
33 | #include "rapidjson/document.h" |
34 | #include "rapidjson/stringbuffer.h" | |
35 | #include "rapidjson/writer.h" | |
3ae143b0 | 36 | #include "webserver.hh" |
6ec5e728 | 37 | #include "ws-api.hh" |
41942bb3 | 38 | #include "logger.hh" |
fb4b38f1 | 39 | |
825fa717 CH |
40 | extern __thread FDMultiplexer* t_fdm; |
41 | ||
fb4b38f1 | 42 | using namespace rapidjson; |
a7a902fb | 43 | |
6ec5e728 CH |
44 | void productServerStatisticsFetch(map<string,string>& out) |
45 | { | |
46 | map<string,string> stats = getAllStatsMap(); | |
47 | out.swap(stats); | |
48 | } | |
49 | ||
c348c0c8 CH |
50 | static void apiWriteConfigFile(const string& filebasename, const string& content) |
51 | { | |
52 | if (::arg()["experimental-api-config-dir"].empty()) { | |
53 | throw ApiException("Config Option \"experimental-api-config-dir\" must be set"); | |
54 | } | |
55 | ||
56 | string filename = ::arg()["experimental-api-config-dir"] + "/" + filebasename + ".conf"; | |
57 | ofstream ofconf(filename.c_str()); | |
58 | if (!ofconf) { | |
59 | throw ApiException("Could not open config fragment file '"+filename+"' for writing: "+stringerror()); | |
60 | } | |
61 | ofconf << "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl; | |
62 | ofconf << content << endl; | |
63 | ofconf.close(); | |
64 | } | |
65 | ||
41942bb3 CH |
66 | static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp) |
67 | { | |
b4ae7322 | 68 | if (req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) { |
41942bb3 CH |
69 | Document document; |
70 | req->json(document); | |
bd0320fe | 71 | const Value &jlist = document["value"]; |
41942bb3 | 72 | |
bd0320fe CH |
73 | if (!document.IsObject()) { |
74 | throw ApiException("Supplied JSON must be an object"); | |
75 | } | |
76 | ||
77 | if (!jlist.IsArray()) { | |
78 | throw ApiException("'value' must be an array"); | |
41942bb3 CH |
79 | } |
80 | ||
faa0f891 CH |
81 | for (SizeType i = 0; i < jlist.Size(); ++i) { |
82 | try { | |
83 | Netmask(jlist[i].GetString()); | |
84 | } catch (NetmaskException &e) { | |
85 | throw ApiException(e.reason); | |
86 | } | |
87 | } | |
88 | ||
41942bb3 CH |
89 | ostringstream ss; |
90 | ||
91 | // Clear allow-from-file if set, so our changes take effect | |
92 | ss << "allow-from-file=" << endl; | |
93 | ||
94 | // Clear allow-from, and provide a "parent" value | |
95 | ss << "allow-from=" << endl; | |
bd0320fe CH |
96 | for (SizeType i = 0; i < jlist.Size(); ++i) { |
97 | ss << "allow-from+=" << jlist[i].GetString() << endl; | |
41942bb3 CH |
98 | } |
99 | ||
100 | apiWriteConfigFile("allow-from", ss.str()); | |
101 | ||
102 | parseACLs(); | |
103 | ||
104 | // fall through to GET | |
105 | } else if (req->method != "GET") { | |
106 | throw HttpMethodNotAllowedException(); | |
107 | } | |
108 | ||
109 | // Return currently configured ACLs | |
110 | Document document; | |
bd0320fe CH |
111 | document.SetObject(); |
112 | ||
113 | Value jlist; | |
114 | jlist.SetArray(); | |
41942bb3 CH |
115 | |
116 | vector<string> entries; | |
117 | t_allowFrom->toStringVector(&entries); | |
118 | ||
119 | BOOST_FOREACH(const string& entry, entries) { | |
120 | Value jentry(entry.c_str(), document.GetAllocator()); // copy | |
bd0320fe | 121 | jlist.PushBack(jentry, document.GetAllocator()); |
41942bb3 CH |
122 | } |
123 | ||
bd0320fe CH |
124 | document.AddMember("name", "allow-from", document.GetAllocator()); |
125 | document.AddMember("value", jlist, document.GetAllocator()); | |
126 | ||
41942bb3 CH |
127 | resp->setBody(document); |
128 | } | |
129 | ||
02945d9a CH |
130 | static void fillZone(const string& zonename, HttpResponse* resp) |
131 | { | |
132 | SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename); | |
133 | if (iter == t_sstorage->domainmap->end()) | |
134 | throw ApiException("Could not find domain '"+zonename+"'"); | |
135 | ||
136 | Document doc; | |
137 | doc.SetObject(); | |
138 | ||
139 | const SyncRes::AuthDomain& zone = iter->second; | |
140 | ||
141 | // id is the canonical lookup key, which doesn't actually match the name (in some cases) | |
142 | string zoneId = apiZoneNameToId(iter->first); | |
7bdd945a CH |
143 | Value jzoneid(zoneId.c_str(), doc.GetAllocator()); // copy |
144 | doc.AddMember("id", jzoneid, doc.GetAllocator()); | |
02945d9a CH |
145 | string url = "/servers/localhost/zones/" + zoneId; |
146 | Value jurl(url.c_str(), doc.GetAllocator()); // copy | |
147 | doc.AddMember("url", jurl, doc.GetAllocator()); | |
148 | doc.AddMember("name", iter->first.c_str(), doc.GetAllocator()); | |
149 | doc.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator()); | |
150 | Value servers; | |
151 | servers.SetArray(); | |
152 | BOOST_FOREACH(const ComboAddress& server, zone.d_servers) { | |
153 | Value value(server.toStringWithPort().c_str(), doc.GetAllocator()); | |
154 | servers.PushBack(value, doc.GetAllocator()); | |
155 | } | |
156 | doc.AddMember("servers", servers, doc.GetAllocator()); | |
157 | bool rd = zone.d_servers.empty() ? false : zone.d_rdForward; | |
158 | doc.AddMember("recursion_desired", rd, doc.GetAllocator()); | |
159 | ||
160 | Value records; | |
161 | records.SetArray(); | |
162 | BOOST_FOREACH(const SyncRes::AuthDomain::records_t::value_type& rr, zone.d_records) { | |
163 | Value object; | |
164 | object.SetObject(); | |
165 | Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy | |
166 | object.AddMember("name", jname, doc.GetAllocator()); | |
167 | Value jtype(rr.qtype.getName().c_str(), doc.GetAllocator()); // copy | |
168 | object.AddMember("type", jtype, doc.GetAllocator()); | |
169 | object.AddMember("ttl", rr.ttl, doc.GetAllocator()); | |
170 | object.AddMember("priority", rr.priority, doc.GetAllocator()); | |
171 | Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy | |
172 | object.AddMember("content", jcontent, doc.GetAllocator()); | |
173 | records.PushBack(object, doc.GetAllocator()); | |
174 | } | |
175 | doc.AddMember("records", records, doc.GetAllocator()); | |
176 | ||
177 | resp->setBody(doc); | |
178 | } | |
179 | ||
180 | static void doCreateZone(const Value& document) | |
181 | { | |
182 | if (::arg()["experimental-api-config-dir"].empty()) { | |
183 | throw ApiException("Config Option \"experimental-api-config-dir\" must be set"); | |
184 | } | |
185 | ||
186 | string zonename = stringFromJson(document, "name"); | |
d9081fac | 187 | // TODO: better validation of zonename - apiZoneNameToId takes care of escaping / however |
02945d9a CH |
188 | if(zonename.empty()) |
189 | throw ApiException("Zone name empty"); | |
190 | ||
191 | if (zonename[zonename.size()-1] != '.') { | |
192 | zonename += "."; | |
193 | } | |
194 | ||
d9081fac | 195 | string singleIPTarget = stringFromJson(document, "single_target_ip", ""); |
02945d9a CH |
196 | string kind = toUpper(stringFromJson(document, "kind")); |
197 | bool rd = boolFromJson(document, "recursion_desired"); | |
198 | string confbasename = "zone-" + apiZoneNameToId(zonename); | |
199 | ||
200 | if (kind == "NATIVE") { | |
201 | if (rd) | |
202 | throw ApiException("kind=Native and recursion_desired are mutually exclusive"); | |
10e69330 | 203 | if(!singleIPTarget.empty()) { |
204 | try { | |
205 | ComboAddress rem(singleIPTarget); | |
206 | if(rem.sin4.sin_family != AF_INET) | |
207 | throw ApiException(""); | |
208 | singleIPTarget = rem.toString(); | |
209 | } | |
210 | catch(...) { | |
211 | throw ApiException("Single IP target '"+singleIPTarget+"' is invalid"); | |
212 | } | |
213 | } | |
02945d9a CH |
214 | string zonefilename = ::arg()["experimental-api-config-dir"] + "/" + confbasename + ".zone"; |
215 | ofstream ofzone(zonefilename.c_str()); | |
216 | if (!ofzone) { | |
217 | throw ApiException("Could not open '"+zonefilename+"' for writing: "+stringerror()); | |
218 | } | |
219 | ofzone << "; Generated by pdns-recursor REST API, DO NOT EDIT" << endl; | |
220 | ofzone << zonename << "\tIN\tSOA\tlocal.zone.\thostmaster."<<zonename<<" 1 1 1 1 1" << endl; | |
7f643957 | 221 | if(!singleIPTarget.empty()) { |
10e69330 | 222 | ofzone <<zonename << "\t3600\tIN\tA\t"<<singleIPTarget<<endl; |
223 | ofzone <<"*."<<zonename << "\t3600\tIN\tA\t"<<singleIPTarget<<endl; | |
7f643957 | 224 | } |
02945d9a CH |
225 | ofzone.close(); |
226 | ||
227 | apiWriteConfigFile(confbasename, "auth-zones+=" + zonename + "=" + zonefilename); | |
228 | } else if (kind == "FORWARDED") { | |
229 | const Value &servers = document["servers"]; | |
230 | if (kind == "FORWARDED" && (!servers.IsArray() || servers.Size() == 0)) | |
231 | throw ApiException("Need at least one upstream server when forwarding"); | |
232 | ||
233 | string serverlist; | |
234 | if (servers.IsArray()) { | |
235 | for (SizeType i = 0; i < servers.Size(); ++i) { | |
236 | if (!serverlist.empty()) { | |
237 | serverlist += ";"; | |
238 | } | |
239 | serverlist += servers[i].GetString(); | |
240 | } | |
241 | } | |
242 | ||
243 | if (rd) { | |
244 | apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename + "=" + serverlist); | |
245 | } else { | |
246 | apiWriteConfigFile(confbasename, "forward-zones+=" + zonename + "=" + serverlist); | |
247 | } | |
248 | } else { | |
249 | throw ApiException("invalid kind"); | |
250 | } | |
251 | } | |
252 | ||
253 | static bool doDeleteZone(const string& zonename) | |
254 | { | |
255 | if (::arg()["experimental-api-config-dir"].empty()) { | |
256 | throw ApiException("Config Option \"experimental-api-config-dir\" must be set"); | |
257 | } | |
258 | ||
259 | string filename; | |
260 | ||
261 | // this one must exist | |
262 | filename = ::arg()["experimental-api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf"; | |
263 | if (unlink(filename.c_str()) != 0) { | |
264 | return false; | |
265 | } | |
266 | ||
267 | // .zone file is optional | |
268 | filename = ::arg()["experimental-api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".zone"; | |
269 | unlink(filename.c_str()); | |
270 | ||
271 | return true; | |
272 | } | |
273 | ||
274 | static void apiServerZones(HttpRequest* req, HttpResponse* resp) | |
275 | { | |
b4ae7322 | 276 | if (req->method == "POST" && !::arg().mustDo("experimental-api-readonly")) { |
02945d9a CH |
277 | if (::arg()["experimental-api-config-dir"].empty()) { |
278 | throw ApiException("Config Option \"experimental-api-config-dir\" must be set"); | |
279 | } | |
280 | ||
281 | Document document; | |
282 | req->json(document); | |
283 | ||
284 | string zonename = stringFromJson(document, "name"); | |
285 | if (zonename[zonename.size()-1] != '.') { | |
286 | zonename += "."; | |
287 | } | |
288 | ||
289 | SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename); | |
290 | if (iter != t_sstorage->domainmap->end()) | |
291 | throw ApiException("Zone already exists"); | |
292 | ||
293 | doCreateZone(document); | |
294 | reloadAuthAndForwards(); | |
295 | fillZone(zonename, resp); | |
296 | return; | |
297 | } | |
298 | ||
299 | if(req->method != "GET") | |
300 | throw HttpMethodNotAllowedException(); | |
301 | ||
302 | Document doc; | |
303 | doc.SetArray(); | |
304 | ||
305 | BOOST_FOREACH(const SyncRes::domainmap_t::value_type& val, *t_sstorage->domainmap) { | |
306 | const SyncRes::AuthDomain& zone = val.second; | |
307 | Value jdi; | |
308 | jdi.SetObject(); | |
309 | // id is the canonical lookup key, which doesn't actually match the name (in some cases) | |
310 | string zoneId = apiZoneNameToId(val.first); | |
7bdd945a CH |
311 | Value jzoneid(zoneId.c_str(), doc.GetAllocator()); // copy |
312 | jdi.AddMember("id", jzoneid, doc.GetAllocator()); | |
02945d9a CH |
313 | string url = "/servers/localhost/zones/" + zoneId; |
314 | Value jurl(url.c_str(), doc.GetAllocator()); // copy | |
315 | jdi.AddMember("url", jurl, doc.GetAllocator()); | |
316 | jdi.AddMember("name", val.first.c_str(), doc.GetAllocator()); | |
317 | jdi.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator()); | |
318 | Value servers; | |
319 | servers.SetArray(); | |
320 | BOOST_FOREACH(const ComboAddress& server, zone.d_servers) { | |
321 | Value value(server.toStringWithPort().c_str(), doc.GetAllocator()); | |
322 | servers.PushBack(value, doc.GetAllocator()); | |
323 | } | |
324 | jdi.AddMember("servers", servers, doc.GetAllocator()); | |
325 | bool rd = zone.d_servers.empty() ? false : zone.d_rdForward; | |
326 | jdi.AddMember("recursion_desired", rd, doc.GetAllocator()); | |
327 | doc.PushBack(jdi, doc.GetAllocator()); | |
328 | } | |
329 | resp->setBody(doc); | |
330 | } | |
331 | ||
332 | static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) | |
333 | { | |
583ea80d | 334 | string zonename = apiZoneIdToName(req->parameters["id"]); |
02945d9a CH |
335 | zonename += "."; |
336 | ||
337 | SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename); | |
338 | if (iter == t_sstorage->domainmap->end()) | |
339 | throw ApiException("Could not find domain '"+zonename+"'"); | |
340 | ||
b4ae7322 | 341 | if(req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) { |
02945d9a CH |
342 | Document document; |
343 | req->json(document); | |
344 | ||
345 | doDeleteZone(zonename); | |
346 | doCreateZone(document); | |
347 | reloadAuthAndForwards(); | |
e2367534 | 348 | fillZone(stringFromJson(document, "name"), resp); |
02945d9a | 349 | } |
b4ae7322 | 350 | else if(req->method == "DELETE" && !::arg().mustDo("experimental-api-readonly")) { |
02945d9a CH |
351 | if (!doDeleteZone(zonename)) { |
352 | throw ApiException("Deleting domain failed"); | |
353 | } | |
354 | ||
355 | reloadAuthAndForwards(); | |
356 | // empty body on success | |
357 | resp->body = ""; | |
37663c3b | 358 | resp->status = 204; // No Content: declare that the zone is gone now |
02945d9a CH |
359 | } else if(req->method == "GET") { |
360 | fillZone(zonename, resp); | |
361 | } else { | |
362 | throw HttpMethodNotAllowedException(); | |
363 | } | |
364 | } | |
365 | ||
37bc3d01 CH |
366 | static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) { |
367 | if(req->method != "GET") | |
368 | throw HttpMethodNotAllowedException(); | |
369 | ||
583ea80d | 370 | string q = req->getvars["q"]; |
37bc3d01 CH |
371 | if (q.empty()) |
372 | throw ApiException("Query q can't be blank"); | |
373 | ||
374 | Document doc; | |
375 | doc.SetArray(); | |
376 | ||
377 | BOOST_FOREACH(const SyncRes::domainmap_t::value_type& val, *t_sstorage->domainmap) { | |
378 | string zoneId = apiZoneNameToId(val.first); | |
379 | if (pdns_ci_find(val.first, q) != string::npos) { | |
380 | Value object; | |
381 | object.SetObject(); | |
382 | object.AddMember("type", "zone", doc.GetAllocator()); | |
383 | Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy | |
384 | object.AddMember("zone_id", jzoneId, doc.GetAllocator()); | |
385 | Value jzoneName(val.first.c_str(), doc.GetAllocator()); // copy | |
386 | object.AddMember("name", jzoneName, doc.GetAllocator()); | |
387 | doc.PushBack(object, doc.GetAllocator()); | |
388 | } | |
389 | ||
390 | // if zone name is an exact match, don't bother with returning all records/comments in it | |
391 | if (val.first == q) { | |
392 | continue; | |
393 | } | |
394 | ||
395 | const SyncRes::AuthDomain& zone = val.second; | |
396 | ||
397 | BOOST_FOREACH(const SyncRes::AuthDomain::records_t::value_type& rr, zone.d_records) { | |
398 | if (pdns_ci_find(rr.qname, q) == string::npos && pdns_ci_find(rr.content, q) == string::npos) | |
399 | continue; | |
400 | ||
401 | Value object; | |
402 | object.SetObject(); | |
403 | object.AddMember("type", "record", doc.GetAllocator()); | |
404 | Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy | |
405 | object.AddMember("zone_id", jzoneId, doc.GetAllocator()); | |
406 | Value jzoneName(val.first.c_str(), doc.GetAllocator()); // copy | |
407 | object.AddMember("zone_name", jzoneName, doc.GetAllocator()); | |
408 | Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy | |
409 | object.AddMember("name", jname, doc.GetAllocator()); | |
410 | Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy | |
411 | object.AddMember("content", jcontent, doc.GetAllocator()); | |
412 | ||
413 | doc.PushBack(object, doc.GetAllocator()); | |
414 | } | |
415 | } | |
416 | resp->setBody(doc); | |
417 | } | |
418 | ||
1ce57618 | 419 | RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm) |
a7a902fb BH |
420 | { |
421 | RecursorControlParser rcp; // inits | |
a7a902fb | 422 | |
88d77d73 | 423 | d_ws = new AsyncWebServer(fdm, arg()["experimental-webserver-address"], arg().asNum("experimental-webserver-port"), arg()["experimental-webserver-password"]); |
825fa717 | 424 | d_ws->bind(); |
867f6abc | 425 | |
3ae143b0 | 426 | // legacy dispatch |
1ce57618 | 427 | d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2)); |
41942bb3 | 428 | d_ws->registerApiHandler("/servers/localhost/config/allow-from", &apiServerConfigAllowFrom); |
6ec5e728 CH |
429 | d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig); |
430 | d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog); | |
37bc3d01 | 431 | d_ws->registerApiHandler("/servers/localhost/search-data", &apiServerSearchData); |
6ec5e728 | 432 | d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics); |
02945d9a CH |
433 | d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail); |
434 | d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones); | |
6ec5e728 CH |
435 | d_ws->registerApiHandler("/servers/localhost", &apiServerDetail); |
436 | d_ws->registerApiHandler("/servers", &apiServer); | |
867f6abc | 437 | |
3ae143b0 | 438 | d_ws->go(); |
867f6abc CH |
439 | } |
440 | ||
1ce57618 | 441 | void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp) |
867f6abc | 442 | { |
e2897a7d | 443 | string command; |
a7a902fb | 444 | |
583ea80d CH |
445 | if(req->getvars.count("command")) { |
446 | command = req->getvars["command"]; | |
447 | req->getvars.erase("command"); | |
3ae143b0 | 448 | } |
a7a902fb BH |
449 | |
450 | map<string, string> stats; | |
e2897a7d | 451 | if(command == "domains") { |
e72500bf CH |
452 | Document doc; |
453 | doc.SetArray(); | |
8465487d | 454 | BOOST_FOREACH(const SyncRes::domainmap_t::value_type& val, *t_sstorage->domainmap) { |
e72500bf CH |
455 | Value jzone; |
456 | jzone.SetObject(); | |
457 | ||
458 | const SyncRes::AuthDomain& zone = val.second; | |
459 | Value zonename(val.first.c_str(), doc.GetAllocator()); | |
460 | jzone.AddMember("name", zonename, doc.GetAllocator()); | |
461 | jzone.AddMember("type", "Zone", doc.GetAllocator()); | |
462 | jzone.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator()); | |
463 | Value servers; | |
464 | servers.SetArray(); | |
465 | BOOST_FOREACH(const ComboAddress& server, zone.d_servers) { | |
466 | Value value(server.toStringWithPort().c_str(), doc.GetAllocator()); | |
467 | servers.PushBack(value, doc.GetAllocator()); | |
9ac4a7c6 | 468 | } |
e72500bf CH |
469 | jzone.AddMember("servers", servers, doc.GetAllocator()); |
470 | bool rdbit = zone.d_servers.empty() ? false : zone.d_rdForward; | |
471 | jzone.AddMember("rdbit", rdbit, doc.GetAllocator()); | |
472 | ||
473 | doc.PushBack(jzone, doc.GetAllocator()); | |
8465487d | 474 | } |
669822d0 | 475 | resp->setBody(doc); |
3ae143b0 | 476 | return; |
41ef0ed4 | 477 | } |
fb4b38f1 | 478 | else if(command == "zone") { |
583ea80d | 479 | string arg_zone = req->getvars["zone"]; |
3ae143b0 | 480 | SyncRes::domainmap_t::const_iterator ret = t_sstorage->domainmap->find(arg_zone); |
fb4b38f1 CH |
481 | if (ret != t_sstorage->domainmap->end()) { |
482 | Document doc; | |
483 | doc.SetObject(); | |
484 | Value root; | |
485 | root.SetObject(); | |
486 | ||
487 | const SyncRes::AuthDomain& zone = ret->second; | |
488 | Value zonename(ret->first.c_str(), doc.GetAllocator()); | |
489 | root.AddMember("name", zonename, doc.GetAllocator()); | |
490 | root.AddMember("type", "Zone", doc.GetAllocator()); | |
491 | root.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator()); | |
492 | Value servers; | |
493 | servers.SetArray(); | |
494 | BOOST_FOREACH(const ComboAddress& server, zone.d_servers) { | |
495 | Value value(server.toStringWithPort().c_str(), doc.GetAllocator()); | |
496 | servers.PushBack(value, doc.GetAllocator()); | |
497 | } | |
498 | root.AddMember("servers", servers, doc.GetAllocator()); | |
499 | bool rdbit = zone.d_servers.empty() ? false : zone.d_rdForward; | |
500 | root.AddMember("rdbit", rdbit, doc.GetAllocator()); | |
501 | ||
502 | Value records; | |
503 | records.SetArray(); | |
504 | BOOST_FOREACH(const SyncRes::AuthDomain::records_t::value_type& rr, zone.d_records) { | |
505 | Value object; | |
506 | object.SetObject(); | |
507 | Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy | |
508 | object.AddMember("name", jname, doc.GetAllocator()); | |
509 | Value jtype(rr.qtype.getName().c_str(), doc.GetAllocator()); // copy | |
510 | object.AddMember("type", jtype, doc.GetAllocator()); | |
511 | object.AddMember("ttl", rr.ttl, doc.GetAllocator()); | |
512 | object.AddMember("priority", rr.priority, doc.GetAllocator()); | |
513 | Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy | |
514 | object.AddMember("content", jcontent, doc.GetAllocator()); | |
515 | records.PushBack(object, doc.GetAllocator()); | |
516 | } | |
517 | root.AddMember("records", records, doc.GetAllocator()); | |
518 | ||
519 | doc.AddMember("zone", root, doc.GetAllocator()); | |
669822d0 | 520 | resp->setBody(doc); |
3ae143b0 | 521 | return; |
fb4b38f1 | 522 | } else { |
6ec5e728 | 523 | resp->body = returnJsonError("Could not find domain '"+arg_zone+"'"); |
3ae143b0 | 524 | return; |
fb4b38f1 CH |
525 | } |
526 | } | |
e2897a7d | 527 | else if(command == "flush-cache") { |
583ea80d | 528 | string canon=toCanonic("", req->getvars["domain"]); |
9ac4a7c6 BH |
529 | int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon)); |
530 | count+=broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon)); | |
531 | stats["number"]=lexical_cast<string>(count); | |
6ec5e728 | 532 | resp->body = returnJsonObject(stats); |
3ae143b0 | 533 | return; |
8465487d | 534 | } |
e2897a7d | 535 | else if(command == "config") { |
a7a902fb BH |
536 | vector<string> items = ::arg().list(); |
537 | BOOST_FOREACH(const string& var, items) { | |
538 | stats[var] = ::arg()[var]; | |
539 | } | |
6ec5e728 | 540 | resp->body = returnJsonObject(stats); |
3ae143b0 | 541 | return; |
a7a902fb | 542 | } |
e2897a7d | 543 | else if(command == "log-grep") { |
6ec5e728 | 544 | // legacy parameter name hack |
583ea80d | 545 | req->getvars["q"] = req->getvars["needle"]; |
6ec5e728 | 546 | apiServerSearchLog(req, resp); |
3ae143b0 | 547 | return; |
9ac4a7c6 | 548 | } |
3ae143b0 | 549 | else if(command == "stats") { |
9ac4a7c6 | 550 | stats = getAllStatsMap(); |
6ec5e728 | 551 | resp->body = returnJsonObject(stats); |
a7a902fb | 552 | return; |
3ae143b0 CH |
553 | } else { |
554 | resp->status = 404; | |
6ec5e728 | 555 | resp->body = returnJsonError("Not found"); |
3ae143b0 | 556 | } |
a7a902fb | 557 | } |
825fa717 CH |
558 | |
559 | ||
560 | void AsyncServerNewConnectionMT(void *p) { | |
561 | AsyncServer *server = (AsyncServer*)p; | |
562 | try { | |
563 | Socket* socket = server->accept(); | |
564 | server->d_asyncNewConnectionCallback(socket); | |
565 | delete socket; | |
566 | } catch (NetworkError &e) { | |
567 | // we're running in a shared process/thread, so can't just terminate/abort. | |
568 | return; | |
569 | } | |
570 | } | |
571 | ||
572 | void AsyncServer::asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback) | |
573 | { | |
574 | d_asyncNewConnectionCallback = callback; | |
575 | fdm->addReadFD(d_server_socket.getHandle(), boost::bind(&AsyncServer::newConnection, this)); | |
576 | } | |
577 | ||
578 | void AsyncServer::newConnection() | |
579 | { | |
580 | MT->makeThread(&AsyncServerNewConnectionMT, this); | |
581 | } | |
582 | ||
583 | ||
584 | void AsyncWebServer::serveConnection(Socket *client) | |
585 | { | |
586 | HttpRequest req; | |
583ea80d CH |
587 | YaHTTP::AsyncRequestLoader yarl; |
588 | yarl.initialize(&req); | |
825fa717 CH |
589 | client->setNonBlocking(); |
590 | ||
591 | string data; | |
592 | try { | |
593 | while(!req.complete) { | |
f0b2bab0 | 594 | data.clear(); |
825fa717 CH |
595 | int bytes = arecvtcp(data, 16384, client, true); |
596 | if (bytes > 0) { | |
597 | req.complete = yarl.feed(data); | |
598 | } else { | |
599 | // read error OR EOF | |
600 | break; | |
601 | } | |
602 | } | |
583ea80d | 603 | yarl.finalize(); |
825fa717 CH |
604 | } catch (YaHTTP::ParseError &e) { |
605 | // request stays incomplete | |
606 | } | |
607 | ||
608 | HttpResponse resp = handleRequest(req); | |
609 | ostringstream ss; | |
610 | resp.write(ss); | |
611 | data = ss.str(); | |
612 | ||
613 | // now send the reply | |
614 | if (asendtcp(data, client) == -1 || data.empty()) { | |
615 | L<<Logger::Error<<"Failed sending reply to HTTP client"<<endl; | |
616 | } | |
617 | } | |
618 | ||
619 | void AsyncWebServer::go() { | |
620 | if (!d_server) | |
621 | return; | |
622 | ((AsyncServer*)d_server)->asyncWaitForConnections(d_fdm, boost::bind(&AsyncWebServer::serveConnection, this, _1)); | |
623 | } |