2431 lines
97 KiB
PHP
2431 lines
97 KiB
PHP
<?php
|
|
|
|
require_once 'MDB2.php';
|
|
require_once 'punycode.class.php';
|
|
|
|
$db = array();
|
|
|
|
define('COMMENT_PATTERN', '/(\s+;|^;)[^\n]*/i');
|
|
define('ORIGIN_PATTERN', '/^\$ORIGIN\s+(.+)\.\s*/msi');
|
|
define('SOA_BEGINS_PATTERN', '/^([^\s]+)?(\s+[\d\w]+)?\s+IN\s+SOA\s+/msi');
|
|
define('FULL_SOA_PATTERN', '/^([^\s]+)?(\s+[\d\w]+)?\s+IN\s+SOA\s+([-\w\d.]*)\.\s+([-\w\d.]*)\s+\((.*)\)/msi');
|
|
define('TIMES_PATTERN', '/\s*(\d+\w?)\s+(\d+\w?)\s+(\d+\w?)\s+(\d+\w?)\s+(\d+\w?)/msi');
|
|
define('TXT_PATTERN', '/^\"(.*)\"/msi');
|
|
define('MX_PATTERN', '/^(\d+)\s+([^\s]*)/msi');
|
|
define('TYPE_PATTERN', '(A|A6|AAAA|AFSDB|APL|ATMA|AXFR|CERT|CNAME|DNAME|DNSKEY|DS|EID|GPOS|HINFO|ISDN|IXFR|KEY|KX|LOC|MAILB|MINFO|MX|NAPTR|NIMLOC|NS|NSAP|NSAP-PTR|NSEC|NXT|OPT|PTR|PX|RP|RRSIG|RT|SIG|SINK|SRV|SSHFP|TKEY|TSIG|TXT|WKS|X25)');
|
|
define('RECORD_PATTERN', '/^([^\s]+)?(\s+[\d][\d\w]*)?(\s+IN)?\s+'.TYPE_PATTERN.'\s+([^\s].*$)/msi');
|
|
define('BIND_TIME_PATTERN', '/^(\d+)([smhdw])/');
|
|
define('IDN_PUNY_PATTERN', '/[^a-z0-9-]/i');
|
|
define('IDN_UTF_PATTERN', '/[^a-z0-9\x80-\xFF-]/i');
|
|
define('BIND_ZONENAME_PATTERN', '/zone\s+"([^"]+)".*$/msi');
|
|
define('BIND_ZONEDATA_PATTERN', '/^([^\s]+)\s+"?([^"]+)"?$/');
|
|
define('BIND_SLAVEMASTER_PATTERN', '/^\{([^\};]+);}$/');
|
|
|
|
function idnToHost($idn) {
|
|
preg_match(IDN_UTF_PATTERN, $idn, $match);
|
|
if (count($match) == 0) {
|
|
$out = ($idn > '') ? Punycode::encodeHostName($idn) : '';
|
|
return ((strlen($out) > 4) && (substr($out, 0, 4) == 'xn--')) ? $out : $idn;
|
|
}
|
|
$tags = explode($match[0], $idn);
|
|
$ret = array();
|
|
foreach ($tags as $tag) {
|
|
$ret[] = ($tag == '') ? '' : idnToHost($tag);
|
|
}
|
|
return implode($match[0], $ret);
|
|
}
|
|
|
|
function hostToIdn($host) {
|
|
preg_match(IDN_PUNY_PATTERN, $host, $match);
|
|
if (count($match) == 0) {
|
|
return ((strlen($host) > 4) && (substr($host, 0, 4) == 'xn--')) ? Punycode::decodeHostName($host) : $host;
|
|
}
|
|
$tags = explode($match[0], $host);
|
|
$ret = array();
|
|
foreach ($tags as $tag) {
|
|
$ret[] = ($tag == '') ? '' : hostToIdn($tag, $match[0]);
|
|
}
|
|
return implode($match[0], $ret);
|
|
}
|
|
|
|
class bindConfig {
|
|
|
|
private $zonedef = array();
|
|
private $err = '';
|
|
|
|
public function __debugInfo() {
|
|
return array(
|
|
'zonedef' => $this->zonedef,
|
|
'err' => $this->err,
|
|
);
|
|
}
|
|
|
|
public function __construct($file = NULL) {
|
|
if (!is_null($file)) {
|
|
return $this->loadConfig($file);
|
|
} else {
|
|
$this->zonedef = array();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public function getErr() {
|
|
return $this->err;
|
|
}
|
|
|
|
public function loadConfig($fpath) {
|
|
if (!is_string($fpath)) {
|
|
$this->err .= "Only string parameter accepted\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
if ($fpath == '') {
|
|
$this->err .= "Given string is empty\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
if (!file_exists($fpath)) {
|
|
$this->err .= "File doesn't exist: '" . $fpath . "'\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$conf = file($fpath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
$one = array();
|
|
$name = '';
|
|
foreach ($conf as $line) {
|
|
$line = preg_replace('/(\/\/.*|;)$/', '', trim($line));
|
|
if ($line == '') {
|
|
continue;
|
|
}
|
|
if ($name == '') {
|
|
preg_match(BIND_ZONENAME_PATTERN, $line, $match);
|
|
if (!isset($match[1])) {
|
|
$this->err .= "Read config error\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$name = $match[1];
|
|
} else {
|
|
if ($line == '}') {
|
|
$this->zonedef[$name] = $one;
|
|
$one = array();
|
|
$name = '';
|
|
} else {
|
|
preg_match(BIND_ZONEDATA_PATTERN, $line, $match);
|
|
if ((!isset($match[1])) ||
|
|
(!isset($match[2]))) {
|
|
$this->err .= "Read config error\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$key = $match[1];
|
|
$data = $match[2];
|
|
if ($key == 'masters') {
|
|
preg_match(BIND_SLAVEMASTER_PATTERN, $data, $match);
|
|
if (!isset($match[1])) {
|
|
$this->err .= "Read config error\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$data = $match[1];
|
|
}
|
|
$one[$key] = $data;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function addConfig($name, $data) {
|
|
$name = strval($name);
|
|
if (($name == '') ||
|
|
(!is_array($data))) {
|
|
$this->err .= "Cannot set configset\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$this->zonedef[$name] = $data;
|
|
return true;
|
|
}
|
|
|
|
public function eraseConfig($name) {
|
|
$name = strval($name);
|
|
if ($name == '') {
|
|
$this->err .= "Cannot set configset\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$this->zonedef[$name] = array();
|
|
return true;
|
|
}
|
|
|
|
public function saveConfig($fname) {
|
|
if (!is_string($fname)) {
|
|
$this->err .= "Only string parameter accepted\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
if ($fname == '') {
|
|
$this->err .= "Given string is empty\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$fh = fopen($fname,'w');
|
|
fwrite($fh, "// SMbind-ng configuration\n\n");
|
|
foreach($this->zonedef as $zone => $def) {
|
|
if (count($def) == 0) {
|
|
continue;
|
|
}
|
|
fwrite($fh, "// Zone " . hostToIdn($zone) . " (" . $def['type'] . ")\n");
|
|
fwrite($fh, "zone \"" . $zone . "\" {\n");
|
|
foreach ($def as $key => $param) {
|
|
switch ($key) {
|
|
case 'file':
|
|
$data = "\"" . $param . "\"";
|
|
break;
|
|
case 'masters':
|
|
$data = "{" . $param . ";}";
|
|
break;
|
|
default:
|
|
$data = $param;
|
|
}
|
|
fwrite($fh, str_pad($key, 10, " ", STR_PAD_LEFT) . " " . $data . ";\n");
|
|
}
|
|
fwrite($fh,"};\n\n");
|
|
}
|
|
fclose($fh);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class Configuration {
|
|
|
|
private $info = array();
|
|
|
|
public function __debugInfo() {
|
|
return $this->info;
|
|
}
|
|
|
|
public function __construct($path) {
|
|
global $db;
|
|
if (is_string($path)) {
|
|
$_CONF['smbind_ng'] = $path;
|
|
$_CONF['title'] = "SMBind-ng";
|
|
$_CONF['version'] = 'v0.91c';
|
|
$_CONF['footer'] = $_CONF['title'] . $_CONF['version'];
|
|
$_CONF['marker'] = "Forked by PtY 2015(GPL)";
|
|
$_CONF['template'] = "default";
|
|
$_CONF['recaptcha'] = false;
|
|
$_CONF['tmp_path'] = $path . "tmp";
|
|
$_CONF['nocaptcha'] = array();
|
|
$_CONF['path'] = "/etc/smbind-ng/zones/";
|
|
$_CONF['conf'] = "/etc/smbind-ng/smbind-ng.conf";
|
|
$_CONF['namedcheckconf'] = (is_executable("/usr/sbin/named-checkconf")) ? "/usr/sbin/named-checkconf" : "";
|
|
$_CONF['namedcheckzone'] = (is_executable("/usr/sbin/named-checkzone")) ? "/usr/sbin/named-checkzone" : "";
|
|
$_CONF['rndc'] = (is_executable("/usr/sbin/rndc")) ? "/usr/sbin/rndc" : "";
|
|
$_CONF['zonesigner'] = "/usr/sbin/zonesigner";
|
|
$_CONF['rollinit'] = "/usr/sbin/rollinit";
|
|
$_CONF['isdnssec'] = false;
|
|
$_CONF['dig'] = (is_executable("/usr/bin/dig")) ? "/usr/bin/dig" : "";
|
|
include $path . 'config/config.php';
|
|
$_CONF['zonesigner'] = (is_executable($_CONF['zonesigner'])) ? $_CONF['zonesigner'] : "";
|
|
$_CONF['rollinit'] = (is_executable($_CONF['rollinit'])) ? $_CONF['rollinit'] : "";
|
|
$_CONF['isdnssec'] = (($_CONF['isdnssec'] === true) && ($_CONF['zonesigner'] != "") && ($_CONF['rollinit'] != "")) ? true : false;
|
|
$_CONF['recaptcha'] = (($_CONF['recaptcha'] === true) && (strlen($_CONF['rc_pubkey']) > 0) && (strlen($_CONF['rc_privkey']) > 0)) ? true : false;
|
|
if(!isset($_CONF['db_host'])) {
|
|
$_CONF['db_host'] = 'localhost';
|
|
}
|
|
if(!isset($_CONF['db_port'])) {
|
|
switch ($_CONF['db_type']) {
|
|
case 'mysql':
|
|
case 'mysqli':
|
|
$_CONF['db_port'] = '3306';
|
|
break;
|
|
case 'pgsql':
|
|
$_CONF['db_port'] = '5432';
|
|
break;
|
|
}
|
|
}
|
|
$dsn = array (
|
|
'phptype' => $_CONF['db_type'],
|
|
'username' => $_CONF['db_user'],
|
|
'password' => $_CONF['db_pass'],
|
|
'database' => $_CONF['db_db'],
|
|
'hostspec' => $_CONF['db_host'],
|
|
'port' => $_CONF['db_port'],
|
|
'charset' => 'utf8',
|
|
);
|
|
$dbopt = array('persistent' => true,);
|
|
$db = MDB2::factory($dsn, $dbopt);
|
|
if (MDB2::isError($db)) {
|
|
die("Database error: " . MDB2::errorMessage($db));
|
|
} else {
|
|
$db->setFetchMode(MDB2_FETCHMODE_ASSOC);
|
|
}
|
|
$query = $db->query("SELECT prefkey, prefval FROM options WHERE preftype = 'normal'");
|
|
if (MDB2::isError($query)) {
|
|
$err = $query->getMessage() . "\n" . $query->getDebugInfo();
|
|
error_log($err);
|
|
die($err);
|
|
}
|
|
while ($res = $query->fetchRow()) {
|
|
$key = $res['prefkey'];
|
|
switch ($key) {
|
|
case 'prins':
|
|
case 'secns':
|
|
$keyparam = substr($key, 0, 3) . '_d' . substr($key, -2);
|
|
break;
|
|
default:
|
|
$keyparam = $key;
|
|
}
|
|
$_CONF[$keyparam] = $res['prefval'];
|
|
}
|
|
$query = $db->query("SELECT prefkey FROM options WHERE prefval = 'on' AND preftype = 'record' ORDER BY prefkey");
|
|
$_CONF['parameters'] = array();
|
|
while ($res = $query->fetchRow()) {
|
|
$_CONF['parameters'][] = $res['prefkey'];
|
|
}
|
|
$query = $db->query("SELECT DISTINCT type FROM records");
|
|
while ($res = $query->fetchRow()) {
|
|
$_CONF['parameters'][] = $res['type'];
|
|
}
|
|
$_CONF['parameters'] = array_unique($_CONF['parameters']);
|
|
$_CONF['dsn'] = $dsn;
|
|
$this->info = $_CONF;
|
|
}
|
|
}
|
|
|
|
public function __call($method, $args) {
|
|
if (is_string($method)) {
|
|
$m = $this->from_CC(substr($method, 3, strlen($method) - 3));
|
|
return array_key_exists($m, $this->info) ? $this->info[$m] : false;
|
|
}
|
|
}
|
|
|
|
public function __set($param, $arg) {
|
|
return true;
|
|
}
|
|
|
|
public function __get($param) {
|
|
$name = strtolower($param);
|
|
$return = (array_key_exists($name, $this->info)) ? $this->info[$name] : NULL;
|
|
if (is_null($return)) {
|
|
switch ($name) {
|
|
case 'range':
|
|
$return = 10;
|
|
break;
|
|
default:
|
|
$return = '';
|
|
}
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
private function from_CC($str) {
|
|
$str = strtolower($str);
|
|
$func = create_function('$c', 'return "_" . strtolower($c[1]);');
|
|
return preg_replace_callback('/([A-Z])/', $func, $str);
|
|
}
|
|
|
|
public function isExists($id) {
|
|
return isset($this->info[strtolower($id)]);
|
|
}
|
|
|
|
}
|
|
|
|
class Session {
|
|
|
|
private $usr = '';
|
|
private $psw = '';
|
|
private $inc = NULL;
|
|
|
|
public function __debugInfo() {
|
|
return array(
|
|
'usr' => $this->usr,
|
|
'psw' => $this->psw,
|
|
'inc' => $this->inc,
|
|
);
|
|
}
|
|
|
|
public function __construct() {
|
|
session_start();
|
|
if ((isset($_SESSION['i'])) && (is_numeric($_SESSION['i'])) && ($_SESSION['i'] > 0)) {
|
|
$this->inc = $_SESSION['i'];
|
|
$this->inc++;
|
|
if ((isset($_SESSION['p'])) && (is_string($_SESSION['p']))) {
|
|
$this->psw = $_SESSION['p'];
|
|
if ((isset($_SESSION['u'])) && (is_string($_SESSION['u']))) {
|
|
$this->usr = $_SESSION['u'];
|
|
} else {
|
|
$this->psw = '';
|
|
}
|
|
}
|
|
} else {
|
|
$this->inc = 1;
|
|
}
|
|
$_SESSION['i'] = $this->inc;
|
|
}
|
|
|
|
public function login($user, $pass) {
|
|
$_SESSION['u'] = $user;
|
|
$_SESSION['p'] = $pass;
|
|
$usr = $user;
|
|
$psw = $pass;
|
|
}
|
|
|
|
public function destroy() {
|
|
$this->usr = '';
|
|
$this->psw = '';
|
|
$this->inc = 0;
|
|
$_SESSION = array();
|
|
session_destroy();
|
|
}
|
|
|
|
public function isEnoughOld() {
|
|
return $this->inc > 3;
|
|
}
|
|
}
|
|
|
|
class User {
|
|
private $data = array(
|
|
'id' => 0,
|
|
'username' => '',
|
|
'realname' => '',
|
|
'password' => '',
|
|
'admin' => false,
|
|
);
|
|
private $mzones = array();
|
|
private $szones = array();
|
|
private $err = '';
|
|
private $db = array();
|
|
|
|
public function __debugInfo() {
|
|
return array(
|
|
'data' => $this->data,
|
|
'mzones' => $this->mzones,
|
|
'szones' => $this->szones,
|
|
'err' => $this->err,
|
|
'db' => NULL,
|
|
);
|
|
}
|
|
|
|
public function __construct($uid = NULL) {
|
|
global $db;
|
|
$this->db = &$db;
|
|
if ((is_null($uid)) &&
|
|
(isset($_SESSION['i'])) &&
|
|
($_SESSION['i'] > 1) &&
|
|
(isset($_SESSION['u'])) &&
|
|
($_SESSION['p'])) {
|
|
$user = $_SESSION['u'];
|
|
$pass = $_SESSION['p'];
|
|
if (is_object($db)) {
|
|
if ((is_string($user)) && (is_string($pass))) {
|
|
$res = $this->db->query("SELECT * FROM users WHERE username ='" . $user . "' AND password = '" . $pass . "'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} elseif ($res->numRows() == 0) {
|
|
$this->err .= "Username or password does not match";
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$row = $res->fetchRow();
|
|
foreach ($row as $key => $value) {
|
|
switch ($key) {
|
|
case 'id':
|
|
$this->data[$key] = intval($value);
|
|
break;
|
|
case 'admin':
|
|
$this->data[$key] = ($value == 'yes');
|
|
break;
|
|
default:
|
|
$this->data[$key] = strval($value);
|
|
}
|
|
}
|
|
$this->loadUserZones();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} elseif (((is_array($uid)) &&
|
|
(is_numeric($uid['id']))) ||
|
|
(is_numeric($uid))) {
|
|
$aid = (is_array($uid)) ? $uid : array('id' => $uid);
|
|
if ($aid['id'] == 0) {
|
|
foreach ($aid as $key => $value) {
|
|
$this->data[$key] = $value;
|
|
}
|
|
$this->data['username'] = ((isset($aid['username'])) && ($aid['username'] > '')) ? $aid['username'] : 'NONE';
|
|
$this->data['realname'] = ((isset($aid['realname'])) && ($aid['realname'] > '')) ? $aid['realname'] : $this->data['username'];
|
|
$this->data['password'] = ((isset($aid['password'])) && ($aid['password'] > '')) ? $aid['password'] : 'NONE';
|
|
$this->data['admin'] = ((isset($aid['admin'])) && ($aid['admin'] > '') && (($aid['admin'] == 'yes') || ($aid['admin'] == 'no'))) ? $aid['admin'] : 'no';
|
|
$res = $this->db->query("SELECT * FROM users WHERE username='" . $this->data['username'] . "'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
if ($res->numRows() > 0) {
|
|
$this->err .= ($this->data['username'] == 'NONE') ? "Previous error cause a problem\n" : "User already exists\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$res = $this->db->query("INSERT INTO users (username, realname, admin, password) VALUES ('" .
|
|
$this->data['username'] . "', '" .
|
|
$this->data['realname'] . "', '" .
|
|
$this->data['admin'] . "', '" .
|
|
$this->data['password'] . "')");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$res = $this->db->query("SELECT id FROM users WHERE username='" .
|
|
$this->data['username'] . "' AND realname='" .
|
|
$this->data['realname'] . "' AND password='" .
|
|
$this->data['password'] . "'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$ret = $res->fetchRow();
|
|
$this->data['id'] = $ret['id'];
|
|
return true;
|
|
} else {
|
|
$self->data['id'] = $aid['id'];
|
|
$res = $this->db->query("SELECT * FROM users WHERE id = '" . $self->data['id'] . "'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} elseif ($res->numRows() == 0) {
|
|
$this->err .= "User not found with this id = " . $self->data['id'];
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$row = $res->fetchRow();
|
|
foreach ($row as $key => $value) {
|
|
switch ($key) {
|
|
case 'id':
|
|
$this->data[$key] = intval($value);
|
|
break;
|
|
case 'admin':
|
|
$this->data[$key] = ($value == 'yes');
|
|
break;
|
|
default:
|
|
$this->data[$key] = strval($value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public function loadUserZones() {
|
|
$WHERE = '';
|
|
if (!$this->isAdmin()) {
|
|
$WHERE .= "WHERE owner = '" . $this->getId() . "' ";
|
|
}
|
|
$res = $this->db->query("SELECT id FROM zones " . $WHERE . "ORDER BY name");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$this->mzones = array();
|
|
while ($rec = $res->fetchRow()) {
|
|
$this->mzones[] = $rec['id'];
|
|
}
|
|
$res = $this->db->query("SELECT id FROM slave_zones " . $WHERE . "ORDER BY name");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$this->szones = array();
|
|
while ($rec = $res->fetchRow()) {
|
|
$this->szones[] = $rec['id'];
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function getUnvalidatedZones($zonetype = NULL) {
|
|
$ret = array();
|
|
if (is_string($zonetype)) {
|
|
$tag = '';
|
|
$db = NULL;
|
|
switch ($zonetype) {
|
|
case 'slave':
|
|
$tag = 'slave_';
|
|
$cnt = sizeof($this->szones);
|
|
$lst = implode(',', $this->szones);
|
|
break;
|
|
case 'master':
|
|
$cnt = sizeof($this->mzones);
|
|
$lst = implode(',', $this->mzones);
|
|
break;
|
|
}
|
|
if ($cnt > 0) {
|
|
$res = $this->db->query("SELECT id, name FROM " . $tag . "zones WHERE id IN (" . $lst . ") AND valid <> 'yes' and updated <> 'del'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
while ($recd = $res->fetchRow()) {
|
|
$recd['name'] = hostToIdn($recd['name']);
|
|
$ret[] = $recd;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
public function getDeletedZones($zonetype = NULL) {
|
|
$ret = array();
|
|
if (is_string($zonetype)) {
|
|
$tag = '';
|
|
$db = NULL;
|
|
switch ($zonetype) {
|
|
case 'slave':
|
|
$tag = 'slave_';
|
|
$cnt = sizeof($this->szones);
|
|
$lst = implode(',', $this->szones);
|
|
break;
|
|
case 'master':
|
|
$cnt = sizeof($this->mzones);
|
|
$lst = implode(',', $this->mzones);
|
|
break;
|
|
}
|
|
if ($cnt > 0) {
|
|
$res = $this->db->query("SELECT id, name FROM " . $tag . "zones WHERE owner = " . $this->data['id'] . " AND updated = 'del'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
while ($recd = $res->fetchRow()) {
|
|
$recd['name'] = hostToIdn($recd['name']);
|
|
$ret[] = $recd;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
public function getCommitableZones($zonetype = NULL) {
|
|
$ret = array();
|
|
if (is_string($zonetype)) {
|
|
$tag = '';
|
|
$db = NULL;
|
|
switch ($zonetype) {
|
|
case 'slave':
|
|
$tag = 'slave_';
|
|
$cnt = sizeof($this->szones);
|
|
$lst = implode(',', $this->szones);
|
|
break;
|
|
case 'master':
|
|
$cnt = sizeof($this->mzones);
|
|
$lst = implode(',', $this->mzones);
|
|
break;
|
|
}
|
|
if ($cnt > 0) {
|
|
$res = $this->db->query("SELECT id, name FROM " . $tag . "zones WHERE id IN (" . $lst . ") AND valid = 'yes' AND updated = 'yes'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
while ($recd = $res->fetchRow()) {
|
|
$recd['name'] = hostToIdn($recd['name']);
|
|
$ret[] = $recd;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
public function eraseUser() {
|
|
$this->loadUserZones();
|
|
$mz =array();
|
|
foreach ($this->mzones as $master) {
|
|
$mz = new masterRecord($master);
|
|
$mz->loadZoneHead();
|
|
$mzh = $mz->getZoneHead();
|
|
$mzh['owner'] = 1;
|
|
$mz->setZoneHead($mzh);
|
|
$mz->saveZoneHead();
|
|
}
|
|
$mz =array();
|
|
$sz =array();
|
|
foreach ($this->szones as $slave) {
|
|
$sz = new slaveRecord($slave);
|
|
$sz->loadZoneHead();
|
|
$szh = $sz->getZoneHead();
|
|
$szh['owner'] = 1;
|
|
$sz->setZoneHead($szh);
|
|
$sz->saveZoneHead();
|
|
}
|
|
$sz =array();
|
|
$res = $this->db->query("DELETE FROM users WHERE id = " . $this->data['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function getMasters($type = 'all') {
|
|
$out = array();
|
|
if ($type == 'live') {
|
|
foreach ($this->mzones as $zoneid) {
|
|
$zone = new masterZone(array('id' => intval($zoneid)));
|
|
$zone->loadZoneHead();
|
|
$head = $zone->getZoneHeadRaw();
|
|
if ($head['updated'] != 'del') {
|
|
$out[] = intval($zoneid);
|
|
}
|
|
}
|
|
} else {
|
|
$out = $this->mzones;
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function getSlaves($type = 'all') {
|
|
$out = array();
|
|
if ($type == 'live') {
|
|
foreach ($this->szones as $zoneid) {
|
|
$zone = new slaveZone(array('id' => intval($zoneid)));
|
|
$zone->loadZoneHead();
|
|
$head = $zone->getZoneHeadRaw();
|
|
if ($head['updated'] != 'del') {
|
|
$out[] = intval($zoneid);
|
|
}
|
|
}
|
|
} else {
|
|
$out = $this->szones;
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function isOwned($id, $type, $state = 'all') {
|
|
$zarr = array();
|
|
switch ($type) {
|
|
case 'master':
|
|
$zarr = $this->getMasters($state);
|
|
break;
|
|
case 'slave':
|
|
$zarr = $this->getSlaves($state);
|
|
break;
|
|
}
|
|
foreach ($zarr as $zid) {
|
|
if ($zid == $id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getName() {
|
|
return $this->data['username'];
|
|
}
|
|
|
|
public function getErr() {
|
|
return $this->err;
|
|
}
|
|
|
|
public function getFullName() {
|
|
return $this->data['realname'];
|
|
}
|
|
|
|
public function isAdmin() {
|
|
return $this->data['admin'];
|
|
}
|
|
|
|
public function getId() {
|
|
return $this->data['id'];
|
|
}
|
|
|
|
public function getPasswordHash() {
|
|
return $this->data['password'];
|
|
}
|
|
|
|
public function getUser() {
|
|
$arr = array();
|
|
foreach (array('id', 'username', 'realname', 'admin') as $key) {
|
|
$arr[$key] = $this->data[$key];
|
|
}
|
|
return $arr;
|
|
}
|
|
|
|
public function getAllusers() {
|
|
$out = array();
|
|
$res = $this->db->query("SELECT id, username, realname, admin FROM users ORDER BY realname");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
} else {
|
|
while ($row = $res->fetchRow()) {
|
|
$out[] = $row;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function loadUserById() {
|
|
$res = $this->db->query("SELECT * FROM users WHERE id = " . $this->data['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} elseif ($res->numRows() == 0) {
|
|
$this->err .= "User not found";
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$row = $res->fetchRow();
|
|
foreach ($row as $key => $value) {
|
|
switch ($key) {
|
|
case 'id':
|
|
$this->data[$key] = intval($value);
|
|
break;
|
|
case 'admin':
|
|
$this->data[$key] = ($value == 'yes');
|
|
break;
|
|
default:
|
|
$this->data[$key] = strval($value);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public function set($rname = NULL, $pass = NULL, $adm = NULL) {
|
|
if ((isset($pass)) || (isset($adm)) || (isset($rname))) {
|
|
$pstr = ((is_null($pass)) || (strlen($pass) != 32)) ? "" : "password = '" . $pass . "'";
|
|
$astr = ((is_null($adm)) || (($adm != 'yes') && ($adm != 'no'))) ? "" : "admin = '" . $adm . "'";
|
|
$rnstr = ((is_null($rname)) || ($rname == '')) ? "" : "realname = '" . $rname . "'";
|
|
$setstr = $pstr;
|
|
if ($astr > '') {
|
|
$setstr .= ($setstr != "") ? ", " . $astr : $astr;
|
|
}
|
|
if ($rnstr > '') {
|
|
$setstr .= ($setstr != "") ? ", " . $rnstr : $rnstr;
|
|
}
|
|
if ($setstr > "") {
|
|
$res = $this->db->query("UPDATE users SET " . $setstr . " WHERE id = " . $this->data['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return $this->loadUserById();
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
class masterRecord {
|
|
|
|
private $record = array(
|
|
'id' => NULL,
|
|
'zone' => NULL,
|
|
'host' => '',
|
|
'type' => '',
|
|
'pri' => 0,
|
|
'destination' => '',
|
|
'ttl' => 0,
|
|
);
|
|
private $db = NULL;
|
|
private $err = '';
|
|
|
|
public function __debugInfo() {
|
|
return array(
|
|
'record' => $this->record,
|
|
'err' => $this->err,
|
|
'db' => NULL,
|
|
);
|
|
}
|
|
|
|
public function __construct($param = NULL) {
|
|
global $db;
|
|
if (is_object($db)) {
|
|
$this->db = &$db;
|
|
}
|
|
if (!is_null($param)) {
|
|
return $this->setRecord($param);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function getId() {
|
|
return $this->record['id'];
|
|
}
|
|
|
|
private function fill_record($param) {
|
|
foreach ($param as $key => $value) {
|
|
switch ($key) {
|
|
case 'id':
|
|
case 'pri':
|
|
case 'ttl':
|
|
case 'zone':
|
|
$this->record[$key] = intval($value);
|
|
break;
|
|
case 'host':
|
|
$this->record[$key] = idnToHost($value);
|
|
break;
|
|
case 'type':
|
|
$this->record[$key] = strtoupper($value);
|
|
break;
|
|
case 'destination':
|
|
switch ($this->record['type']) {
|
|
case 'MX':
|
|
case 'CNAME':
|
|
case 'SRV':
|
|
case 'PTR':
|
|
case 'NS':
|
|
$this->record[$key] = idnToHost($value);
|
|
break;
|
|
default:
|
|
$this->record[$key] = strval($value);
|
|
}
|
|
break;
|
|
default:
|
|
$this->record[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function setRecord($param) {
|
|
if (is_string($param)) {
|
|
if (!$this->parseRecord($param)) {
|
|
return false;
|
|
}
|
|
} elseif (is_numeric($param)) {
|
|
$this->record['id'] = $param;
|
|
} elseif (is_array($param)) {
|
|
$this->fill_record($param);
|
|
} else {
|
|
ob_start();
|
|
var_dump($param);
|
|
$this->err .= "Unidentified parameter" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private function bind_time_format($value) {
|
|
if (preg_match(BIND_TIME_PATTERN, strtolower($value), $match)) {
|
|
$value = $match[1];
|
|
switch ($match[2]) {
|
|
case "s":
|
|
$multiplier = 1;
|
|
break;
|
|
case "m":
|
|
$multiplier = 60;
|
|
break;
|
|
case "h":
|
|
$multiplier = 3600;
|
|
break;
|
|
case "d":
|
|
$multiplier = 86400;
|
|
break;
|
|
case "w":
|
|
$multiplier = 604800;
|
|
break;
|
|
}
|
|
$value = $value*$multiplier;
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
private function parseRecord($buffer) {
|
|
if (preg_match(RECORD_PATTERN, $buffer, $match)) {
|
|
$this->record['host'] = $match[1];
|
|
if ($this->record['host'] == '') {
|
|
$this->record['host'] = '@';
|
|
}
|
|
$this->record['type'] = strtoupper($match[4]);
|
|
if (isset($match[2])) {
|
|
$this->record['ttl'] = intval($this->bind_time_format($match[2]));
|
|
} else {
|
|
$this->record['ttl'] = 0;
|
|
}
|
|
switch ($this->record['type']) {
|
|
case 'MX':
|
|
if (preg_match(MX_PATTERN, $match[5], $match)) {
|
|
$this->record['pri'] = intval($match[1]);
|
|
$this->record['destination'] = idnToHost($match[2]);
|
|
} else {
|
|
ob_start();
|
|
var_dump($buffer);
|
|
$this->err .= "MX cannot be parsed" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return NULL;
|
|
}
|
|
break;
|
|
case 'SRV':
|
|
case 'CNAME':
|
|
case 'NS':
|
|
case 'PTR':
|
|
$this->record['destination'] = idnToHost($match[5]);
|
|
break;
|
|
case 'TXT':
|
|
$this->record['destination'] = idnToHost(preg_replace('/(^"+|"+$|"+\s*"+)/msi', '', trim($match[5])));
|
|
break;
|
|
default:
|
|
$this->record['destination'] = $match[5];
|
|
}
|
|
return true;
|
|
} else {
|
|
ob_start();
|
|
var_dump($buffer);
|
|
$this->err .= "Record cannot be parsed" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function getRecordRaw() {
|
|
return $this->record;
|
|
}
|
|
|
|
public function getRecord() {
|
|
$out = $this->record;
|
|
$out['host'] = hostToIdn($out['host']);
|
|
switch ($out['type']) {
|
|
case 'MX':
|
|
case 'SRV':
|
|
case 'NS':
|
|
case 'CNAME':
|
|
case 'PTR':
|
|
$out['destination'] = hostToIdn($out['destination']);
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
private function is_identified() {
|
|
return (is_numeric($this->record['id']) && ($this->record['id'] > 0));
|
|
}
|
|
|
|
public function loadRecord() {
|
|
if ($this->is_identified()) {
|
|
$res = $this->db->query('SELECT * FROM records WHERE id =' . $this->record['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$row = $res->fetchRow();
|
|
if (is_array($row)) {
|
|
$this->fill_record($row);
|
|
return true;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
} else {
|
|
ob_start();
|
|
var_dump($this-record);
|
|
$this->err .= "Record is not identified" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function getErr() {
|
|
return $this->err;
|
|
}
|
|
|
|
private function is_complete() {
|
|
return (($this->record['zone'] > 0) &&
|
|
($this->record['type'] > '') &&
|
|
(
|
|
($this->record['host'] > '') ||
|
|
($this->record['destination'] > '')
|
|
));
|
|
}
|
|
|
|
private function find_record() {
|
|
$res = $this->db->query("SELECT id FROM records WHERE " .
|
|
"zone = " . $this->record['zone'] . " AND " .
|
|
"host = '" . $this->record['host'] . "' AND " .
|
|
"ttl = " . $this->record['ttl'] . " AND " .
|
|
"type = '" . $this->record['type'] . "' AND " .
|
|
"pri = " . $this->record['pri'] . " AND " .
|
|
"destination = '" . $this->record['destination'] . "'"
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return 0;
|
|
} else {
|
|
$value = $res->fetchRow();
|
|
return $value['id'];
|
|
}
|
|
}
|
|
|
|
public function saveRecord() {
|
|
if ($this->is_complete()) {
|
|
if ($this->record['host'] == '') {
|
|
$this->record['host'] = '@';
|
|
} elseif ($this->record['destination'] == '') {
|
|
$this->record['destination'] = '@';
|
|
}
|
|
if ($this->record['type'] == 'MX') {
|
|
$this->record['pri'] = ($this->record['pri'] == 0) ? 10 : $this->record['pri'];
|
|
} else {
|
|
$this->record['pri'] = 0;
|
|
}
|
|
if ((is_numeric($this->record['id'])) && ($this->record['id'] > 0)) {
|
|
$res = $this->db->query("UPDATE records SET " .
|
|
"zone = " . $this->record['zone'] . ", " .
|
|
"host = '" . $this->record['host'] . "', " .
|
|
"ttl = " . $this->record['ttl'] . ", " .
|
|
"type = '" . $this->record['type'] . "', " .
|
|
"pri = " . $this->record['pri'] . ", " .
|
|
"destination = '" . $this->record['destination'] .
|
|
"' WHERE id = " . $this->record['id']
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
return $this->loadRecord();
|
|
}
|
|
} else {
|
|
$id = $this->find_record();
|
|
if ($id > 0) {
|
|
$this->record['id'] = $id;
|
|
return true;
|
|
} else {
|
|
$res = $this->db->query("INSERT INTO records (zone, host, ttl, type, pri, destination) VALUES (" .
|
|
$this->record['zone'] . ", '" .
|
|
$this->record['host'] . "', " .
|
|
$this->record['ttl'] . ", '" .
|
|
$this->record['type'] . "', " .
|
|
$this->record['pri'] . ", '" .
|
|
$this->record['destination'] . "')"
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$id = $this->find_record();
|
|
if ($id > 0) {
|
|
$this->record['id'] = $id;
|
|
return true;
|
|
} else {
|
|
if ($this->err == '') {
|
|
ob_start();
|
|
var_dump($this->record);
|
|
$this->err .= "Unknown write error" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
ob_start();
|
|
var_dump($this->record);
|
|
$this->err .= "Record is not complete" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function eraseRecord() {
|
|
if ($this->is_identified()) {
|
|
$res = $this->db->query("DELETE FROM records WHERE id = " . $this->record['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
} else {
|
|
ob_start();
|
|
var_dump($this->record);
|
|
$this->err .= "Record is not set" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
class slaveZone {
|
|
private $head = array(
|
|
'id' => NULL,
|
|
'name' => '',
|
|
'master' => '',
|
|
'owner' => 0,
|
|
'updated' => 'no',
|
|
'valid' => 'may',
|
|
);
|
|
private $db = NULL;
|
|
private $err = '';
|
|
|
|
public function __debugInfo() {
|
|
return array(
|
|
'head' => $this->head,
|
|
'err' => $this->err,
|
|
'db' => NULL,
|
|
);
|
|
}
|
|
|
|
public function __construct($param = NULL) {
|
|
global $db;
|
|
if (is_object($db)) {
|
|
$this->db = &$db;
|
|
}
|
|
if (!is_null($param)) {
|
|
return $this->setZoneHead($param);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function setZoneHead($param) {
|
|
if (is_string($param)) {
|
|
$this->head['name'] = idnToHost($param);
|
|
} elseif (is_numeric($param)) {
|
|
$this->head['id'] = $param;
|
|
} elseif (is_array($param)) {
|
|
$this->fill_head($param);
|
|
} else {
|
|
ob_start();
|
|
var_dump($param);
|
|
$this->err .= "Unidentified parameter" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private function fill_head($param) {
|
|
foreach ($param as $key => $value) {
|
|
switch ($key) {
|
|
case 'id':
|
|
case 'owner':
|
|
$this->head[$key] = intval($value);
|
|
break;
|
|
case 'master':
|
|
case 'name':
|
|
$this->head[$key] = idnToHost($value);
|
|
break;
|
|
default:
|
|
$this->head[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
private function is_identified() {
|
|
return ((isset($this->head['id'])) || ($this->head['name'] > ''));
|
|
}
|
|
|
|
private function notIdent($complete = false) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$head = ob_get_clean();
|
|
if ($complete) {
|
|
$this->err .= "Zone is not complete" . "\n" . $head;
|
|
error_log($this->err);
|
|
} else {
|
|
$this->err .= "Unidentified zone" . "\n" . $head;
|
|
error_log($this->err);
|
|
}
|
|
}
|
|
|
|
public function loadZoneHead() {
|
|
if ($this->is_identified()) {
|
|
$where = ' WHERE ';
|
|
if (isset($this->head['id'])) {
|
|
$where .= "id = " . $this->head['id'];
|
|
} else {
|
|
$where .= "name = '" . $this->head['name'] . "'";
|
|
}
|
|
$res = $this->db->query("SELECT * FROM slave_zones" . $where);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$row = $res->fetchRow();
|
|
if (is_array($row)) {
|
|
$this->fill_head($row);
|
|
return true;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
} else {
|
|
notIdent();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function eraseZone() {
|
|
if (!$this->is_identified()) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$this->err .= "Zone head is not complete" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$where = ' WHERE ';
|
|
if ($this->head['id'] >>= 0) {
|
|
$where .= "id = " . $this->head['id'];
|
|
} else {
|
|
$where .= "name = '" . $this->head['name'] . "'";
|
|
}
|
|
$res = $this->db->query("DELETE FROM slave_zones " . $where);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$this->clearZone();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public function clearZone() {
|
|
|
|
$hd = array(
|
|
'id' => NULL,
|
|
'name' => '',
|
|
'master' => '',
|
|
'owner' => 0,
|
|
'updated' => 'no',
|
|
'valid' => 'may',
|
|
);
|
|
}
|
|
|
|
public function dumpZone($dig) {
|
|
if ($this->is_identified()) {
|
|
if (!$this->is_complete()) {
|
|
$this->loadZoneHead();
|
|
}
|
|
$cmd = $dig . " axfr @" . $this->head['master'] . " " . $this->head['name'] . ". +time=2 +tries=2 +retry=1 2>/dev/null";
|
|
unset($coutput);
|
|
exec($cmd, $coutput, $exit);
|
|
$out = '';
|
|
foreach ($coutput as $line) {
|
|
$val = preg_replace('/(^;.*$|\r|\n)/', '', $line);
|
|
$out .= ($val > '') ? $val . "\n" : '';
|
|
}
|
|
return $out;
|
|
} else {
|
|
$this->err .= "Zone identification failed\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function validateZone($dig) {
|
|
$out = $this->dumpZone($dig);
|
|
if ((isset($out)) && ($out > '')) {
|
|
return true;
|
|
} elseif (isset($out)) {
|
|
$err = "Zone transfer failed\n";
|
|
error_log($err);
|
|
$this->err .= $err;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getZoneHeadRaw() {
|
|
return $this->head;
|
|
}
|
|
|
|
public function getZoneHead() {
|
|
$out = array();
|
|
foreach ($this->head as $key => $value) {
|
|
switch ($key) {
|
|
case 'master':
|
|
case 'name':
|
|
$out[$key] = hostToIdn($value);
|
|
break;
|
|
default:
|
|
$out[$key] = $value;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function getErr() {
|
|
return $this->err;
|
|
}
|
|
|
|
private function is_complete() {
|
|
return (($this->head['name'] > '') && ($this->head['master'] > '') && ($this->head['owner'] >0));
|
|
}
|
|
|
|
public function doCommit() {
|
|
if (!$this->is_complete()) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$this->err .= "Zone head is not complete" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$res = $this->db->query("UPDATE slave_zones SET " .
|
|
"updated = 'no' " .
|
|
"WHERE id = " . $this->head['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return $this->loadZoneHead();
|
|
}
|
|
|
|
public function saveZoneHead() {
|
|
if (!$this->is_complete()) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$this->err .= "Zone head is not complete" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
if (!isset($this->head['id'])) {
|
|
$vld = (isset($this->head['valid'])) ? $this->head['valid'] : 'may';
|
|
$upd = 'yes';
|
|
$res = $this->db->query("INSERT INTO slave_zones " .
|
|
"(name, master, valid, owner, updated) " .
|
|
"VALUES ('" . $this->head['name'] .
|
|
"', '" . $this->head['master'] .
|
|
"', '" . $vld .
|
|
"', " . $this->head['owner'] .
|
|
", '" . $upd .
|
|
"')");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return $this->loadZoneHead();
|
|
} else {
|
|
$vld = (($this->head['valid'] == 'yes') || ($this->head['valid'] == 'no')) ? $this->head['valid'] : 'may';
|
|
$upd = ((isset($this->head['updated'])) && ($this->head['updated'] != 'del')) ? 'yes' : $this->head['updated'];
|
|
$res = $this->db->query("UPDATE slave_zones SET " .
|
|
"name = '" . $this->head['name'] . "', " .
|
|
"master = '" . $this->head['master'] . "', " .
|
|
"valid = '" . $vld . "', " .
|
|
"owner = " . $this->head['owner'] . ", " .
|
|
"updated = '" . $upd . "' " .
|
|
"WHERE id = " . $this->head['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return $this->loadZoneHead();
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class masterZone {
|
|
|
|
private $head = array(
|
|
'id' => NULL,
|
|
'name' => '',
|
|
'pri_dns' => '',
|
|
'sec_dns' => '',
|
|
'serial' => 0,
|
|
'refresh' => 0,
|
|
'retry' => 0,
|
|
'expire' => 0,
|
|
'ttl' => 0,
|
|
'valid' => 'may',
|
|
'owner' => 0,
|
|
'updated' => 'no',
|
|
'secured' => 'no',
|
|
);
|
|
private $db = NULL;
|
|
private $records = array();
|
|
private $err = '';
|
|
private $isloaded = FALSE;
|
|
private $msg = '';
|
|
|
|
public function __debugInfo() {
|
|
return array(
|
|
'head' => $this->head,
|
|
'records' => $this->records,
|
|
'err' => $this->err,
|
|
'isloaded' => $this->isloaded,
|
|
'msg' => $this->msg,
|
|
'db' => NULL,
|
|
);
|
|
}
|
|
|
|
public function __construct($param = NULL) {
|
|
global $db;
|
|
if (is_object($db)) {
|
|
$this->db = &$db;
|
|
}
|
|
if (!is_null($param)) {
|
|
return $this->setZoneHead($param);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function getZoneHeadRaw() {
|
|
return $this->head;
|
|
}
|
|
|
|
private function tab_to_space($line, $tab = 8, $nbsp = FALSE) {
|
|
while (($t = mb_strpos($line,"\t")) !== FALSE) {
|
|
$preTab = $t ? mb_substr($line, 0, $t) : '';
|
|
$line = $preTab . str_repeat($nbsp?chr(7):' ', $tab-(mb_strlen($preTab)%$tab)) . mb_substr($line, $t+1);
|
|
}
|
|
return $nbsp ? str_replace($nbsp?chr(7):' ', ' ', $line) : $line;
|
|
}
|
|
|
|
private function split_text_record($line, $length = 76) {
|
|
$line = $this->tab_to_space($line);
|
|
$slices = $line;
|
|
if(strlen($line) > $length) {
|
|
$pos = stripos($line, "\"");
|
|
if ($pos !== false) {
|
|
if($pos>$length-2) {
|
|
$slices = substr($line, 0, $pos) . "(\n";
|
|
$line = $this->tab_to_space("\t\t\t\t\t" . substr($line,$pos) . " )");
|
|
} else {
|
|
$allline = substr($line,0,$pos) . "( " . substr($line,$pos) . " )";
|
|
$slices = substr($allline,0,$length-1) . "\"\n";
|
|
$line = $this->tab_to_space("\t\t\t\t\t\t \"" . substr($allline,$length-1));
|
|
}
|
|
while (strlen($line) > $length) {
|
|
$slices .= substr($line, 0, $length-1) . "\"\n";
|
|
$line = $this->tab_to_space("\t\t\t\t\t\t \"" . substr($line,$length-1));
|
|
}
|
|
if(strlen($line) > 0) { $slices .= $line; }
|
|
}
|
|
}
|
|
return $slices;
|
|
}
|
|
|
|
private function prettyer($name,$ttl,$type,$pri,$target) {
|
|
$line = str_pad($name, 31) . " " . $ttl . "\t" . "IN " . "$type";
|
|
if (strlen($type)<5) {
|
|
$line .= "\t";
|
|
}
|
|
$line .= " " . $pri;
|
|
if (strlen($pri) > 0){
|
|
$line .= " ";
|
|
}
|
|
if ($type == "TXT"){
|
|
$target = "\"" . $target . "\"";
|
|
}
|
|
return $this->split_text_record($line . $target, 116);
|
|
}
|
|
|
|
|
|
public function getConf($hostmaster) {
|
|
$out = "\$TTL " . $this->head['ttl'] . "\n";
|
|
$out .= $this->prettyer("@", "", "SOA", "", $this->head['pri_dns'] . ". " . $hostmaster . ".") . " (\n";
|
|
$out .= $this->tab_to_space("\t\t\t\t\t" . $this->head['serial'] . "\t; Serial\n") .
|
|
$this->tab_to_space("\t\t\t\t\t" . $this->head['refresh'] . "\t\t; Refresh\n") .
|
|
$this->tab_to_space("\t\t\t\t\t" . $this->head['retry'] . "\t\t; Retry\n") .
|
|
$this->tab_to_space("\t\t\t\t\t" . $this->head['expire'] . "\t\t; Expire\n") .
|
|
$this->tab_to_space("\t\t\t\t\t" . $this->head['ttl'] . ")\t\t; Negative Cache TTL\n;\n");
|
|
foreach (array('pri_dns', 'sec_dns') as $ns) {
|
|
$out .= ($this->head[$ns] != '') ? $this->prettyer("@",'','NS','',$this->head[$ns] . ".") . "\n" : "";
|
|
}
|
|
foreach ($this->getRecordsRaw() as $record) {
|
|
$row = $record->getRecordRaw();
|
|
$pri = ($row['type'] == 'MX') ? $row['pri'] : '';
|
|
$ttl = ($row['ttl'] > 0) ? $row['ttl'] : '';
|
|
$out .= $this->prettyer($row['host'],$ttl,$row['type'],$pri,$row['destination']) . "\n";
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function getMsg() {
|
|
return $this->msg;
|
|
}
|
|
|
|
public function writeZone($file, $hostmaster) {
|
|
if (!$this->isloaded) {
|
|
if (!$this->loadZone()) {
|
|
$this->err .= "Unable to load zone\n";
|
|
error_log ($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
$zonedata = $this->getConf($hostmaster);
|
|
$fh = fopen($file, "w");
|
|
fwrite($fh, $zonedata . "\n");
|
|
fclose($fh);
|
|
return true;
|
|
}
|
|
|
|
public function validateZone($file, $hostmaster, $checkzonecmd) {
|
|
if ($this->writeZone($file, $hostmaster)) {
|
|
$cmd = $checkzonecmd . " -i local " . $this->head['name'] . " " . $file . " 2>/dev/stdout";
|
|
unset($coutput);
|
|
exec($cmd, $coutput, $exit);
|
|
$rows = sizeof($coutput);
|
|
$return[0] = ($coutput[$rows-1] == 'OK');
|
|
if (($return[0]) && ($exit == 0)) {
|
|
$rows--;
|
|
$return[1] = '';
|
|
} else {
|
|
$return[1] = 'Exitcode: ' . $exit . "\n";
|
|
}
|
|
$return[1] .= implode("<br />", $coutput);
|
|
if (!$return[0]) {
|
|
$this->err = $return[1];
|
|
error_log("ERROR\nCMD: " . $cmd . "\nExit: " . $exit . "\n" . implode("\n", $coutput));
|
|
$this->head['valid'] = 'no';
|
|
} else {
|
|
$this->head['valid'] = 'yes';
|
|
}
|
|
$this->saveZoneHead();
|
|
return $return;
|
|
}
|
|
$log = "Zone cannot be validate\n";
|
|
$this->err .= $log;
|
|
error_log($log);
|
|
return array(false,"Problem in prerequisites\n");
|
|
}
|
|
|
|
public function getZoneHead() {
|
|
$out = array();
|
|
foreach ($this->head as $key => $value) {
|
|
switch ($key) {
|
|
case 'pri_dns':
|
|
case 'sec_dns':
|
|
case 'name':
|
|
$out[$key] = hostToIdn($value);
|
|
break;
|
|
default:
|
|
$out[$key] = $value;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function getErr() {
|
|
return $this->err;
|
|
}
|
|
|
|
public function getId() {
|
|
return $this->head['id'];
|
|
}
|
|
|
|
private function fill_head($param) {
|
|
foreach ($param as $key => $value) {
|
|
switch ($key) {
|
|
case 'id':
|
|
case 'serial':
|
|
case 'retry':
|
|
case 'refresh':
|
|
case 'expire':
|
|
case 'ttl':
|
|
case 'owner':
|
|
$this->head[$key] = intval($value);
|
|
break;
|
|
case 'pri_dns':
|
|
case 'sec_dns':
|
|
case 'name':
|
|
$this->head[$key] = idnToHost($value);
|
|
break;
|
|
default:
|
|
$this->head[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function setZoneHead($param) {
|
|
if (is_string($param)) {
|
|
$this->head['name'] = idnToHost($param);
|
|
} elseif (is_numeric($param)) {
|
|
$this->head['id'] = $param;
|
|
} elseif (is_array($param)) {
|
|
$this->fill_head($param);
|
|
} else {
|
|
ob_start();
|
|
var_dump($param);
|
|
$this->err .= "Unidentified parameter" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private function is_identified() {
|
|
return ((isset($this->head['id'])) || ($this->head['name'] > ''));
|
|
}
|
|
|
|
private function notIdent($complete = false) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$head = ob_get_clean();
|
|
if ($complete) {
|
|
$this->err .= "Zone is not complete" . "\n" . $head;
|
|
error_log($this->err);
|
|
} else {
|
|
$this->err .= "Unidentified zone" . "\n" . $head;
|
|
error_log($this->err);
|
|
}
|
|
}
|
|
|
|
public function loadZoneHead() {
|
|
if ($this->is_identified()) {
|
|
$where = ' WHERE ';
|
|
if (isset($this->head['id'])) {
|
|
$where .= "id = " . $this->head['id'];
|
|
} else {
|
|
$where .= "name = '" . $this->head['name'] . "'";
|
|
}
|
|
$res = $this->db->query("SELECT * FROM zones" . $where);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$row = $res->fetchRow();
|
|
if (is_array($row)) {
|
|
$this->fill_head($row);
|
|
return true;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
} else {
|
|
$this->notIdent();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function loadZoneRecords() {
|
|
if ($this->is_complete()) {
|
|
$res = $this->db->query("SELECT id FROM records WHERE zone = " . $this->head['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
while ($row = $res->fetchRow()) {
|
|
$id = intval($row['id']);
|
|
$this->records[$id] = new masterRecord($row);
|
|
if (!$this->records[$id]->loadRecord()) {
|
|
$this->err .= $this->records[$id]->getErr();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
} else {
|
|
notIdent(true);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function eraseRecord($id) {
|
|
$found = false;
|
|
foreach ($this->records as $key => $entry) {
|
|
if ($key == $id) {
|
|
$found = true;
|
|
if (!$entry->eraseRecord()) {
|
|
$this->err .= $entry->getErr();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if ($found) {
|
|
$this->head['valid'] = 'may';
|
|
$this->saveZoneHead();
|
|
$this->records = array();
|
|
$this->loadZoneRecords();
|
|
return true;
|
|
}
|
|
$this->err .= "Record id not found" . "\n" . $id;
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
|
|
public function addRecord($param = NULL) {
|
|
if ((is_numeric($this->head['id'])) && ($this->head['id'] > 0)) {
|
|
$nrec = new masterRecord($param);
|
|
$nrec->setRecord(array( 'zone' => $this->head['id']));
|
|
$urec = $nrec->getRecord();
|
|
if ((
|
|
($urec['host'] > '') ||
|
|
($urec['destination'] > '')
|
|
) &&
|
|
($urec['host'] != $urec['destination']) &&
|
|
($urec['type'] > '')) {
|
|
$this->records[] = $nrec;
|
|
$this->head['valid'] = 'may';
|
|
return true;
|
|
} else {
|
|
ob_start();
|
|
var_dump($param);
|
|
$this->err .= "Record is empty" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
} else {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$this->err .= "Zone has not defined yet" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private function bind_time_format($value) {
|
|
if (preg_match(BIND_TIME_PATTERN, strtolower($value), $match)) {
|
|
$value = $match[1];
|
|
switch ($match[2]) {
|
|
case "s":
|
|
$multiplier = 1;
|
|
break;
|
|
case "m":
|
|
$multiplier = 60;
|
|
break;
|
|
case "h":
|
|
$multiplier = 3600;
|
|
break;
|
|
case "d":
|
|
$multiplier = 86400;
|
|
break;
|
|
case "w":
|
|
$multiplier = 604800;
|
|
break;
|
|
}
|
|
$value = $value*$multiplier;
|
|
}
|
|
return $value;
|
|
}
|
|
|
|
|
|
public function parseZone($rows, $zonename, $owner = 1) {
|
|
if (!is_array($rows)) {
|
|
ob_start();
|
|
var_dump($rows);
|
|
$this->err .= "Zone can be parsed from an array only" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$this->clearZone();
|
|
$this->head['name'] = idnToHost($zonename);
|
|
$soafound = false;
|
|
$soabegins = false;
|
|
$soadata = '';
|
|
$recrow = '';
|
|
foreach ($rows as $row) {
|
|
$row = preg_replace(COMMENT_PATTERN, ' ', trim($row));
|
|
$row = ($row == " ") ? '' : $row;
|
|
if ($soafound === false) {
|
|
if (preg_match(ORIGIN_PATTERN, $row, $match)) {
|
|
$zone = strtolower($match[1]);
|
|
if ($zone != $expectedname) {
|
|
$this->clearZone();
|
|
$this->err .= "Given zone not matches with the expected (" . $zone . "<=>" . $expectedzone . ")";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
if (preg_match(SOA_BEGINS_PATTERN, $row, $match)) {
|
|
$soabegins = true;
|
|
}
|
|
if ($soabegins) {
|
|
$soadata .= $row;
|
|
}
|
|
if (preg_match(FULL_SOA_PATTERN, $soadata, $match)) {
|
|
$prins = $match[3];
|
|
if(preg_match(TIMES_PATTERN, $match[5], $match2)) {
|
|
$soafound = true;
|
|
$serial = $match2[1];
|
|
$refresh = $this->bind_time_format($match2[2]);
|
|
$retry = $this->bind_time_format($match2[3]);
|
|
$expire = $this->bind_time_format($match2[4]);
|
|
$ttl = $this->bind_time_format($match2[5]);
|
|
if (($this->setZoneHead(
|
|
array(
|
|
'serial' => intval($serial),
|
|
'refresh' => intval($refresh),
|
|
'retry' => intval($retry),
|
|
'expire' => intval($expire),
|
|
'ttl' => intval($ttl),
|
|
'owner' => intval($owner),
|
|
'pri_dns' => strval($prins),
|
|
'sec_dns' => '##EMPTY##',
|
|
))) && ($this->saveZoneHead())) {
|
|
$soafound = true;
|
|
} else {
|
|
$this->err .= "Head cannot be set" . "\n" . $soadata;
|
|
$this->clearZone();
|
|
return false;
|
|
}
|
|
} else {
|
|
$this->err .= "SOA record cannot be parsed" . "\n" . $soadata;
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if ($recrow != '') {
|
|
$rowpart = trim($row);
|
|
$recrow .= $rowpart;
|
|
$end = strpos($recrow, ')');
|
|
if ($end > 0) {
|
|
$recrow = substr($recrow, 0, $end);
|
|
$recd = new masterRecord(array('zone' => $this->head['id']));
|
|
if ($recd->setRecord($recrow)) {
|
|
$parsed = $recd->getRecordRaw();
|
|
if (
|
|
($this->head['sec_dns'] == '##EMPTY##') &&
|
|
($parsed['type'] == 'NS') &&
|
|
($parsed['destination'] != $self->head['pri_dns']) &&
|
|
(
|
|
($parsed['host'] == '@') ||
|
|
($parsed['host'] == '')
|
|
)
|
|
) {
|
|
$self->head['sec_dns'] == $parsed['destination'];
|
|
}
|
|
$this->records[] = $recd;
|
|
$recrow = '';
|
|
} else {
|
|
$this->err .= $recd->getErr();
|
|
return false;
|
|
}
|
|
}
|
|
} elseif ($row > '') {
|
|
$end = strpos($row, '(');
|
|
if ($end > 0) {
|
|
$row = preg_replace('/\(/', '', $row);
|
|
$recrow = $row;
|
|
}
|
|
$end = strpos($recrow, ')');
|
|
if ($end > 0) {
|
|
$recrow = substr($recrow, 0, $end);
|
|
$recd = new masterRecord(array('zone' => $this->head['id']));
|
|
if ($recd->setRecord($recrow, $this->head['name'])) {
|
|
$this->records[] = $recd;
|
|
$recrow = '';
|
|
} else {
|
|
$this->err .= $recd->getErr();
|
|
return false;
|
|
}
|
|
} elseif ($recrow == '') {
|
|
$recd = new masterRecord(array('zone' => $this->head['id']));
|
|
if ($recd->setRecord($row, $this->head['name'])) {
|
|
$this->records[] = $recd;
|
|
} else {
|
|
$this->err .= $recd->getErr();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$recs = array();
|
|
foreach ($this->records as $each) {
|
|
$rhd = $each->getRecordRaw();
|
|
if (($rhd['type'] == 'NS')) {
|
|
if (($rhd['host'] == '@') && ($rhd['destination'] == $this->head['pri_dns'] . '.')) {
|
|
continue;
|
|
} elseif ($rhd['host'] == '@') {
|
|
$this->head['sec_dns'] = ($this->head['sec_dns'] == '##EMPTY##') ? preg_replace('/\.$/', '', $rhd['destination']) : $this->head['sec_dns'];
|
|
continue;
|
|
}
|
|
}
|
|
$recs[] = $each;
|
|
}
|
|
$this->records = $recs;
|
|
$this->saveZone();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function loadZone() {
|
|
$ret = $this->loadZoneHead();
|
|
if (($ret) && (!is_null($this->head['id']))) {
|
|
$this->isloaded = $this->loadZoneRecords();
|
|
return $this->isloaded;
|
|
} elseif (is_null($ret)) {
|
|
return NULL;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function getRecordsRaw() {
|
|
return $this->records;
|
|
}
|
|
|
|
public function getRecords($ordered = false) {
|
|
$out = array();
|
|
foreach ($this->records as $key => $each) {
|
|
if ($ordered) {
|
|
$out[] = $each->getRecord();
|
|
} else {
|
|
$out[$key] = $each->getRecord();
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
public function getZoneRaw() {
|
|
$out = array();
|
|
$out[] = $this->getZoneHeadRaw();
|
|
$out[] = $this->getRecordsRaw();
|
|
}
|
|
|
|
public function getZone() {
|
|
$out = array();
|
|
$out[] = $this->getZoneHead();
|
|
$out[] = $this->getRecords();
|
|
return $out;
|
|
}
|
|
|
|
public function refresh_secure($zonedir) {
|
|
|
|
$files = glob($zonedir . "{K,dsset-,}" . $this->head['name'] . ".{*private,*key,krf,}", GLOB_BRACE);
|
|
$hit = 0;
|
|
$names = array();
|
|
foreach ($files as $key => $file) {
|
|
$name = basename($file);
|
|
switch ($name) {
|
|
case $this->head['name'] . '.krf':
|
|
case 'dsset-' . $this->head['name'] . '.':
|
|
$hit++;
|
|
break;
|
|
default:
|
|
$pattern = '/^K' . $this->head['name'] . '\.\+\d+\+\d+\.(private|key)/';
|
|
preg_match($pattern, $name, $match);
|
|
$hit += ($match[0] == $name) ? 1 : 0;
|
|
}
|
|
$names[$key] = $name;
|
|
}
|
|
$filesok = ($hit >= 8);
|
|
$res = $this->db->query("SELECT id, dsset, krf FROM dnssec_zones WHERE zone = " . $this->head['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$dbok = ($res->numRows() == 1);
|
|
if ($dbok) {
|
|
$sel = $res->fetchRow();
|
|
$id = $sel['id'];
|
|
$dsset = $sel['dsset'];
|
|
$krf = $sel['krf'];
|
|
if ($filesok) {
|
|
$dssetf = file_get_contents($zonedir . "dsset-" . $this->head['name'] . '.');
|
|
$krff = file_get_contents($zonedir . $this->head['name'] . '.krf');
|
|
if (($dsset != $dssetf) || ($krf != $krff)) {
|
|
$res = $this->db->query("UPDATE dnssec_zones SET " .
|
|
"dsset = '" . $dssetf . "', " .
|
|
"krf = '" . $krff . "' WHERE id = " . $id
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
$skeys = array();
|
|
$keys = array();
|
|
foreach ($names as $name) {
|
|
$bn = basename($name, '.key');
|
|
if ($bn . '.key' == $name) {
|
|
$keys[] = $bn;
|
|
$skeys[] = "'" . $bn . "'";
|
|
}
|
|
}
|
|
$res = $this->db->query("UPDATE dnssec_keys SET archive = 'yes' WHERE " .
|
|
"dszone = " . $id . " AND " .
|
|
"archive = 'no'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$res = $this->db->query("UPDATE dnssec_keys SET archive = 'no' WHERE " .
|
|
"dszone = " . $id . " AND " .
|
|
"filename IN (" . implode(",", $skeys) . ")");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
foreach ($keys as $keyf) {
|
|
$res = $this->db->query("SELECT id, fkey, fprivate FROM dnssec_keys WHERE filename = '" . $keyf . "' AND dszone = " . $id . " AND archive = 'no'");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$kfile = file_get_contents($zonedir . $keyf . '.key');
|
|
$pfile = file_get_contents($zonedir . $keyf . '.private');
|
|
if ($res->numRows() == 0) {
|
|
$this->db->query("INSERT INTO dnssec_keys (dszone, filename, fkey, fprivate, archive) VALUES ('" . $id . "','" .
|
|
$keyf . "', '" . $kfile . "', '" .
|
|
$pfile . "', 'no');"
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
} else {
|
|
$row = $res->fetchRow();
|
|
if (($kfile != $row['fkey']) || ($pfile != $row['fprivate'])) {
|
|
$res = $this->db->query("UPDATE dnssec_keys SET " .
|
|
"fkey = '" . $kfile . "', " .
|
|
"fprivate = '" . $pfile .
|
|
"' WHERE dszone = " . $id .
|
|
" AND filename = '" . $keyf .
|
|
"' AND archive = 'no'"
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
$fh = fopen($zonedir . "dsset-" . $this->head['name'] . ".", 'w');
|
|
fwrite($fh, $sel['dsset']);
|
|
fclose($fh);
|
|
$fh = fopen($zonedir . $this->head['name'] . ".krf", 'w');
|
|
fwrite($fh, $sel['krf'] . "\n\n");
|
|
fclose($fh);
|
|
$id = $sel['id'];
|
|
$res = $this->db->query("SELECT * FROM dnssec_keys WHERE " .
|
|
" dszone = " . $id . " AND " .
|
|
" archive = 'no'"
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
if ($res->numRows() < 3) {
|
|
$this->err .= "Missing key records\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
while ($rec = $res->fetchRow()) {
|
|
foreach (array('private', 'key') as $ext) {
|
|
$fh = fopen($zonedir . $rec['filename'] . "." . $ext, 'w');
|
|
fwrite($fh, $rec['f' . $ext] . "\n");
|
|
fclose($fh);
|
|
}
|
|
}
|
|
}
|
|
} elseif ($filesok) {
|
|
$dsset = '';
|
|
$krf = '';
|
|
$keyset = array();
|
|
foreach ($names as $name) {
|
|
switch ($name) {
|
|
case 'dsset-' . $this->head['name'] . '.':
|
|
$dsset = file_get_contents($zonedir . $name);
|
|
break;
|
|
case $this->head['name'] . '.krf':
|
|
$krf = file_get_contents($zonedir . $name);
|
|
break;
|
|
default:
|
|
$ext = (basename($name, '.key') == $name) ? 'private' : 'key';
|
|
$base = basename($name, '.' . $ext);
|
|
$keyset[$base][$ext] = file_get_contents($zonedir . $name);
|
|
}
|
|
}
|
|
if (($krf == '') || ($dsset == '')) {
|
|
$this->err .= "Incomplete DSSET\n";
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$res = $this->db->query("INSERT INTO dnssec_zones (zone, krf, dsset) VALUES ('" .
|
|
$this->head['id'] . "', '" .
|
|
$krf . "', '" .
|
|
$dsset . "')"
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$res = $this->db->query("SELECT id FROM dnssec_zones WHERE zone = " . $this->head['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$rec = $res->fetchRow();
|
|
$id = $rec['id'];
|
|
$ok = 0;
|
|
foreach ($keyset as $name => $arr) {
|
|
$key = $arr['key'];
|
|
$private = $arr['private'];
|
|
if (($key == '') || ($private == '')) {
|
|
$this->err .= "Incomplete KEYSET\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$res = $this->db->query("INSERT INTO dnssec_keys (dszone, filename, fkey, fprivate, archive) VALUES (" .
|
|
$id . ", '" .
|
|
$name . "', '" .
|
|
$key . "', '" .
|
|
$private . "', 'no')"
|
|
);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$ok++;
|
|
}
|
|
if ($ok < 3) {
|
|
$this->err .= "Not enough KEYSET\n";
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public function doSecure($zonedir, $zonesigner, $rollinit, $rollerconf) {
|
|
if (!$this->is_complete()) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$this->err .= "Zone head is not complete" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$err = $this->err;
|
|
$param = (($this->refresh_secure($zonedir)) && ($err == $this->err)) ? '' : ' -genkeys -usensec3';
|
|
if ($err != $this->err) return false;
|
|
$cmd = $zonesigner . $param . " -zone " . $this->head['name'] . " " . $zonedir . $this->head['name'] . " 2>/dev/stdout";
|
|
unset($coutput);
|
|
$currpath = getcwd();
|
|
chdir($zonedir);
|
|
exec($cmd, $coutput, $signexit);
|
|
chdir($currpath);
|
|
if ($signexit != 0) {
|
|
$this->err .= "Zonesigner error (" . $signexit . "):\n" . implode("\n",$coutput);
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$this->msg .= "Zonesigner output (" . $signexit . "):\n " . implode("\n ",$coutput) . "\n";
|
|
}
|
|
if (!$this->refresh_secure($zonedir)) return false;
|
|
$rollf = file($rollerconf, FILE_IGNORE_NEW_LINES );
|
|
$noroll = false;
|
|
foreach ($rollf as $row) {
|
|
preg_match('/^\s*roll\s+"' . $this->head['name'] . '"\s*/', $row, $match);
|
|
$noroll = (isset($match[0]) && ($row == $match[0]));
|
|
if ($noroll) break;
|
|
}
|
|
if (!$noroll) {
|
|
$cmd = $rollinit . " " . $this->head['name'] .
|
|
" -zone " . $zonedir . $this->head['name'] . '.signed' .
|
|
" -keyrec " . $zonedir . $this->head['name'] . ".krf " .
|
|
" -directory " . $zonedir . " 2>/dev/stdout";
|
|
unset($coutput);
|
|
exec($cmd, $coutput, $exit);
|
|
if ($exit != 0) {
|
|
$this->err .= "Rollerd error(" . $exit . "):\n" . implode("\n ",$coutput) . "\n";
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$fh = fopen($rollerconf, "a+");
|
|
fwrite($fh, "\n# rollinit config for zone " . hostToIdn($this->head['name']) . ":\n" . implode("\n", $coutput) . "\n");
|
|
fclose($fh);
|
|
$this->msg .= "\n Rollerd for zone " . hostToIdn($this->head['name']) . " is configured now\n";
|
|
}
|
|
} else {
|
|
$this->msg .= "\n Rollerd for zone " . hostToIdn($this->head['name']) . " has already set\n";
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private function is_complete() {
|
|
return (($this->head['name'] > '') &&
|
|
($this->head['pri_dns'] > '') &&
|
|
($this->head['sec_dns'] > '') &&
|
|
($this->head['refresh'] > 0) &&
|
|
($this->head['retry'] > 0) &&
|
|
($this->head['expire'] > 0) &&
|
|
($this->head['ttl'] > 0) &&
|
|
($this->head['owner'] > 0));
|
|
}
|
|
|
|
public function doCommit() {
|
|
if (!$this->is_complete()) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$this->err .= "Zone head is not complete" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$res = $this->db->query("UPDATE zones SET " .
|
|
"updated = 'no' " .
|
|
"WHERE id = " . $this->head['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return $this->loadZoneHead();
|
|
}
|
|
|
|
public function saveZoneHead() {
|
|
if (!$this->is_complete()) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$this->err .= "Zone head is not complete" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
if (!isset($this->head['id'])) {
|
|
$srl =intval(date('Ymd') . '01');
|
|
$vld = (isset($this->head['valid'])) ? $this->head['valid'] : 'may';
|
|
$upd = 'yes';
|
|
$res = $this->db->query("INSERT INTO zones " .
|
|
"(name, pri_dns, sec_dns, serial, refresh, retry, expire, ttl, valid, owner, updated, secured) " .
|
|
"VALUES ('" . $this->head['name'] .
|
|
"', '" . $this->head['pri_dns'] .
|
|
"', '" . $this->head['sec_dns'] .
|
|
"', " . $srl .
|
|
", " . $this->head['refresh'] .
|
|
", " . $this->head['retry'] .
|
|
", " . $this->head['expire'] .
|
|
", " . $this->head['ttl'] .
|
|
", '" . $vld .
|
|
"', " . $this->head['owner'] .
|
|
", '" . $upd .
|
|
"', '" . $this->head['secured'] . "')");
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return $this->loadZone();
|
|
} else {
|
|
$srl = ($this->head['updated'] == 'no') ? intval(date('Ymd') . '01') : $this->head['serial'];
|
|
if ($srl <= $this->head['serial']) {
|
|
$srl = ($this->head['updated'] == 'no') ? $this->head['serial'] + 1 : $this->head['serial'];
|
|
}
|
|
$vld = (($this->head['valid'] == 'yes') || ($this->head['valid'] == 'no')) ? $this->head['valid'] : 'may';
|
|
$upd = ((isset($this->head['updated'])) && ($this->head['updated'] != 'del')) ? 'yes' : $this->head['updated'];
|
|
$res = $this->db->query("UPDATE zones SET " .
|
|
"name = '" . $this->head['name'] . "', " .
|
|
"pri_dns = '" . $this->head['pri_dns'] . "', " .
|
|
"sec_dns = '" . $this->head['sec_dns'] . "', " .
|
|
"serial = " . $srl . ", " .
|
|
"refresh = " . $this->head['refresh'] . ", " .
|
|
"retry = " . $this->head['retry'] . ", " .
|
|
"expire = " . $this->head['expire'] . ", " .
|
|
"ttl = " . $this->head['ttl'] . ", " .
|
|
"valid = '" . $vld . "', " .
|
|
"owner = " . $this->head['owner'] . ", " .
|
|
"updated = '" . $upd . "', " .
|
|
"secured = '" . $this->head['secured'] . "' " .
|
|
"WHERE id = " . $this->head['id']);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
return $this->loadZoneHead();
|
|
}
|
|
}
|
|
|
|
public function saveZone() {
|
|
if ($this->saveZoneHead()) {
|
|
foreach ($this->records as $each) {
|
|
if (!$each->saveRecord()) {
|
|
$this->err = $each->getErr();
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public function eraseZone() {
|
|
if (!$this->is_identified()) {
|
|
ob_start();
|
|
var_dump($this->head);
|
|
$this->err .= "Zone head is not complete" . "\n" . ob_get_clean();
|
|
error_log($this->err);
|
|
return false;
|
|
} else {
|
|
$where = ' WHERE ';
|
|
if ($this->head['id'] >>= 0) {
|
|
$where .= "id = " . $this->head['id'];
|
|
} else {
|
|
$where .= "name = '" . $this->head['name'] . "'";
|
|
}
|
|
$res = $this->db->query("DELETE FROM zones " . $where);
|
|
if (MDB2::isError($res)) {
|
|
$this->err .= $res->getMessage() . "\n" . $res->getDebugInfo();
|
|
error_log($this->err);
|
|
return false;
|
|
}
|
|
$this->clearZone();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public function clearZone() {
|
|
$id = $this->head['id'];
|
|
$head = array(
|
|
'id' => $id,
|
|
'name' => '',
|
|
'pri_dns' => '',
|
|
'sec_dns' => '',
|
|
'serial' => 0,
|
|
'refresh' => 0,
|
|
'retry' => 0,
|
|
'expire' => 0,
|
|
'ttl' => 0,
|
|
'valid' => 'may',
|
|
'owner' => 0,
|
|
'updated' => 'no',
|
|
'secured' => 'no',
|
|
);
|
|
$this->head = $head;
|
|
$this->records = array();
|
|
$this->isloaded = FALSE;
|
|
}
|
|
}
|