Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions model/zone.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ public function add_or_update_resource_record_set(ResourceRecordSet $rrset) {
$change_record->type = $rrset->type;
$change_record->content = $record->content;
$change_record->disabled = $record->disabled;
$change_record->{'set-ptr'} = $record->{'set-ptr'};
$change->records[] = $change_record;
}
$change->comments = array();
Expand Down Expand Up @@ -807,10 +806,8 @@ private function process_rrset_action($update, &$trash, &$revs_missing, &$revs_u
$rr = new ResourceRecord;
$rr->content = $record->content;
$rr->disabled = ($record->enabled === 'No' || $record->enabled === false);
if(!$autocreate_ptr || $rr->disabled) {
$rr->{'set-ptr'} = false;
} else {
$rr->{'set-ptr'} = $zone_dir->check_reverse_record_zone($rrset->name, $rrset->type, $rr->content, $revs_missing, $revs_updated);
if($autocreate_ptr && !$rr->disabled) {
$zone_dir->create_reverse_record($rrset->name, $rrset->type, $rr->content, $rrset->ttl, $revs_missing, $revs_updated);
}
$rrset->add_resource_record($rr);
}
Expand Down Expand Up @@ -840,16 +837,19 @@ private function process_rrset_action($update, &$trash, &$revs_missing, &$revs_u
$rrset->ttl = DNSTime::expand($update->ttl);
$record_count = 0;
foreach($update->records as $record) {
if(!empty($record->delete)) continue;
if(!empty($record->delete)) {
if(!empty($record->remove_ptr) && ($update->oldtype == 'A' || $update->oldtype == 'AAAA')) {
$zone_dir->delete_reverse_record($update->oldname, $update->oldtype, $record->content, $revs_updated);
}
continue;
}
$record_count++;
$record->content = DNSContent::encode($record->content, $update->type, $this->name);
$rr = new ResourceRecord;
$rr->content = $record->content;
$rr->disabled = ($record->enabled === 'No' || $record->enabled === false);
if(!$autocreate_ptr || $rr->disabled) {
$rr->{'set-ptr'} = false;
} else {
$rr->{'set-ptr'} = $zone_dir->check_reverse_record_zone($rrset->name, $rrset->type, $rr->content, $revs_missing, $revs_updated);
if($autocreate_ptr && !$rr->disabled) {
$zone_dir->create_reverse_record($rrset->name, $rrset->type, $rr->content, $rrset->ttl, $revs_missing, $revs_updated);
}
$rrset->add_resource_record($rr);
}
Expand All @@ -871,8 +871,14 @@ private function process_rrset_action($update, &$trash, &$revs_missing, &$revs_u
if(!isset($this->rrsets[$update->oldname.' '.$update->oldtype])) {
throw new BadData('Tried to delete a non-existent resource recordset: '.$update->oldname.' '.$update->oldtype.'.');
}
$change->before = serialize($this->rrsets[$update->oldname.' '.$update->oldtype]);
$this->delete_resource_record_set($this->rrsets[$update->oldname.' '.$update->oldtype]);
$rrset = $this->rrsets[$update->oldname.' '.$update->oldtype];
$change->before = serialize($rrset);
if(!empty($update->remove_ptr) && ($update->oldtype == 'A' || $update->oldtype == 'AAAA')) {
foreach($rrset->list_resource_records() as $rr) {
$zone_dir->delete_reverse_record($update->oldname, $update->oldtype, $rr->content, $revs_updated);
}
}
$this->delete_resource_record_set($rrset);
break;
}
return $change;
Expand Down
130 changes: 128 additions & 2 deletions model/zonedirectory.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,18 @@ public function list_accounts() {
}

/**
* Check the list of zones to see if a suitable reverse zone exists for the forward record.
* Check the list of zones to see if a suitable reverse zone exists for the forward record, and if
* so create/update the matching PTR record via a direct call to the PowerDNS API.
* PowerDNS's own "set-ptr" record attribute that used to do this automatically has been removed,
* so DNS UI must create the PTR record itself.
* @param string $name of DNS record
* @param string $type of DNS record
* @param string $address that DNS record points to
* @param int $ttl to use for the PTR record
* @param array $revs_missing keep track of reverse zones that are missing
* @param array $revs_updated keep track of reverse zones that will be updated
*/
public function check_reverse_record_zone($name, $type, $address, &$revs_missing, &$revs_notify) {
public function create_reverse_record($name, $type, $address, $ttl, &$revs_missing, &$revs_notify) {
global $zone_dir, $active_user;

if($type == 'A') {
Expand Down Expand Up @@ -270,6 +274,25 @@ public function check_reverse_record_zone($name, $type, $address, &$revs_missing
}
}
}
// Create the PTR record directly via the PowerDNS API
$ptr_rrset = new ResourceRecordSet;
$ptr_rrset->name = $reverse_address;
$ptr_rrset->type = 'PTR';
$ptr_rrset->ttl = $ttl;
$ptr_record = new ResourceRecord;
$ptr_record->content = $name;
$ptr_record->disabled = false;
$ptr_rrset->add_resource_record($ptr_record);
try {
$reverse_zone->add_or_update_resource_record_set($ptr_rrset);
$reverse_zone->commit_changes();
} catch(ResourceRecordInvalid $e) {
$alert = new UserAlert;
$alert->content = "Failed to create reverse record for $address pointing to $name: ".$e->getMessage();
$alert->class = 'warning';
$active_user->add_alert($alert);
return false;
}
// Add reverse zone to list of zones to send a notify for
$revs_notify[$reverse_zone->pdns_id] = $reverse_zone;
return true;
Expand All @@ -284,6 +307,109 @@ public function check_reverse_record_zone($name, $type, $address, &$revs_missing
return false;
}

/**
* Locate the reverse (PTR) RRset for a forward record's address, if one exists.
* Searches for an appropriate reverse zone by starting with the full domain name, and
* removing subdomains until a match is found or there is nothing left to remove.
* @param string $type of forward DNS record (A or AAAA)
* @param string $address that the forward DNS record points to
* @return array|null array(Zone $reverse_zone, ResourceRecordSet $rrset), or null if none found
*/
private function find_reverse_rrset($type, $address) {
global $zone_dir;

if($type == 'A') {
$reverse_address = implode('.', array_reverse(explode('.', $address))).'.in-addr.arpa.';
} elseif($type == 'AAAA') {
$address = ipv6_address_expand($address);
$reverse_address = implode('.', array_reverse(str_split(str_replace(':', '', $address)))).'.ip6.arpa.';
} else {
return null;
}
$reverse_zone_name = $reverse_address;
do {
try {
$reverse_zone = $zone_dir->get_zone_by_name($reverse_zone_name);
foreach($reverse_zone->list_resource_record_sets() as $rrset) {
if($rrset->name == $reverse_address && $rrset->type == 'PTR') {
return array($reverse_zone, $rrset);
}
}
return null;
} catch(ZoneNotFound $e) {
}
} while($this->remove_subdomain($reverse_zone_name));
return null;
}

/**
* Check whether a reverse (PTR) record exists pointing back at the given forward record.
* @param string $name of DNS record
* @param string $type of DNS record
* @param string $address that DNS record points to
* @return bool true if a matching PTR record exists
*/
public function has_reverse_record($name, $type, $address) {
$found = $this->find_reverse_rrset($type, $address);
if(is_null($found)) return false;
list(, $rrset) = $found;
foreach($rrset->list_resource_records() as $rr) {
if($rr->content == $name) return true;
}
return false;
}

/**
* Check the list of zones to see if a suitable reverse zone exists for the forward record, and if
* so remove the matching PTR record via a direct call to the PowerDNS API.
* @param string $name of DNS record whose PTR record should be removed
* @param string $type of DNS record
* @param string $address that DNS record pointed to
* @param array $revs_notify keep track of reverse zones that will be updated
* @return bool true if a PTR record was found and removed
*/
public function delete_reverse_record($name, $type, $address, &$revs_notify) {
global $active_user;

$found = $this->find_reverse_rrset($type, $address);
if(is_null($found)) return false;
list($reverse_zone, $rrset) = $found;
$remaining_rrs = array();
$matched = false;
foreach($rrset->list_resource_records() as $rr) {
if($rr->content == $name) {
$matched = true;
} else {
$remaining_rrs[] = $rr;
}
}
if(!$matched) return false;
try {
if(count($remaining_rrs) == 0) {
$reverse_zone->delete_resource_record_set($rrset);
} else {
$new_rrset = new ResourceRecordSet;
$new_rrset->name = $rrset->name;
$new_rrset->type = $rrset->type;
$new_rrset->ttl = $rrset->ttl;
foreach($remaining_rrs as $rr) {
$new_rrset->add_resource_record($rr);
}
$reverse_zone->add_or_update_resource_record_set($new_rrset);
}
$reverse_zone->commit_changes();
} catch(ResourceRecordInvalid $e) {
$alert = new UserAlert;
$alert->content = "Failed to remove reverse record for $address pointing to $name: ".$e->getMessage();
$alert->class = 'warning';
$active_user->add_alert($alert);
return false;
}
// Add reverse zone to list of zones to send a notify for
$revs_notify[$reverse_zone->pdns_id] = $reverse_zone;
return true;
}

/**
* Given a DNS name, remove the bottom-level subdomain from it.
* @param string $address DNS name
Expand Down
31 changes: 30 additions & 1 deletion public_html/extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,34 @@ $(function() {
button.append(' Delete');
tr.removeClass('delete');
tr.data('delete', false);
tr.data('remove-ptr', false);
update_changed(button);
} else {
button.text('Undelete');
tr.addClass('delete');
tr.data('delete', true);
if(!tr.data('newrow') && (tr.data('type') == 'A' || tr.data('type') == 'AAAA')) {
var content = $('td.content input', tr).val();
// Only bother asking if a matching reverse (PTR) record actually exists
$.ajax({
url: '../api/v2/zones/' + encodeURIComponent(form.data('zone')) + '/reverse-records',
method: 'GET',
data: {name: tr.data('name'), type: tr.data('type'), address: content},
dataType: 'json'
}).done(function(response) {
if(response && response.exists) {
tr.data('remove-ptr', confirm('A reverse (PTR) record exists pointing to ' + content + '. Also remove it?'));
} else {
tr.data('remove-ptr', false);
}
}).fail(function() {
tr.data('remove-ptr', false);
}).always(function() {
update_changed(button);
});
return;
}
tr.data('remove-ptr', false);
}
update_changed(button);
}
Expand Down Expand Up @@ -309,10 +333,15 @@ $(function() {
rrchanged = true;
rrspan.appendChild(document.createTextNode(' '));
var span = document.createElement('span');
$(span).text('Resource record deleted.');
var deletetext = 'Resource record deleted.';
if($(this).data('remove-ptr')) deletetext += ' Reverse (PTR) record will also be removed.';
$(span).text(deletetext);
$(span).addClass('text-warning');
rrspan.appendChild(span);
record['delete'] = true;
if(update.type == 'A' || update.type == 'AAAA') {
record['remove_ptr'] = !!$(this).data('remove-ptr');
}
} else {
activerows++;
if(record['enabled'] == 'Yes') enabledrows++;
Expand Down
19 changes: 19 additions & 0 deletions views/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
} else {
$api->zone_changes($router->vars['id']);
}
break;
case 'reverse-records':
$api->zone_reverse_records($router->vars['id']);
break;
}
} else {
$api->zone($router->vars['id']);
Expand Down Expand Up @@ -148,6 +152,21 @@ public function update_zone_rrsets($zone_name) {
$this->output(null);
}

public function zone_reverse_records($zone_name) {
$this->options(array('GET' => 'check_zone_reverse_record'), array($zone_name));
}

public function check_zone_reverse_record($zone_name) {
global $zone_dir, $active_user;
$zone = $zone_dir->get_zone_by_name($zone_name);
if(!$active_user->admin && !$active_user->access_to($zone)) throw new AccessDenied;
if(!isset($_GET['name']) || !isset($_GET['type']) || !isset($_GET['address'])) throw new BadData('Missing required parameters.');
if($_GET['type'] != 'A' && $_GET['type'] != 'AAAA') throw new BadData('Type must be A or AAAA.');
$name = utf8_to_punycode(DNSName::canonify($_GET['name'], $zone->name));
$exists = $zone_dir->has_reverse_record($name, $_GET['type'], $_GET['address']);
$this->output(array('exists' => $exists));
}

public function zone_changes($zone_name) {
$this->options(array('GET' => 'list_zone_changes'), array($zone_name));
}
Expand Down