]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
JSON API: Add 'set-ptr' PTR-creating functionality 1312/head
authorChristian Hofstaedtler <christian@hofstaedtler.name>
Mon, 3 Mar 2014 15:11:36 +0000 (16:11 +0100)
committerChristian Hofstaedtler <christian@hofstaedtler.name>
Mon, 3 Mar 2014 15:11:36 +0000 (16:11 +0100)
pdns/json.cc
pdns/json.hh
pdns/ws-auth.cc
regression-tests.api/test_Zones.py

index 1ae341a022dbaa46e050da0144dfa471d647e4a9..9709076a92a67ab10e043ea485c168d6cfd5b2d7 100644 (file)
@@ -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;
index b79503938d77a8934cb0d4b5c51cc58ce03f1696..863f8538d637913526cc77a72ab29c4ef28c9f05 100644 (file)
@@ -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
 {
index b8d22697c99f98d180dfce66c381ed1140f10167..157952a834f8931dc271a88fcdb9676276ea289e 100644 (file)
@@ -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<DNSResourceRecord> new_records;
     vector<Comment> new_comments;
+    vector<DNSResourceRecord> 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<DNSResourceRecord>(1, rr))) {
+        throw ApiException("PTR-Hosting backend does not support editing records.");
+      }
+      sd.db->commitTransaction();
+    }
+
   }
   else
     throw ApiException("Changetype not understood");
index f257acb83548fc289b03353e514c4c7eaea7a7de..3055d75c15ddd3baf79819f8977a1ab3fe58aca5 100644 (file)
@@ -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):