1 /*
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  */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include <boost/tokenizer.hpp>
27 #include "namespaces.hh"
28 #include "ws-api.hh"
29 #include "json.hh"
30 #include "version.hh"
31 #include "arguments.hh"
32 #include "dnsparser.hh"
33 #include "responsestats.hh"
34 #ifndef RECURSOR
35 #include "statbag.hh"
36 #endif
37 #include <stdio.h>
38 #include <string.h>
39 #include <ctype.h>
40 #include <sys/types.h>
41 #include <iomanip>
42 
43 using json11::Json;
44 
45 extern string s_programname;
46 extern ResponseStats g_rs;
47 #ifndef RECURSOR
48 extern StatBag S;
49 #endif
50 
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 
61 static char *
strcasestr(const char * s1,const char * s2)62 strcasestr(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 
getServerDetail()92 static 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   };
102 }
103 
104 /* Return information about the supported API versions.
105  * The format of this MUST NEVER CHANGE at it's not versioned.
106  */
apiDiscovery(HttpRequest * req,HttpResponse * resp)107 void 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->setJsonBody(doc);
118 }
119 
apiServer(HttpRequest * req,HttpResponse * resp)120 void apiServer(HttpRequest* req, HttpResponse* resp) {
121   if(req->method != "GET")
122     throw HttpMethodNotAllowedException();
123 
124   Json doc = Json::array {getServerDetail()};
125   resp->setJsonBody(doc);
126 }
127 
apiServerDetail(HttpRequest * req,HttpResponse * resp)128 void apiServerDetail(HttpRequest* req, HttpResponse* resp) {
129   if(req->method != "GET")
130     throw HttpMethodNotAllowedException();
131 
132   resp->setJsonBody(getServerDetail());
133 }
134 
apiServerConfig(HttpRequest * req,HttpResponse * resp)135 void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
136   if(req->method != "GET")
137     throw HttpMethodNotAllowedException();
138 
139   vector<string> items = ::arg().list();
140   string value;
141   Json::array doc;
142   for(const string& item : items) {
143     if(item.find("password") != string::npos || item.find("api-key") != string::npos)
144       value = "***";
145     else
146       value = ::arg()[item];
147 
148     doc.push_back(Json::object {
149       { "type", "ConfigSetting" },
150       { "name", item },
151       { "value", value },
152     });
153   }
154   resp->setJsonBody(doc);
155 }
156 
apiServerStatistics(HttpRequest * req,HttpResponse * resp)157 void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
158   if(req->method != "GET")
159     throw HttpMethodNotAllowedException();
160 
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->setJsonBody(doc);
176 
177     return;
178   }
179 
180   typedef map<string, string> stat_items_t;
181   stat_items_t general_stats;
182   productServerStatisticsFetch(general_stats);
183 
184   for(const auto& item : general_stats) {
185     doc.push_back(Json::object {
186       { "type", "StatisticItem" },
187       { "name", item.first },
188       { "value", item.second },
189     });
190   }
191 
192   auto resp_qtype_stats = g_rs.getQTypeResponseCounts();
193   auto resp_size_stats = g_rs.getSizeResponseCounts();
194   auto resp_rcode_stats = g_rs.getRCodeResponseCounts();
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" },
208       { "name", "response-by-qtype" },
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 
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 
250 #ifndef RECURSOR
251   if (!req->getvars.count("includerings") ||
252        req->getvars["includerings"] != "false") {
253     for(const auto& ringName : S.listRings()) {
254       Json::array values;
255       const auto& ring = S.getRing(ringName);
256       for(const auto& item : ring) {
257         if (item.second == 0)
258           continue;
259 
260         values.push_back(Json::object {
261           { "name", item.first },
262           { "value", std::to_string(item.second) },
263         });
264       }
265 
266       doc.push_back(Json::object {
267         { "type", "RingStatisticItem" },
268         { "name", ringName },
269         { "size", std::to_string(S.getRingSize(ringName)) },
270         { "value", values },
271       });
272     }
273   }
274 #endif
275 
276   resp->setJsonBody(doc);
277 }
278 
apiNameToDNSName(const string & name)279 DNSName apiNameToDNSName(const string& name) {
280   if (!isCanonical(name)) {
281     throw ApiException("DNS Name '" + name + "' is not canonical");
282   }
283   try {
284     return DNSName(name);
285   } catch (...) {
286     throw ApiException("Unable to parse DNS Name '" + name + "'");
287   }
288 }
289 
apiZoneIdToName(const string & id)290 DNSName apiZoneIdToName(const string& id) {
291   string zonename;
292   ostringstream ss;
293 
294   if(id.empty())
295     throw HttpBadRequestException();
296 
297   std::size_t lastpos = 0, pos = 0;
298   while ((pos = id.find('=', lastpos)) != string::npos) {
299     ss << id.substr(lastpos, pos-lastpos);
300     char c;
301     // decode tens
302     if (id[pos+1] >= '0' && id[pos+1] <= '9') {
303       c = id[pos+1] - '0';
304     } else if (id[pos+1] >= 'A' && id[pos+1] <= 'F') {
305       c = id[pos+1] - 'A' + 10;
306     } else {
307       throw HttpBadRequestException();
308     }
309     c = c * 16;
310 
311     // decode unit place
312     if (id[pos+2] >= '0' && id[pos+2] <= '9') {
313       c += id[pos+2] - '0';
314     } else if (id[pos+2] >= 'A' && id[pos+2] <= 'F') {
315       c += id[pos+2] - 'A' + 10;
316     } else {
317       throw HttpBadRequestException();
318     }
319 
320     ss << c;
321 
322     lastpos = pos+3;
323   }
324   if (lastpos < pos) {
325     ss << id.substr(lastpos, pos-lastpos);
326   }
327 
328   zonename = ss.str();
329 
330   try {
331     return DNSName(zonename);
332   } catch (...) {
333     throw ApiException("Unable to parse DNS Name '" + zonename + "'");
334   }
335 }
336 
apiZoneNameToId(const DNSName & dname)337 string apiZoneNameToId(const DNSName& dname) {
338   string name=dname.toString();
339   ostringstream ss;
340 
341   for(char iter : name) {
342     if ((iter >= 'A' && iter <= 'Z') ||
343         (iter >= 'a' && iter <= 'z') ||
344         (iter >= '0' && iter <= '9') ||
345         (iter == '.') || (iter == '-')) {
346       ss << iter;
347     } else {
348       ss << (boost::format("=%02X") % (int)iter);
349     }
350   }
351 
352   string id = ss.str();
353 
354   // add trailing dot
355   if (id.size() == 0 || id.substr(id.size()-1) != ".") {
356     id += ".";
357   }
358 
359   // special handling for the root zone, as a dot on it's own doesn't work
360   // everywhere.
361   if (id == ".") {
362     id = (boost::format("=%02X") % (int)('.')).str();
363   }
364   return id;
365 }
366 
apiCheckNameAllowedCharacters(const string & name)367 void apiCheckNameAllowedCharacters(const string& name) {
368   if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos)
369     throw ApiException("Name '"+name+"' contains unsupported characters");
370 }
371 
apiCheckQNameAllowedCharacters(const string & qname)372 void apiCheckQNameAllowedCharacters(const string& qname) {
373   if (qname.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname.substr(2));
374   else apiCheckNameAllowedCharacters(qname);
375 }
376