<?php

/*  Poweradmin, a friendly web-based admin tool for PowerDNS.
 *  See <http://www.poweradmin.org> for more details.
 *
 *  Copyright 2007-2009  Rejo Zenger <rejo@zenger.nl>
 *  Copyright 2010-2014  Poweradmin Development Team
 *      <http://www.poweradmin.org/credits.html>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

require_once('templates.inc.php');

/**
 * DNS record functions
 *
 * @package Poweradmin
 * @copyright   2007-2010 Rejo Zenger <rejo@zenger.nl>
 * @copyright   2010-2014 Poweradmin Development Team
 * @license     http://opensource.org/licenses/GPL-3.0 GPL
 */

/** Check if Zone ID exists
 *
 * @param int $zid Zone ID
 *
 * @return boolean|int Domain count or false on failure
 */
function zone_id_exists($zid) {
    global $db;
    $query = "SELECT COUNT(id) FROM domains WHERE id = " . $db->quote($zid, 'integer');
    $count = $db->queryOne($query);
    if (PEAR::isError($count)) {
        error($count->getMessage());
        return false;
    }
    return $count;
}

/** Get Zone ID from Record ID
 *
 * @param int $rid Record ID
 *
 * @return int Zone ID
 */
function get_zone_id_from_record_id($rid) {
    global $db;
    $query = "SELECT domain_id FROM records WHERE id = " . $db->quote($rid, 'integer');
    $zid = $db->queryOne($query);
    return $zid;
}

/** Count Zone Records for Zone ID
 *
 * @param int $zone_id Zone ID
 *
 * @return int Record count
 */
function count_zone_records($zone_id) {
    global $db;
    $sqlq = "SELECT COUNT(id) FROM records WHERE domain_id = " . $db->quote($zone_id, 'integer');
    $record_count = $db->queryOne($sqlq);
    return $record_count;
}

/** Get SOA record content for Zone ID
 *
 * @param int $zone_id Zone ID
 *
 * @return string SOA content
 */
function get_soa_record($zone_id) {
    global $db;

    $sqlq = "SELECT content FROM records WHERE type = " . $db->quote('SOA', 'text') . " AND domain_id = " . $db->quote($zone_id, 'integer');
    $result = $db->queryOne($sqlq);

    return $result;
}

/** Get SOA Serial Number
 *
 * @param string $soa_rec SOA record content
 *
 * @return string SOA serial
 */
function get_soa_serial($soa_rec) {
    $soa = explode(" ", $soa_rec);
    return $soa[2];
}

/** Get Next Date
 *
 * @param string $curr_date Current date in YYYYMMDD format
 *
 * @return string Date +1 day
 */
function get_next_date($curr_date) {
    $next_date = date('Ymd', strtotime('+1 day', strtotime($curr_date)));
    return $next_date;
}

/** Get Next Serial
 *
 * Zone transfer to zone slave(s) will occur only if the serial number
 * of the SOA RR is arithmetically greater that the previous one
 * (as defined by RFC-1982).
 *
 * The serial should be updated, unless:
 *
 * - the serial is set to "0", see http://doc.powerdns.com/types.html#id482176
 *
 * - set a fresh serial ONLY if the existing serial is lower than the current date
 *
 * - update date in serial if it reaches limit of revisions for today or do you
 * think that ritual suicide is better in such case?
 *
 * "This works unless you will require to make more than 99 changes until the new
 * date is reached - in which case perhaps ritual suicide is the best option."
 * http://www.zytrax.com/books/dns/ch9/serial.html
 *
 * @param string $curr_serial Current Serial No
 * @param string $today Optional date for "today"
 *
 * @return string Next serial number
 */
function get_next_serial($curr_serial, $today = '') {
    // Autoserial
    if ($curr_serial == 0) {
        return 0;
    }

    // Serial number could be a not date based
    if ($curr_serial < 1979999999) {
        return $curr_serial+1;
    }

    // Reset the serial number, Bind was written in the early 1980s
    if ($curr_serial == 1979999999) {
        return 1;
    }

    if ($today == '') {
        set_timezone();
        $today = date('Ymd');
    }

    $revision = (int) substr($curr_serial, -2);
    $ser_date = substr($curr_serial, 0, 8);

    if ($curr_serial == '0') {
        $serial = $curr_serial;
    } elseif ($curr_serial == $today . '99') {
        $serial = get_next_date($today) . '00';
    } else {
        if (strcmp($today, $ser_date) === 0) {
            // Current serial starts with date of today, so we need to update the revision only.
            ++$revision;
        } elseif (strncmp($today, $curr_serial, 8) === -1) {
            // Reuse existing serial date if it's in the future
            $today = substr($curr_serial, 0, 8);

            // Get next date if revision reaches maximum per day (99) limit otherwise increment the counter
            if ($revision == 99) {
                $today = get_next_date($today);
                $revision = "00";
            } else {
                ++$revision;
            }
        } else {
            // Current serial did not start of today, so it's either an older
            // serial, therefore set a fresh serial
            $revision = "00";
        }

        // Create new serial out of existing/updated date and revision
        $serial = $today . str_pad($revision, 2, "0", STR_PAD_LEFT);
    }

    return $serial;
}

/** Update SOA record
 *
 * @param int $domain_id Domain ID
 * @param string $content SOA content to set
 *
 * @return boolean true if success
 */
function update_soa_record($domain_id, $content) {
    global $db;

    $sqlq = "UPDATE records SET content = " . $db->quote($content, 'text') . " WHERE domain_id = " . $db->quote($domain_id, 'integer') . " AND type = " . $db->quote('SOA', 'text');
    $response = $db->query($sqlq);

    if (PEAR::isError($response)) {
        error($response->getMessage());
        return false;
    }

    return true;
}

/** Set SOA serial in SOA content
 *
 * @param string $soa_rec SOA record content
 * @param string $serial New serial number
 *
 * @return string Updated SOA record
 */
function set_soa_serial($soa_rec, $serial) {
    // Split content of current SOA record into an array.
    $soa = explode(" ", $soa_rec);
    $soa[2] = $serial;

    // Build new SOA record content
    $soa_rec = join(" ", $soa);
    chop($soa_rec);

    return $soa_rec;
}

/** Return SOA record
 *
 * Returns SOA record with incremented serial number
 *
 * @param int $soa_rec Current SOA record
 *
 * @return boolean true if success
 */
function get_updated_soa_record($soa_rec) {
    $curr_serial = get_soa_serial($soa_rec);
    $new_serial = get_next_serial($curr_serial);

    if ($curr_serial != $new_serial) {
        return set_soa_serial($soa_rec, $new_serial);
    }

    return set_soa_serial($soa_rec, $curr_serial);
}

/** Update SOA serial
 *
 * Increments SOA serial to next possible number
 *
 * @param int $domain_id Domain ID
 *
 * @return boolean true if success
 */
function update_soa_serial($domain_id) {
    $soa_rec = get_soa_record($domain_id);
    if ($soa_rec == NULL) {
        return false;
    }

    $curr_serial = get_soa_serial($soa_rec);
    $new_serial = get_next_serial($curr_serial);

    if ($curr_serial != $new_serial) {
        $soa_rec = set_soa_serial($soa_rec, $new_serial);
        return update_soa_record($domain_id, $soa_rec);
    }

    return true;
}

/** Get Zone comment
 *
 * @param int $zone_id Zone ID
 *
 * @return string Zone Comment
 */
function get_zone_comment($zone_id) {
    global $db;
    $query = "SELECT comment FROM zones WHERE domain_id = " . $db->quote($zone_id, 'integer');
    $comment = $db->queryOne($query);

    if ($comment == "0") {
        $comment = '';
    }

    return $comment;
}

/** Edit the zone comment
 *
 * This function validates it if correct it inserts it into the database.
 *
 * @param int $zone_id Zone ID
 * @param string $comment Comment to set
 *
 * @return boolean true on success
 */
