<?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.91d'; $_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)) { $sq = "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']; $res = $this->db->query($sq); // error_log($sq); 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 { $sq = "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'] . "')"; $res = $this->db->query($sq); // error_log($sq); 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()) { $sq = "DELETE FROM records WHERE id = " . $this->record['id']; // error_log($sq); $res = $this->db->query($sq); 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'] . " ORDER BY type,host"); 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; } }