]>
Commit | Line | Data |
---|---|---|
6ec5e728 CH |
1 | /* |
2 | Copyright (C) 2002 - 2014 PowerDNS.COM BV | |
3 | ||
4 | This program is free software; you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License version 2 | |
6 | as published by the Free Software Foundation | |
7 | ||
8 | Additionally, the license of this program contains a special | |
9 | exception which allows to distribute the program in binary form when | |
10 | it is linked against OpenSSL. | |
11 | ||
12 | This program is distributed in the hope that it will be useful, | |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | GNU General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU General Public License | |
18 | along with this program; if not, write to the Free Software | |
19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | #include <boost/foreach.hpp> | |
22 | #include <boost/tokenizer.hpp> | |
23 | #include <boost/circular_buffer.hpp> | |
24 | #include "namespaces.hh" | |
25 | #include "ws-api.hh" | |
26 | #include "json.hh" | |
27 | #include "config.h" | |
28 | #include "version.hh" | |
29 | #include "arguments.hh" | |
30 | #include <stdio.h> | |
31 | #include <string.h> | |
32 | #include <ctype.h> | |
33 | #include <sys/types.h> | |
3c3c006b | 34 | #include <iomanip> |
6ec5e728 CH |
35 | |
36 | #ifndef HAVE_STRCASESTR | |
37 | ||
38 | /* | |
39 | * strcasestr() locates the first occurrence in the string s1 of the | |
40 | * sequence of characters (excluding the terminating null character) | |
41 | * in the string s2, ignoring case. strcasestr() returns a pointer | |
42 | * to the located string, or a null pointer if the string is not found. | |
43 | * If s2 is empty, the function returns s1. | |
44 | */ | |
45 | ||
46 | static char * | |
47 | strcasestr(const char *s1, const char *s2) | |
48 | { | |
49 | int *cm = __trans_lower; | |
50 | const uchar_t *us1 = (const uchar_t *)s1; | |
51 | const uchar_t *us2 = (const uchar_t *)s2; | |
52 | const uchar_t *tptr; | |
53 | int c; | |
54 | ||
55 | if (us2 == NULL || *us2 == '\0') | |
56 | return ((char *)us1); | |
57 | ||
58 | c = cm[*us2]; | |
59 | while (*us1 != '\0') { | |
60 | if (c == cm[*us1++]) { | |
61 | tptr = us1; | |
62 | while (cm[c = *++us2] == cm[*us1++] && c != '\0') | |
63 | continue; | |
64 | if (c == '\0') | |
65 | return ((char *)tptr - 1); | |
66 | us1 = tptr; | |
67 | us2 = (const uchar_t *)s2; | |
68 | c = cm[*us2]; | |
69 | } | |
70 | } | |
71 | ||
72 | return (NULL); | |
73 | } | |
74 | ||
75 | #endif // HAVE_STRCASESTR | |
76 | ||
77 | using namespace rapidjson; | |
78 | ||
79 | static void fillServerDetail(Value& out, Value::AllocatorType& allocator) | |
80 | { | |
81 | Value jdaemonType(productTypeApiType().c_str(), allocator); | |
82 | out.SetObject(); | |
83 | out.AddMember("type", "Server", allocator); | |
84 | out.AddMember("id", "localhost", allocator); | |
85 | out.AddMember("url", "/servers/localhost", allocator); | |
86 | out.AddMember("daemon_type", jdaemonType, allocator); | |
87 | out.AddMember("version", VERSION, allocator); | |
88 | out.AddMember("config_url", "/servers/localhost/config{/config_setting}", allocator); | |
89 | out.AddMember("zones_url", "/servers/localhost/zones{/zone}", allocator); | |
90 | } | |
91 | ||
92 | void apiServer(HttpRequest* req, HttpResponse* resp) { | |
93 | if(req->method != "GET") | |
94 | throw HttpMethodNotAllowedException(); | |
95 | ||
96 | Document doc; | |
97 | doc.SetArray(); | |
98 | Value server; | |
99 | fillServerDetail(server, doc.GetAllocator()); | |
100 | doc.PushBack(server, doc.GetAllocator()); | |
669822d0 | 101 | resp->setBody(doc); |
6ec5e728 CH |
102 | } |
103 | ||
104 | void apiServerDetail(HttpRequest* req, HttpResponse* resp) { | |
105 | if(req->method != "GET") | |
106 | throw HttpMethodNotAllowedException(); | |
107 | ||
108 | Document doc; | |
109 | fillServerDetail(doc, doc.GetAllocator()); | |
669822d0 | 110 | resp->setBody(doc); |
6ec5e728 CH |
111 | } |
112 | ||
113 | void apiServerConfig(HttpRequest* req, HttpResponse* resp) { | |
114 | if(req->method != "GET") | |
115 | throw HttpMethodNotAllowedException(); | |
116 | ||
117 | vector<string> items = ::arg().list(); | |
118 | string value; | |
119 | Document doc; | |
120 | doc.SetArray(); | |
121 | BOOST_FOREACH(const string& item, items) { | |
122 | Value jitem; | |
123 | jitem.SetObject(); | |
124 | jitem.AddMember("type", "ConfigSetting", doc.GetAllocator()); | |
125 | ||
126 | Value jname(item.c_str(), doc.GetAllocator()); | |
127 | jitem.AddMember("name", jname, doc.GetAllocator()); | |
128 | ||
129 | if(item.find("password") != string::npos) | |
130 | value = "***"; | |
131 | else | |
132 | value = ::arg()[item]; | |
133 | ||
134 | Value jvalue(value.c_str(), doc.GetAllocator()); | |
135 | jitem.AddMember("value", jvalue, doc.GetAllocator()); | |
136 | ||
137 | doc.PushBack(jitem, doc.GetAllocator()); | |
138 | } | |
669822d0 | 139 | resp->setBody(doc); |
6ec5e728 CH |
140 | } |
141 | ||
142 | static string logGrep(const string& q, const string& fname, const string& prefix) | |
143 | { | |
144 | FILE* ptr = fopen(fname.c_str(), "r"); | |
145 | if(!ptr) { | |
146 | return "[]"; | |
147 | } | |
148 | boost::shared_ptr<FILE> fp(ptr, fclose); | |
149 | ||
150 | string line; | |
151 | string needle = q; | |
152 | trim_right(needle); | |
153 | ||
154 | boost::replace_all(needle, "%20", " "); | |
155 | boost::replace_all(needle, "%22", "\""); | |
156 | ||
157 | boost::tokenizer<boost::escaped_list_separator<char> > t(needle, boost::escaped_list_separator<char>("\\", " ", "\"")); | |
158 | vector<string> matches(t.begin(), t.end()); | |
159 | matches.push_back(prefix); | |
160 | ||
161 | boost::circular_buffer<string> lines(200); | |
162 | while(stringfgets(fp.get(), line)) { | |
163 | vector<string>::const_iterator iter; | |
164 | for(iter = matches.begin(); iter != matches.end(); ++iter) { | |
165 | if(!strcasestr(line.c_str(), iter->c_str())) | |
166 | break; | |
167 | } | |
168 | if(iter == matches.end()) { | |
169 | trim_right(line); | |
170 | lines.push_front(line); | |
171 | } | |
172 | } | |
173 | ||
174 | Document doc; | |
175 | doc.SetArray(); | |
176 | if(!lines.empty()) { | |
177 | BOOST_FOREACH(const string& line, lines) { | |
178 | doc.PushBack(line.c_str(), doc.GetAllocator()); | |
179 | } | |
180 | } | |
181 | return makeStringFromDocument(doc); | |
182 | } | |
183 | ||
184 | void apiServerSearchLog(HttpRequest* req, HttpResponse* resp) { | |
185 | if(req->method != "GET") | |
186 | throw HttpMethodNotAllowedException(); | |
187 | ||
188 | string prefix; | |
189 | switch (versionGetProduct()) { | |
190 | case ProductAuthoritative: | |
191 | prefix = " pdns["; | |
192 | break; | |
193 | case ProductRecursor: | |
194 | prefix = " pdns_recursor["; | |
195 | break; | |
196 | } | |
197 | resp->body = logGrep(req->parameters["q"], ::arg()["experimental-logfile"], prefix); | |
198 | } | |
199 | ||
200 | void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { | |
201 | if(req->method != "GET") | |
202 | throw HttpMethodNotAllowedException(); | |
203 | ||
204 | map<string,string> items; | |
205 | productServerStatisticsFetch(items); | |
206 | ||
207 | Document doc; | |
208 | doc.SetArray(); | |
209 | typedef map<string, string> items_t; | |
f29b72dc | 210 | BOOST_FOREACH(const items_t::value_type& item, items) { |
6ec5e728 CH |
211 | Value jitem; |
212 | jitem.SetObject(); | |
213 | jitem.AddMember("type", "StatisticItem", doc.GetAllocator()); | |
214 | ||
215 | Value jname(item.first.c_str(), doc.GetAllocator()); | |
216 | jitem.AddMember("name", jname, doc.GetAllocator()); | |
217 | ||
218 | Value jvalue(item.second.c_str(), doc.GetAllocator()); | |
219 | jitem.AddMember("value", jvalue, doc.GetAllocator()); | |
220 | ||
221 | doc.PushBack(jitem, doc.GetAllocator()); | |
222 | } | |
223 | ||
669822d0 | 224 | resp->setBody(doc); |
6ec5e728 | 225 | } |
3c3c006b CH |
226 | |
227 | string apiZoneIdToName(const string& id) { | |
228 | string zonename; | |
229 | ostringstream ss; | |
230 | ||
231 | if(id.empty()) | |
232 | throw HttpBadRequestException(); | |
233 | ||
234 | std::size_t lastpos = 0, pos = 0; | |
235 | while ((pos = id.find('=', lastpos)) != string::npos) { | |
236 | ss << id.substr(lastpos, pos-lastpos); | |
237 | if ((id[pos+1] >= '0' && id[pos+1] <= '9') && | |
238 | (id[pos+2] >= '0' && id[pos+2] <= '9')) { | |
239 | char c = ((id[pos+1] - '0')*10) + (id[pos+2] - '0'); | |
240 | ss << c; | |
241 | } else { | |
242 | throw HttpBadRequestException(); | |
243 | } | |
244 | ||
245 | lastpos = pos+3; | |
246 | } | |
247 | if (lastpos < pos) { | |
248 | ss << id.substr(lastpos, pos-lastpos); | |
249 | } | |
250 | ||
251 | zonename = ss.str(); | |
252 | ||
253 | // strip trailing dot | |
254 | if (zonename.substr(zonename.size()-1) == ".") { | |
161a8b69 | 255 | zonename.resize(zonename.size()-1); |
3c3c006b CH |
256 | } |
257 | return zonename; | |
258 | } | |
259 | ||
260 | string apiZoneNameToId(const string& name) { | |
261 | ostringstream ss; | |
262 | ||
263 | for(string::const_iterator iter = name.begin(); iter != name.end(); ++iter) { | |
264 | if ((*iter >= 'A' && *iter <= 'Z') || | |
265 | (*iter >= 'a' && *iter <= 'z') || | |
266 | (*iter >= '0' && *iter <= '9') || | |
267 | (*iter == '.') || (*iter == '-')) { | |
268 | ss << *iter; | |
269 | } else { | |
270 | ss << "=" << std::setfill('0') << std::setw(2) << (int)(*iter); | |
271 | } | |
272 | } | |
273 | ||
161a8b69 CH |
274 | string id = ss.str(); |
275 | ||
3c3c006b | 276 | // add trailing dot |
161a8b69 CH |
277 | if (id.substr(id.size()-1) != ".") { |
278 | id += "."; | |
279 | } | |
3c3c006b CH |
280 | |
281 | // special handling for the root zone, as a dot on it's own doesn't work | |
282 | // everywhere. | |
283 | if (id == ".") { | |
284 | id = (boost::format("=%d") % (int)('.')).str(); | |
285 | } | |
286 | return id; | |
287 | } |