function edit_zone_comment($zone_id, $comment) {

    if (verify_permission('zone_content_edit_others')) {
        $perm_content_edit = "all";
    } elseif (verify_permission('zone_content_edit_own')) {
        $perm_content_edit = "own";
    } else {
        $perm_content_edit = "none";
    }

    $user_is_zone_owner = verify_user_is_owner_zoneid($zone_id);
    $zone_type = get_domain_type($zone_id);

    if ($zone_type == "SLAVE" || $perm_content_edit == "none" || ($perm_content_edit == "own" && $user_is_zone_owner == "0")) {
        error(ERR_PERM_EDIT_COMMENT);
        return false;
    } else {
        global $db;

        $query = "SELECT COUNT(*) FROM zones WHERE domain_id=" . $db->quote($zone_id, 'integer');

        $count = $db->queryOne($query);

        if ($count > 0) {
            $query = "UPDATE zones
				SET comment=" . $db->quote($comment, 'text') . "
				WHERE domain_id=" . $db->quote($zone_id, 'integer');
            $result = $db->query($query);
            if (PEAR::isError($result)) {
                error($result->getMessage());
                return false;
            }
        } else {
            $query = "INSERT INTO zones (domain_id, owner, comment)
				VALUES(" . $db->quote($zone_id, 'integer') . ",1," . $db->quote($comment, 'text') . ")";
            $result = $db->query($query);
            if (PEAR::isError($result)) {
                error($result->getMessage());
                return false;
            }
        }
    }
    return true;
}

/** Edit a record
 *
 * This function validates it if correct it inserts it into the database.
 *
 * @param mixed[] $record Record structure to update
 *
 * @return boolean true if successful
 */
function edit_record($record) {

    if (verify_permission('zone_content_edit_others')) {
        $perm_content_edit = "all";
    } elseif (verify_permission('zone_content_edit_own')) {
        $perm_content_edit = "own";
    } else {
        $perm_content_edit = "none";
    }

    $user_is_zone_owner = verify_user_is_owner_zoneid($record['zid']);
    $zone_type = get_domain_type($record['zid']);

    if ($zone_type == "SLAVE" || $perm_content_edit == "none" || ($perm_content_edit == "own" && $user_is_zone_owner == "0")) {
        error(ERR_PERM_EDIT_RECORD);
        return false;
    } else {
        global $db;
        if (validate_input($record['rid'], $record['zid'], $record['type'], $record['content'], $record['name'], $record['prio'], $record['ttl'])) {
            $name = strtolower($record['name']); // powerdns only searches for lower case records
            if ($record['type'] == "SPF" || $record['type'] == "TXT") {
                $content = $db->quote(stripslashes('\"' . $record['content'] . '\"'), 'text');
            } else {
                $content = $db->quote($record['content'], 'text');
            }
            $query = "UPDATE records
				SET name=" . $db->quote($name, 'text') . ",
				type=" . $db->quote($record['type'], 'text') . ",
				content=" . $content . ",
				ttl=" . $db->quote($record['ttl'], 'integer') . ",
				prio=" . $db->quote($record['prio'], 'integer') . ",
				change_date=" . $db->quote(time(), 'integer') . "
				WHERE id=" . $db->quote($record['rid'], 'integer');
            $result = $db->query($query);
            if (PEAR::isError($result)) {
                error($result->getMessage());
                return false;
            }
            return true;
        }
        return false;
    }
}

/** Add a record
 *
 * This function validates it if correct it inserts it into the database.
 *
 * @param int $zone_id Zone ID
 * @param string $name Name part of record
 * @param string $type Type of record
 * @param string $content Content of record
 * @param int $ttl Time-To-Live of record
 * @param int $prio Priority of record
 *
 * @return boolean true if successful
 */
function add_record($zone_id, $name, $type, $content, $ttl, $prio) {
    global $db;
    global $pdnssec_use;

    if (verify_permission('zone_content_edit_others')) {
        $perm_content_edit = "all";
    } elseif (verify_permission('zone_content_edit_own')) {
        $perm_content_edit = "own";
    } else {
        $perm_content_edit = "none";
    }

    $user_is_zone_owner = verify_user_is_owner_zoneid($zone_id);
    $zone_type = get_domain_type($zone_id);

    if ($zone_type == "SLAVE" || $perm_content_edit == "none" || ($perm_content_edit == "own" && $user_is_zone_owner == "0")) {
        error(ERR_PERM_ADD_RECORD);
        return false;
    } else {
        $response = $db->beginTransaction();
        if (validate_input(-1, $zone_id, $type, $content, $name, $prio, $ttl)) {
            $change = time();
            $name = strtolower($name); // powerdns only searches for lower case records
            if ($type == "SPF" || $type == "TXT") {
                $content = $db->quote(stripslashes('\"' . $content . '\"'), 'text');
            } else {
                $content = $db->quote($content, 'text');
            }
            $query = "INSERT INTO records (domain_id, name, type, content, ttl, prio, change_date) VALUES ("
                    . $db->quote($zone_id, 'integer') . ","
                    . $db->quote($name, 'text') . ","
                    . $db->quote($type, 'text') . ","
                    . $content . ","
                    . $db->quote($ttl, 'integer') . ","
                    . $db->quote($prio, 'integer') . ","
                    . $db->quote($change, 'integer') . ")";
            $response = $db->exec($query);
            if (PEAR::isError($response)) {
                error($response->getMessage());
                $response = $db->rollback();
                return false;
            } else {
                $response = $db->commit();
                if ($type != 'SOA') {
                    update_soa_serial($zone_id);
                }
                if ($pdnssec_use) {
                    dnssec_rectify_zone($zone_id);
                }
                return true;
            }
        } else {
            return false;
        }
    }
}

/** Add Supermaster
 *
 * Add a trusted supermaster to the global supermasters table
 *
 * @param string $master_ip Supermaster IP address
 * @param string $ns_name Hostname of supermasterfound in NS records for domain
 * @param string $account Account name used for tracking
 *
 * @return boolean true on success
 */
function add_supermaster($master_ip, $ns_name, $account) {
    global $db;
    if (!is_valid_ipv4($master_ip) && !is_valid_ipv6($master_ip)) {
        error(ERR_DNS_IP);
        return false;
    }
    if (!is_valid_hostname_fqdn($ns_name, 0)) {
        error(ERR_DNS_HOSTNAME);
        return false;
    }
    if (!validate_account($account)) {
        error(sprintf(ERR_INV_ARGC, "add_supermaster", "given account name is invalid (alpha chars only)"));
        return false;
    }
    if (supermaster_ip_name_exists($master_ip, $ns_name)) {
        error(ERR_SM_EXISTS);
        return false;
    } else {
        $db->query("INSERT INTO supermasters VALUES (" . $db->quote($master_ip, 'text') . ", " . $db->quote($ns_name, 'text') . ", " . $db->quote($account, 'text') . ")");
        return true;
    }
}

/** Delete Supermaster
 *
 * Delete a supermaster from the global supermasters table
 *
 * @param string $master_ip Supermaster IP address
 * @param string $ns_name Hostname of supermaster
 *
 * @return boolean true on success
 */
function delete_supermaster($master_ip, $ns_name) {
    global $db;
    if (is_valid_ipv4($master_ip) || is_valid_ipv6($master_ip) || is_valid_hostname_fqdn($ns_name, 0)) {
        $db->query("DELETE FROM supermasters WHERE ip = " . $db->quote($master_ip, 'text') .
                " AND nameserver = " . $db->quote($ns_name, 'text'));
        return true;
    } else {
        error(sprintf(ERR_INV_ARGC, "delete_supermaster", "No or no valid ipv4 or ipv6 address given."));
    }
}

/** Get Supermaster Info from IP
 *
 * Retrieve supermaster details from supermaster IP address
 *
 * @param string $master_ip Supermaster IP address
 *
 * @return mixed[] array of supermaster details
 */
function get_supermaster_info_from_ip($master_ip) {
    global $db;
    if (is_valid_ipv4($master_ip) || is_valid_ipv6($master_ip)) {
        $result = $db->queryRow("SELECT ip,nameserver,account FROM supermasters WHERE ip = " . $db->quote($master_ip, 'text'));

        $ret = array(
            "master_ip" => $result["ip"],
            "ns_name" => $result["nameserver"],
            "account" => $result["account"]
        );

        return $ret;
    } else {
        error(sprintf(ERR_INV_ARGC, "get_supermaster_info_from_ip", "No or no valid ipv4 or ipv6 address given."));
    }
}

/** Get record details from Record ID
 *
 * @param $rid Record ID
 *
 * @return mixed[] array of record details [rid,zid,name,type,content,ttl,prio,change_date]
 */
function get_record_details_from_record_id($rid) {
    global $db;

    $query = "SELECT id AS rid, domain_id AS zid, name, type, content, ttl, prio, change_date FROM records WHERE id = " . $db->quote($rid, 'integer');

    $response = $db->query($query);
    if (PEAR::isError($response)) {
        error($response->getMessage());
        return false;
    }

    $return = $response->fetchRow();
    return $return;
}

