Merge pull request #94 from tuxis-ie/pdns-40

Create a version 1.0, to manage Pdns 4.0.0 and greater
This commit is contained in:
Tuxis Internet Engineering V.O.F 2016-08-05 13:49:47 +02:00 committed by GitHub
commit 854c067e1f
13 changed files with 1278 additions and 617 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
includes/config.inc.php includes/config.inc.php
nsedit.sublime*

View file

@ -10,8 +10,10 @@ Features
======== ========
* Import BIND- or AXFR-style dumps of your existing zones * Import BIND- or AXFR-style dumps of your existing zones
* Add/remove zones and records * Add/remove zones and records
* Clone zones
* Show the DNSsec details of a zone * Show the DNSsec details of a zone
* Multiple user support * Multiple user support
* Allow logging of all actions in NSEdit, including exporting the log in JSON-format
* [experimental] nsedit API, to create zones from another system * [experimental] nsedit API, to create zones from another system
User support User support
@ -32,7 +34,7 @@ Requirements
* php sqlite3 * php sqlite3
* php curl * php curl
* php with openssl support * php with openssl support
* PowerDNS with the experimental JSON-api enabled (3.4.0 should do. For Pdns > 4.0.0 you ***NEED*** v1.0 of NSEdit) * PowerDNS with the JSON-api enabled. Version 4.0.0 or greater
Installing Installing
========== ==========
@ -41,8 +43,8 @@ Installing
- Run git clone in the directory where you want to run nsedit from - Run git clone in the directory where you want to run nsedit from
: ```git clone https://github.com/tuxis-ie/nsedit.git``` : ```git clone https://github.com/tuxis-ie/nsedit.git```
- Select tag v0.9 or skip this if you want to run from master - Select tag v1.0 or skip this if you want to run from master
: ```git checkout tags/v0.9``` : ```git checkout tags/v1.0```
* Via releases * Via releases
- Download the zip-file from [Releases](https://github.com/tuxis-ie/nsedit/releases) - Download the zip-file from [Releases](https://github.com/tuxis-ie/nsedit/releases)

BIN
img/delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
img/delete_inverted.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

View file

@ -0,0 +1,124 @@
<?php
include_once('includes/config.inc.php');
class ApiHandler {
public function __construct() {
global $apiip, $apiport, $apipass, $apiproto, $apisslverify;
$this->headers = Array();
$this->hostname = $apiip;
$this->port = $apiport;
$this->auth = $apipass;
$this->proto = $apiproto;
$this->sslverify = $apisslverify;
$this->curlh = curl_init();
$this->method = 'GET';
$this->content = FALSE;
$this->apiurl = '';
}
public function addheader($field, $content) {
$this->headers[$field] = $content;
}
private function authheaders() {
$this->addheader('X-API-Key', $this->auth);
}
private function apiurl() {
$tmp = new ApiHandler();
$tmp->url = '/api';
$tmp->go();
if ($tmp->json[0]['version'] <= 1) {
$this->apiurl = $tmp->json[0]['url'];
} else {
throw new Exception("Unsupported API version");
}
}
private function curlopts() {
$this->authheaders();
$this->addheader('Accept', 'application/json');
curl_reset($this->curlh);
curl_setopt($this->curlh, CURLOPT_HTTPHEADER, Array());
curl_setopt($this->curlh, CURLOPT_RETURNTRANSFER, 1);
if (strcasecmp($this->proto, 'https')) {
curl_setopt($this->curlh, CURLOPT_SSL_VERIFYPEER, $this->sslverify);
}
$setheaders = Array();
foreach ($this->headers as $k => $v) {
array_push($setheaders, join(": ", Array($k, $v)));
}
curl_setopt($this->curlh, CURLOPT_HTTPHEADER, $setheaders);
}
private function baseurl() {
return $this->proto.'://'.$this->hostname.':'.$this->port.$this->apiurl;
}
private function go() {
$this->curlopts();
if ($this->content) {
$this->addheader('Content-Type', 'application/json');
curl_setopt($this->curlh, CURLOPT_POST, 1);
curl_setopt($this->curlh, CURLOPT_POSTFIELDS, $this->content);
}
switch ($this->method) {
case 'POST':
curl_setopt($this->curlh, CURLOPT_POST, 1);
break;
case 'GET':
curl_setopt($this->curlh, CURLOPT_POST, 0);
break;
case 'DELETE':
case 'PATCH':
case 'PUT':
curl_setopt($this->curlh, CURLOPT_CUSTOMREQUEST, $this->method);
break;
}
curl_setopt($this->curlh, CURLOPT_URL, $this->baseurl().$this->url);
//print "Here we go:\n";
//print "Request: ".$this->method.' '.$this->baseurl().$this->url."\n";
//if ($this->content != '') {
// print "Content: ".$this->content."\n";
//}
$return = curl_exec($this->curlh);
$code = curl_getinfo($this->curlh, CURLINFO_HTTP_CODE);
$json = json_decode($return, 1);
if (isset($json['error'])) {
throw new Exception("API Error $code: ".$json['error']);
} elseif ($code < 200 || $code >= 300) {
if ($code == 401) {
throw new Exception("Authentication failed. Have you configured your authmethod correct?");
}
throw new Exception("Curl Error: $code ".curl_error($this->curlh));
}
$this->json = $json;
}
public function call() {
if (substr($this->url, 0, 1) == '/') {
$this->apiurl();
} else {
$this->apiurl = '/';
}
$this->go();
}
}

125
includes/class/PdnsApi.php Normal file
View file

@ -0,0 +1,125 @@
<?php
include_once('ApiHandler.php');
class PdnsAPI {
public function __construct() {
$this->http = new ApiHandler();
}
public function listzones($q = FALSE) {
$api = clone $this->http;
$api->method = 'GET';
if ($q) {
$api->url = "/servers/localhost/search-data?q=*".$q."*&max=25";
$api->call();
$ret = Array();
$seen = Array();
foreach ($api->json as $result) {
if (isset($seen[$result['zone_id']])) {
continue;
}
$zone = $this->loadzone($result['zone_id']);
unset($zone['rrsets']);
array_push($ret, $zone);
$seen[$result['zone_id']] = 1;
}
return $ret;
}
$api->url = "/servers/localhost/zones";
$api->call();
return $api->json;
}
public function loadzone($zoneid) {
$api = clone $this->http;
$api->method = 'GET';
$api->url = "/servers/localhost/zones/$zoneid";
$api->call();
return $api->json;
}
public function exportzone($zoneid) {
$api = clone $this->http;
$api->method = 'GET';
$api->url = "/servers/localhost/zones/$zoneid/export";
$api->call();
return $api->json;
}
public function savezone($zone) {
$api = clone $this->http;
// We have to split up RRSets and Zoneinfo.
// First, update the zone
$zonedata = $zone;
unset($zonedata['id']);
unset($zonedata['url']);
unset($zonedata['rrsets']);
if (!isset($zone['serial']) or gettype($zone['serial']) != 'integer') {
$api->method = 'POST';
$api->url = '/servers/localhost/zones';
$api->content = json_encode($zonedata);
$api->call();
return $api->json;
}
$api->method = 'PUT';
$api->url = $zone['url'];
$api->content = json_encode($zonedata);
$api->call();
// Then, update the rrsets
if (count($zone['rrsets']) > 0) {
$api->method = 'PATCH';
$api->content = json_encode(Array('rrsets' => $zone['rrsets']));
$api->call();
}
return $this->loadzone($zone['id']);
}
public function deletezone($zoneid) {
$api = clone $this->http;
$api->method = 'DELETE';
$api->url = "/servers/localhost/zones/$zoneid";
$api->call();
return $api->json;
}
public function getzonekeys($zoneid) {
$ret = array();
$api = clone $this->http;
$api->method = 'GET';
$api->url = "/servers/localhost/zones/$zoneid/cryptokeys";
$api->call();
foreach ($api->json as $key) {
if (!isset($key['active']))
continue;
$key['dstxt'] = $zoneid . ' IN DNSKEY '.$key['dnskey']."\n\n";
if (isset($key['ds'])) {
foreach ($key['ds'] as $ds) {
$key['dstxt'] .= $zoneid . ' IN DS '.$ds."\n";
}
unset($key['ds']);
}
array_push($ret, $key);
}
return $ret;
}
}
?>

334
includes/class/Zone.php Normal file
View file

@ -0,0 +1,334 @@
<?php
class Zone {
public function __construct() {
$this->id = '';
$this->name = '';
$this->kind = '';
$this->url = '';
$this->serial = '';
$this->dnssec = '';
$this->soa_edit = '';
$this->soa_edit_api = '';
$this->keyinfo = '';
$this->account = '';
$this->zone = FALSE;
$this->nameservers = Array();
$this->rrsets = Array();
$this->masters = Array();
}
public function parse($data) {
$this->setId($data['id']);
$this->setName($data['name']);
$this->setKind($data['kind']);
$this->setDnssec($data['dnssec']);
$this->setAccount($data['account']);
$this->setSerial($data['serial']);
$this->url = $data['url'];
if (isset($data['soa_edit']))
$this->setSoaEdit($data['soa_edit']);
if (isset($data['soa_edit_api']))
$this->setSoaEditApi($data['soa_edit_api']);
foreach ($data['masters'] as $master) {
$this->addMaster($master);
}
if (isset($data['rrsets'])) {
foreach ($data['rrsets'] as $rrset) {
$toadd = new RRSet($rrset['name'], $rrset['type']);
foreach ($rrset['comments'] as $comment) {
$toadd->addComment($comment['content'], $comment['account'], $comment['modified_at']);
}
foreach ($rrset['records'] as $record) {
$toadd->addRecord($record['content'], $record['disabled']);
}
$toadd->setTtl($rrset['ttl']);
array_push($this->rrsets, $toadd);
}
}
}
public function importData($data) {
$this->zone = $data;
}
public function setKeyinfo($info) {
$this->keyinfo = $info;
}
public function addNameserver($nameserver) {
foreach ($this->nameservers as $ns) {
if ($nameserver == $ns) {
throw new Exception("We already have this as a nameserver");
}
}
array_push($this->nameservers, $nameserver);
}
public function setSerial($serial) {
$this->serial = $serial;
}
public function setSoaEdit($soaedit) {
$this->soa_edit = $soaedit;
}
public function setSoaEditApi($soaeditapi) {
$this->soa_edit_api = $soaeditapi;
}
public function setName($name) {
$this->name = $name;
}
public function setKind($kind) {
$this->kind = $kind;
}
public function setAccount($account) {
$this->account = $account;
}
public function setDnssec($dnssec) {
$this->dnssec = $dnssec;
}
public function setId($id) {
$this->id = $id;
}
public function addMaster($ip) {
foreach ($this->masters as $master) {
if ($ip == $master) {
throw new Exception("We already have this as a master");
}
}
array_push($this->masters, $ip);
}
public function eraseMasters() {
$this->masters = Array();
}
public function addRRSet($name, $type, $content, $disabled = FALSE, $ttl = 3600, $setptr = FALSE) {
if ($this->getRRSet($name, $type) !== FALSE) {
throw new Exception("This rrset already exists.");
}
$rrset = new RRSet($name, $type, $content, $disabled, $ttl, $setptr);
array_push($this->rrsets, $rrset);
}
public function addRecord($name, $type, $content, $disabled = FALSE, $ttl = 3600, $setptr = FALSE) {
$rrset = $this->getRRSet($name, $type);
if ($rrset) {
$rrset->addRecord($content, $disabled, $setptr);
} else {
$this->addRRSet($name, $type, $content, $disabled, $ttl, $setptr);
}
return $this->getRecord($name, $type, $content);
}
public function getRecord($name, $type, $content) {
$rrset = $this->getRRSet($name, $type);
foreach ($rrset->exportRecords() as $record) {
if ($record['content'] == $content) {
$record['name'] = $rrset->name;
$record['ttl'] = $rrset->ttl;
$record['type'] = $rrset->type;
$id = json_encode($record);
$record['id'] = $id;
return $record;
}
}
}
public function getRRSet($name, $type) {
foreach ($this->rrsets as $rrset) {
if ($rrset->name == $name and $rrset->type == $type) {
return $rrset;
}
}
return FALSE;
}
public function rrsets2records() {
$ret = Array();
foreach ($this->rrsets as $rrset) {
foreach ($rrset->exportRecords() as $record) {
$record['name'] = $rrset->name;
$record['ttl'] = $rrset->ttl;
$record['type'] = $rrset->type;
$id = json_encode($record);
$record['id'] = $id;
array_push($ret, $record);
}
}
return $ret;
}
public function export() {
$ret = Array();
$ret['account'] = $this->account;
$ret['nameservers'] = $this->nameservers;
$ret['kind'] = $this->kind;
$ret['name'] = $this->name;
$ret['soa_edit'] = $this->soa_edit;
$ret['soa_edit_api'] = $this->soa_edit_api;
if ($this->zone) {
$ret['zone'] = $this->zone;
return $ret;
}
$ret['dnssec'] = $this->dnssec;
if ($this->dnssec) {
$ret['keyinfo'] = $this->keyinfo;
}
$ret['id'] = $this->id;
$ret['masters'] = $this->masters;
$ret['rrsets'] = $this->exportRRSets();
$ret['serial'] = $this->serial;
$ret['url'] = $this->url;
return $ret;
}
private function exportRRSets() {
$ret = Array();
foreach ($this->rrsets as $rrset) {
array_push($ret, $rrset->export());
}
return $ret;
}
}
class RRSet {
public function __construct($name = '', $type = '', $content = '', $disabled = FALSE, $ttl = 3600, $setptr = FALSE) {
$this->name = $name;
$this->type = $type;
$this->ttl = $ttl;
$this->changetype = 'REPLACE';
$this->records = Array();
$this->comments = Array();
if (isset($content) and $content != '') {
$this->addRecord($content, $disabled, $setptr);
}
}
public function delete() {
$this->changetype = 'DELETE';
}
public function setTtl($ttl) {
$this->ttl = $ttl;
}
public function setName($name) {
$this->name = $name;
}
public function addRecord($content, $disabled = FALSE, $setptr = FALSE) {
foreach ($this->records as $record) {
if ($record->content == $content) {
throw Exception("Record already exists");
}
}
$record = new Record($content, $disabled, $setptr);
array_push($this->records, $record);
}
public function deleteRecord($content) {
foreach ($this->records as $idx => $record) {
if ($record->content == $content) {
unset($this->records[$idx]);
}
}
}
public function addComment($content, $account, $modified_at = FALSE) {
$comment = new Comment($content, $account, $modified_at);
array_push($this->comments, $comment);
}
public function export() {
$ret = Array();
$ret['comments'] = $this->exportComments();
$ret['name'] = $this->name;
$ret['records'] = $this->exportRecords();
if ($this->changetype != 'DELETE') {
$ret['ttl'] = $this->ttl;
}
$ret['type'] = $this->type;
$ret['changetype'] = $this->changetype;
return $ret;
}
public function exportRecords() {
$ret = Array();
foreach ($this->records as $record) {
if ($this->type != "A" and $this->type != "AAAA") {
$record->setptr = FALSE;
}
array_push($ret, $record->export());
}
return $ret;
}
public function exportComments() {
$ret = Array();
foreach ($this->comments as $comment) {
array_push($ret, $comment->export());
}
return $ret;
}
}
class Record {
public function __construct($content, $disabled = FALSE, $setptr = FALSE) {
$this->content = $content;
$this->disabled = $disabled;
$this->setptr = $setptr;
}
public function export() {
$ret;
$ret['content'] = $this->content;
$ret['disabled'] = ( bool ) $this->disabled;
if ($this->setptr) {
$ret['set-ptr'] = ( bool ) TRUE;
}
return $ret;
}
}
class Comment {
public function __construct($content, $account, $modified_at) {
$this->content = $content;
$this->account = $account;
$this->modified_at = $modified_at;
}
public function export() {
$ret;
$ret['content'] = $this->content;
$ret['account'] = $this->account;
$ret['modified_at'] = $this->modified_at;
}
}
?>

View file

@ -1,24 +1,14 @@
<?php <?php
$apiuser = ''; # The PowerDNS API username. Leave empty for authmethod='xapikey' (see AUTHENTICATION) $apipass = ''; # The PowerDNS API-key
$apipass = ''; # The PowerDNS API-user password or the PowerDNS-API key (see AUTHENTICATION)
$apiip = ''; # The IP of the PowerDNS API $apiip = ''; # The IP of the PowerDNS API
$apiport = '8081'; # The port of the PowerDNS API $apiport = '8081'; # The port of the PowerDNS API
$apivers = 0; # The version of the PowerDNS API. 0 == experimental, 1 = v1 (pdns 4.0)
$apisid = 'localhost'; # PowerDNS's :server_id
$apiproto = 'http'; # http | https $apiproto = 'http'; # http | https
$apisslverify = FALSE; # Verify SSL Certificate if using https for apiproto $apisslverify = FALSE; # Verify SSL Certificate if using https for apiproto
$allowzoneadd = FALSE; # Allow normal users to add zones $allowzoneadd = FALSE; # Allow normal users to add zones
$logging = TRUE;
### AUTHENTICATION ###
# The first versions of the PowerDNS API used the standard webserver password
# for authentication, newer versions use an X-API-Key mechanism. NSEdit tries
# to autodetect the method you should use, but that does affect performance.
# For optimal performance, configure the right method.
# (Should be 'auto', 'xapikey' or 'userpass')
$authmethod = 'auto';
# If you configure this, nsedit will try to authenticate via WeFact too. # If you configure this, nsedit will try to authenticate via WeFact too.
# Debtors will be added to the sqlitedatabase with their crypted password. # Debtors will be added to the sqlitedatabase with their crypted password.
#$wefactapiurl = 'https://yourdomain/Pro/apiv2/api.php'; #$wefactapiurl = 'https://yourdomain/Pro/apiv2/api.php';
@ -51,8 +41,8 @@ $templates[] = array(
$defaults['soa_edit'] = 'INCEPTION-INCREMENT'; $defaults['soa_edit'] = 'INCEPTION-INCREMENT';
$defaults['soa_edit_api'] = 'INCEPTION-INCREMENT'; $defaults['soa_edit_api'] = 'INCEPTION-INCREMENT';
$defaults['defaulttype'] = 'Master'; # Choose between 'Native' or 'Master' $defaults['defaulttype'] = 'Master'; # Choose between 'Native' or 'Master'
$defaults['ns'][0] = 'unconfigured.primaryns'; # The value of the first NS-record $defaults['ns'][0] = 'unconfigured.primaryns.'; # The value of the first NS-record
$defaults['ns'][1] = 'unconfigured.secondaryns'; # The value of the second NS-record $defaults['ns'][1] = 'unconfigured.secondaryns.'; # The value of the second NS-record
$defaults['ttl'] = 3600; # Default TTL for records $defaults['ttl'] = 3600; # Default TTL for records
$defaults['disabled'] = false; # Default disabled state $defaults['disabled'] = false; # Default disabled state

View file

@ -4,21 +4,11 @@ include('config.inc.php');
$blocklogin = FALSE; $blocklogin = FALSE;
if ((!isset($apipass) or empty($apipass)) or (!isset($apiip) or empty($apiip)) or (!isset($apiport) or empty($apiport)) or (!isset($apivers) or is_null($apivers))) { if ((!isset($apipass) or empty($apipass)) or (!isset($apiip) or empty($apiip)) or (!isset($apiport) or empty($apiport))) {
$errormsg = 'You need to configure your settings for the PowerDNS API. See <a href="doc/apiconf.txt">doc/apiconf.txt</a>'; $errormsg = 'You need to configure your settings for the PowerDNS API. See <a href="doc/apiconf.txt">doc/apiconf.txt</a>';
$blocklogin = TRUE; $blocklogin = TRUE;
} }
if (!preg_match('/^[01]$/', $apivers)) {
$errormsg = "The value for \$apivers is incorrect your config";
$blocklogin = TRUE;
}
if (!isset($authmethod) or !preg_match('/^(xapikey|userpass|auto)$/', $authmethod)) {
$errormsg = "The value for \$authmethod is incorrect in your config";
$blocklogin = TRUE;
}
if (!isset($apiproto) or !preg_match('/^http(s)?$/', $apiproto)) { if (!isset($apiproto) or !preg_match('/^http(s)?$/', $apiproto)) {
$errormsg = "The value for \$apiproto is incorrect in your config. Did you configure it?"; $errormsg = "The value for \$apiproto is incorrect in your config. Did you configure it?";
$blocklogin = TRUE; $blocklogin = TRUE;
@ -51,12 +41,6 @@ if (!isset($logo) or empty($logo)) {
/* No need to change stuf below */ /* No need to change stuf below */
if ($apivers == 0) {
$apipath = "";
} elseif ($apivers == 1) {
$apipath = "/api/v1";
}
if (function_exists('curl_init') === FALSE) { if (function_exists('curl_init') === FALSE) {
$errormsg = "You need PHP Curl to run nsedit"; $errormsg = "You need PHP Curl to run nsedit";
$blocklogin = TRUE; $blocklogin = TRUE;
@ -144,6 +128,7 @@ function do_db_auth($u, $p) {
$db->close(); $db->close();
if ($userinfo and $userinfo['password'] and (crypt($p, $userinfo['password']) === $userinfo['password'])) { if ($userinfo and $userinfo['password'] and (crypt($p, $userinfo['password']) === $userinfo['password'])) {
writelog('Succesful login.');
return TRUE; return TRUE;
} }
@ -167,6 +152,11 @@ function add_user($username, $isadmin = FALSE, $password = '') {
$ret = $q->execute(); $ret = $q->execute();
$db->close(); $db->close();
if ($isadmin) {
writelog("Added user $username as admin.");
} else {
writelog("Added user $username.");
}
return $ret; return $ret;
} }
@ -183,10 +173,12 @@ function update_user($username, $isadmin, $password) {
$q->bindValue(1, (int)(bool)$isadmin, SQLITE3_INTEGER); $q->bindValue(1, (int)(bool)$isadmin, SQLITE3_INTEGER);
$q->bindValue(2, $password, SQLITE3_TEXT); $q->bindValue(2, $password, SQLITE3_TEXT);
$q->bindValue(3, $username, SQLITE3_TEXT); $q->bindValue(3, $username, SQLITE3_TEXT);
writelog("Updating password and/or settings for $username. Admin: ".(int)(bool)$isadmin);
} else { } else {
$q = $db->prepare('UPDATE users SET isadmin = ? WHERE emailaddress = ?'); $q = $db->prepare('UPDATE users SET isadmin = ? WHERE emailaddress = ?');
$q->bindValue(1, (int)(bool)$isadmin, SQLITE3_INTEGER); $q->bindValue(1, (int)(bool)$isadmin, SQLITE3_INTEGER);
$q->bindValue(2, $username, SQLITE3_TEXT); $q->bindValue(2, $username, SQLITE3_TEXT);
writelog("Updating settings for $username. Admin: ".(int)(bool)$isadmin);
} }
$ret = $q->execute(); $ret = $q->execute();
$db->close(); $db->close();
@ -194,13 +186,14 @@ function update_user($username, $isadmin, $password) {
return $ret; return $ret;
} }
function delete_user($id) { function delete_user($username) {
$db = get_db(); $db = get_db();
$q = $db->prepare('DELETE FROM users WHERE id = ?'); $q = $db->prepare('DELETE FROM users WHERE id = ?');
$q->bindValue(1, $id, SQLITE3_INTEGER); $q->bindValue(1, $id, SQLITE3_INTEGER);
$ret = $q->execute(); $ret = $q->execute();
$db->close(); $db->close();
writelog("Deleted user $username.");
return $ret; return $ret;
} }
@ -258,7 +251,55 @@ function user_template_names() {
return $templatenames; return $templatenames;
} }
function getlogs() {
global $logging;
if ($logging !== TRUE)
return;
$db = get_db();
$r = $db->query('SELECT * FROM logs ORDER BY timestamp DESC');
$ret = array();
while ($row = $r->fetchArray(SQLITE3_ASSOC)) {
array_push($ret, $row);
}
return $ret;
}
function clearlogs() {
global $logging;
if ($logging !== TRUE)
return;
$db = get_db();
$q = $db->query('DELETE FROM logs;');
$db->close();
writelog("Logtable truncated.");
}
function writelog($line) {
global $logging;
if ($logging !== TRUE)
return;
try {
$db = get_db();
$q = $db->prepare('CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY,
user TEXT NOT NULL,
log TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP);');
$ret = $q->execute();
$q = $db->prepare('INSERT INTO logs (user, log) VALUES (:user, :log)');
$q->bindValue(':user', get_sess_user(), SQLITE3_TEXT);
$q->bindValue(':log', $line, SQLITE3_TEXT);
$q->execute();
$db->close();
} catch (Exception $e) {
return jtable_respond(null, 'error', $e->getMessage());
}
}
/* This function was taken from https://gist.github.com/rsky/5104756 to make /* This function was taken from https://gist.github.com/rsky/5104756 to make
it available on older php versions. Thanks! */ it available on older php versions. Thanks! */

499
index.php
View file

@ -113,6 +113,9 @@ if ($blocklogin === TRUE) {
<div id="wrap"> <div id="wrap">
<div id="dnssecinfo"> <div id="dnssecinfo">
</div> </div>
<div id="clearlogs" style="display: none;">
Are you sure you want to clear all the logs? Maybe save them first?
</div>
<div id="menu" class="jtable-main-container <?php if ($menutype === 'horizontal') { ?>horizontal<?php } ?>"> <div id="menu" class="jtable-main-container <?php if ($menutype === 'horizontal') { ?>horizontal<?php } ?>">
<div class="jtable-title menu-title"> <div class="jtable-title menu-title">
<div class="jtable-title-text"> <div class="jtable-title-text">
@ -123,6 +126,7 @@ if ($blocklogin === TRUE) {
<li><a href="#" id="zoneadmin">Zones</a></li> <li><a href="#" id="zoneadmin">Zones</a></li>
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
<li><a href="#" id="useradmin">Users</a></li> <li><a href="#" id="useradmin">Users</a></li>
<li><a href="#" id="logadmin">Logs</a></li>
<?php } ?> <?php } ?>
<li><a href="#" id="aboutme">About me</a></li> <li><a href="#" id="aboutme">About me</a></li>
<li><a href="index.php?logout=1">Logout</a></li> <li><a href="index.php?logout=1">Logout</a></li>
@ -135,6 +139,7 @@ if ($blocklogin === TRUE) {
<div id="zones"> <div id="zones">
<?php if (is_adminuser() or $allowzoneadd === TRUE) { ?> <?php if (is_adminuser() or $allowzoneadd === TRUE) { ?>
<div style="visibility: hidden;" id="ImportZone"></div> <div style="visibility: hidden;" id="ImportZone"></div>
<div style="visibility: hidden;" id="CloneZone"></div>
<?php } ?> <?php } ?>
<div class="tables" id="MasterZones"> <div class="tables" id="MasterZones">
<div class="searchbar" id="searchbar"> <div class="searchbar" id="searchbar">
@ -147,6 +152,9 @@ if ($blocklogin === TRUE) {
<div id="users"> <div id="users">
<div class="tables" id="Users"></div> <div class="tables" id="Users"></div>
</div> </div>
<div id="logs">
<div class="tables" id="Logs"></div>
</div>
<?php } ?> <?php } ?>
<div id="AboutMe"> <div id="AboutMe">
@ -224,7 +232,7 @@ function displayDnssecIcon(zone) {
function displayExportIcon(zone) { function displayExportIcon(zone) {
var $img = $('<img class="list clickme" src="img/export.png" title="Export zone" />'); var $img = $('<img class="list clickme" src="img/export.png" title="Export zone" />');
$img.click(function () { $img.click(function () {
var $zexport = $.getJSON("zones.php?zone="+zone.record.name+"&action=export", function(data) { var $zexport = $.getJSON("zones.php?zoneid="+zone.record.id+"&action=export", function(data) {
blob = new Blob([data.Record.zone], { type: 'text/plain' }); blob = new Blob([data.Record.zone], { type: 'text/plain' });
var dl = document.createElement('a'); var dl = document.createElement('a');
dl.addEventListener('click', function(ev) { dl.addEventListener('click', function(ev) {
@ -251,7 +259,11 @@ function displayContent(fieldName, zone) {
var zspan = $('<span class="lightgrey">').text(zone); var zspan = $('<span class="lightgrey">').text(zone);
return lspan.add(zspan); return lspan.add(zspan);
} else { } else {
return $('<span>').text(data.record[fieldName]); var text = data.record[fieldName];
if (typeof data.record[fieldName] == 'boolean') {
text == false ? text = 'No' : text = 'Yes';
}
return $('<span>').text(text);
} }
} }
} }
@ -305,16 +317,16 @@ $(document).ready(function () {
listClass: 'dnssec' listClass: 'dnssec'
}, },
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
owner: { account: {
title: 'Owner', title: 'Account',
width: '8%', width: '8%',
display: displayContent('owner'), display: displayContent('account'),
options: function(data) { options: function(data) {
return 'users.php?action=listoptions&e='+$epoch; return 'users.php?action=listoptions&e='+$epoch;
}, },
defaultValue: 'admin', defaultValue: 'admin',
inputClass: 'owner', inputClass: 'account',
listClass: 'owner' listClass: 'account'
}, },
<?php } ?> <?php } ?>
kind: { kind: {
@ -363,7 +375,7 @@ $(document).ready(function () {
title: 'Records in ' + zone.record.name, title: 'Records in ' + zone.record.name,
openChildAsAccordion: true, openChildAsAccordion: true,
actions: { actions: {
listAction: 'zones.php?action=listrecords&zoneurl=' + zone.record.url listAction: 'zones.php?action=listrecords&zoneid=' + zone.record.id
}, },
fields: { fields: {
name: { name: {
@ -428,17 +440,179 @@ $(document).ready(function () {
hoverAnimation: true, hoverAnimation: true,
hoverAnimationDuration: 60, hoverAnimationDuration: 60,
hoverAnimationEasing: undefined, hoverAnimationEasing: undefined,
items: [{ items: [
<?php if (is_adminuser() or $allowzoneadd === TRUE) { ?> <?php if (is_adminuser() or $allowzoneadd === TRUE) { ?>
{
icon: 'jtable/lib/themes/metro/add.png', icon: 'jtable/lib/themes/metro/add.png',
text: 'Import a new zone', text: 'Import a new zone',
click: function() { click: function() {
$('#ImportZone').jtable('showCreateForm'); $('#ImportZone').jtable('showCreateForm');
} }
},
{
icon: 'jtable/lib/themes/metro/add.png',
text: 'Clone a zone',
click: function() {
$('#CloneZone').jtable('showCreateForm');
}
},
<?php } ?> <?php } ?>
}], ],
}, },
sorting: false, sorting: false,
selecting: true,
selectOnRowClick: true,
selectionChanged: function (data) {
var $selectedRows = $('#MasterZones').jtable('selectedRows');
$selectedRows.each(function () {
var zone = $(this).data('record');
$('#MasterZones').jtable('openChildTable',
$(this).closest('tr'), {
title: 'Records in ' + zone.name,
messages: {
addNewRecord: 'Add to ' + zone.name,
noDataAvailable: 'No records for ' + zone.name
},
paging: true,
sorting: true,
pageSize: 20,
openChildAsAccordion: true,
actions: {
listAction: 'zones.php?action=listrecords&zoneid=' + zone.id,
createAction: 'zones.php?action=createrecord&zoneid=' + zone.id,
deleteAction: 'zones.php?action=deleterecord&zoneid=' + zone.id,
updateAction: 'zones.php?action=editrecord&zoneid=' + zone.id
},
fields: {
domid: {
create: true,
type: 'hidden',
defaultValue: zone.id
},
id: {
key: true,
type: 'hidden',
create: false,
edit: false,
list: false
},
domain: {
create: true,
type: 'hidden',
defaultValue: zone.name
},
name: {
title: 'Label',
width: '7%',
sorting: true,
create: true,
display: displayContent('name', zone.name),
inputClass: 'name',
listClass: 'name'
},
type: {
title: 'Type',
width: '2%',
options: function() {
zonename = new String(zone.name);
if (zonename.match(/(\.in-addr|\.ip6)\.arpa/)) {
return {
'PTR': 'PTR',
'NS': 'NS',
'MX': 'MX',
'TXT': 'TXT',
'SOA': 'SOA',
'A': 'A',
'AAAA': 'AAAA',
'CERT': 'CERT',
'CNAME': 'CNAME',
'LOC': 'LOC',
'NAPTR': 'NAPTR',
'SPF': 'SPF',
'SRV': 'SRV',
'SSHFP': 'SSHFP',
'TLSA': 'TLSA',
};
}
return {
'A': 'A',
'AAAA': 'AAAA',
'CERT': 'CERT',
'CNAME': 'CNAME',
'LOC': 'LOC',
'MX': 'MX',
'NAPTR': 'NAPTR',
'NS': 'NS',
'PTR': 'PTR',
'SOA': 'SOA',
'SPF': 'SPF',
'SRV': 'SRV',
'SSHFP': 'SSHFP',
'TLSA': 'TLSA',
'TXT': 'TXT',
};
},
display: displayContent('type'),
create: true,
inputClass: 'type',
listClass: 'type'
},
content: {
title: 'Content',
width: '30%',
create: true,
sorting: true,
display: displayContent('content'),
inputClass: 'content',
listClass: 'content'
},
ttl: {
title: 'TTL',
width: '2%',
create: true,
sorting: false,
display: displayContent('ttl'),
defaultValue: '<?php echo $defaults['ttl']; ?>',
inputClass: 'ttl',
listClass: 'ttl'
},
setptr: {
title: 'Set PTR Record',
width: '2%',
list: false,
create: true,
defaultValue: 'false',
inputClass: 'setptr',
listClass: 'setptr',
options: function() {
return {
'0': 'No',
'1': 'Yes',
};
},
},
disabled: {
title: 'Disabled',
width: '2%',
create: true,
sorting: false,
display: displayContent('disabled'),
defaultValue: '<?php echo $defaults['disabled'] ? 'No' : 'Yes'; ?>',
inputClass: 'disabled',
listClass: 'disabled',
options: function() {
return {
'0': 'No',
'1': 'Yes',
};
},
},
}
}, function (data) {
data.childTable.jtable('load');
});
});
},
openChildAsAccordion: true, openChildAsAccordion: true,
actions: { actions: {
listAction: 'zones.php?action=list', listAction: 'zones.php?action=list',
@ -472,16 +646,16 @@ $(document).ready(function () {
listClass: 'dnssec' listClass: 'dnssec'
}, },
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
owner: { account: {
title: 'Owner', title: 'Account',
width: '8%', width: '8%',
display: displayContent('owner'), display: displayContent('account'),
options: function(data) { options: function(data) {
return 'users.php?action=listoptions&e='+$epoch; return 'users.php?action=listoptions&e='+$epoch;
}, },
defaultValue: 'admin', defaultValue: 'admin',
inputClass: 'owner', inputClass: 'account',
listClass: 'owner' listClass: 'account'
}, },
<?php } ?> <?php } ?>
kind: { kind: {
@ -533,143 +707,6 @@ $(document).ready(function () {
inputClass: 'serial', inputClass: 'serial',
listClass: 'serial' listClass: 'serial'
}, },
records: {
width: '5%',
title: 'Records',
edit: false,
create: false,
display: function (zone) {
var $img = $('<img class="list" src="img/list.png" title="Records" />');
$img.click(function () {
$('#MasterZones').jtable('openChildTable',
$img.closest('tr'), {
title: 'Records in ' + zone.record.name,
messages: {
addNewRecord: 'Add to ' + zone.record.name,
noDataAvailable: 'No records for ' + zone.record.name
},
paging: true,
pageSize: 20,
openChildAsAccordion: true,
actions: {
listAction: 'zones.php?action=listrecords&zoneurl=' + zone.record.url,
createAction: 'zones.php?action=createrecord&zoneurl=' + zone.record.url,
deleteAction: 'zones.php?action=deleterecord&zoneurl=' + zone.record.url,
updateAction: 'zones.php?action=editrecord&zoneurl=' + zone.record.url
},
fields: {
domid: {
create: true,
type: 'hidden',
defaultValue: zone.record.id
},
id: {
key: true,
type: 'hidden',
create: false,
edit: false,
list: false
},
domain: {
create: true,
type: 'hidden',
defaultValue: zone.record.name
},
name: {
title: 'Label',
width: '7%',
create: true,
display: displayContent('name', zone.record.name),
inputClass: 'name',
listClass: 'name'
},
type: {
title: 'Type',
width: '2%',
options: function() {
zonename = new String(zone.record.name);
if (zonename.match(/(\.in-addr|\.ip6)\.arpa/)) {
return {
'PTR': 'PTR',
'NS': 'NS',
'MX': 'MX',
'TXT': 'TXT',
'SOA': 'SOA',
'A': 'A',
'AAAA': 'AAAA',
'CERT': 'CERT',
'CNAME': 'CNAME',
'LOC': 'LOC',
'NAPTR': 'NAPTR',
'SPF': 'SPF',
'SRV': 'SRV',
'SSHFP': 'SSHFP',
'TLSA': 'TLSA',
};
}
return {
'A': 'A',
'AAAA': 'AAAA',
'CERT': 'CERT',
'CNAME': 'CNAME',
'LOC': 'LOC',
'MX': 'MX',
'NAPTR': 'NAPTR',
'NS': 'NS',
'PTR': 'PTR',
'SOA': 'SOA',
'SPF': 'SPF',
'SRV': 'SRV',
'SSHFP': 'SSHFP',
'TLSA': 'TLSA',
'TXT': 'TXT',
};
},
display: displayContent('type'),
create: true,
inputClass: 'type',
listClass: 'type'
},
content: {
title: 'Content',
width: '30%',
create: true,
display: displayContent('content'),
inputClass: 'content',
listClass: 'content'
},
ttl: {
title: 'TTL',
width: '2%',
create: true,
display: displayContent('ttl'),
defaultValue: '<?php echo $defaults['ttl']; ?>',
inputClass: 'ttl',
listClass: 'ttl'
},
disabled: {
title: 'Disabled',
width: '2%',
create: true,
display: displayContent('disabled'),
defaultValue: '<?php echo $defaults['disabled'] ? 'false' : 'true'; ?>',
inputClass: 'disabled',
listClass: 'disabled',
options: function() {
return {
'0': 'false',
'1': 'true',
};
},
},
}
}, function (data) {
data.childTable.jtable('load');
})
});
return $img;
}
},
exportzone: { exportzone: {
title: '', title: '',
width: '1%', width: '1%',
@ -695,13 +732,13 @@ $(document).ready(function () {
inputClass: 'domain' inputClass: 'domain'
}, },
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
owner: { account: {
title: 'Owner', title: 'Account',
options: function(data) { options: function(data) {
return 'users.php?action=listoptions&e='+$epoch; return 'users.php?action=listoptions&e='+$epoch;
}, },
defaultValue: 'admin', defaultValue: 'admin',
inputClass: 'owner' inputClass: 'account'
}, },
<?php } ?> <?php } ?>
kind: { kind: {
@ -742,6 +779,51 @@ $(document).ready(function () {
} }
}); });
$('#CloneZone').jtable({
title: 'Clone zone',
actions: {
createAction: 'zones.php?action=clone'
},
fields: {
id: {
key: true,
type: 'hidden'
},
sourcename: {
title: 'Source domain',
options: function(data) {
return 'zones.php?action=formzonelist&e='+$epoch;
},
inputClass: 'sourcename'
},
destname: {
title: 'Domain',
inputClass: 'destname'
},
account: {
title: 'Account',
options: function(data) {
return 'users.php?action=listoptions&e='+$epoch;
},
defaultValue: 'admin',
inputClass: 'account'
},
kind: {
title: 'Type',
options: {'Native': 'Native', 'Master': 'Master'},
defaultValue: '<?php echo $defaults['defaulttype']; ?>',
edit: false,
inputClass: 'type'
},
},
recordAdded: function() {
$("#MasterZones").jtable('load');
$("#SlaveZones").jtable('load');
}
});
$('#domsearch').addClear({ $('#domsearch').addClear({
onClear: function() { $('#MasterZones').jtable('load'); } onClear: function() { $('#MasterZones').jtable('load'); }
}); });
@ -772,26 +854,39 @@ $(document).ready(function () {
}); });
<?php if (is_adminuser()) { ?> <?php if (is_adminuser()) { ?>
$('#Logs').hide();
$('#Users').hide(); $('#Users').hide();
$('#AboutMe').hide(); $('#AboutMe').hide();
$('#aboutme').click(function () { $('#aboutme').click(function () {
$('#Logs').hide();
$('#Users').hide(); $('#Users').hide();
$('#MasterZones').hide(); $('#MasterZones').hide();
$('#SlaveZones').hide(); $('#SlaveZones').hide();
$('#AboutMe').show(); $('#AboutMe').show();
}); });
$('#useradmin').click(function () { $('#useradmin').click(function () {
$('#Users').show(); $('#Logs').hide();
$('#MasterZones').hide(); $('#MasterZones').hide();
$('#SlaveZones').hide(); $('#SlaveZones').hide();
$('#AboutMe').hide(); $('#AboutMe').hide();
$('#Users').jtable('load');
$('#Users').show();
}); });
$('#zoneadmin').click(function () { $('#zoneadmin').click(function () {
$('#Logs').hide();
$('#Users').hide(); $('#Users').hide();
$('#AboutMe').hide(); $('#AboutMe').hide();
$('#MasterZones').show(); $('#MasterZones').show();
$('#SlaveZones').show(); $('#SlaveZones').show();
}); });
$('#logadmin').click(function () {
$('#Users').hide();
$('#AboutMe').hide();
$('#MasterZones').hide();
$('#SlaveZones').hide();
$('#Logs').jtable('load');
$('#Logs').show();
});
$('#Users').jtable({ $('#Users').jtable({
title: 'Users', title: 'Users',
paging: true, paging: true,
@ -808,12 +903,9 @@ $(document).ready(function () {
deleteConfirmation: 'This user will be deleted. Are you sure?' deleteConfirmation: 'This user will be deleted. Are you sure?'
}, },
fields: { fields: {
id: {
key: true,
type: 'hidden'
},
emailaddress: { emailaddress: {
title: 'User', title: 'User',
key: true,
display: displayContent('emailaddress'), display: displayContent('emailaddress'),
inputClass: 'emailaddress', inputClass: 'emailaddress',
listClass: 'emailaddress' listClass: 'emailaddress'
@ -838,7 +930,92 @@ $(document).ready(function () {
$("#SlaveZones").jtable('reload'); $("#SlaveZones").jtable('reload');
} }
}); });
$('#Users').jtable('load');
$('#Logs').jtable({
title: 'Logs',
paging: true,
pageSize: 20,
sorting: false,
actions: {
listAction: 'logs.php?action=list',
deleteAction: 'logs.php?action=delete',
},
messages: {
deleteConfirmation: 'This entry will be deleted. Are you sure?'
},
toolbar: {
hoverAnimation: true,
hoverAnimationDuration: 60,
hoverAnimationEasing: undefined,
items: [
{
icon: 'img/delete_inverted.png',
text: 'Clear logs',
click: function() {
$("#clearlogs").dialog({
modal: true,
title: "Clear all logs",
width: 'auto',
buttons: {
Ok: function() {
$.get("logs.php?action=clear");
$( this ).dialog( "close" );
$('#Logs').jtable('load');
},
Cancel: function() {
$( this ).dialog( "close" );
return false;
}
}
});
}
},
{
icon: 'img/export.png',
text: 'Save logs',
click: function () {
var $zexport = $.get("logs.php?action=export", function(data) {
console.log(data);
blob = new Blob([data], { type: 'text/plain' });
var dl = document.createElement('a');
dl.addEventListener('click', function(ev) {
dl.href = URL.createObjectURL(blob);
dl.download = 'nseditlogs.txt';
}, false);
if (document.createEvent) {
var event = document.createEvent("MouseEvents");
event.initEvent("click", true, true);
dl.dispatchEvent(event);
}
});
}
}
],
},
fields: {
id: {
title: 'key',
key: true,
type: 'hidden'
},
user: {
title: 'User',
width: '10%',
display: displayContent('user'),
},
log: {
title: 'Log',
width: '80%',
display: displayContent('log'),
},
timestamp: {
title: 'Timestamp',
width: '10%',
display: displayContent('timestamp')
}
}
});
<?php } ?> <?php } ?>
$('#MasterZones').jtable('load'); $('#MasterZones').jtable('load');
$('#SlaveZones').jtable('load'); $('#SlaveZones').jtable('load');

51
logs.php Normal file
View file

@ -0,0 +1,51 @@
<?php
include_once('includes/config.inc.php');
include_once('includes/session.inc.php');
include_once('includes/misc.inc.php');
if (!is_csrf_safe()) {
header('Status: 403');
header('Location: ./index.php');
jtable_respond(null, 'error', "Authentication required");
}
if (!is_adminuser()) {
header('Status: 403');
jtable_respond(null, 'error', "You need adminprivileges to get here");
}
if (!isset($_GET['action'])) {
header('Status: 400');
jtable_respond(null, 'error', 'No action given');
}
switch ($_GET['action']) {
case "list":
global $logging;
if ($logging !== TRUE)
jtable_respond(null, 'error', 'Logging is disabled');
jtable_respond(getlogs());
break;
case "delete":
if ($emailaddress != '' and delete_user($emailaddress) !== FALSE) {
jtable_respond(null, 'delete');
} else {
jtable_respond(null, 'error', 'Could not delete user');
}
break;
case "export":
print json_encode(getlogs());
break;
case "clear":
clearlogs();
break;
default:
jtable_respond(null, 'error', 'Invalid action');
break;
}

View file

@ -85,7 +85,7 @@ case "update":
break; break;
case "delete": case "delete":
if (delete_user($_POST['id']) !== FALSE) { if ($emailaddress != '' and delete_user($emailaddress) !== FALSE) {
jtable_respond(null, 'delete'); jtable_respond(null, 'delete');
} else { } else {
jtable_respond(null, 'error', 'Could not delete user'); jtable_respond(null, 'error', 'Could not delete user');

638
zones.php
View file

@ -3,6 +3,8 @@
include_once('includes/config.inc.php'); include_once('includes/config.inc.php');
include_once('includes/session.inc.php'); include_once('includes/session.inc.php');
include_once('includes/misc.inc.php'); include_once('includes/misc.inc.php');
include_once('includes/class/PdnsApi.php');
include_once('includes/class/Zone.php');
if (!is_csrf_safe()) { if (!is_csrf_safe()) {
header('Status: 403'); header('Status: 403');
@ -10,117 +12,6 @@ if (!is_csrf_safe()) {
jtable_respond(null, 'error', "Authentication required"); jtable_respond(null, 'error', "Authentication required");
} }
function api_request($path, $opts = null, $type = null) {
global $apiproto, $apisslverify, $apisid, $apiuser, $apipass, $apiip, $apiport, $authmethod, $apipath;
$url = "$apiproto://$apiip:$apiport${apipath}${path}";
if ($authmethod == "auto") {
$ad = curl_init();
if ( strcasecmp( $apiproto, 'https' ) == 0 ) {
curl_setopt($ad, CURLOPT_SSL_VERIFYPEER, $apisslverify);
}
curl_setopt($ad, CURLOPT_HTTPHEADER, array('X-API-Key: '.$apipass));
curl_setopt($ad, CURLOPT_URL, "$apiproto://$apiip:$apiport/servers/localhost/statistics");
curl_setopt($ad, CURLOPT_RETURNTRANSFER, 1);
curl_exec($ad);
if (curl_getinfo($ad, CURLINFO_HTTP_CODE) == 401) {
$authmethod = 'userpass';
} else {
$authmethod = 'xapikey';
}
}
$headers = array();
array_push($headers, 'Accept: application/json');
$ch = curl_init();
if ($authmethod == "xapikey") {
array_push($headers, 'X-API-Key: '.$apipass);
} else {
curl_setopt($ch, CURLOPT_USERPWD, "$apiuser:$apipass");
}
if ( strcasecmp( $apiproto, 'https' ) == 0 ) {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $apisslverify);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
if ($opts) {
if (!$type) {
$type = 'POST';
}
$postdata = json_encode($opts);
array_push($headers, 'Content-Type: application/json');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postdata);
}
switch ($type) {
case 'DELETE':
case 'PATCH':
case 'PUT':
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $type);
break;
case 'POST':
break;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$return = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$json = json_decode($return, 1);
if (isset($json['error'])) {
jtable_respond(null, 'error', "API Error $code: ".$json['error']);
} elseif ($code < 200 || $code >= 300) {
if ($code == 401) {
$code = "Authentication failed. Have you configured your authmethod correct?";
}
jtable_respond(null, 'error', "API Error: $code");
}
return $json;
}
function zones_api_request($opts = null, $type = 'POST') {
global $apisid;
return api_request("/servers/${apisid}/zones", $opts, $type);
}
function get_all_zones() {
return zones_api_request();
}
function _get_zone_by_key($key, $value) {
if ($value !== '') {
foreach (get_all_zones() as $zone) {
if ($zone[$key] === $value) {
$zone['owner'] = get_zone_owner($zone['name'], 'admin');
if (!check_owner($zone)) {
jtable_respond(null, 'error', 'Access denied');
}
return $zone;
}
}
}
header('Status: 404 Not found');
jtable_respond(null, 'error', "Zone not found");
}
function get_zone_by_url($zoneurl) {
return _get_zone_by_key('url', $zoneurl);
}
function get_zone_by_id($zoneid) {
return _get_zone_by_key('id', $zoneid);
}
function get_zone_by_name($zonename) {
return _get_zone_by_key('name', $zonename);
}
/* This function is taken from: /* This function is taken from:
http://pageconfig.com/post/how-to-validate-ascii-text-in-php and got fixed by http://pageconfig.com/post/how-to-validate-ascii-text-in-php and got fixed by
#powerdns */ #powerdns */
@ -130,116 +21,7 @@ function is_ascii($string) {
} }
function _valid_label($name) { function _valid_label($name) {
return is_ascii($name) && ( bool ) preg_match("/^([-.a-z0-9_\/\*]+)?$/i", $name ); return is_ascii($name) && ( bool ) preg_match("/^([-.a-z0-9_\/\*]+)?.$/i", $name );
}
function make_record($zone, $input) {
global $defaults;
$name = isset($input['name']) ? $input['name'] : '';
if ('' == $name) {
$name = $zone['name'];
} elseif (string_ends_with($name, '.')) {
# "absolute" name, shouldn't append zone[name] - but check.
$name = substr($name, 0, -1);
if (!string_ends_with($name, $zone['name'])) {
jtable_respond(null, 'error', "Name $name not in zone ".$zone['name']);
}
} else if (!string_ends_with($name, $zone['name'])) {
$name = $name . '.' . $zone['name'];
}
$ttl = (int) ((isset($input['ttl']) && $input['ttl']) ? $input['ttl'] : $defaults['ttl']);
$type = isset($input['type']) ? $input['type'] : '';
$disabled = (bool) (isset($input['disabled']) && $input['disabled']);
$content = isset($input['content']) ? $input['content'] : '';
if ($type === 'TXT') {
# empty TXT records are ok, otherwise require surrounding quotes: "..."
if (strlen($content) == 1 || substr($content, 0, 1) !== '"' || substr($content, -1) !== '"') {
# fix quoting: first escape all \, then all ", then surround with quotes.
$content = '"'.str_replace('"', '\\"', str_replace('\\', '\\\\', $content)).'"';
}
}
if (!_valid_label($name)) {
jtable_respond(null, 'error', "Please only use [a-z0-9_/.-]");
}
if (!$type) {
jtable_respond(null, 'error', "Require a type");
}
if (!is_ascii($content)) {
jtable_respond(null, 'error', "Please only use ASCII-characters in your fields");
}
return array(
'disabled' => $disabled,
'type' => $type,
'name' => $name,
'ttl' => $ttl,
'content' => $content);
}
function update_records($zone, $name_and_type, $inputs) {
# need one "record" to extract name and type, in case we have no inputs
# (deletion of all records)
$name_and_type = make_record($zone, $name_and_type);
$name = $name_and_type['name'];
$type = $name_and_type['type'];
$records = array();
foreach ($inputs as $input) {
$record = make_record($zone, $input);
if ($record['name'] !== $name || $record['type'] !== $type) {
jtable_respond(null, 'error', "Records not matching");
}
array_push($records, $record);
}
if (!_valid_label($name)) {
jtable_respond(null, 'error', "Please only use [a-z0-9_/.-]");
}
$patch = array(
'rrsets' => array(array(
'name' => $name,
'type' => $type,
'changetype' => count($records) ? 'REPLACE' : 'DELETE',
'records' => $records)));
api_request($zone['url'], $patch, 'PATCH');
}
function create_record($zone, $input) {
$record = make_record($zone, $input);
$records = get_records_by_name_type($zone, $record['name'], $record['type']);
array_push($records, $record);
$patch = array(
'rrsets' => array(array(
'name' => $record['name'],
'type' => $record['type'],
'changetype' => 'REPLACE',
'records' => $records)));
api_request($zone['url'], $patch, 'PATCH');
return $record;
}
function get_records_by_name_type($zone, $name, $type) {
$zone = api_request($zone['url']);
$records = array();
foreach ($zone['records'] as $record) {
if ($record['name'] == $name and $record['type'] == $type) {
array_push($records, $record);
}
}
return $records;
} }
function decode_record_id($id) { function decode_record_id($id) {
@ -255,39 +37,6 @@ function decode_record_id($id) {
return $record; return $record;
} }
# get all records with same name and type but different id (content)
# requires records with id to be present
# SOA records match always, regardless of content.
function get_records_except($zone, $exclude) {
$is_soa = ($exclude['type'] == 'SOA');
$found = false;
$zone = api_request($zone['url']);
$records = array();
foreach ($zone['records'] as $record) {
if ($record['name'] == $exclude['name'] and $record['type'] == $exclude['type']) {
if ($is_soa) {
# SOA changes all the time (serial); we can't match it in a sane way.
# OTOH we know it is unique anyway - just pretend we found a match.
$found = true;
} elseif ($record['content'] != $exclude['content']
or $record['ttl'] != $exclude['ttl']
or $record['disabled'] != $exclude['disabled']) {
array_push($records, $record);
} else {
$found = true;
}
}
}
if (!$found) {
header("Status: 404 Not Found");
jtable_respond(null, 'error', "Didn't find record with id");
}
return $records;
}
function compareName($a, $b) { function compareName($a, $b) {
$a = array_reverse(explode('.', $a)); $a = array_reverse(explode('.', $a));
$b = array_reverse(explode('.', $b)); $b = array_reverse(explode('.', $b));
@ -321,29 +70,47 @@ function rrtype_compare($a, $b) {
} }
} }
function record_compare($a, $b) { function record_compare_default($a, $b) {
if ($cmp = compareName($a['name'], $b['name'])) return $cmp; if ($cmp = compareName($a['name'], $b['name'])) return $cmp;
if ($cmp = rrtype_compare($a['type'], $b['type'])) return $cmp; if ($cmp = rrtype_compare($a['type'], $b['type'])) return $cmp;
if ($cmp = strnatcasecmp($a['content'], $b['content'])) return $cmp; if ($cmp = strnatcasecmp($a['content'], $b['content'])) return $cmp;
return 0; return 0;
} }
function add_db_zone($zonename, $ownername) { function record_compare_name($a, $b) {
if (valid_user($ownername) === false) { return record_compare_default($a, $b);
jtable_respond(null, 'error', "$ownername is not a valid username"); }
function record_compare_type($a, $b) {
if ($cmp = rrtype_compare($a['type'], $b['type'])) return $cmp;
if ($cmp = compareName($a['name'], $b['name'])) return $cmp;
if ($cmp = strnatcasecmp($a['content'], $b['content'])) return $cmp;
return 0;
}
function record_compare_content($a, $b) {
if ($cmp = strnatcasecmp($a['content'], $b['content'])) return $cmp;
if ($cmp = compareName($a['name'], $b['name'])) return $cmp;
if ($cmp = rrtype_compare($a['type'], $b['type'])) return $cmp;
return 0;
}
function add_db_zone($zonename, $accountname) {
if (valid_user($accountname) === false) {
jtable_respond(null, 'error', "$accountname is not a valid username");
} }
if (!_valid_label($zonename)) { if (!_valid_label($zonename)) {
jtable_respond(null, 'error', "$zonename is not a valid zonename"); jtable_respond(null, 'error', "$zonename is not a valid zonename");
} }
if (is_apiuser() && !user_exists($ownername)) { if (is_apiuser() && !user_exists($accountname)) {
add_user($ownername); add_user($accountname);
} }
$db = get_db(); $db = get_db();
$q = $db->prepare("INSERT OR REPLACE INTO zones (zone, owner) VALUES (?, (SELECT id FROM users WHERE emailaddress = ?))"); $q = $db->prepare("INSERT OR REPLACE INTO zones (zone, owner) VALUES (?, (SELECT id FROM users WHERE emailaddress = ?))");
$q->bindValue(1, $zonename, SQLITE3_TEXT); $q->bindValue(1, $zonename, SQLITE3_TEXT);
$q->bindValue(2, $ownername, SQLITE3_TEXT); $q->bindValue(2, $accountname, SQLITE3_TEXT);
$q->execute(); $q->execute();
$db->close(); $db->close();
} }
@ -359,7 +126,7 @@ function delete_db_zone($zonename) {
$db->close(); $db->close();
} }
function get_zone_owner($zonename, $default) { function get_zone_account($zonename, $default) {
if (!_valid_label($zonename)) { if (!_valid_label($zonename)) {
jtable_respond(null, 'error', "$zonename is not a valid zonename"); jtable_respond(null, 'error', "$zonename is not a valid zonename");
} }
@ -376,28 +143,8 @@ function get_zone_owner($zonename, $default) {
return $default; return $default;
} }
function get_zone_keys($zone) { function check_account($zone) {
$ret = array(); return is_adminuser() or ($zone->account === get_sess_user());
foreach (api_request($zone['url'] . "/cryptokeys") as $key) {
if (!isset($key['active']))
continue;
$key['dstxt'] = $zone['name'] . ' IN DNSKEY '.$key['dnskey']."\n\n";
if (isset($key['ds'])) {
foreach ($key['ds'] as $ds) {
$key['dstxt'] .= $zone['name'] . ' IN DS '.$ds."\n";
}
unset($key['ds']);
}
$ret[] = $key;
}
return $ret;
}
function check_owner($zone) {
return is_adminuser() or ($zone['owner'] === get_sess_user());
} }
if (isset($_GET['action'])) { if (isset($_GET['action'])) {
@ -406,34 +153,73 @@ if (isset($_GET['action'])) {
jtable_respond(null, 'error', 'No action given'); jtable_respond(null, 'error', 'No action given');
} }
try {
$api = new PdnsAPI;
switch ($action) { switch ($action) {
case "list": case "list":
case "listslaves": case "listslaves":
$return = array(); $return = Array();
$q = isset($_POST['domsearch']) ? $_POST['domsearch'] : false; $q = isset($_POST['domsearch']) ? $_POST['domsearch'] : false;
foreach (get_all_zones() as $zone) { foreach ($api->listzones($q) as $sresult) {
$zone['owner'] = get_zone_owner($zone['name'], 'admin'); $zone = new Zone();
if (!check_owner($zone)) $zone->parse($sresult);
$zone->setAccount(get_zone_account($zone->name, 'admin'));
if (!check_account($zone))
continue; continue;
if ($q && !preg_match("/$q/", $zone['name'])) { if ($action == "listslaves" and $zone->kind == "Slave") {
continue; array_push($return, $zone->export());
} elseif ($action == "list" and $zone->kind != "Slave") {
if ($zone->dnssec) {
$zone->setKeyinfo($api->getzonekeys($zone->id));
} }
array_push($return, $zone->export());
if ($action == "listslaves" and $zone['kind'] == "Slave") {
array_push($return, $zone);
} elseif ($action == "list" and $zone['kind'] != "Slave") {
if ($zone['dnssec']) {
$zone['keyinfo'] = get_zone_keys($zone);
}
array_push($return, $zone);
} }
} }
usort($return, "zone_compare"); usort($return, "zone_compare");
jtable_respond($return); jtable_respond($return);
break; break;
case "listrecords":
$zonedata = $api->loadzone($_GET['zoneid']);
$zone = new Zone();
$zone->parse($zonedata);
$records = $zone->rrsets2records();
if (isset($_GET['jtSorting'])) {
list($scolumn, $sorder) = preg_split("/ /", $_GET['jtSorting']);
switch ($scolumn) {
case "type":
usort($records, "record_compare_type");
break;
case "content":
usort($records, "record_compare_content");
break;
default:
usort($records, "record_compare_name");
break;
}
if ($sorder == "DESC") {
$records = array_reverse($records);
}
} else {
usort($records, "record_compare_name");
}
jtable_respond($records);
break;
case "delete":
$zone = $api->loadzone($_POST['id']);
$api->deletezone($_POST['id']);
delete_db_zone($zone['name']);
writelog("Deleted zone ".$zone['name']);
jtable_respond(null, 'delete');
break;
case "create": case "create":
$zonename = isset($_POST['name']) ? $_POST['name'] : ''; $zonename = isset($_POST['name']) ? $_POST['name'] : '';
$zonekind = isset($_POST['kind']) ? $_POST['kind'] : ''; $zonekind = isset($_POST['kind']) ? $_POST['kind'] : '';
@ -449,55 +235,50 @@ case "create":
jtable_respond(null, 'error', "Not enough data"); jtable_respond(null, 'error', "Not enough data");
} }
$createOptions = array( $zone = new Zone();
'name' => $zonename, $zone->setKind($zonekind);
'kind' => $zonekind, $zone->setName($zonename);
);
$nameservers = array();
foreach($_POST['nameserver'] as $ns) {
if (isset($ns) && !empty($ns)) {
array_push($nameservers, $ns);
}
}
if ($zonekind != "Slave") { if ($zonekind != "Slave") {
$createOptions['nameservers'] = $nameservers; if (!isset($_POST['zone']) or isset($_POST['owns'])) {
if (!isset($_POST['zone'])) { foreach ($_POST['nameserver'] as $ns) {
if (0 == count($nameservers)) { $zone->addNameserver($ns);
jtable_respond(null, 'error', "Require nameservers");
} }
} else { } else {
$createOptions['zone'] = $_POST['zone']; $zone->importData($_POST['zone']);
} }
if (isset($defaults['soa_edit_api'])) { if (isset($defaults['soa_edit_api'])) {
$createOptions['soa_edit_api'] = $defaults['soa_edit_api']; $zone->setSoaEditApi($defaults['soa_edit_api']);
} }
if (isset($defaults['soa_edit'])) { if (isset($defaults['soa_edit'])) {
$createOptions['soa_edit'] = $defaults['soa_edit']; $zone->setSoaEdit($defaults['soa_edit']);
} }
} else { // Slave } else { // Slave
if (isset($_POST['masters'])) { if (isset($_POST['masters'])) {
$createOptions['masters'] = preg_split('/[,;\s]+/', $_POST['masters'], null, PREG_SPLIT_NO_EMPTY); foreach (preg_split('/[,;\s]+/', $_POST['masters'], null, PREG_SPLIT_NO_EMPTY) as $master) {
$zone->addMaster($master);
} }
if (0 == count($createOptions['masters'])) {
jtable_respond(null, 'error', "Slave requires master servers");
} }
} }
// only admin user and original owner can "recreate" zones that are already // only admin user and original account can "recreate" zones that are already
// present in our own db but got lost in pdns. // present in our own db but got lost in pdns.
if (!is_adminuser() && get_sess_user() !== get_zone_owner($zonename, get_sess_user())) { if (!is_adminuser() && get_sess_user() !== get_zone_account($zonename, get_sess_user())) {
jtable_respond(null, 'error', 'Zone already owned by someone else'); jtable_respond(null, 'error', 'Zone already owned by someone else');
} }
$zone = zones_api_request($createOptions); $api->savezone($zone->export());
$zonename = $zone['name'];
if (is_adminuser() && isset($_POST['owner'])) { $zone = new Zone();
add_db_zone($zonename, $_POST['owner']); $zone->parse($api->loadzone($zonename));
$zonename = $zone->name;
if (is_adminuser() && isset($_POST['account'])) {
add_db_zone($zonename, $_POST['account']);
$zone->setAccount($_POST['account']);
} else { } else {
add_db_zone($zonename, get_sess_user()); add_db_zone($zonename, get_sess_user());
$zone->setAccount(get_sess_user());
} }
if (isset($_POST['template']) && $_POST['template'] != 'None') { if (isset($_POST['template']) && $_POST['template'] != 'None') {
@ -505,137 +286,156 @@ case "create":
if ($template['name'] !== $_POST['template']) continue; if ($template['name'] !== $_POST['template']) continue;
foreach ($template['records'] as $record) { foreach ($template['records'] as $record) {
if ($record['type'] == 'NS' and array_search($record['content'], $nameservers) !== FALSE) { $rrset = $zone->getRRSet($record['label'], $record['type']);
continue; if ($rrset) {
$rrset->delete();
} }
if (isset($record['label'])) {
$record['name'] = $record['label'];
unset($record['label']);
} }
create_record($zone, $record); $api->savezone($zone->export());
foreach ($template['records'] as $record) {
$zone->addRecord($record['name'], $record['type'], $record['content']);
} }
break; break;
} }
} }
if (isset($_POST['zone']) && isset($_POST['owns']) && $_POST['owns'] && count($nameservers)) { $zone = $api->savezone($zone->export());
$records = array(); writelog("Created zone ".$zone['name']);
foreach ($nameservers as $ns) {
array_push($records, array('type' => 'NS', 'content' => $ns));
}
update_records($zone, $records[0], $records);
}
unset($zone['records']);
unset($zone['comments']);
jtable_respond($zone, 'single'); jtable_respond($zone, 'single');
break; break;
case "update": case "update":
$zone = get_zone_by_id(isset($_POST['id']) ? $_POST['id'] : ''); $zone = new Zone();
$zone->parse($api->loadzone($_POST['id']));
$zoneaccount = isset($_POST['account']) ? $_POST['account'] : $zone->account;
$zoneowner = isset($_POST['owner']) ? $_POST['owner'] : $zone['owner']; if ($zone->account !== $zoneaccount) {
if ($zone['owner'] !== $zoneowner) {
if (!is_adminuser()) { if (!is_adminuser()) {
header("Status: 403 Access denied"); header("Status: 403 Access denied");
jtable_respond(null, 'error', "Can't change owner"); jtable_respond(null, 'error', "Can't change account");
} else { } else {
add_db_zone($zone['name'], $zoneowner); add_db_zone($zone->name, $zoneaccount);
$zone['owner'] = $zoneowner; $zone->setAccount($zoneaccount);
} }
} }
$update = false;
if (isset($_POST['masters'])) { if (isset($_POST['masters'])) {
$zone['masters'] = preg_split('/[,;\s]+/', $_POST['masters'], null, PREG_SPLIT_NO_EMPTY); $zone->eraseMasters();
$update = true; foreach(preg_split('/[,;\s]+/', $_POST['masters'], null, PREG_SPLIT_NO_EMPTY) as $master) {
$zone->addMaster($master);
}
} }
if ($update) { writelog("Updated zone ".$zone->name);
$zoneUpdate = $zone; jtable_respond($api->savezone($zone->export()), 'single');
unset($zoneUpdate['id']);
unset($zoneUpdate['url']);
unset($zoneUpdate['owner']);
$newZone = api_request($zone['url'], $zoneUpdate, 'PUT');
$newZone['owner'] = $zone['owner'];
} else {
$newZone = $zone;
}
unset($newZone['records']);
unset($newZone['comments']);
jtable_respond($newZone, 'single');
break;
case "delete":
$zone = get_zone_by_id(isset($_POST['id']) ? $_POST['id'] : '');
api_request($zone['url'], array(), 'DELETE');
delete_db_zone($zone['name']);
jtable_respond(null, 'delete');
break;
case "listrecords":
$zone = get_zone_by_url(isset($_GET['zoneurl']) ? $_GET['zoneurl'] : '');
$a = api_request($zone['url']);
$records = $a['records'];
foreach ($records as &$record) {
$record['id'] = json_encode($record);
}
unset($record);
usort($records, "record_compare");
jtable_respond($records);
break; break;
case "createrecord": case "createrecord":
$zone = get_zone_by_url(isset($_GET['zoneurl']) ? $_GET['zoneurl'] : ''); $zone = new Zone();
$record = create_record($zone, $_POST); $zone->parse($api->loadzone($_GET['zoneid']));
$record['id'] = json_encode($record); $name = isset($_POST['name']) ? $_POST['name'] : '';
$type = $_POST['type'];
$content = $_POST['content'];
if ('' == $name) {
$name = $zone->name;
} elseif (string_ends_with($name, '.')) {
# "absolute" name, shouldn't append zone[name] - but check.
$name = substr($name, 0, -1);
if (!string_ends_with($name, $zone->name)) {
jtable_respond(null, 'error', "Name $name not in zone ".$zone->name);
}
} else if (!string_ends_with($name, $zone->name)) {
$name = $name . '.' . $zone->name;
}
if (!_valid_label($name)) {
jtable_respond(null, 'error', "Please only use [a-z0-9_/.-]");
}
if (!$type) {
jtable_respond(null, 'error', "Require a type");
}
if (!is_ascii($content)) {
jtable_respond(null, 'error', "Please only use ASCII-characters in your fields");
}
$record = $zone->addRecord($name, $type, $content, $_POST['disabled'], $_POST['ttl'], $_POST['setptr']);
$api->savezone($zone->export());
writelog("Created record: ".$record['id']);
jtable_respond($record, 'single'); jtable_respond($record, 'single');
break; break;
case "editrecord": case "editrecord":
$zone = get_zone_by_url(isset($_GET['zoneurl']) ? $_GET['zoneurl'] : ''); $zone = new Zone();
$zone->parse($api->loadzone($_GET['zoneid']));
$old_record = decode_record_id(isset($_POST['id']) ? $_POST['id'] : ''); $old_record = decode_record_id(isset($_POST['id']) ? $_POST['id'] : '');
$records = get_records_except($zone, $old_record); $rrset = $zone->getRRSet($old_record['name'], $old_record['type']);
$rrset->deleteRecord($old_record['content']);
$zone->addRecord($_POST['name'], $_POST['type'], $_POST['content'], $_POST['disabled'], $_POST['ttl'], $_POST['setptr']);
$record = make_record($zone, $_POST); $api->savezone($zone->export());
if ($record['name'] !== $old_record['name'] || $record['type'] !== $old_record['type']) { $record = $zone->getRecord($_POST['name'], $_POST['type'], $_POST['content']);
# rename or retype: writelog("Updated record ".$_POST['id']." to ".$record['id']);
$newRecords = get_records_by_name_type($zone, $record['name'], $record['type']);
array_push($newRecords, $record);
update_records($zone, $old_record, $records); # remove from old list
update_records($zone, $record, $newRecords); # add to new list
} else {
array_push($records, $record);
update_records($zone, $record, $records);
}
$record['id'] = json_encode($record);
jtable_respond($record, 'single'); jtable_respond($record, 'single');
break; break;
case "deleterecord": case "deleterecord":
$zone = get_zone_by_url(isset($_GET['zoneurl']) ? $_GET['zoneurl'] : ''); $zone = new Zone();
$zone->parse($api->loadzone($_GET['zoneid']));
$old_record = decode_record_id(isset($_POST['id']) ? $_POST['id'] : ''); $old_record = decode_record_id(isset($_POST['id']) ? $_POST['id'] : '');
$rrset = $zone->getRRSet($old_record['name'], $old_record['type']);
$rrset->deleteRecord($old_record['content']);
$records = get_records_except($zone, $old_record); $api->savezone($zone->export());
update_records($zone, $old_record, $records); writelog("Deleted record ".$_POST['id']);
jtable_respond(null, 'delete'); jtable_respond(null, 'delete');
break; break;
case "export": case "export":
$zone = $_GET['zone']; writelog("Exported zone ".$_GET['zoneid']);
$export = api_request("/servers/${apisid}/zones/${zone}/export"); jtable_respond($api->exportzone($_GET['zoneid']), 'single');
break;
jtable_respond($export, 'single'); case "clone":
$name = $_POST['destname'];
$src = $_POST['sourcename'];
if (!string_ends_with($name, '.')) {
$name = $name.".";
}
if (!_valid_label($name)) {
jtable_respond(null, 'error', "Invalid destination zonename");
}
$srczone = new Zone();
$srczone->parse($api->loadzone($src));
$srczone->setId('');
$srczone->setName($name);
$srczone->setSerial('');
$zone = $api->savezone($srczone->export());
$srczone->parse($zone);
foreach ($srczone->rrsets as $rrset) {
$newname = $rrset->name;
$newname = preg_replace('/'.$src.'$/', $name, $newname);
$rrset->setName($newname);
}
$zone = $api->savezone($srczone->export());
writelog("Cloned zone $src into $name");
jtable_respond($zone, 'single');
break; break;
case "gettemplatenameservers": case "gettemplatenameservers":
@ -669,7 +469,23 @@ case "getformnameservers":
} }
} }
break; break;
case "formzonelist":
$zones = $api->listzones();
$ret = array();
foreach ($zones as $zone) {
if ($zone['kind'] == 'Slave')
continue;
array_push($ret, array(
'DisplayText' => $zone['name'],
'Value' => $zone['id']));
}
jtable_respond($ret, 'options');
break;
default: default:
jtable_respond(null, 'error', 'No such action'); jtable_respond(null, 'error', 'No such action');
break; break;
} }
} catch (Exception $e) {
jtable_respond(null, 'error', $e->getMessage());
}