From: Christian Hofstaedtler Date: Mon, 3 Mar 2014 15:11:36 +0000 (+0100) Subject: JSON API: Add 'set-ptr' PTR-creating functionality X-Git-Tag: rec-3.6.0-rc1~153^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F1312%2Fhead;p=thirdparty%2Fpdns.git JSON API: Add 'set-ptr' PTR-creating functionality --- diff --git a/pdns/json.cc b/pdns/json.cc index 1ae341a022..9709076a92 100644 --- a/pdns/json.cc +++ b/pdns/json.cc @@ -79,6 +79,19 @@ bool boolFromJson(const rapidjson::Value& container, const char* key) } } +bool boolFromJson(const rapidjson::Value& container, const char* key, const bool default_value) +{ + if (!container.IsObject()) { + throw JsonException("Container was not an object."); + } + const Value& val = container[key]; + if (val.IsBool()) { + return val.GetBool(); + } else { + return default_value; + } +} + string makeStringFromDocument(const Document& doc) { StringBuffer output; diff --git a/pdns/json.hh b/pdns/json.hh index b79503938d..863f8538d6 100644 --- a/pdns/json.hh +++ b/pdns/json.hh @@ -34,6 +34,7 @@ int intFromJson(const rapidjson::Value& container, const char* key, const int de std::string stringFromJson(const rapidjson::Value& container, const char* key); std::string stringFromJson(const rapidjson::Value& container, const char* key, const std::string& default_value); bool boolFromJson(const rapidjson::Value& container, const char* key); +bool boolFromJson(const rapidjson::Value& container, const char* key, const bool default_value); class JsonException : public std::runtime_error { diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index b8d22697c9..157952a834 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -531,6 +531,42 @@ static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) { throw HttpMethodNotAllowedException(); } +static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) { + if (rr.qtype.getCode() == QType::A) { + uint32_t ip; + if (!IpToU32(rr.content, &ip)) { + throw ApiException("PTR: Invalid IP address given"); + } + ptr->qname = (boost::format("%u.%u.%u.%u.in-addr.arpa") + % ((ip >> 24) & 0xff) + % ((ip >> 16) & 0xff) + % ((ip >> 8) & 0xff) + % ((ip ) & 0xff) + ).str(); + } else if (rr.qtype.getCode() == QType::AAAA) { + ComboAddress ca(rr.content); + string tmp; + for (int group = 0; group < 8; ++group) { + tmp += (boost::format("%04x") % ntohs(ca.sin6.sin6_addr.s6_addr16[group])).str(); + } + ostringstream ss; + size_t npos = tmp.size(); + while (npos--) { + ss << tmp[npos] << "."; + } + ss << "ip6.arpa"; + ptr->qname = ss.str(); + } else { + throw ApiException("Unsupported PTR source '" + rr.qname + "' type '" + rr.qtype.getName() + "'"); + } + + ptr->qtype = "PTR"; + ptr->ttl = rr.ttl; + ptr->disabled = rr.disabled; + ptr->priority = 0; + ptr->content = rr.qname; +} + static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) { if(req->method != "PATCH") throw HttpMethodNotAllowedException(); @@ -563,6 +599,7 @@ static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) { else if (changetype == "REPLACE") { vector new_records; vector new_comments; + vector new_ptrs; bool replace_records = false; bool replace_comments = false; @@ -597,6 +634,22 @@ static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) { throw ApiException("Record "+rr.qname+" IN "+rr.qtype.getName()+" "+rr.content+": "+e.what()); } + if ((rr.qtype.getCode() == QType::A || rr.qtype.getCode() == QType::AAAA) && + boolFromJson(record, "set-ptr", false) == true) { + DNSResourceRecord ptr; + makePtr(rr, &ptr); + + // verify that there's a zone for the PTR + DNSPacket fakePacket; + SOAData sd; + fakePacket.qtype = QType::PTR; + if (!B.getAuth(&fakePacket, &sd, ptr.qname, 0)) + throw ApiException("Could not find domain for PTR '"+ptr.qname+"' requested for '"+ptr.content+"'"); + + ptr.domain_id = sd.domain_id; + new_ptrs.push_back(ptr); + } + new_records.push_back(rr); } } @@ -636,6 +689,24 @@ static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) { } } di.backend->commitTransaction(); + + // now the PTRs + BOOST_FOREACH(const DNSResourceRecord& rr, new_ptrs) { + DNSPacket fakePacket; + SOAData sd; + sd.db = (DNSBackend *)-1; + fakePacket.qtype = QType::PTR; + + if (!B.getAuth(&fakePacket, &sd, rr.qname, 0)) + throw ApiException("Could not find domain for PTR '"+rr.qname+"' requested for '"+rr.content+"' (while saving)"); + + sd.db->startTransaction(rr.qname); + if (!sd.db->replaceRRSet(sd.domain_id, rr.qname, rr.qtype, vector(1, rr))) { + throw ApiException("PTR-Hosting backend does not support editing records."); + } + sd.db->commitTransaction(); + } + } else throw ApiException("Changetype not understood"); diff --git a/regression-tests.api/test_Zones.py b/regression-tests.api/test_Zones.py index f257acb835..3055d75c15 100644 --- a/regression-tests.api/test_Zones.py +++ b/regression-tests.api/test_Zones.py @@ -405,6 +405,87 @@ class AuthZones(ApiTestCase): self.assertEquals([r for r in data['records'] if r['type'] == 'NS'], payload2['records']) self.assertEquals(data['comments'], payload['comments']) + def test_ZoneAutoPtrIPv4(self): + revzone = '0.2.192.in-addr.arpa' + self.create_zone(name=revzone) + payload, zone = self.create_zone() + name = payload['name'] + # replace with qname mismatch + payload = { + 'changetype': 'replace', + 'name': name, + 'type': 'A', + 'records': [ + { + "name": name, + "type": "A", + "priority": 0, + "ttl": 3600, + "content": '192.2.0.2', + "disabled": False, + "set-ptr": True + } + ] + } + r = self.session.patch( + self.url("/servers/localhost/zones/" + name + "/rrset"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertSuccessJson(r) + r = self.session.get(self.url("/servers/localhost/zones/" + revzone)) + recs = r.json()['records'] + print recs + revrec = [rec for rec in recs if rec['type'] == 'PTR'] + self.assertEquals(revrec, [{ + u'content': name, + u'disabled': False, + u'ttl': 3600, + u'priority': 0, + u'type': u'PTR', + u'name': u'2.0.2.192.in-addr.arpa' + }]) + + def test_ZoneAutoPtrIPv6(self): + # 2001:DB8::bb:aa + revzone = '8.b.d.0.1.0.0.2.ip6.arpa' + self.create_zone(name=revzone) + payload, zone = self.create_zone() + name = payload['name'] + # replace with qname mismatch + payload = { + 'changetype': 'replace', + 'name': name, + 'type': 'AAAA', + 'records': [ + { + "name": name, + "type": "AAAA", + "priority": 0, + "ttl": 3600, + "content": '2001:DB8::bb:aa', + "disabled": False, + "set-ptr": True + } + ] + } + r = self.session.patch( + self.url("/servers/localhost/zones/" + name + "/rrset"), + data=json.dumps(payload), + headers={'content-type': 'application/json'}) + self.assertSuccessJson(r) + r = self.session.get(self.url("/servers/localhost/zones/" + revzone)) + recs = r.json()['records'] + print recs + revrec = [rec for rec in recs if rec['type'] == 'PTR'] + self.assertEquals(revrec, [{ + u'content': name, + u'disabled': False, + u'ttl': 3600, + u'priority': 0, + u'type': u'PTR', + u'name': u'a.a.0.0.b.b.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa' + }]) + @unittest.skipIf(not isRecursor(), "Not applicable") class RecursorZones(ApiTestCase):