/** Delete a record by a given record id
 *
 * @param int $rid Record ID
 *
 * @return boolean true on success
 */
function delete_record($rid) {
    global $db;

    if (verify_permission('zone_content_edit_others')) {
        $perm_content_edit = "all";
    } elseif (verify_permission('zone_content_edit_own')) {
        $perm_content_edit = "own";
    } else {
        $perm_content_edit = "none";
    }

    // Determine ID of zone first.
    $record = get_record_details_from_record_id($rid);
    $user_is_zone_owner = verify_user_is_owner_zoneid($record['zid']);

    if ($perm_content_edit == "all" || ($perm_content_edit == "own" && $user_is_zone_owner == "1" )) {
        if ($record['type'] == "SOA") {
            error(_('You are trying to delete the SOA record. You are not allowed to remove it, unless you remove the entire zone.'));
        } else {
            $query = "DELETE FROM records WHERE id = " . $db->quote($rid, 'integer');
            $response = $db->query($query);
            if (PEAR::isError($response)) {
                error($response->getMessage());
                return false;
            }
            return true;
        }
    } else {
        error(ERR_PERM_DEL_RECORD);
        return false;
    }
}

/** Delete record reference to zone template
 *
 * @param int $rid Record ID
 *
 * @return boolean true on success
 */
function delete_record_zone_templ($rid) {
    global $db;

    $query = "DELETE FROM records_zone_templ WHERE record_id = " . $db->quote($rid, 'integer');
    $response = $db->query($query);
    if (PEAR::isError($response)) {
        error($response->getMessage());
        return false;
    }

    return true;
}

/**
 * Add a domain to the database
 *
 * A domain is name obligatory, so is an owner.
 * return values: true when succesful.
 *
 * Empty means templates dont have to be applied.
 *
 * This functions eats a template and by that it inserts various records.
 * first we start checking if something in an arpa record
 * remember to request nextID's from the database to be able to insert record.
 * if anything is invalid the function will error
 *
 * @param string $domain A domain name
 * @param int $owner Owner ID for domain
 * @param string $type Type of domain ['NATIVE','MASTER','SLAVE']
 * @param string $slave_master Master server hostname for domain
 * @param int|string $zone_template ID of zone template ['none' or int]
 *
 * @return boolean true on success
 */
function add_domain($domain, $owner, $type, $slave_master, $zone_template) {
    if (verify_permission('zone_master_add')) {
        $zone_master_add = "1";
    }
    if (verify_permission('zone_slave_add')) {
        $zone_slave_add = "1";
    }

    // TODO: make sure only one is possible if only one is enabled
    if ($zone_master_add == "1" || $zone_slave_add == "1") {

        global $db;
        global $dns_ns1;
        global $dns_hostmaster;
        global $dns_ttl;
        global $db_layer;
        global $db_type;

        if (($domain && $owner && $zone_template) ||
                (preg_match('/in-addr.arpa/i', $domain) && $owner && $zone_template) ||
                $type == "SLAVE" && $domain && $owner && $slave_master) {

            $response = $db->query("INSERT INTO domains (name, type) VALUES (" . $db->quote($domain, 'text') . ", " . $db->quote($type, 'text') . ")");
            if (PEAR::isError($response)) {
                error($response->getMessage());
                return false;
            }

            if ($db_layer == 'MDB2' && ($db_type == 'mysql' || $db_type == 'pgsql')) {
                $domain_id = $db->lastInsertId('domains', 'id');
            } else if ($db_layer == 'PDO' && $db_type == 'pgsql') {
                $domain_id = $db->lastInsertId('domains_id_seq');
            } else {
                $domain_id = $db->lastInsertId();
            }

            if (PEAR::isError($domain_id)) {
                error($domain_id->getMessage());
                return false;
            }

            $response = $db->query("INSERT INTO zones (domain_id, owner, zone_templ_id) VALUES (" . $db->quote($domain_id, 'integer') . ", " . $db->quote($owner, 'integer') . ", " . $db->quote(($zone_template == "none") ? 0 : $zone_template, 'integer') . ")");
            if (PEAR::isError($response)) {
                error($response->getMessage());
                return false;
            }

            if ($type == "SLAVE") {
                $response = $db->query("UPDATE domains SET master = " . $db->quote($slave_master, 'text') . " WHERE id = " . $db->quote($domain_id, 'integer'));
                if (PEAR::isError($response)) {
                    error($response->getMessage());
                    return false;
                }
                return true;
            } else {
                $now = time();
                if ($zone_template == "none" && $domain_id) {
                    $ns1 = $dns_ns1;
                    $hm = $dns_hostmaster;
                    $ttl = $dns_ttl;

                    set_timezone();

                    $serial = date("Ymd");
                    $serial .= "00";

                    $query = "INSERT INTO records (domain_id, name, content, type, ttl, prio, change_date) VALUES ("
                            . $db->quote($domain_id, 'integer') . ","
                            . $db->quote($domain, 'text') . ","
                            . $db->quote($ns1 . ' ' . $hm . ' ' . $serial . ' 28800 7200 604800 86400', 'text') . ","
                            . $db->quote('SOA', 'text') . ","
                            . $db->quote($ttl, 'integer') . ","
                            . $db->quote(0, 'integer') . ","
                            . $db->quote($now, 'integer') . ")";
                    $response = $db->query($query);
                    if (PEAR::isError($response)) {
                        error($response->getMessage());
                        return false;
                    }
                    return true;
                } elseif ($domain_id && is_numeric($zone_template)) {
                    global $dns_ttl;

                    $templ_records = get_zone_templ_records($zone_template);
                    if ($templ_records != -1) {
                        foreach ($templ_records as $r) {
                            if ((preg_match('/in-addr.arpa/i', $domain) && ($r["type"] == "NS" || $r["type"] == "SOA")) || (!preg_match('/in-addr.arpa/i', $domain))) {
                                $name = parse_template_value($r["name"], $domain);
                                $type = $r["type"];
                                $content = parse_template_value($r["content"], $domain);
                                $ttl = $r["ttl"];
                                $prio = intval($r["prio"]);

                                if (!$ttl) {
                                    $ttl = $dns_ttl;
                                }

                                $query = "INSERT INTO records (domain_id, name, type, content, ttl, prio, change_date) VALUES ("
                                        . $db->quote($domain_id, 'integer') . ","
                                        . $db->quote($name, 'text') . ","
                                        . $db->quote($type, 'text') . ","
                                        . $db->quote($content, 'text') . ","
                                        . $db->quote($ttl, 'integer') . ","
                                        . $db->quote($prio, 'integer') . ","
                                        . $db->quote($now, 'integer') . ")";
                                $response = $db->query($query);
                                if (PEAR::isError($response)) {
                                    error($response->getMessage());
                                    return false;
                                }

                                if ($db_layer == 'MDB2' && ($db_type == 'mysql' || $db_type == 'pgsql')) {
                                    $record_id = $db->lastInsertId('records', 'id');
                                } else if ($db_layer == 'PDO' && $db_type == 'pgsql') {
                                    $record_id = $db->lastInsertId('records_id_seq');
                                } else {
                                    $record_id = $db->lastInsertId();
                                }

                                if (PEAR::isError($record_id)) {
                                    error($record_id->getMessage());
                                    return false;
                                }

                                $query = "INSERT INTO records_zone_templ (domain_id, record_id, zone_templ_id) VALUES ("
                                        . $db->quote($domain_id, 'integer') . ","
                                        . $db->quote($record_id, 'integer') . ","
                                        . $db->quote($r['zone_templ_id'], 'integer') . ")";
                                $response = $db->query($query);
                                if (PEAR::isError($response)) {
                                    error($response->getMessage());
                                    return false;
                                }
                            }
                        }
                    }
                    return true;
                } else {
                    error(sprintf(ERR_INV_ARGC, "add_domain", "could not create zone"));
                }
            }
        } else {
            error(sprintf(ERR_INV_ARG, "add_domain"));
        }
    } else {
        error(ERR_PERM_ADD_ZONE_MASTER);
        return false;
    }
}

/** Deletes a domain by a given id
 *
 * Function always succeeds. If the field is not found in the database, thats what we want anyway.
 *
 * @param int $id Zone ID
 *
 * @return boolean true on success
 */
