<?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):' ', '&nbsp;', $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;
        }
    }