]>
Commit | Line | Data |
---|---|---|
6ec5e728 | 1 | /* |
6edbf68a PL |
2 | * This file is part of PowerDNS or dnsdist. |
3 | * Copyright -- PowerDNS.COM B.V. and its contributors | |
4 | * | |
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. | |
8 | * | |
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. | |
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 | |
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
21 | */ | |
870a0fe4 AT |
22 | #ifdef HAVE_CONFIG_H |
23 | #include "config.h" | |
24 | #endif | |
fa8fd4d2 | 25 | |
6ec5e728 CH |
26 | #include <boost/tokenizer.hpp> |
27 | #include <boost/circular_buffer.hpp> | |
28 | #include "namespaces.hh" | |
29 | #include "ws-api.hh" | |
30 | #include "json.hh" | |
6ec5e728 CH |
31 | #include "version.hh" |
32 | #include "arguments.hh" | |
33 | #include <stdio.h> | |
34 | #include <string.h> | |
35 | #include <ctype.h> | |
36 | #include <sys/types.h> | |
3c3c006b | 37 | #include <iomanip> |
6ec5e728 | 38 | |
fd99e845 | 39 | extern string s_programname; |
f461cf07 | 40 | using json11::Json; |
fd99e845 | 41 | |
6ec5e728 CH |
42 | #ifndef HAVE_STRCASESTR |
43 | ||
44 | /* | |
45 | * strcasestr() locates the first occurrence in the string s1 of the | |
46 | * sequence of characters (excluding the terminating null character) | |
47 | * in the string s2, ignoring case. strcasestr() returns a pointer | |
48 | * to the located string, or a null pointer if the string is not found. | |
49 | * If s2 is empty, the function returns s1. | |
50 | */ | |
51 | ||
52 | static char * | |
53 | strcasestr(const char *s1, const char *s2) | |
54 | { | |
55 | int *cm = __trans_lower; | |
56 | const uchar_t *us1 = (const uchar_t *)s1; | |
57 | const uchar_t *us2 = (const uchar_t *)s2; | |
58 | const uchar_t *tptr; | |
59 | int c; | |
60 | ||
61 | if (us2 == NULL || *us2 == '\0') | |
62 | return ((char *)us1); | |
63 | ||
64 | c = cm[*us2]; | |
65 | while (*us1 != '\0') { | |
66 | if (c == cm[*us1++]) { | |
67 | tptr = us1; | |
68 | while (cm[c = *++us2] == cm[*us1++] && c != '\0') | |
69 | continue; | |
70 | if (c == '\0') | |
71 | return ((char *)tptr - 1); | |
72 | us1 = tptr; | |
73 | us2 = (const uchar_t *)s2; | |
74 | c = cm[*us2]; | |
75 | } | |
76 | } | |
77 | ||
78 | return (NULL); | |
79 | } | |
80 | ||
81 | #endif // HAVE_STRCASESTR | |
82 | ||
f461cf07 CH |
83 | static Json getServerDetail() { |
84 | return Json::object { | |
85 | { "type", "Server" }, | |
86 | { "id", "localhost" }, | |
87 | { "url", "/api/v1/servers/localhost" }, | |
88 | { "daemon_type", productTypeApiType() }, | |
89 | { "version", getPDNSVersion() }, | |
90 | { "config_url", "/api/v1/servers/localhost/config{/config_setting}" }, | |
91 | { "zones_url", "/api/v1/servers/localhost/zones{/zone}" } | |
92 | }; | |
6ec5e728 CH |
93 | } |
94 | ||
9e6d2033 CH |
95 | /* Return information about the supported API versions. |
96 | * The format of this MUST NEVER CHANGE at it's not versioned. | |
97 | */ | |
98 | void apiDiscovery(HttpRequest* req, HttpResponse* resp) { | |
99 | if(req->method != "GET") | |
100 | throw HttpMethodNotAllowedException(); | |
101 | ||
102 | Json version1 = Json::object { | |
103 | { "version", 1 }, | |
104 | { "url", "/api/v1" } | |
105 | }; | |
106 | Json doc = Json::array { version1 }; | |
107 | ||
108 | resp->setBody(doc); | |
109 | } | |
110 | ||
6ec5e728 CH |
111 | void apiServer(HttpRequest* req, HttpResponse* resp) { |
112 | if(req->method != "GET") | |
113 | throw HttpMethodNotAllowedException(); | |
114 | ||
f461cf07 | 115 | Json doc = Json::array {getServerDetail()}; |
669822d0 | 116 | resp->setBody(doc); |
6ec5e728 CH |
117 | } |
118 | ||
119 | void apiServerDetail(HttpRequest* req, HttpResponse* resp) { | |
120 | if(req->method != "GET") | |
121 | throw HttpMethodNotAllowedException(); | |
122 | ||
f461cf07 | 123 | resp->setBody(getServerDetail()); |
6ec5e728 CH |
124 | } |
125 | ||
126 | void apiServerConfig(HttpRequest* req, HttpResponse* resp) { | |
127 | if(req->method != "GET") | |
128 | throw HttpMethodNotAllowedException(); | |
129 | ||
130 | vector<string> items = ::arg().list(); | |
131 | string value; | |
9857fdbc CH |
132 | Json::array doc; |
133 | for(const string& item : items) { | |
4a1dd79d | 134 | if(item.find("password") != string::npos || item.find("api-key") != string::npos) |
6ec5e728 CH |
135 | value = "***"; |
136 | else | |
137 | value = ::arg()[item]; | |
138 | ||
9857fdbc CH |
139 | doc.push_back(Json::object { |
140 | { "type", "ConfigSetting" }, | |
141 | { "name", item }, | |
142 | { "value", value }, | |
143 | }); | |
6ec5e728 | 144 | } |
669822d0 | 145 | resp->setBody(doc); |
6ec5e728 CH |
146 | } |
147 | ||
8dce71ea | 148 | static Json logGrep(const string& q, const string& fname, const string& prefix) |
6ec5e728 CH |
149 | { |
150 | FILE* ptr = fopen(fname.c_str(), "r"); | |
151 | if(!ptr) { | |
6825bb9a | 152 | throw ApiException("Opening \"" + fname + "\" failed: " + stringerror()); |
6ec5e728 | 153 | } |
dd7da6cd | 154 | std::shared_ptr<FILE> fp(ptr, fclose); |
6ec5e728 CH |
155 | |
156 | string line; | |
157 | string needle = q; | |
158 | trim_right(needle); | |
159 | ||
160 | boost::replace_all(needle, "%20", " "); | |
161 | boost::replace_all(needle, "%22", "\""); | |
162 | ||
163 | boost::tokenizer<boost::escaped_list_separator<char> > t(needle, boost::escaped_list_separator<char>("\\", " ", "\"")); | |
164 | vector<string> matches(t.begin(), t.end()); | |
165 | matches.push_back(prefix); | |
166 | ||
167 | boost::circular_buffer<string> lines(200); | |
168 | while(stringfgets(fp.get(), line)) { | |
169 | vector<string>::const_iterator iter; | |
170 | for(iter = matches.begin(); iter != matches.end(); ++iter) { | |
171 | if(!strcasestr(line.c_str(), iter->c_str())) | |
172 | break; | |
173 | } | |
174 | if(iter == matches.end()) { | |
175 | trim_right(line); | |
176 | lines.push_front(line); | |
177 | } | |
178 | } | |
179 | ||
8dce71ea | 180 | Json::array items; |
dd079764 RG |
181 | for(const string& iline : lines) { |
182 | items.push_back(iline); | |
6ec5e728 | 183 | } |
8dce71ea | 184 | return items; |
6ec5e728 CH |
185 | } |
186 | ||
187 | void apiServerSearchLog(HttpRequest* req, HttpResponse* resp) { | |
188 | if(req->method != "GET") | |
189 | throw HttpMethodNotAllowedException(); | |
190 | ||
fd99e845 | 191 | string prefix = " " + s_programname + "["; |
8dce71ea | 192 | resp->setBody(logGrep(req->getvars["q"], ::arg()["api-logfile"], prefix)); |
6ec5e728 CH |
193 | } |
194 | ||
195 | void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { | |
196 | if(req->method != "GET") | |
197 | throw HttpMethodNotAllowedException(); | |
198 | ||
199 | map<string,string> items; | |
200 | productServerStatisticsFetch(items); | |
201 | ||
9aeac747 | 202 | Json::array doc; |
6ec5e728 | 203 | typedef map<string, string> items_t; |
9aeac747 CH |
204 | for(const items_t::value_type& item : items) { |
205 | doc.push_back(Json::object { | |
206 | { "type", "StatisticItem" }, | |
207 | { "name", item.first }, | |
208 | { "value", item.second }, | |
209 | }); | |
6ec5e728 CH |
210 | } |
211 | ||
669822d0 | 212 | resp->setBody(doc); |
6ec5e728 | 213 | } |
3c3c006b | 214 | |
c576d0c5 CH |
215 | DNSName apiNameToDNSName(const string& name) { |
216 | if (!isCanonical(name)) { | |
217 | throw ApiException("DNS Name '" + name + "' is not canonical"); | |
218 | } | |
219 | try { | |
220 | return DNSName(name); | |
221 | } catch (...) { | |
222 | throw ApiException("Unable to parse DNS Name '" + name + "'"); | |
223 | } | |
224 | } | |
225 | ||
8171ab83 | 226 | DNSName apiZoneIdToName(const string& id) { |
3c3c006b CH |
227 | string zonename; |
228 | ostringstream ss; | |
229 | ||
230 | if(id.empty()) | |
231 | throw HttpBadRequestException(); | |
232 | ||
233 | std::size_t lastpos = 0, pos = 0; | |
234 | while ((pos = id.find('=', lastpos)) != string::npos) { | |
235 | ss << id.substr(lastpos, pos-lastpos); | |
1dbe38ba CH |
236 | char c; |
237 | // decode tens | |
238 | if (id[pos+1] >= '0' && id[pos+1] <= '9') { | |
239 | c = id[pos+1] - '0'; | |
240 | } else if (id[pos+1] >= 'A' && id[pos+1] <= 'F') { | |
241 | c = id[pos+1] - 'A' + 10; | |
3c3c006b CH |
242 | } else { |
243 | throw HttpBadRequestException(); | |
244 | } | |
808bc955 | 245 | c = c * 16; |
1dbe38ba CH |
246 | |
247 | // decode unit place | |
248 | if (id[pos+2] >= '0' && id[pos+2] <= '9') { | |
249 | c += id[pos+2] - '0'; | |
250 | } else if (id[pos+2] >= 'A' && id[pos+2] <= 'F') { | |
251 | c += id[pos+2] - 'A' + 10; | |
252 | } else { | |
253 | throw HttpBadRequestException(); | |
254 | } | |
255 | ||
256 | ss << c; | |
3c3c006b CH |
257 | |
258 | lastpos = pos+3; | |
259 | } | |
260 | if (lastpos < pos) { | |
261 | ss << id.substr(lastpos, pos-lastpos); | |
262 | } | |
263 | ||
264 | zonename = ss.str(); | |
265 | ||
1d6b70f9 CH |
266 | try { |
267 | return DNSName(zonename); | |
268 | } catch (...) { | |
269 | throw ApiException("Unable to parse DNS Name '" + zonename + "'"); | |
270 | } | |
3c3c006b CH |
271 | } |
272 | ||
8171ab83 | 273 | string apiZoneNameToId(const DNSName& dname) { |
274 | string name=dname.toString(); | |
3c3c006b CH |
275 | ostringstream ss; |
276 | ||
277 | for(string::const_iterator iter = name.begin(); iter != name.end(); ++iter) { | |
278 | if ((*iter >= 'A' && *iter <= 'Z') || | |
279 | (*iter >= 'a' && *iter <= 'z') || | |
280 | (*iter >= '0' && *iter <= '9') || | |
281 | (*iter == '.') || (*iter == '-')) { | |
282 | ss << *iter; | |
283 | } else { | |
1dbe38ba | 284 | ss << (boost::format("=%02X") % (int)(*iter)); |
3c3c006b CH |
285 | } |
286 | } | |
287 | ||
161a8b69 CH |
288 | string id = ss.str(); |
289 | ||
3c3c006b | 290 | // add trailing dot |
406497f5 | 291 | if (id.size() == 0 || id.substr(id.size()-1) != ".") { |
161a8b69 CH |
292 | id += "."; |
293 | } | |
3c3c006b CH |
294 | |
295 | // special handling for the root zone, as a dot on it's own doesn't work | |
296 | // everywhere. | |
297 | if (id == ".") { | |
406497f5 | 298 | id = (boost::format("=%02X") % (int)('.')).str(); |
3c3c006b CH |
299 | } |
300 | return id; | |
301 | } | |
1d6b70f9 | 302 | |
cc015fba AT |
303 | void apiCheckNameAllowedCharacters(const string& name) { |
304 | if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos) | |
305 | throw ApiException("Name '"+name+"' contains unsupported characters"); | |
1d6b70f9 | 306 | } |
cb9b5901 AT |
307 | |
308 | void apiCheckQNameAllowedCharacters(const string& qname) { | |
309 | if (qname.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname.substr(2)); | |
310 | else apiCheckNameAllowedCharacters(qname); | |
311 | } |