function delete_domain($id) {
    global $db;

    if (verify_permission('zone_content_edit_others')) {
        $perm_edit = "all";
    } elseif (verify_permission('zone_content_edit_own')) {
        $perm_edit = "own";
    } else {
        $perm_edit = "none";
    }
    $user_is_zone_owner = verify_user_is_owner_zoneid($id);

    if ($perm_edit == "all" || ( $perm_edit == "own" && $user_is_zone_owner == "1")) {
        if (is_numeric($id)) {
            $db->query("DELETE FROM zones WHERE domain_id=" . $db->quote($id, 'integer'));
            $db->query("DELETE FROM domains WHERE id=" . $db->quote($id, 'integer'));
            $db->query("DELETE FROM records WHERE domain_id=" . $db->quote($id, 'integer'));
            $db->query("DELETE FROM records_zone_templ WHERE domain_id=" . $db->quote($id, 'integer'));
            return true;
        } else {
            error(sprintf(ERR_INV_ARGC, "delete_domain", "id must be a number"));
            return false;
        }
    } else {
        error(ERR_PERM_DEL_ZONE);
    }
}

/** Record ID to Domain ID
 *
 * Gets the id of the domain by a given record id
 *
 * @param int $id Record ID
 * @return int Domain ID of record
 */
function recid_to_domid($id) {
    global $db;
    if (is_numeric($id)) {
        $result = $db->query("SELECT domain_id FROM records WHERE id=" . $db->quote($id, 'integer'));
        $r = $result->fetchRow();
        return $r["domain_id"];
    } else {
        error(sprintf(ERR_INV_ARGC, "recid_to_domid", "id must be a number"));
    }
}

/** Change owner of a domain
 *
 * @param int $zone_id Zone ID
 * @param int $user_id User ID
 *
 * @return boolean true when succesful
 */
function add_owner_to_zone($zone_id, $user_id) {
    global $db;
    if ((verify_permission('zone_meta_edit_others')) || (verify_permission('zone_meta_edit_own')) && verify_user_is_owner_zoneid($_GET["id"])) {
        // User is allowed to make change to meta data of this zone.
        if (is_numeric($zone_id) && is_numeric($user_id) && is_valid_user($user_id)) {
            if ($db->queryOne("SELECT COUNT(id) FROM zones WHERE owner=" . $db->quote($user_id, 'integer') . " AND domain_id=" . $db->quote($zone_id, 'integer')) == 0) {
                $zone_templ_id = get_zone_template($zone_id);
                if ($zone_templ_id == NULL)
                    $zone_templ_id = 0;
                $db->query("INSERT INTO zones (domain_id, owner, zone_templ_id) VALUES("
                        . $db->quote($zone_id, 'integer') . ", "
                        . $db->quote($user_id, 'integer') . ", "
                        . $db->quote($zone_templ_id, 'integer') . ")"
                );
            }
            return true;
        } else {
            error(sprintf(ERR_INV_ARGC, "add_owner_to_zone", "$zone_id / $user_id"));
        }
    } else {
        return false;
    }
}

/** Delete owner from zone
 *
 * @param int $zone_id Zone ID
 * @param int $user_id User ID
 *
 * @return boolean true on success
 */
function delete_owner_from_zone($zone_id, $user_id) {
    global $db;
    if ((verify_permission('zone_meta_edit_others')) || (verify_permission('zone_meta_edit_own')) && verify_user_is_owner_zoneid($_GET["id"])) {
        // User is allowed to make change to meta data of this zone.
        if (is_numeric($zone_id) && is_numeric($user_id) && is_valid_user($user_id)) {
            // TODO: Next if() required, why not just execute DELETE query?
            if ($db->queryOne("SELECT COUNT(id) FROM zones WHERE owner=" . $db->quote($user_id, 'integer') . " AND domain_id=" . $db->quote($zone_id, 'integer')) != 0) {
                $db->query("DELETE FROM zones WHERE owner=" . $db->quote($user_id, 'integer') . " AND domain_id=" . $db->quote($zone_id, 'integer'));
            }
            return true;
        } else {
            error(sprintf(ERR_INV_ARGC, "delete_owner_from_zone", "$zone_id / $user_id"));
        }
    } else {
        return false;
    }
}

/** Retrieve all supported dns record types
 *
 * This function might be deprecated.
 *
 * @return string[] array of types
 */
function get_record_types() {
    global $rtypes;
    return $rtypes;
}

/** Retrieve all records by a given type and domain id
 *
 * Example get all records that are of type A from domain id 1
 *
 * <code>
 * get_records_by_type_from_domid('A', 1)
 * </code>
 *
 * @param string $type Record type
 * @param int $recid Record ID
 *
 * @return object a DB class result object
 */
function get_records_by_type_from_domid($type, $recid) {
    global $rtypes;
    global $db;

    // Does this type exist?
    if (!in_array(strtoupper($type), $rtypes)) {
        error(sprintf(ERR_INV_ARGC, "get_records_from_type", "this is not a supported record"));
    }

    // Get the domain id.
    $domid = recid_to_domid($recid);

    $result = $db->query("select id, type from records where domain_id=" . $db->quote($recid, 'integer') . " and type=" . $db->quote($type, 'text'));
    return $result;
}

/** Get Record Type for Record ID
 *
 * Retrieves the type of a record from a given id.
 *
 * @param int $id Record ID
 * @return string Record type (one of the records types in $rtypes assumable).
 */
function get_recordtype_from_id($id) {
    global $db;
    if (is_numeric($id)) {
        $result = $db->query("SELECT type FROM records WHERE id=" . $db->quote($id, 'integer'));
        $r = $result->fetchRow();
        return $r["type"];
    } else {
        error(sprintf(ERR_INV_ARG, "get_recordtype_from_id"));
    }
}

/** Get Name from Record ID
 *
 * Retrieves the name (e.g. bla.test.com) of a record by a given id.
 *
 * @param int $id Record ID
 * @return string Name part of record
 */
function get_name_from_record_id($id) {
    global $db;
    if (is_numeric($id)) {
        $result = $db->query("SELECT name FROM records WHERE id=" . $db->quote($id, 'integer'));
        $r = $result->fetchRow();
        return $r["name"];
    } else {
        error(sprintf(ERR_INV_ARG, "get_name_from_record_id"));
    }
}

/** Get Zone Name from Zone ID
 *
 * @param int $zid Zone ID
 *
 * @return string Domain name
 */
function get_zone_name_from_id($zid) {
    global $db;

    if (is_numeric($zid)) {
        $result = $db->queryRow("SELECT name FROM domains WHERE id=" . $db->quote($zid, 'integer'));
        if ($result) {
            return $result["name"];
        } else {
            error(sprintf("Zone does not exist."));
            return false;
        }
    } else {
        error(sprintf(ERR_INV_ARGC, "get_zone_name_from_id", "Not a valid domainid: $zid"));
    }
}

/** Get zone id from name
 *
 * @param string $zname Zone name
 * @return int Zone ID
 */
function get_zone_id_from_name($zname) {
    global $db;

    if (!empty($zname)) {
        $result = $db->queryRow("SELECT id FROM domains WHERE name=" . $db->quote($zname, 'text'));
        if ($result) {
            return $result["id"];
        } elseif ($rows == "0") {
            error(sprintf("Zone does not exist."));
            return false;
        }
    } else {
        error(sprintf(ERR_INV_ARGC, "get_zone_id_from_name", "Not a valid domainname: $zname"));
    }
}

/** Get Zone details from Zone ID
 *
 * @param int $zid Zone ID
 * @return mixed[] array of zone details [type,name,master_ip,record_count]
 */
function get_zone_info_from_id($zid) {

    if (verify_permission('zone_content_view_others')) {
        $perm_view = "all";
    } elseif (verify_permission('zone_content_view_own')) {
        $perm_view = "own";
    } else {
        $perm_view = "none";
    }

    if ($perm_view == "none") {
        error(ERR_PERM_VIEW_ZONE);
    } else {
        global $db;

        $query = "SELECT 	domains.type AS type,
					domains.name AS name,
					domains.master AS master_ip,
					count(records.domain_id) AS record_count
					FROM domains LEFT OUTER JOIN records ON domains.id = records.domain_id
					WHERE domains.id = " . $db->quote($zid, 'integer') . "
					GROUP BY domains.id, domains.type, domains.name, domains.master";
        $result = $db->queryRow($query);
        $return = array(
            "name" => $result['name'],
            "type" => $result['type'],
            "master_ip" => $result['master_ip'],
            "record_count" => $result['record_count']
        );
        return $return;
    }
}

/** Convert IPv6 Address to PTR
 *
 * @param string $ip IPv6 Address
 * @return string PTR form of address
 */
