]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-api.cc
Merge pull request #7689 from rgacogne/dnsdist-14-packetcache
[thirdparty/pdns.git] / pdns / ws-api.cc
CommitLineData
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"
118d3b31
CH
33#include "dnsparser.hh"
34#include "responsestats.hh"
2977c6bb 35#ifndef RECURSOR
118d3b31 36#include "statbag.hh"
2977c6bb 37#endif
6ec5e728
CH
38#include <stdio.h>
39#include <string.h>
40#include <ctype.h>
41#include <sys/types.h>
3c3c006b 42#include <iomanip>
6ec5e728 43
f461cf07 44using json11::Json;
fd99e845 45
118d3b31
CH
46extern string s_programname;
47extern ResponseStats g_rs;
48#ifndef RECURSOR
49extern StatBag S;
50#endif
51
6ec5e728
CH
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
62static char *
63strcasestr(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
f461cf07
CH
93static 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 };
6ec5e728
CH
103}
104
9e6d2033
CH
105/* Return information about the supported API versions.
106 * The format of this MUST NEVER CHANGE at it's not versioned.
107 */
108void 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
6ec5e728
CH
121void apiServer(HttpRequest* req, HttpResponse* resp) {
122 if(req->method != "GET")
123 throw HttpMethodNotAllowedException();
124
f461cf07 125 Json doc = Json::array {getServerDetail()};
669822d0 126 resp->setBody(doc);
6ec5e728
CH
127}
128
129void apiServerDetail(HttpRequest* req, HttpResponse* resp) {
130 if(req->method != "GET")
131 throw HttpMethodNotAllowedException();
132
f461cf07 133 resp->setBody(getServerDetail());
6ec5e728
CH
134}
135
136void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
137 if(req->method != "GET")
138 throw HttpMethodNotAllowedException();
139
140 vector<string> items = ::arg().list();
141 string value;
9857fdbc
CH
142 Json::array doc;
143 for(const string& item : items) {
4a1dd79d 144 if(item.find("password") != string::npos || item.find("api-key") != string::npos)
6ec5e728
CH
145 value = "***";
146 else
147 value = ::arg()[item];
148
9857fdbc
CH
149 doc.push_back(Json::object {
150 { "type", "ConfigSetting" },
151 { "name", item },
152 { "value", value },
153 });
6ec5e728 154 }
669822d0 155 resp->setBody(doc);
6ec5e728
CH
156}
157
6ec5e728
CH
158void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
159 if(req->method != "GET")
160 throw HttpMethodNotAllowedException();
161
5376a5d7
RG
162 Json::array doc;
163 string name = req->getvars["statistic"];
164 if (!name.empty()) {
165 auto stat = productServerStatisticsFetch(name);
166 if (!stat) {
167 throw ApiException("Unknown statistic name");
168 }
169
170 doc.push_back(Json::object {
171 { "type", "StatisticItem" },
172 { "name", name },
173 { "value", std::to_string(*stat) },
174 });
175
176 resp->setBody(doc);
177
178 return;
179 }
180
118d3b31
CH
181 typedef map<string, string> stat_items_t;
182 stat_items_t general_stats;
183 productServerStatisticsFetch(general_stats);
184
118d3b31 185 for(const auto& item : general_stats) {
9aeac747
CH
186 doc.push_back(Json::object {
187 { "type", "StatisticItem" },
188 { "name", item.first },
189 { "value", item.second },
190 });
6ec5e728
CH
191 }
192
5376a5d7
RG
193 auto resp_qtype_stats = g_rs.getQTypeResponseCounts();
194 auto resp_size_stats = g_rs.getSizeResponseCounts();
195 auto resp_rcode_stats = g_rs.getRCodeResponseCounts();
118d3b31
CH
196 {
197 Json::array values;
198 for(const auto& item : resp_qtype_stats) {
199 if (item.second == 0)
200 continue;
201 values.push_back(Json::object {
202 { "name", DNSRecordContent::NumberToType(item.first) },
203 { "value", std::to_string(item.second) },
204 });
205 }
206
207 doc.push_back(Json::object {
208 { "type", "MapStatisticItem" },
87798d5e 209 { "name", "response-by-qtype" },
118d3b31
CH
210 { "value", values },
211 });
212 }
213
214 {
215 Json::array values;
216 for(const auto& item : resp_size_stats) {
217 if (item.second == 0)
218 continue;
219
220 values.push_back(Json::object {
221 { "name", std::to_string(item.first) },
222 { "value", std::to_string(item.second) },
223 });
224 }
225
226 doc.push_back(Json::object {
227 { "type", "MapStatisticItem" },
228 { "name", "response-sizes" },
229 { "value", values },
230 });
231 }
232
87798d5e
TB
233 {
234 Json::array values;
235 for(const auto& item : resp_rcode_stats) {
236 if (item.second == 0)
237 continue;
238 values.push_back(Json::object {
239 { "name", RCode::to_s(item.first) },
240 { "value", std::to_string(item.second) },
241 });
242 }
243
244 doc.push_back(Json::object {
245 { "type", "MapStatisticItem" },
246 { "name", "response-by-rcode" },
247 { "value", values },
248 });
249 }
250
118d3b31
CH
251#ifndef RECURSOR
252 for(const auto& ringName : S.listRings()) {
253 Json::array values;
254 const auto& ring = S.getRing(ringName);
255 for(const auto& item : ring) {
256 if (item.second == 0)
257 continue;
258
259 values.push_back(Json::object {
260 { "name", item.first },
261 { "value", std::to_string(item.second) },
262 });
263 }
264
265 doc.push_back(Json::object {
266 { "type", "RingStatisticItem" },
267 { "name", ringName },
268 { "size", std::to_string(S.getRingSize(ringName)) },
269 { "value", values },
270 });
271 }
272#endif
273
669822d0 274 resp->setBody(doc);
6ec5e728 275}
3c3c006b 276
c576d0c5
CH
277DNSName apiNameToDNSName(const string& name) {
278 if (!isCanonical(name)) {
279 throw ApiException("DNS Name '" + name + "' is not canonical");
280 }
281 try {
282 return DNSName(name);
283 } catch (...) {
284 throw ApiException("Unable to parse DNS Name '" + name + "'");
285 }
286}
287
8171ab83 288DNSName apiZoneIdToName(const string& id) {
3c3c006b
CH
289 string zonename;
290 ostringstream ss;
291
292 if(id.empty())
293 throw HttpBadRequestException();
294
295 std::size_t lastpos = 0, pos = 0;
296 while ((pos = id.find('=', lastpos)) != string::npos) {
297 ss << id.substr(lastpos, pos-lastpos);
1dbe38ba
CH
298 char c;
299 // decode tens
300 if (id[pos+1] >= '0' && id[pos+1] <= '9') {
301 c = id[pos+1] - '0';
302 } else if (id[pos+1] >= 'A' && id[pos+1] <= 'F') {
303 c = id[pos+1] - 'A' + 10;
3c3c006b
CH
304 } else {
305 throw HttpBadRequestException();
306 }
808bc955 307 c = c * 16;
1dbe38ba
CH
308
309 // decode unit place
310 if (id[pos+2] >= '0' && id[pos+2] <= '9') {
311 c += id[pos+2] - '0';
312 } else if (id[pos+2] >= 'A' && id[pos+2] <= 'F') {
313 c += id[pos+2] - 'A' + 10;
314 } else {
315 throw HttpBadRequestException();
316 }
317
318 ss << c;
3c3c006b
CH
319
320 lastpos = pos+3;
321 }
322 if (lastpos < pos) {
323 ss << id.substr(lastpos, pos-lastpos);
324 }
325
326 zonename = ss.str();
327
1d6b70f9
CH
328 try {
329 return DNSName(zonename);
330 } catch (...) {
331 throw ApiException("Unable to parse DNS Name '" + zonename + "'");
332 }
3c3c006b
CH
333}
334
8171ab83 335string apiZoneNameToId(const DNSName& dname) {
336 string name=dname.toString();
3c3c006b
CH
337 ostringstream ss;
338
339 for(string::const_iterator iter = name.begin(); iter != name.end(); ++iter) {
340 if ((*iter >= 'A' && *iter <= 'Z') ||
341 (*iter >= 'a' && *iter <= 'z') ||
342 (*iter >= '0' && *iter <= '9') ||
343 (*iter == '.') || (*iter == '-')) {
344 ss << *iter;
345 } else {
1dbe38ba 346 ss << (boost::format("=%02X") % (int)(*iter));
3c3c006b
CH
347 }
348 }
349
161a8b69
CH
350 string id = ss.str();
351
3c3c006b 352 // add trailing dot
406497f5 353 if (id.size() == 0 || id.substr(id.size()-1) != ".") {
161a8b69
CH
354 id += ".";
355 }
3c3c006b
CH
356
357 // special handling for the root zone, as a dot on it's own doesn't work
358 // everywhere.
359 if (id == ".") {
406497f5 360 id = (boost::format("=%02X") % (int)('.')).str();
3c3c006b
CH
361 }
362 return id;
363}
1d6b70f9 364
cc015fba
AT
365void apiCheckNameAllowedCharacters(const string& name) {
366 if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos)
367 throw ApiException("Name '"+name+"' contains unsupported characters");
1d6b70f9 368}
cb9b5901
AT
369
370void apiCheckQNameAllowedCharacters(const string& qname) {
371 if (qname.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname.substr(2));
372 else apiCheckNameAllowedCharacters(qname);
373}