2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
26 #include <boost/tokenizer.hpp>
27 #include <boost/circular_buffer.hpp>
28 #include "namespaces.hh"
32 #include "arguments.hh"
33 #include "dnsparser.hh"
34 #include "responsestats.hh"
41 #include <sys/types.h>
46 extern string s_programname
;
47 extern ResponseStats g_rs
;
52 #ifndef HAVE_STRCASESTR
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.
63 strcasestr(const char *s1
, const char *s2
)
65 int *cm
= __trans_lower
;
66 const uchar_t
*us1
= (const uchar_t
*)s1
;
67 const uchar_t
*us2
= (const uchar_t
*)s2
;
71 if (us2
== NULL
|| *us2
== '\0')
75 while (*us1
!= '\0') {
76 if (c
== cm
[*us1
++]) {
78 while (cm
[c
= *++us2
] == cm
[*us1
++] && c
!= '\0')
81 return ((char *)tptr
- 1);
83 us2
= (const uchar_t
*)s2
;
91 #endif // HAVE_STRCASESTR
93 static Json
getServerDetail() {
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}" }
105 /* Return information about the supported API versions.
106 * The format of this MUST NEVER CHANGE at it's not versioned.
108 void apiDiscovery(HttpRequest
* req
, HttpResponse
* resp
) {
109 if(req
->method
!= "GET")
110 throw HttpMethodNotAllowedException();
112 Json version1
= Json::object
{
116 Json doc
= Json::array
{ version1
};
121 void apiServer(HttpRequest
* req
, HttpResponse
* resp
) {
122 if(req
->method
!= "GET")
123 throw HttpMethodNotAllowedException();
125 Json doc
= Json::array
{getServerDetail()};
129 void apiServerDetail(HttpRequest
* req
, HttpResponse
* resp
) {
130 if(req
->method
!= "GET")
131 throw HttpMethodNotAllowedException();
133 resp
->setBody(getServerDetail());
136 void apiServerConfig(HttpRequest
* req
, HttpResponse
* resp
) {
137 if(req
->method
!= "GET")
138 throw HttpMethodNotAllowedException();
140 vector
<string
> items
= ::arg().list();
143 for(const string
& item
: items
) {
144 if(item
.find("password") != string::npos
|| item
.find("api-key") != string::npos
)
147 value
= ::arg()[item
];
149 doc
.push_back(Json::object
{
150 { "type", "ConfigSetting" },
158 void apiServerStatistics(HttpRequest
* req
, HttpResponse
* resp
) {
159 if(req
->method
!= "GET")
160 throw HttpMethodNotAllowedException();
163 string name
= req
->getvars
["statistic"];
165 auto stat
= productServerStatisticsFetch(name
);
167 throw ApiException("Unknown statistic name");
170 doc
.push_back(Json::object
{
171 { "type", "StatisticItem" },
173 { "value", std::to_string(*stat
) },
181 typedef map
<string
, string
> stat_items_t
;
182 stat_items_t general_stats
;
183 productServerStatisticsFetch(general_stats
);
185 for(const auto& item
: general_stats
) {
186 doc
.push_back(Json::object
{
187 { "type", "StatisticItem" },
188 { "name", item
.first
},
189 { "value", item
.second
},
193 auto resp_qtype_stats
= g_rs
.getQTypeResponseCounts();
194 auto resp_size_stats
= g_rs
.getSizeResponseCounts();
195 auto resp_rcode_stats
= g_rs
.getRCodeResponseCounts();
198 for(const auto& item
: resp_qtype_stats
) {
199 if (item
.second
== 0)
201 values
.push_back(Json::object
{
202 { "name", DNSRecordContent::NumberToType(item
.first
) },
203 { "value", std::to_string(item
.second
) },
207 doc
.push_back(Json::object
{
208 { "type", "MapStatisticItem" },
209 { "name", "response-by-qtype" },
216 for(const auto& item
: resp_size_stats
) {
217 if (item
.second
== 0)
220 values
.push_back(Json::object
{
221 { "name", std::to_string(item
.first
) },
222 { "value", std::to_string(item
.second
) },
226 doc
.push_back(Json::object
{
227 { "type", "MapStatisticItem" },
228 { "name", "response-sizes" },
235 for(const auto& item
: resp_rcode_stats
) {
236 if (item
.second
== 0)
238 values
.push_back(Json::object
{
239 { "name", RCode::to_s(item
.first
) },
240 { "value", std::to_string(item
.second
) },
244 doc
.push_back(Json::object
{
245 { "type", "MapStatisticItem" },
246 { "name", "response-by-rcode" },
252 for(const auto& ringName
: S
.listRings()) {
254 const auto& ring
= S
.getRing(ringName
);
255 for(const auto& item
: ring
) {
256 if (item
.second
== 0)
259 values
.push_back(Json::object
{
260 { "name", item
.first
},
261 { "value", std::to_string(item
.second
) },
265 doc
.push_back(Json::object
{
266 { "type", "RingStatisticItem" },
267 { "name", ringName
},
268 { "size", std::to_string(S
.getRingSize(ringName
)) },
277 DNSName
apiNameToDNSName(const string
& name
) {
278 if (!isCanonical(name
)) {
279 throw ApiException("DNS Name '" + name
+ "' is not canonical");
282 return DNSName(name
);
284 throw ApiException("Unable to parse DNS Name '" + name
+ "'");
288 DNSName
apiZoneIdToName(const string
& id
) {
293 throw HttpBadRequestException();
295 std::size_t lastpos
= 0, pos
= 0;
296 while ((pos
= id
.find('=', lastpos
)) != string::npos
) {
297 ss
<< id
.substr(lastpos
, pos
-lastpos
);
300 if (id
[pos
+1] >= '0' && id
[pos
+1] <= '9') {
302 } else if (id
[pos
+1] >= 'A' && id
[pos
+1] <= 'F') {
303 c
= id
[pos
+1] - 'A' + 10;
305 throw HttpBadRequestException();
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;
315 throw HttpBadRequestException();
323 ss
<< id
.substr(lastpos
, pos
-lastpos
);
329 return DNSName(zonename
);
331 throw ApiException("Unable to parse DNS Name '" + zonename
+ "'");
335 string
apiZoneNameToId(const DNSName
& dname
) {
336 string name
=dname
.toString();
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
== '-')) {
346 ss
<< (boost::format("=%02X") % (int)(*iter
));
350 string id
= ss
.str();
353 if (id
.size() == 0 || id
.substr(id
.size()-1) != ".") {
357 // special handling for the root zone, as a dot on it's own doesn't work
360 id
= (boost::format("=%02X") % (int)('.')).str();
365 void apiCheckNameAllowedCharacters(const string
& name
) {
366 if (name
.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos
)
367 throw ApiException("Name '"+name
+"' contains unsupported characters");
370 void apiCheckQNameAllowedCharacters(const string
& qname
) {
371 if (qname
.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname
.substr(2));
372 else apiCheckNameAllowedCharacters(qname
);