function convert_ipv6addr_to_ptrrec($ip) {
// rev-patch
// taken from: http://stackoverflow.com/questions/6619682/convert-ipv6-to-nibble-format-for-ptr-records
// PHP (>= 5.1.0, or 5.3+ on Windows), use the inet_pton
//      $ip = '2001:db8::567:89ab';

    $addr = inet_pton($ip);
    $unpack = unpack('H*hex', $addr);
    $hex = $unpack['hex'];
    $arpa = implode('.', array_reverse(str_split($hex))) . '.ip6.arpa';
    return $arpa;
}

/** Get Best Matching in-addr.arpa Zone ID from Domain Name
 *
 * @param string $domain Domain name
 *
 * @return int Zone ID
 */
function get_best_matching_zone_id_from_name($domain) {
// rev-patch
// tring to find the correct zone
// %ip6.arpa and %in-addr.arpa is looked for

    global $db;

    $match = 72; // the longest ip6.arpa has a length of 72
    $found_domain_id = -1;

    // get all reverse-zones
    $query = "SELECT name, id FROM domains
                   WHERE name like " . $db->quote('%.arpa', 'text') . "
                   ORDER BY length(name) DESC";

    $response = $db->query($query);
    if (PEAR::isError($response)) {
        error($response->getMessage());
        return false;
    }
    if ($response) {
        while ($r = $response->fetchRow()) {
            $pos = stripos($domain, $r["name"]);
            if ($pos !== false) {
                // one possible searched $domain is found
                if ($pos < $match) {
                    $match = $pos;
                    $found_domain_id = $r["id"];
                }
            }
        }
    } else {
        return -1;
    }
    return $found_domain_id;
}

/** Check if Domain Exists
 *
 * Check if a domain is already existing.
 *
 * @param string $domain Domain name
 * @return boolean true if existing, false if it doesnt exist.
 */
function domain_exists($domain) {
    global $db;

    if (is_valid_hostname_fqdn($domain, 0)) {
        $result = $db->queryRow("SELECT id FROM domains WHERE name=" . $db->quote($domain, 'text'));
        return ($result ? true : false);
    } else {
        error(ERR_DOMAIN_INVALID);
    }
}

/** Get All Supermasters
 *
 * Gets an array of arrays of supermaster details
 *
 * @return array[] supermasters detail [master_ip,ns_name,account]s
 */
function get_supermasters() {
    global $db;

    $result = $db->query("SELECT ip, nameserver, account FROM supermasters");
    if (PEAR::isError($result)) {
        error($result->getMessage());
        return false;
    }

    $ret = array();

    while ($r = $result->fetchRow()) {
        $ret[] = array(
            "master_ip" => $r["ip"],
            "ns_name" => $r["nameserver"],
            "account" => $r["account"],
        );
    }
    return (sizeof($ret) == 0 ? -1 : $ret);
}

/** Check if Supermaster IP address exists
 *
 * @param string $master_ip Supermaster IP
 *
 * @return boolean true if exists, otherwise false
 */
function supermaster_exists($master_ip) {
    global $db;
    if (is_valid_ipv4($master_ip, false) || is_valid_ipv6($master_ip)) {
        $result = $db->queryOne("SELECT ip FROM supermasters WHERE ip = " . $db->quote($master_ip, 'text'));
        return ($result ? true : false);
    } else {
        error(sprintf(ERR_INV_ARGC, "supermaster_exists", "No or no valid IPv4 or IPv6 address given."));
    }
}

/** Check if Supermaster IP Address and NS Name combo exists
 *
 * @param string $master_ip Supermaster IP Address
 * @param string $ns_name Supermaster NS Name
 *
 * @return boolean true if exists, false otherwise
 */
function supermaster_ip_name_exists($master_ip, $ns_name) {
    global $db;
    if ((is_valid_ipv4($master_ip) || is_valid_ipv6($master_ip)) && is_valid_hostname_fqdn($ns_name, 0)) {
        $result = $db->queryOne("SELECT ip FROM supermasters WHERE ip = " . $db->quote($master_ip, 'text') .
                " AND nameserver = " . $db->quote($ns_name, 'text'));
        return ($result ? true : false);
    } else {
        error(sprintf(ERR_INV_ARGC, "supermaster_exists", "No or no valid IPv4 or IPv6 address given."));
    }
}

/** Get Zones
 *
 * @param string $perm View Zone Permissions ['own','all','none']
 * @param int $userid Requesting User ID
 * @param string $letterstart Starting letters to match [default='all']
 * @param int $rowstart Start from row in set [default=0]
 * @param int $rowamount Max number of rows to fetch for this query when not 'all' [default=999999]
 * @param string $sortby Column to sort results by [default='name']
 *
 * @return boolean|mixed[] false or array of zone details [id,name,type,count_records]
 */
function get_zones($perm, $userid = 0, $letterstart = 'all', $rowstart = 0, $rowamount = 999999, $sortby = 'name') {
    global $db;
    global $db_type;
    global $sql_regexp;

    if ($letterstart == '_') {
        $letterstart = '\_';
    }

    $sql_add = '';
    if ($perm != "own" && $perm != "all") {
        error(ERR_PERM_VIEW_ZONE);
        return false;
    } else {
        if ($perm == "own") {
            $sql_add = " AND zones.domain_id = domains.id
				AND zones.owner = " . $db->quote($userid, 'integer');
        }
        if ($letterstart != 'all' && $letterstart != 1) {
            $sql_add .=" AND domains.name LIKE " . $db->quote($letterstart . "%", 'text') . " ";
        } elseif ($letterstart == 1) {
            $sql_add .=" AND substring(domains.name,1,1) " . $sql_regexp . " '^[[:digit:]]'";
        }
    }

    if ($sortby != 'count_records') {
        $sortby = 'domains.' . $sortby;
    }

    $natural_sort = 'LENGTH(domains.name), domains.name';
    if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'sqlite' || $db_type == 'sqlite3') {
        $natural_sort = 'domains.name+0<>0 DESC, domains.name+0, domains.name';
    }
    $sql_sortby = ($sortby == 'domains.name' ? $natural_sort : $sortby . ', ' . $natural_sort);

    $sqlq = "SELECT domains.id,
			domains.name,
			domains.type,
			Record_Count.count_records
			FROM domains
			LEFT JOIN zones ON domains.id=zones.domain_id
			LEFT JOIN (
				SELECT COUNT(domain_id) AS count_records, domain_id FROM records GROUP BY domain_id
			) Record_Count ON Record_Count.domain_id=domains.id
			WHERE 1=1" . $sql_add . "
			GROUP BY domains.name, domains.id, domains.type, Record_Count.count_records
			ORDER BY " . $sql_sortby;

    if ($letterstart != 'all') {
        $db->setLimit($rowamount, $rowstart);
    }
    $result = $db->query($sqlq);

    $ret = array();
    while ($r = $result->fetchRow()) {
        //fixme: name is not guaranteed to be unique with round-robin record sets
        $ret[$r["name"]] = array(
            "id" => $r["id"],
            "name" => $r["name"],
            "type" => $r["type"],
            "count_records" => $r["count_records"]
        );
    }
    return $ret;
}

// TODO: letterstart limitation and userid permission limitiation should be applied at the same time?
// fixme: letterstart 'all' forbids searching for domains that actually start with 'all'
/** Get Count of Zones
 *
 * @param string $perm 'all', 'own' uses session 'userid'
 * @param string $letterstart Starting letters to match [default='all']
 *
 * @return int Count of zones matched
 */
function zone_count_ng($perm, $letterstart = 'all') {
    global $db;
    global $sql_regexp;

    $fromTable = 'domains';
    $sql_add = '';

    if ($perm != "own" && $perm != "all") {
        $zone_count = "0";
    } else {
        if ($perm == "own") {
            $sql_add = " AND zones.domain_id = domains.id
					AND zones.owner = " . $db->quote($_SESSION['userid'], 'integer');
            $fromTable .= ',zones';
        }

        if ($letterstart != 'all' && $letterstart != 1) {
            $sql_add .=" AND domains.name LIKE " . $db->quote($letterstart . "%", 'text') . " ";
        } elseif ($letterstart == 1) {
            $sql_add .=" AND substring(domains.name,1,1) " . $sql_regexp . " '^[[:digit:]]'";
        }

# XXX: do we really need this distinct directive as it's unsupported in sqlite)
#		$sqlq = "SELECT COUNT(distinct domains.id) AS count_zones

        $sqlq = "SELECT COUNT(domains.id) AS count_zones
			FROM " . $fromTable . "	WHERE 1=1
			" . $sql_add;
        $zone_count = $db->queryOne($sqlq);
    }
    return $zone_count;
}

