]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-api.cc
Merge pull request #6136 from zeha/apitests
[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"
33#include <stdio.h>
34#include <string.h>
35#include <ctype.h>
36#include <sys/types.h>
3c3c006b 37#include <iomanip>
6ec5e728 38
fd99e845 39extern string s_programname;
f461cf07 40using json11::Json;
fd99e845 41
6ec5e728
CH
42#ifndef HAVE_STRCASESTR
43
44/*
45 * strcasestr() locates the first occurrence in the string s1 of the
46 * sequence of characters (excluding the terminating null character)
47 * in the string s2, ignoring case. strcasestr() returns a pointer
48 * to the located string, or a null pointer if the string is not found.
49 * If s2 is empty, the function returns s1.
50 */
51
52static char *
53strcasestr(const char *s1, const char *s2)
54{
55 int *cm = __trans_lower;
56 const uchar_t *us1 = (const uchar_t *)s1;
57 const uchar_t *us2 = (const uchar_t *)s2;
58 const uchar_t *tptr;
59 int c;
60
61 if (us2 == NULL || *us2 == '\0')
62 return ((char *)us1);
63
64 c = cm[*us2];
65 while (*us1 != '\0') {
66 if (c == cm[*us1++]) {
67 tptr = us1;
68 while (cm[c = *++us2] == cm[*us1++] && c != '\0')
69 continue;
70 if (c == '\0')
71 return ((char *)tptr - 1);
72 us1 = tptr;
73 us2 = (const uchar_t *)s2;
74 c = cm[*us2];
75 }
76 }
77
78 return (NULL);
79}
80
81#endif // HAVE_STRCASESTR
82
f461cf07
CH
83static Json getServerDetail() {
84 return Json::object {
85 { "type", "Server" },
86 { "id", "localhost" },
87 { "url", "/api/v1/servers/localhost" },
88 { "daemon_type", productTypeApiType() },
89 { "version", getPDNSVersion() },
90 { "config_url", "/api/v1/servers/localhost/config{/config_setting}" },
91 { "zones_url", "/api/v1/servers/localhost/zones{/zone}" }
92 };
6ec5e728
CH
93}
94
9e6d2033
CH
95/* Return information about the supported API versions.
96 * The format of this MUST NEVER CHANGE at it's not versioned.
97 */
98void apiDiscovery(HttpRequest* req, HttpResponse* resp) {
99 if(req->method != "GET")
100 throw HttpMethodNotAllowedException();
101
102 Json version1 = Json::object {
103 { "version", 1 },
104 { "url", "/api/v1" }
105 };
106 Json doc = Json::array { version1 };
107
108 resp->setBody(doc);
109}
110
6ec5e728
CH
111void apiServer(HttpRequest* req, HttpResponse* resp) {
112 if(req->method != "GET")
113 throw HttpMethodNotAllowedException();
114
f461cf07 115 Json doc = Json::array {getServerDetail()};
669822d0 116 resp->setBody(doc);
6ec5e728
CH
117}
118
119void apiServerDetail(HttpRequest* req, HttpResponse* resp) {
120 if(req->method != "GET")
121 throw HttpMethodNotAllowedException();
122
f461cf07 123 resp->setBody(getServerDetail());
6ec5e728
CH
124}
125
126void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
127 if(req->method != "GET")
128 throw HttpMethodNotAllowedException();
129
130 vector<string> items = ::arg().list();
131 string value;
9857fdbc
CH
132 Json::array doc;
133 for(const string& item : items) {
4a1dd79d 134 if(item.find("password") != string::npos || item.find("api-key") != string::npos)
6ec5e728
CH
135 value = "***";
136 else
137 value = ::arg()[item];
138
9857fdbc
CH
139 doc.push_back(Json::object {
140 { "type", "ConfigSetting" },
141 { "name", item },
142 { "value", value },
143 });
6ec5e728 144 }
669822d0 145 resp->setBody(doc);
6ec5e728
CH
146}
147
8dce71ea 148static Json logGrep(const string& q, const string& fname, const string& prefix)
6ec5e728
CH
149{
150 FILE* ptr = fopen(fname.c_str(), "r");
151 if(!ptr) {
6825bb9a 152 throw ApiException("Opening \"" + fname + "\" failed: " + stringerror());
6ec5e728 153 }
dd7da6cd 154 std::shared_ptr<FILE> fp(ptr, fclose);
6ec5e728
CH
155
156 string line;
157 string needle = q;
158 trim_right(needle);
159
160 boost::replace_all(needle, "%20", " ");
161 boost::replace_all(needle, "%22", "\"");
162
163 boost::tokenizer<boost::escaped_list_separator<char> > t(needle, boost::escaped_list_separator<char>("\\", " ", "\""));
164 vector<string> matches(t.begin(), t.end());
165 matches.push_back(prefix);
166
167 boost::circular_buffer<string> lines(200);
168 while(stringfgets(fp.get(), line)) {
169 vector<string>::const_iterator iter;
170 for(iter = matches.begin(); iter != matches.end(); ++iter) {
171 if(!strcasestr(line.c_str(), iter->c_str()))
172 break;
173 }
174 if(iter == matches.end()) {
175 trim_right(line);
176 lines.push_front(line);
177 }
178 }
179
8dce71ea 180 Json::array items;
dd079764
RG
181 for(const string& iline : lines) {
182 items.push_back(iline);
6ec5e728 183 }
8dce71ea 184 return items;
6ec5e728
CH
185}
186
187void apiServerSearchLog(HttpRequest* req, HttpResponse* resp) {
188 if(req->method != "GET")
189 throw HttpMethodNotAllowedException();
190
fd99e845 191 string prefix = " " + s_programname + "[";
8dce71ea 192 resp->setBody(logGrep(req->getvars["q"], ::arg()["api-logfile"], prefix));
6ec5e728
CH
193}
194
195void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
196 if(req->method != "GET")
197 throw HttpMethodNotAllowedException();
198
199 map<string,string> items;
200 productServerStatisticsFetch(items);
201
9aeac747 202 Json::array doc;
6ec5e728 203 typedef map<string, string> items_t;
9aeac747
CH
204 for(const items_t::value_type& item : items) {
205 doc.push_back(Json::object {
206 { "type", "StatisticItem" },
207 { "name", item.first },
208 { "value", item.second },
209 });
6ec5e728
CH
210 }
211
669822d0 212 resp->setBody(doc);
6ec5e728 213}
3c3c006b 214
c576d0c5
CH
215DNSName apiNameToDNSName(const string& name) {
216 if (!isCanonical(name)) {
217 throw ApiException("DNS Name '" + name + "' is not canonical");
218 }
219 try {
220 return DNSName(name);
221 } catch (...) {
222 throw ApiException("Unable to parse DNS Name '" + name + "'");
223 }
224}
225
8171ab83 226DNSName apiZoneIdToName(const string& id) {
3c3c006b
CH
227 string zonename;
228 ostringstream ss;
229
230 if(id.empty())
231 throw HttpBadRequestException();
232
233 std::size_t lastpos = 0, pos = 0;
234 while ((pos = id.find('=', lastpos)) != string::npos) {
235 ss << id.substr(lastpos, pos-lastpos);
1dbe38ba
CH
236 char c;
237 // decode tens
238 if (id[pos+1] >= '0' && id[pos+1] <= '9') {
239 c = id[pos+1] - '0';
240 } else if (id[pos+1] >= 'A' && id[pos+1] <= 'F') {
241 c = id[pos+1] - 'A' + 10;
3c3c006b
CH
242 } else {
243 throw HttpBadRequestException();
244 }
808bc955 245 c = c * 16;
1dbe38ba
CH
246
247 // decode unit place
248 if (id[pos+2] >= '0' && id[pos+2] <= '9') {
249 c += id[pos+2] - '0';
250 } else if (id[pos+2] >= 'A' && id[pos+2] <= 'F') {
251 c += id[pos+2] - 'A' + 10;
252 } else {
253 throw HttpBadRequestException();
254 }
255
256 ss << c;
3c3c006b
CH
257
258 lastpos = pos+3;
259 }
260 if (lastpos < pos) {
261 ss << id.substr(lastpos, pos-lastpos);
262 }
263
264 zonename = ss.str();
265
1d6b70f9
CH
266 try {
267 return DNSName(zonename);
268 } catch (...) {
269 throw ApiException("Unable to parse DNS Name '" + zonename + "'");
270 }
3c3c006b
CH
271}
272
8171ab83 273string apiZoneNameToId(const DNSName& dname) {
274 string name=dname.toString();
3c3c006b
CH
275 ostringstream ss;
276
277 for(string::const_iterator iter = name.begin(); iter != name.end(); ++iter) {
278 if ((*iter >= 'A' && *iter <= 'Z') ||
279 (*iter >= 'a' && *iter <= 'z') ||
280 (*iter >= '0' && *iter <= '9') ||
281 (*iter == '.') || (*iter == '-')) {
282 ss << *iter;
283 } else {
1dbe38ba 284 ss << (boost::format("=%02X") % (int)(*iter));
3c3c006b
CH
285 }
286 }
287
161a8b69
CH
288 string id = ss.str();
289
3c3c006b 290 // add trailing dot
406497f5 291 if (id.size() == 0 || id.substr(id.size()-1) != ".") {
161a8b69
CH
292 id += ".";
293 }
3c3c006b
CH
294
295 // special handling for the root zone, as a dot on it's own doesn't work
296 // everywhere.
297 if (id == ".") {
406497f5 298 id = (boost::format("=%02X") % (int)('.')).str();
3c3c006b
CH
299 }
300 return id;
301}
1d6b70f9 302
cc015fba
AT
303void apiCheckNameAllowedCharacters(const string& name) {
304 if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos)
305 throw ApiException("Name '"+name+"' contains unsupported characters");
1d6b70f9 306}
cb9b5901
AT
307
308void apiCheckQNameAllowedCharacters(const string& qname) {
309 if (qname.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname.substr(2));
310 else apiCheckNameAllowedCharacters(qname);
311}