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