/** Get Zone Count for Owner User ID
 *
 * @param int $uid User ID
 *
 * @return int Count of Zones matched
 */
function zone_count_for_uid($uid) {
    global $db;
    $query = "SELECT COUNT(domain_id)
			FROM zones
			WHERE owner = " . $db->quote($uid, 'integer') . "
			ORDER BY domain_id";
    $zone_count = $db->queryOne($query);
    return $zone_count;
}

/** Get a Record from an Record ID
 *
 * Retrieve all fields of the record and send it back to the function caller.
 *
 * @param int $id Record ID
 * @return int|mixed[] array of record detail, or -1 if nothing found
 */
function get_record_from_id($id) {
    global $db;
    if (is_numeric($id)) {
        $result = $db->queryRow("SELECT id, domain_id, name, type, content, ttl, prio, change_date FROM records WHERE id=" . $db->quote($id, 'integer'));
        if ($result) {
            $ret = array(
                "id" => $result["id"],
                "domain_id" => $result["domain_id"],
                "name" => $result["name"],
                "type" => $result["type"],
                "content" => $result["content"],
                "ttl" => $result["ttl"],
                "prio" => $result["prio"],
                "change_date" => $result["change_date"]
            );
            return $ret;
        } else {
            return -1;
        }
    } else {
        error(sprintf(ERR_INV_ARG, "get_record_from_id"));
    }
}

/** Get all records from a domain id.
 *
 * Retrieve all fields of the records and send it back to the function caller.
 *
 * @param int $id Domain ID
 * @param int $rowstart Starting row [default=0]
 * @param int $rowamount Number of rows to return in this query [default=999999]
 * @param string $sortby Column to sort by [default='name']
 *
 * @return int|mixed[] array of record detail, or -1 if nothing found
 */
