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