]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/ws-api.cc
Merge pull request #7057 from mind04/sd-scopemask
[thirdparty/pdns.git] / pdns / ws-api.cc
1 /*
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 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <boost/tokenizer.hpp>
27 #include <boost/circular_buffer.hpp>
28 #include "namespaces.hh"
29 #include "ws-api.hh"
30 #include "json.hh"
31 #include "version.hh"
32 #include "arguments.hh"
33 #include "dnsparser.hh"
34 #include "responsestats.hh"
35 #ifndef RECURSOR
36 #include "statbag.hh"
37 #endif
38 #include <stdio.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <sys/types.h>
42 #include <iomanip>
43
44 using json11::Json;
45
46 extern string s_programname;
47 extern ResponseStats g_rs;
48 #ifndef RECURSOR
49 extern StatBag S;
50 #endif
51
52 #ifndef HAVE_STRCASESTR
53
54 /*
55 * strcasestr() locates the first occurrence in the string s1 of the
56 * sequence of characters (excluding the terminating null character)
57 * in the string s2, ignoring case. strcasestr() returns a pointer
58 * to the located string, or a null pointer if the string is not found.
59 * If s2 is empty, the function returns s1.
60 */
61
62 static char *
63 strcasestr(const char *s1, const char *s2)
64 {
65 int *cm = __trans_lower;
66 const uchar_t *us1 = (const uchar_t *)s1;
67 const uchar_t *us2 = (const uchar_t *)s2;
68 const uchar_t *tptr;
69 int c;
70
71 if (us2 == NULL || *us2 == '\0')
72 return ((char *)us1);
73
74 c = cm[*us2];
75 while (*us1 != '\0') {
76 if (c == cm[*us1++]) {
77 tptr = us1;
78 while (cm[c = *++us2] == cm[*us1++] && c != '\0')
79 continue;
80 if (c == '\0')
81 return ((char *)tptr - 1);
82 us1 = tptr;
83 us2 = (const uchar_t *)s2;
84 c = cm[*us2];
85 }
86 }
87
88 return (NULL);
89 }
90
91 #endif // HAVE_STRCASESTR
92
93 static Json getServerDetail() {
94 return Json::object {
95 { "type", "Server" },
96 { "id", "localhost" },
97 { "url", "/api/v1/servers/localhost" },
98 { "daemon_type", productTypeApiType() },
99 { "version", getPDNSVersion() },
100 { "config_url", "/api/v1/servers/localhost/config{/config_setting}" },
101 { "zones_url", "/api/v1/servers/localhost/zones{/zone}" }
102 };
103 }
104
105 /* Return information about the supported API versions.
106 * The format of this MUST NEVER CHANGE at it's not versioned.
107 */
108 void apiDiscovery(HttpRequest* req, HttpResponse* resp) {
109 if(req->method != "GET")
110 throw HttpMethodNotAllowedException();
111
112 Json version1 = Json::object {
113 { "version", 1 },
114 { "url", "/api/v1" }
115 };
116 Json doc = Json::array { version1 };
117
118 resp->setBody(doc);
119 }
120
121 void apiServer(HttpRequest* req, HttpResponse* resp) {
122 if(req->method != "GET")
123 throw HttpMethodNotAllowedException();
124
125 Json doc = Json::array {getServerDetail()};
126 resp->setBody(doc);
127 }
128
129 void apiServerDetail(HttpRequest* req, HttpResponse* resp) {
130 if(req->method != "GET")
131 throw HttpMethodNotAllowedException();
132
133 resp->setBody(getServerDetail());
134 }
135
136 void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
137 if(req->method != "GET")
138 throw HttpMethodNotAllowedException();
139
140 vector<string> items = ::arg().list();
141 string value;
142 Json::array doc;
143 for(const string& item : items) {
144 if(item.find("password") != string::npos || item.find("api-key") != string::npos)
145 value = "***";
146 else
147 value = ::arg()[item];
148
149 doc.push_back(Json::object {
150 { "type", "ConfigSetting" },
151 { "name", item },
152 { "value", value },
153 });
154 }
155 resp->setBody(doc);
156 }
157
158 void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
159 if(req->method != "GET")
160 throw HttpMethodNotAllowedException();
161
162 typedef map<string, string> stat_items_t;
163 stat_items_t general_stats;
164 productServerStatisticsFetch(general_stats);
165
166 auto resp_qtype_stats = g_rs.getQTypeResponseCounts();
167 auto resp_size_stats = g_rs.getSizeResponseCounts();
168
169 Json::array doc;
170 for(const auto& item : general_stats) {
171 doc.push_back(Json::object {
172 { "type", "StatisticItem" },
173 { "name", item.first },
174 { "value", item.second },
175 });
176 }
177
178 {
179 Json::array values;
180 for(const auto& item : resp_qtype_stats) {
181 if (item.second == 0)
182 continue;
183 values.push_back(Json::object {
184 { "name", DNSRecordContent::NumberToType(item.first) },
185 { "value", std::to_string(item.second) },
186 });
187 }
188
189 doc.push_back(Json::object {
190 { "type", "MapStatisticItem" },
191 { "name", "queries-by-qtype" },
192 { "value", values },
193 });
194 }
195
196 {
197 Json::array values;
198 for(const auto& item : resp_size_stats) {
199 if (item.second == 0)
200 continue;
201
202 values.push_back(Json::object {
203 { "name", std::to_string(item.first) },
204 { "value", std::to_string(item.second) },
205 });
206 }
207
208 doc.push_back(Json::object {
209 { "type", "MapStatisticItem" },
210 { "name", "response-sizes" },
211 { "value", values },
212 });
213 }
214
215 #ifndef RECURSOR
216 for(const auto& ringName : S.listRings()) {
217 Json::array values;
218 const auto& ring = S.getRing(ringName);
219 for(const auto& item : ring) {
220 if (item.second == 0)
221 continue;
222
223 values.push_back(Json::object {
224 { "name", item.first },
225 { "value", std::to_string(item.second) },
226 });
227 }
228
229 doc.push_back(Json::object {
230 { "type", "RingStatisticItem" },
231 { "name", ringName },
232 { "size", std::to_string(S.getRingSize(ringName)) },
233 { "value", values },
234 });
235 }
236 #endif
237
238 resp->setBody(doc);
239 }
240
241 DNSName apiNameToDNSName(const string& name) {
242 if (!isCanonical(name)) {
243 throw ApiException("DNS Name '" + name + "' is not canonical");
244 }
245 try {
246 return DNSName(name);
247 } catch (...) {
248 throw ApiException("Unable to parse DNS Name '" + name + "'");
249 }
250 }
251
252 DNSName apiZoneIdToName(const string& id) {
253 string zonename;
254 ostringstream ss;
255
256 if(id.empty())
257 throw HttpBadRequestException();
258
259 std::size_t lastpos = 0, pos = 0;
260 while ((pos = id.find('=', lastpos)) != string::npos) {
261 ss << id.substr(lastpos, pos-lastpos);
262 char c;
263 // decode tens
264 if (id[pos+1] >= '0' && id[pos+1] <= '9') {
265 c = id[pos+1] - '0';
266 } else if (id[pos+1] >= 'A' && id[pos+1] <= 'F') {
267 c = id[pos+1] - 'A' + 10;
268 } else {
269 throw HttpBadRequestException();
270 }
271 c = c * 16;
272
273 // decode unit place
274 if (id[pos+2] >= '0' && id[pos+2] <= '9') {
275 c += id[pos+2] - '0';
276 } else if (id[pos+2] >= 'A' && id[pos+2] <= 'F') {
277 c += id[pos+2] - 'A' + 10;
278 } else {
279 throw HttpBadRequestException();
280 }
281
282 ss << c;
283
284 lastpos = pos+3;
285 }
286 if (lastpos < pos) {
287 ss << id.substr(lastpos, pos-lastpos);
288 }
289
290 zonename = ss.str();
291
292 try {
293 return DNSName(zonename);
294 } catch (...) {
295 throw ApiException("Unable to parse DNS Name '" + zonename + "'");
296 }
297 }
298
299 string apiZoneNameToId(const DNSName& dname) {
300 string name=dname.toString();
301 ostringstream ss;
302
303 for(string::const_iterator iter = name.begin(); iter != name.end(); ++iter) {
304 if ((*iter >= 'A' && *iter <= 'Z') ||
305 (*iter >= 'a' && *iter <= 'z') ||
306 (*iter >= '0' && *iter <= '9') ||
307 (*iter == '.') || (*iter == '-')) {
308 ss << *iter;
309 } else {
310 ss << (boost::format("=%02X") % (int)(*iter));
311 }
312 }
313
314 string id = ss.str();
315
316 // add trailing dot
317 if (id.size() == 0 || id.substr(id.size()-1) != ".") {
318 id += ".";
319 }
320
321 // special handling for the root zone, as a dot on it's own doesn't work
322 // everywhere.
323 if (id == ".") {
324 id = (boost::format("=%02X") % (int)('.')).str();
325 }
326 return id;
327 }
328
329 void apiCheckNameAllowedCharacters(const string& name) {
330 if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos)
331 throw ApiException("Name '"+name+"' contains unsupported characters");
332 }
333
334 void apiCheckQNameAllowedCharacters(const string& qname) {
335 if (qname.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname.substr(2));
336 else apiCheckNameAllowedCharacters(qname);
337 }