function get_records_from_domain_id($id, $rowstart = 0, $rowamount = 999999, $sortby = 'name') {
    global $db;
    global $db_type;

    $result = array();
    if (is_numeric($id)) {
        if ((isset($_SESSION[$id . "_ispartial"])) && ($_SESSION[$id . "_ispartial"] == 1)) {
            $db->setLimit($rowamount, $rowstart);
            $result = $db->query("SELECT record_owners.record_id as id
					FROM record_owners,domains,records
					WHERE record_owners.user_id = " . $db->quote($_SESSION["userid"], 'integer') . "
					AND record_owners.record_id = records.id
					AND records.domain_id = " . $db->quote($id, 'integer') . "
					GROUP BY record_owners.record_id ORDER BY records." . $sortby);

            $ret = array();
            if ($result) {
                $ret[] = array();
                $retcount = 0;
                while ($r = $result->fetchRow()) {
                    // Call get_record_from_id for each row.
                    $ret[$retcount] = get_record_from_id($r["id"]);
                    $retcount++;
                }
                $result = $ret;
            } else {
                return -1;
            }
        } else {
            $db->setLimit($rowamount, $rowstart);

            $natural_sort = 'LENGTH(records.name), records.name';
            if ($db_type == 'mysql' || $db_type == 'mysqli' || $db_type == 'sqlite' || $db_type == 'sqlite3') {
                $natural_sort = 'records.name+0<>0 DESC, records.name+0, records.name';
            }
            $sql_sortby = ($sortby == 'name' ? $natural_sort : $sortby . ', ' . $natural_sort);

            $result = $db->query("SELECT id FROM records WHERE domain_id=" . $db->quote($id, 'integer') . " ORDER BY " . $sql_sortby);
            $ret = array();
            if ($result) {
                $ret[] = array();
                $retcount = 0;
                while ($r = $result->fetchRow()) {
                    // Call get_record_from_id for each row.
                    $ret[$retcount] = get_record_from_id($r["id"]);
                    $retcount++;
                }
                $result = $ret;
            } else {
                return -1;
            }
            $result = order_domain_results($result, $sortby);
            return $result;
        }
    } else {
        error(sprintf(ERR_INV_ARG, "get_records_from_domain_id"));
    }
}

/** Sort Domain Records intelligently
 *
 * @param string[] $domains Array of domains
 * @param string $sortby Column to sort by [default='name','type','content','prio','ttl']
 *
 * @return mixed[] array of records detail
 */
function order_domain_results($domains, $sortby) {
    $results = array();
    $soa = array();
    $ns = array();

    foreach ($domains as $key => $domain) {
        switch ($domain['type']) {
            case 'SOA':
                $soa[] = $domain;
                unset($domains[$key]);
                break;
            case 'NS':
                $ns[] = $domain;
                unset($domains[$key]);
                break;
            default:
                continue;
        }
    }

    switch ($sortby) {
        case 'name':
            usort($domains, 'sort_domain_results_by_name');
            break;
        case 'type':
            usort($domains, 'sort_domain_results_by_type');
            break;
        case 'content':
            usort($domains, 'sort_domain_results_by_content');
            break;
        case 'prio':
            usort($domains, 'sort_domain_results_by_prio');
            break;
        case 'ttl':
            usort($domains, 'sort_domain_results_by_ttl');
            break;
        default:
            usort($domains, 'sort_domain_results_by_name');
            break;
    }

    $results = array_merge($soa, $ns);
    $results = array_merge($results, $domains);

    return $results;
}

/** Sort records by name
 *
 * @param mixed[] $a A
 * @param mixed[] $b B
 *
 * @return mixed[] result of strnatcmp
 */
function sort_domain_results_by_name($a, $b) {
    return strnatcmp($a['name'], $b['name']);
}

/** Sort records by type
 *
 * @param mixed[] $a A
 * @param mixed[] $b B
 *
 * @return mixed[] result of strnatcmp
 */
function sort_domain_results_by_type($a, $b) {
    if ($a['type'] != $b['type']) {
        return strnatcmp($a['type'], $b['type']);
    } else {
        return strnatcmp($a['name'], $b['name']);
    }
}

/** Sort records by content
 *
 * @param mixed[] $a A
 * @param mixed[] $b B
 *
 * @return mixed[] result of strnatcmp
 */
function sort_domain_results_by_content($a, $b) {
    if ($a['content'] != $b['content']) {
        return strnatcmp($a['content'], $b['content']);
    } else {
        return strnatcmp($a['name'], $b['name']);
    }
}

/** Sort records by prio
 *
 * @param mixed[] $a A
 * @param mixed[] $b B
 *
 * @return mixed[] result of strnatcmp
 */
function sort_domain_results_by_prio($a, $b) {
    if ($a['prio'] != $b['prio']) {
        return strnatcmp($a['prio'], $b['prio']);
    } else {
        return strnatcmp($a['name'], $b['name']);
    }
}

/** Sort records by TTL
 *
 * @param mixed[] $a A
 * @param mixed[] $b B
 *
 * @return mixed[] result of strnatcmp
 */
function sort_domain_results_by_ttl($a, $b) {
    if ($a['ttl'] != $b['ttl']) {
        return strnatcmp($a['ttl'], $b['ttl']);
    } else {
        return strnatcmp($a['name'], $b['name']);
    }
}

/** Get list of owners for Domain ID
 *
 * @param int $id Domain ID
 *
 * @return mixed[] array of owners [id,fullename]
 */
function get_users_from_domain_id($id) {
    global $db;
    $sqlq = "SELECT owner FROM zones WHERE domain_id =" . $db->quote($id, 'integer');
    $id_owners = $db->query($sqlq);
    if ($id_owners) {
        while ($r = $id_owners->fetchRow()) {
            $fullname = $db->queryOne("SELECT fullname FROM users WHERE id=" . $r['owner']);
            $owners[] = array(
                "id" => $r['owner'],
                "fullname" => $fullname
            );
        }
    } else {
        return -1;
    }
    return $owners;
}

/** Search for Zone or Record
 *
 * @param string $search_string  String to search
 * @param string $perm User permitted to view 'all' or 'own' zones
 * @param string $zone_sortby Column to sort domain results [default='name']
 * @param string $record_sortby Column to sort record results by [default='name']
 * @param boolean $wildcards Add wildcards automatically
 * @param boolean $arpa Search reverse records automatically
 *
 * @return mixed[] 'zones' => array of zones, 'records' => array of records
 */
function search_zone_and_record($search_string, $perm, $zone_sortby = 'name', $record_sortby = 'name', $wildcards = true, $arpa = true) {
    global $db;

    $search_string = trim($search_string);

    $sql_add_from = '';
    $sql_add_where = '';
    $arpa_search = '';

    $return_zones = array();
    $return_records = array();

    if (verify_permission('zone_content_view_others')) {
        $perm_view = "all";
    } elseif (verify_permission('zone_content_view_own')) {
        $perm_view = "own";
    } else {
        $perm_view = "none";
    }

    if (verify_permission('zone_content_edit_others')) {
        $perm_content_edit = "all";
    } elseif (verify_permission('zone_content_edit_own')) {
        $perm_content_edit = "own";
    } else {
        $perm_content_edit = "none";
    }

    if ($perm == "all") {
        $sql_add_from = ", zones, users ";
        $sql_add_where = " AND zones.domain_id = domains.id AND users.id = " . $db->quote($_SESSION['userid'], 'integer');
    }

    if ($perm == "own") {
        $sql_add_from = ", zones, users ";
        $sql_add_where = " AND zones.domain_id = domains.id AND users.id = " . $db->quote($_SESSION['userid'], 'integer') . " AND zones.owner = " . $db->quote($_SESSION['userid'], 'integer');
    }


    if ($arpa) {
        if (preg_match("/^[0-9\.]+$/", $search_string)) {
            $quads = explode('.', $search_string);
            $arpa_search = join('.', array_reverse($quads));
        }
        if (preg_match("/^[0-9a-f]{0,4}:([0-9a-f]{0,4}:){0,6}[0-9a-f]{0,4}$/i", $search_string)) {
            //TODO ipv6 search
        }
    }

    $query = "SELECT
			domains.id AS zid,
			domains.name AS name,
			domains.type AS type,
			domains.master AS master,
                        zones.owner AS owner
			FROM domains" . $sql_add_from . "
			WHERE " . ($arpa_search ? "(" : "") .
            " domains.name LIKE " . $db->quote(($wildcards ? "%" : "") . $search_string . ($wildcards ? "%" : ""), 'text')
            . ($arpa_search ? " OR domains.name LIKE " . $db->quote("%" . $arpa_search . "%in-addr.arpa", 'text') . ")" : "")
            . $sql_add_where . "
                        ORDER BY " . $zone_sortby;

    $response = $db->query($query);
    if (PEAR::isError($response)) {
        error($response->getMessage());
        return false;
    }

    $cached_owners = array();
    while ($r = $response->fetchRow()) {
        $owner = '';
        if (isset($cached_owners[$r['owner']])) {
            $owner = $cached_owners[$r['owner']];
        } else {
            $owner = get_owner_from_id($r['owner']);
            $cached_owners[$r['owner']] = $owner;
        }

        $return_zones[] = array(
            "zid" => $r['zid'],
            "name" => $r['name'],
            "type" => $r['type'],
            "master" => $r['master'],
            "owner" => $owner);
    }

    $sql_add_from = '';
    $sql_add_where = '';

    // Search for matching records

    if ($perm == "own") {
        $sql_add_from = ", zones ";
        $sql_add_where = " AND zones.domain_id = records.domain_id AND zones.owner = " . $db->quote($_SESSION['userid'], 'integer');
    }

    $query = "SELECT
			records.id AS rid,
			records.name AS name,
			records.type AS type,
			records.content AS content,
			records.ttl AS ttl,
			records.prio AS prio,
			records.domain_id AS zid
			FROM records" . $sql_add_from . "
			WHERE (records.name LIKE " . $db->quote(($wildcards ? "%" : "") . $search_string . ($wildcards ? "%" : ""), 'text') . " OR records.content LIKE " . $db->quote(($wildcards ? "%" : "") . $search_string . ($wildcards ? "%" : ""), 'text')
            . ($arpa_search ? " OR records.name LIKE " . $db->quote("%" . $arpa_search . "%in-addr.arpa", 'text') : "")
            . ")"
            . $sql_add_where . "
			ORDER BY " . $record_sortby;

    $response = $db->query($query);
    if (PEAR::isError($response)) {
        error($response->getMessage());
        return false;
    }

    while ($r = $response->fetchRow()) {
        $return_records[] = array(
            "rid" => $r['rid'],
            "name" => $r['name'],
            "type" => $r['type'],
            "content" => $r['content'],
            "ttl" => $r['ttl'],
            "zid" => $r['zid'],
            "prio" => $r['prio']);
    }
    return array('zones' => $return_zones, 'records' => $return_records);
}

/** Get Domain Type for Domain ID
 *
 * @param int $id Domain ID
 *
 * @return string Domain Type [NATIVE,MASTER,SLAVE]
 */
function get_domain_type($id) {
    global $db;
    if (is_numeric($id)) {
        $type = $db->queryOne("SELECT type FROM domains WHERE id = " . $db->quote($id, 'integer'));
        if ($type == "") {
            $type = "NATIVE";
        }
        return $type;
    } else {
        error(sprintf(ERR_INV_ARG, "get_record_from_id", "no or no valid zoneid given"));
    }
}

/** Get Slave Domain's Master
 *
 * @param int $id Domain ID
 *
 * @return string Master server
 */
function get_domain_slave_master($id) {
    global $db;
    if (is_numeric($id)) {
        $slave_master = $db->queryOne("SELECT master FROM domains WHERE type = 'SLAVE' and id = " . $db->quote($id, 'integer'));
        return $slave_master;
    } else {
        error(sprintf(ERR_INV_ARG, "get_domain_slave_master", "no or no valid zoneid given"));
    }
}

/** Change Zone Type
 *
 * @param string $type New Zone Type [NATIVE,MASTER,SLAVE]
 * @param int $id Zone ID
 *
 * @return null
 */
function change_zone_type($type, $id) {
    global $db;
    $add = '';
    if (is_numeric($id)) {
        // It is not really neccesary to clear the field that contains the IP address
        // of the master if the type changes from slave to something else. PowerDNS will
        // ignore the field if the type isn't something else then slave. But then again,
        // it's much clearer this way.
        if ($type != "SLAVE") {
            $add = ", master=" . $db->quote('', 'text');
        }
        $result = $db->query("UPDATE domains SET type = " . $db->quote($type, 'text') . $add . " WHERE id = " . $db->quote($id, 'integer'));
    } else {
        error(sprintf(ERR_INV_ARG, "change_domain_type", "no or no valid zoneid given"));
    }
}

/** Change Slave Zone's Master IP Address
 *
 * @param int $zone_id Zone ID
 * @param string $ip_slave_master Master IP Address
 *
 * @return null
 */
function change_zone_slave_master($zone_id, $ip_slave_master) {
    global $db;
    if (is_numeric($zone_id)) {
        if (are_multipe_valid_ips($ip_slave_master)) {
            $result = $db->query("UPDATE domains SET master = " . $db->quote($ip_slave_master, 'text') . " WHERE id = " . $db->quote($zone_id, 'integer'));
        } else {
            error(sprintf(ERR_INV_ARGC, "change_domain_ip_slave_master", "This is not a valid IPv4 or IPv6 address: $ip_slave_master"));
        }
    } else {
        error(sprintf(ERR_INV_ARG, "change_domain_type", "no or no valid zoneid given"));
    }
}

/** Get Serial for Zone ID
 *
 * @param int $zid Zone ID
 *
 * @return boolean|string Serial Number or false if not found
 */
function get_serial_by_zid($zid) {
    global $db;
    if (is_numeric($zid)) {
        $query = "SELECT content FROM records where TYPE = " . $db->quote('SOA', 'text') . " and domain_id = " . $db->quote($zid, 'integer');
        $rr_soa = $db->queryOne($query);
        if (PEAR::isError($rr_soa)) {
            error($rr_soa->getMessage());
            return false;
        }
        $rr_soa_fields = explode(" ", $rr_soa);
    } else {
        error(sprintf(ERR_INV_ARGC, "get_serial_by_zid", "id must be a number"));
        return false;
    }
    return $rr_soa_fields[2];
}

/** Validate Account is valid string
 *
 * @param string $account Account name alphanumeric and ._-
 *
 * @return boolean true is valid, false otherwise
 */
function validate_account($account) {
    if (preg_match("/^[A-Z0-9._-]+$/i", $account)) {
        return true;
    } else {
        return false;
    }
}

/** Get Zone Template ID for Zone ID
 *
 * @param int $zone_id Zone ID
 *
 * @return int Zone Template ID
 */
function get_zone_template($zone_id) {
    global $db;
    $query = "SELECT zone_templ_id FROM zones WHERE domain_id = " . $db->quote($zone_id, 'integer');
    $zone_templ_id = $db->queryOne($query);
    return $zone_templ_id;
}

/** Update Zone Templatea ID for Zone ID
 *
 * @param int $zone_id Zone ID
 * @param int $new_zone_template_id New Zone Template ID
 *
 * @return boolean true on success, false otherwise
 */
function update_zone_template($zone_id, $new_zone_template_id) {
    global $db;
    $query = "UPDATE zones
			SET zone_templ_id = " . $db->quote($new_zone_template_id, 'integer') . "
			WHERE id = " . $db->quote($zone_id, 'integer');
    $response = $db->query($query);
    if (PEAR::isError($response)) {
        error($response->getMessage());
        return false;
    }
    return true;
}

/** Update All Zone Records for Zone ID with Zone Template
 *
 * @param int $zone_id Zone ID to update
 * @param int $zone_template_id Zone Template ID to use for update
 *
 * @return null
 */
function update_zone_records($zone_id, $zone_template_id) {
    global $db;
    global $dns_ttl;

    if (verify_permission('zone_content_edit_others')) {
        $perm_edit = "all";
    } elseif (verify_permission('zone_content_edit_own')) {
        $perm_edit = "own";
    } else {
        $perm_edit = "none";
    }

    $user_is_zone_owner = verify_user_is_owner_zoneid($zone_id);

    if (verify_permission('zone_master_add')) {
        $zone_master_add = "1";
    }

    if (verify_permission('zone_slave_add')) {
        $zone_slave_add = "1";
    }

    $soa_rec = get_soa_record($zone_id);
    $response = $db->beginTransaction();

    if (0 != $zone_template_id) {
        if ($perm_edit == "all" || ( $perm_edit == "own" && $user_is_zone_owner == "1")) {
            if (is_numeric($zone_id)) {
                $db->exec("DELETE FROM records WHERE id IN (SELECT record_id FROM records_zone_templ WHERE "
                        . "domain_id = " . $db->quote($zone_id, 'integer') . " AND "
                        . "zone_templ_id = " . $db->quote($zone_template_id, 'integer') . ")");
                $db->exec("DELETE FROM records_zone_templ WHERE domain_id = " . $db->quote($zone_id, 'integer'));
            } else {
                error(sprintf(ERR_INV_ARGC, "delete_domain", "id must be a number"));
            }
        } else {
            error(ERR_PERM_DEL_ZONE);
        }

        if ($zone_master_add == "1" || $zone_slave_add == "1") {
            $domain = get_zone_name_from_id($zone_id);
            $now = time();
            $templ_records = get_zone_templ_records($zone_template_id);

            if ($templ_records == -1) {
                return;
            }

            foreach ($templ_records as $r) {
                //fixme: appears to be a bug and regex match should occur against $domain
                if ((preg_match('/in-addr.arpa/i', $zone_id) && ($r["type"] == "NS" || $r["type"] == "SOA")) || (!preg_match('/in-addr.arpa/i', $zone_id))) {
                    $name = parse_template_value($r["name"], $domain);
                    $type = $r["type"];
                    if ($type == "SOA") {
                        $content = get_updated_soa_record($soa_rec);
                        echo $content;
                    } else {
                        $content = parse_template_value($r["content"], $domain);
                    }

                    $ttl = $r["ttl"];
                    $prio = intval($r["prio"]);

                    if (!$ttl) {
                        $ttl = $dns_ttl;
                    }

                    $query = "INSERT INTO records (domain_id, name, type, content, ttl, prio, change_date) VALUES ("
                            . $db->quote($zone_id, 'integer') . ","
                            . $db->quote($name, 'text') . ","
                            . $db->quote($type, 'text') . ","
                            . $db->quote($content, 'text') . ","
                            . $db->quote($ttl, 'integer') . ","
                            . $db->quote($prio, 'integer') . ","
                            . $db->quote($now, 'integer') . ")";
                    $response = $db->exec($query);

                    if ($db_layer == 'MDB2' && ($db_type == 'mysql' || $db_type == 'pgsql')) {
                        $record_id = $db->lastInsertId('records', 'id');
                    } else if ($db_layer == 'PDO' && $db_type == 'pgsql') {
                        $record_id = $db->lastInsertId('records_id_seq');
                    } else {
                        $record_id = $db->lastInsertId();
                    }

                    $query = "INSERT INTO records_zone_templ (domain_id, record_id, zone_templ_id) VALUES ("
                            . $db->quote($zone_id, 'integer') . ","
                            . $db->quote($record_id, 'integer') . ","
                            . $db->quote($zone_template_id, 'integer') . ")";
                    $response = $db->query($query);
                }
            }
        }
    }

    $query = "UPDATE zones
                    SET zone_templ_id = " . $db->quote($zone_template_id, 'integer') . "
                    WHERE domain_id = " . $db->quote($zone_id, 'integer');
    $response = $db->exec($query);

    if (PEAR::isError($response)) {
        $response = $db->rollback();
    } else {
        $response = $db->commit();
    }
}

/** Delete array of domains
 *
 * Deletes a domain by a given id.
 * Function always succeeds. If the field is not found in the database, thats what we want anyway.
 *
 * @param int[] $domains Array of Domain IDs to delete
 *
 * @return boolean true on success, false otherwise
 */
function delete_domains($domains) {
    global $db;
    $error = false;
    $return = false;
    $response = $db->beginTransaction();

    foreach ($domains as $id) {
        if (verify_permission('zone_content_edit_others')) {
            $perm_edit = "all";
        } elseif (verify_permission('zone_content_edit_own')) {
            $perm_edit = "own";
        } else {
            $perm_edit = "none";
        }
        $user_is_zone_owner = verify_user_is_owner_zoneid($id);

        if ($perm_edit == "all" || ( $perm_edit == "own" && $user_is_zone_owner == "1")) {
            if (is_numeric($id)) {
                $zone_type = get_domain_type($id);
                if ($zone_type == 'MASTER') {
                    $zone_name = get_zone_name_from_id($id);
                    dnssec_unsecure_zone($zone_name);
                }

                $db->exec("DELETE FROM zones WHERE domain_id=" . $db->quote($id, 'integer'));
                $db->exec("DELETE FROM domains WHERE id=" . $db->quote($id, 'integer'));
                $db->exec("DELETE FROM records WHERE domain_id=" . $db->quote($id, 'integer'));
                $db->query("DELETE FROM records_zone_templ WHERE domain_id=" . $db->quote($id, 'integer'));
            } else {
                error(sprintf(ERR_INV_ARGC, "delete_domains", "id must be a number"));
                $error = true;
            }
        } else {
            error(ERR_PERM_DEL_ZONE);
            $error = true;
        }
    }

    if (PEAR::isError($response)) {
        $response = $db->rollback();
        $commit = false;
    } else {
        $response = $db->commit();
        $commit = true;
    }

    if (true == $commit && false == $error) {
        $return = true;
    }

    return $return;
}

/** Check if record exists
 *
 * @param string $name Record name
 *
 * @return boolean true on success, false on failure
 */
function record_name_exists($name) {
    global $db;
    $query = "SELECT COUNT(id) FROM records WHERE name = " . $db->quote($name, 'text');
    $count = $db->queryOne($query);
    return ($count == "1" ? true : false);
}

/** Return domain level for given name
 *
 * @param string $name Zone name
 *
 * @return int domain level
 */
function get_domain_level($name) {
    return substr_count($name, '.') + 1;
}

/** Return domain second level domain for given name
 *
 * @param string $name Zone name
 *
 * @return string 2nd level domain name
 */
function get_second_level_domain($name) {
    $domain_parts = explode('.', $name);
    $domain_parts = array_reverse($domain_parts);
    return $domain_parts[1] . '.' . $domain_parts[0];
}

/** Get zone list which use templates
 *
 * @param resource $db DB link
 *
 * @return mixed[] Array with domain and template ids
 */
function get_zones_with_templates($db) {
    $query = "SELECT id, domain_id, zone_templ_id FROM zones WHERE zone_templ_id <> 0";
    $result = $db->query($query);
    $zones = array();
    while ($zone = $result->fetchRow()) {
        $zones[]=$zone;
    }
    return $zones;
}

/** Get records by domain id
 *
 *
 */
function get_records_by_domain_id($db, $domain_id) {
    $query = "SELECT id, name, type, content FROM records WHERE domain_id = " . $db->quote($domain_id, 'integer');
    $result = $db->query($query);
    $records = array();
    while ($zone_records = $result->fetchRow()) {
        $records[]=$zone_records;
    }
    return $records;
}