/* * This file is part of PowerDNS or dnsdist. * Copyright -- PowerDNS.COM B.V. and its contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the GNU General Public License as * published by the Free Software Foundation. * * In addition, for the avoidance of any doubt, permission is granted to * link this program with OpenSSL and to (re)distribute the binaries * produced as the result of such linking. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "namespaces.hh" #include "ws-api.hh" #include "json.hh" #include "version.hh" #include "arguments.hh" #include "dnsparser.hh" #include "responsestats.hh" #ifndef RECURSOR #include "statbag.hh" #endif #include #include #include #include #include using json11::Json; extern string s_programname; extern ResponseStats g_rs; #ifndef RECURSOR extern StatBag S; #endif #ifndef HAVE_STRCASESTR /* * strcasestr() locates the first occurrence in the string s1 of the * sequence of characters (excluding the terminating null character) * in the string s2, ignoring case. strcasestr() returns a pointer * to the located string, or a null pointer if the string is not found. * If s2 is empty, the function returns s1. */ static char * strcasestr(const char *s1, const char *s2) { int *cm = __trans_lower; const uchar_t *us1 = (const uchar_t *)s1; const uchar_t *us2 = (const uchar_t *)s2; const uchar_t *tptr; int c; if (us2 == NULL || *us2 == '\0') return ((char *)us1); c = cm[*us2]; while (*us1 != '\0') { if (c == cm[*us1++]) { tptr = us1; while (cm[c = *++us2] == cm[*us1++] && c != '\0') continue; if (c == '\0') return ((char *)tptr - 1); us1 = tptr; us2 = (const uchar_t *)s2; c = cm[*us2]; } } return (NULL); } #endif // HAVE_STRCASESTR static Json getServerDetail() { return Json::object { { "type", "Server" }, { "id", "localhost" }, { "url", "/api/v1/servers/localhost" }, { "daemon_type", productTypeApiType() }, { "version", getPDNSVersion() }, { "config_url", "/api/v1/servers/localhost/config{/config_setting}" }, { "zones_url", "/api/v1/servers/localhost/zones{/zone}" } }; } /* Return information about the supported API versions. * The format of this MUST NEVER CHANGE at it's not versioned. */ void apiDiscovery(HttpRequest* req, HttpResponse* resp) { if(req->method != "GET") throw HttpMethodNotAllowedException(); Json version1 = Json::object { { "version", 1 }, { "url", "/api/v1" } }; Json doc = Json::array { version1 }; resp->setJsonBody(doc); } void apiServer(HttpRequest* req, HttpResponse* resp) { if(req->method != "GET") throw HttpMethodNotAllowedException(); Json doc = Json::array {getServerDetail()}; resp->setJsonBody(doc); } void apiServerDetail(HttpRequest* req, HttpResponse* resp) { if(req->method != "GET") throw HttpMethodNotAllowedException(); resp->setJsonBody(getServerDetail()); } void apiServerConfig(HttpRequest* req, HttpResponse* resp) { if(req->method != "GET") throw HttpMethodNotAllowedException(); vector items = ::arg().list(); string value; Json::array doc; for(const string& item : items) { if(item.find("password") != string::npos || item.find("api-key") != string::npos) value = "***"; else value = ::arg()[item]; doc.push_back(Json::object { { "type", "ConfigSetting" }, { "name", item }, { "value", value }, }); } resp->setJsonBody(doc); } void apiServerStatistics(HttpRequest* req, HttpResponse* resp) { if(req->method != "GET") throw HttpMethodNotAllowedException(); Json::array doc; string name = req->getvars["statistic"]; if (!name.empty()) { auto stat = productServerStatisticsFetch(name); if (!stat) { throw ApiException("Unknown statistic name"); } doc.push_back(Json::object { { "type", "StatisticItem" }, { "name", name }, { "value", std::to_string(*stat) }, }); resp->setJsonBody(doc); return; } typedef map stat_items_t; stat_items_t general_stats; productServerStatisticsFetch(general_stats); for(const auto& item : general_stats) { doc.push_back(Json::object { { "type", "StatisticItem" }, { "name", item.first }, { "value", item.second }, }); } auto resp_qtype_stats = g_rs.getQTypeResponseCounts(); auto resp_size_stats = g_rs.getSizeResponseCounts(); auto resp_rcode_stats = g_rs.getRCodeResponseCounts(); { Json::array values; for(const auto& item : resp_qtype_stats) { if (item.second == 0) continue; values.push_back(Json::object { { "name", DNSRecordContent::NumberToType(item.first) }, { "value", std::to_string(item.second) }, }); } doc.push_back(Json::object { { "type", "MapStatisticItem" }, { "name", "response-by-qtype" }, { "value", values }, }); } { Json::array values; for(const auto& item : resp_size_stats) { if (item.second == 0) continue; values.push_back(Json::object { { "name", std::to_string(item.first) }, { "value", std::to_string(item.second) }, }); } doc.push_back(Json::object { { "type", "MapStatisticItem" }, { "name", "response-sizes" }, { "value", values }, }); } { Json::array values; for(const auto& item : resp_rcode_stats) { if (item.second == 0) continue; values.push_back(Json::object { { "name", RCode::to_s(item.first) }, { "value", std::to_string(item.second) }, }); } doc.push_back(Json::object { { "type", "MapStatisticItem" }, { "name", "response-by-rcode" }, { "value", values }, }); } #ifndef RECURSOR if (!req->getvars.count("includerings") || req->getvars["includerings"] != "false") { for(const auto& ringName : S.listRings()) { Json::array values; const auto& ring = S.getRing(ringName); for(const auto& item : ring) { if (item.second == 0) continue; values.push_back(Json::object { { "name", item.first }, { "value", std::to_string(item.second) }, }); } doc.push_back(Json::object { { "type", "RingStatisticItem" }, { "name", ringName }, { "size", std::to_string(S.getRingSize(ringName)) }, { "value", values }, }); } } #endif resp->setJsonBody(doc); } DNSName apiNameToDNSName(const string& name) { if (!isCanonical(name)) { throw ApiException("DNS Name '" + name + "' is not canonical"); } try { return DNSName(name); } catch (...) { throw ApiException("Unable to parse DNS Name '" + name + "'"); } } DNSName apiZoneIdToName(const string& id) { string zonename; ostringstream ss; if(id.empty()) throw HttpBadRequestException(); std::size_t lastpos = 0, pos = 0; while ((pos = id.find('=', lastpos)) != string::npos) { ss << id.substr(lastpos, pos-lastpos); char c; // decode tens if (id[pos+1] >= '0' && id[pos+1] <= '9') { c = id[pos+1] - '0'; } else if (id[pos+1] >= 'A' && id[pos+1] <= 'F') { c = id[pos+1] - 'A' + 10; } else { throw HttpBadRequestException(); } c = c * 16; // decode unit place if (id[pos+2] >= '0' && id[pos+2] <= '9') { c += id[pos+2] - '0'; } else if (id[pos+2] >= 'A' && id[pos+2] <= 'F') { c += id[pos+2] - 'A' + 10; } else { throw HttpBadRequestException(); } ss << c; lastpos = pos+3; } if (lastpos < pos) { ss << id.substr(lastpos, pos-lastpos); } zonename = ss.str(); try { return DNSName(zonename); } catch (...) { throw ApiException("Unable to parse DNS Name '" + zonename + "'"); } } string apiZoneNameToId(const DNSName& dname) { string name=dname.toString(); ostringstream ss; for(char iter : name) { if ((iter >= 'A' && iter <= 'Z') || (iter >= 'a' && iter <= 'z') || (iter >= '0' && iter <= '9') || (iter == '.') || (iter == '-')) { ss << iter; } else { ss << (boost::format("=%02X") % (int)iter); } } string id = ss.str(); // add trailing dot if (id.size() == 0 || id.substr(id.size()-1) != ".") { id += "."; } // special handling for the root zone, as a dot on it's own doesn't work // everywhere. if (id == ".") { id = (boost::format("=%02X") % (int)('.')).str(); } return id; } void apiCheckNameAllowedCharacters(const string& name) { if (name.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890_/.-") != std::string::npos) throw ApiException("Name '"+name+"' contains unsupported characters"); } void apiCheckQNameAllowedCharacters(const string& qname) { if (qname.compare(0, 2, "*.") == 0) apiCheckNameAllowedCharacters(qname.substr(2)); else apiCheckNameAllowedCharacters(qname); }