DNS-Module erstellen: Unterschied zwischen den Versionen

Aus sourceDESK Wiki
Wechseln zu: Navigation, Suche
(Die Seite wurde neu angelegt: „== Grundaufbau == ... == Methoden == ...“)
 
 
Zeile 1: Zeile 1:
 +
Entwickler können ein neues [[DNS-System]] an sourceDESK relativ leicht anbinden. Dazu muss ein DNS-Modul erstellt werden.
 +
 
== Grundaufbau ==
 
== Grundaufbau ==
...
+
Ein DNS-Modul bekommt ein eigenes Verzeichnis unter ''modules/dns''. In diesem Verzeichnis muss sich eine PHP-Datei mit dem gleichen Namen wie das Verzeichnis und der Endung ''.php'' befinden. In dieser PHP-Datei wird eine Klasse definiert, die von der Klasse ''DNSProvider'' erbt.
 +
 
 +
== Attribute ==
 +
Es werden mehrere Attribute innerhalb der erstellten Klasse benötigt, die höchstens ''protected'' sein dürfen:
 +
 
 +
* ''$short'' gibt den Kurznamen des Moduls an, das entspricht dem Verzeichnis- bzw. Dateinamen
 +
* ''$name'' gibt einen Anzeigenamen für das Modul an
 +
* ''$version'' definiert die Version des Moduls
  
 
== Methoden ==
 
== Methoden ==
...
+
Folgende Methoden können definiert werden:
 +
 
 +
* ''getSettings()'' gibt die verfügbaren Modul-Einstellungen als Array zurück - auf sie kann nachher über das Objekt (stdClass) ''$this->options'' zugegriffen werden
 +
* ''addZone($domain, Array $ns, $defaultIp, $defaultIp6 = null)'' soll eine neue Zone anlegen
 +
* ''getZones()'' soll eine Liste der vorhandenen Zonennamen zurückliefern
 +
* ''getZone($domain, $force = false)'' soll die Records einer Zone abrufen
 +
* ''recordTypes($admin = false)'' soll die verfügbaren RR-Typen zurückgeben
 +
* ''addRecord($domain, $record, $hidden, $admin)'' soll einen RR anlegen
 +
* ''refreshZone($domain)'' soll die Zone auf allen Servern neu laden
 +
* ''editRecord($domain, $record, $new, $force)'' soll einen Record ändern
 +
* ''editHidden($domain, $record, $hidden)'' soll die Hidden-Eigenschaft für einen Record setzen
 +
* ''removeRecord($domain, $record, $force)'' soll einen Record löschen
 +
* ''removeZone($domain)'' soll eine Zone löschen
 +
* ''addDynDNS($domain, $sub, $password)'' soll einen DynDNS-Record erstellen
 +
* ''getDynDNS($domain)'' soll die DynDNS-Records einer Zone auflisten
 +
* ''delDynDNS($domain, $sub)'' soll einen DynDNS-Record löschen
 +
* ''updateDynDNS($domain, $password, $ip, $ip6)'' soll einen DynDNS-Record aktualisieren
 +
* ''pushToSlave($domain)'' soll eine Zone an die Slave-Server pushen
 +
 
 +
== Beispiel-Code ==
 +
<source lang="php">
 +
<?php
 +
 
 +
class PowerDNS extends DNSProvider {
 +
protected $short = "powerdns";
 +
protected $name = "PowerDNS (MySQL/MariaDB)";
 +
protected $version = "1.0";
 +
 
 +
public function getSettings() {
 +
return Array(
 +
"db_host" => Array("type" => "text", "name" => "Datenbank-Host"),
 +
"db_user" => Array("type" => "text", "name" => "Datenbank-Benutzer"),
 +
"db_password" => Array("type" => "password", "name" => "Datenbank-Passwort"),
 +
"db_name" => Array("type" => "text", "name" => "Datenbank"),
 +
"hint" => Array("name" => "Hinweis", "help" => "Es muss die PowerDNS-Datenbankstruktur vorhanden sein. In der Tabelle records muss noch eine Spalte 'hidden' vorhanden sein (Integer, L&auml;nge: 1, Standard: 0). Au&szlig;erdem noch eine weitere Spalte 'dyndns' vom Typ Varchar (L&auml;nge: 255, Standard: leer).", "type" => "hint"),
 +
"ws_ipv4" => Array("type" => "text", "name" => "IPv4-Adresse des Webservers", "placeholder" => "Nur wenn URL-Weiterleitungen konfiguriert sind (siehe Wiki)"),
 +
"ws_ipv6" => Array("type" => "text", "name" => "IPv6-Adresse des Webservers", "placeholder" => "Optional"),
 +
);
 +
}
 +
 
 +
public function addZone($domain, Array $ns, $ip, $ip6 = null) {
 +
global $CFG;
 +
 
 +
$domain = $this->idn($domain);
 +
 
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
if ($ip == "8.8.8.8") {
 +
$ip = "5.189.157.67";
 +
}
 +
 
 +
if (!$db->query("INSERT INTO domains (`name`, `type`) VALUES ('" . $db->real_escape_string($domain) . "', 'MASTER')")) {
 +
return false;
 +
}
 +
 
 +
$zoneId = $db->insert_id;
 +
 
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'SOA', '" . $ns[0] . " " . str_replace("@", ".", $CFG['PAGEMAIL']) . ". " . date("Ymd") . "01 3600 900 604800 3600', 3600, 0)");
 +
 
 +
foreach ($ns as $n) {
 +
if (!empty($n)) {
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'NS', '$n', 3600, 0)");
 +
}
 +
}
 +
 
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'A', '" . $db->real_escape_string($ip) . "', 3600, 0)");
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, 'www." . $db->real_escape_string($domain) . "', 'A', '" . $db->real_escape_string($ip) . "', 3600, 0)");
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '*." . $db->real_escape_string($domain) . "', 'A', '" . $db->real_escape_string($ip) . "', 3600, 0)");
 +
 
 +
if (filter_var($ip6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'AAAA', '" . $db->real_escape_string($ip6) . "', 3600, 0)");
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, 'www." . $db->real_escape_string($domain) . "', 'AAAA', '" . $db->real_escape_string($ip6) . "', 3600, 0)");
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '*." . $db->real_escape_string($domain) . "', 'AAAA', '" . $db->real_escape_string($ip6) . "', 3600, 0)");
 +
}
 +
 
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'MX', '" . $db->real_escape_string($domain) . "', 3600, 0)");
 +
 
 +
return true;
 +
}
 +
 
 +
public function idn($domain) {
 +
$idn = new IdnaConvert;
 +
return $idn->encode($domain);
 +
}
 +
 
 +
private function getConnection() {
 +
@$db = new MySQLi($this->options->db_host, $this->options->db_user, $this->options->db_password, $this->options->db_name);
 +
return $db->connect_errno ? false : $db;
 +
}
 +
 
 +
public function getZones() {
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT name, id FROM domains ORDER BY name ASC");
 +
$arr = Array();
 +
 
 +
while ($row = $sql->fetch_object()) {
 +
$arr[$this->idd($row->name)] = $db->query("SELECT COUNT(*) AS c FROM records WHERE domain_id = {$row->id}")->fetch_object()->c;
 +
}
 +
 
 +
return $arr;
 +
}
 +
 
 +
public function idd($domain) {
 +
$idn = new IdnaConvert;
 +
return $idn->decode($domain);
 +
}
 +
 
 +
public function getZone($domain, $force = 0) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
 
 +
$typeOrder = $force ? "`type` = 'SOA' DESC, `type` = 'NS' DESC, " : "";
 +
foreach ($this->recordTypes() as $t) {
 +
$typeOrder .= "`type` = '" . $t . "' DESC, ";
 +
}
 +
 
 +
$typeOrder = rtrim($typeOrder, ", ");
 +
$hidden = $force ? " AND hidden < 2" : " AND hidden = 0 AND dyndns = ''";
 +
 
 +
$sql = $db->query("SELECT * FROM records WHERE domain_id = " . intval($id) . "$hidden ORDER BY $typeOrder, name ASC");
 +
$records = Array();
 +
while ($row = $sql->fetch_object()) {
 +
if (in_array($row->type, $this->recordTypes()) || $force) {
 +
$records[$row->id] = Array($this->trimDomain($row->name, $domain), $row->type, $this->idd($row->content), $row->ttl, $row->prio, $row->hidden, $row->dyndns);
 +
}
 +
}
 +
 
 +
$sql = $db->query("SELECT * FROM redirects WHERE hostname LIKE '%" . $db->real_escape_string($domain) . "' ORDER BY hostname ASC");
 +
while ($row = $sql->fetch_object()) {
 +
$sql2 = $db->query("SELECT * FROM records WHERE domain_id = " . intval($id) . " AND hidden = 2 AND name LIKE '" . $db->real_escape_string($row->hostname) . "' LIMIT 1");
 +
if ($sql2->num_rows != 1) {
 +
continue;
 +
}
 +
 
 +
$i = $sql2->fetch_object();
 +
 
 +
$records[$i->id] = Array($this->trimDomain($row->hostname, $domain), $row->type == "REDIRECT" ? "URL" : "IFRAME", $row->target, $i->ttl, $i->prio, 0, 0);
 +
}
 +
 
 +
return $records;
 +
}
 +
 
 +
public function recordTypes($admin = false) {
 +
if (!$admin) {
 +
$a = Array("MX", "A", "AAAA", "CNAME", "URL", "IFRAME", "SPF", "SRV", "TXT", "AFSDB", "CERT", "DHCID", "DLV", "DNSKEY", "DS", "EUI48", "EUI64", "HINFO", "IPSECKEY", "KEY", "KX", "LOC", "MINFO", "MR", "NAPTR", "NSEC", "NSEC3", "NSEC3PARAM", "OPT", "PTR", "RKEY", "RP", "RRSIG", "SSHFP", "TLSA", "TSIG", "WKS");
 +
}
 +
 
 +
$a = Array("SOA", "NS", "MX", "A", "AAAA", "CNAME", "URL", "IFRAME", "SPF", "SRV", "TXT", "AFSDB", "CERT", "DHCID", "DLV", "DNSKEY", "DS", "EUI48", "EUI64", "HINFO", "IPSECKEY", "KEY", "KX", "LOC", "MINFO", "MR", "NAPTR", "NSEC", "NSEC3", "NSEC3PARAM", "OPT", "PTR", "RKEY", "RP", "RRSIG", "SSHFP", "TLSA", "TSIG", "WKS");
 +
 
 +
if (empty($this->options->ws_ipv4) || !filter_var($this->options->ws_ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
 +
unset($a[array_search("URL", $a)]);
 +
unset($a[array_search("IFRAME", $a)]);
 +
}
 +
 
 +
return $a;
 +
}
 +
 
 +
private function trimDomain($text, $domain, $r = true) {
 +
if (substr($text, strlen("." . $domain) / -1) == "." . $domain) {
 +
return substr($text, 0, strlen("." . $domain) / -1);
 +
}
 +
 
 +
if (substr($text, strlen($domain) / -1) == $domain) {
 +
return substr($text, 0, strlen($domain) / -1);
 +
}
 +
 
 +
if (!$r) {
 +
return $text;
 +
}
 +
 
 +
return $this->trimDomain($text, $this->idn($domain), false);
 +
}
 +
 
 +
public function addRecord($domain, $record, $hidden = 0, $admin = true) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
 
 +
$record = $this->sanitizeRecord($domain, $record, $admin);
 +
if (!$record) {
 +
return false;
 +
}
 +
 
 +
$name = $db->real_escape_string($record[0]);
 +
$type = $db->real_escape_string($record[1]);
 +
$content = $db->real_escape_string($this->idn($record[2]));
 +
$ttl = $db->real_escape_string($record[3]);
 +
$prio = $db->real_escape_string($record[4]);
 +
 
 +
if ($type == "URL" || $type == "IFRAME") {
 +
$type = "A";
 +
$hidden = 2;
 +
$content = $this->options->ws_ipv4;
 +
 
 +
if (!empty($this->options->ws_ipv6) && filter_var($this->options->ws_ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
 +
$this->addRecord($domain, Array($record[0], "AAAA", $this->options->ws_ipv6, $record[3], $record[4]), 2);
 +
}
 +
 
 +
$db->query("INSERT INTO redirects (hostname, type, target) VALUES ('" . $db->real_escape_string($record[0]) . "', '" . ($record[1] == "URL" ? "REDIRECT" : "FRAME") . "', '" . $db->real_escape_string($record[2]) . "')");
 +
}
 +
 
 +
if ($db->query("INSERT INTO `records` (`name`, `type`, `content`, `ttl`, `prio`, `domain_id`, `hidden`) VALUES ('$name', '$type', '$content', $ttl, $prio, " . intval($id) . ", $hidden)")) {
 +
$record[0] = $this->trimDomain($record[0], $domain);
 +
$this->refreshZone($domain);
 +
return $record;
 +
}
 +
 
 +
return false;
 +
}
 +
 
 +
private function sanitizeRecord($domain, $record, $admin = false) {
 +
$domain = $this->idn($domain);
 +
$record[0] = $this->trimDomain($record[0], $domain);
 +
 
 +
if (!ctype_alnum(str_replace(Array(".", "-", "_", '@', '*'), "", $record[0]) . "a")) {
 +
return false;
 +
}
 +
 
 +
if ($record[0] == "@") {
 +
$record[0] = "";
 +
}
 +
 
 +
if (!empty($record[0])) {
 +
$record[0] .= ".";
 +
}
 +
 
 +
$record[0] .= $domain;
 +
 
 +
if (!in_array($record[1], $this->recordTypes(), $admin)) {
 +
return false;
 +
}
 +
 
 +
if ($record[1] == "A" && !filter_var($record[2], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
 +
return false;
 +
}
 +
 
 +
if ($record[1] == "AAAA" && !filter_var($record[2], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
 +
return false;
 +
}
 +
 
 +
if (!is_numeric($record[3]) || $record[3] < 180) {
 +
$record[3] = 3600;
 +
}
 +
 
 +
if (!is_numeric($record[4]) || $record[4] < 0) {
 +
$record[4] = 0;
 +
}
 +
 
 +
return $record;
 +
}
 +
 
 +
private function refreshZone($domain) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT * FROM records WHERE name = '" . $db->real_escape_string($domain) . "' AND type = 'SOA'");
 +
while ($row = $sql->fetch_object()) {
 +
$ex = explode(" ", $row->content);
 +
$old = $ex[2];
 +
$soa = date("Ymd") . "01";
 +
while ($old >= $soa) {
 +
$soa++;
 +
}
 +
 
 +
$ex[2] = $soa;
 +
$db->query("UPDATE records SET content = '" . $db->real_escape_string(implode(" ", $ex)) . "' WHERE id = {$row->id}");
 +
}
 +
}
 +
 
 +
public function editRecord($domain, $record, $new, $force = 0) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
 
 +
$new = $this->sanitizeRecord($domain, $new);
 +
if (!$new) {
 +
return false;
 +
}
 +
 
 +
$name = $db->real_escape_string($new[0]);
 +
$type = $db->real_escape_string($new[1]);
 +
$content = $db->real_escape_string($this->idn($new[2]));
 +
$ttl = $db->real_escape_string($new[3]);
 +
$prio = $db->real_escape_string($new[4]);
 +
$hidden = $force ? "" : " AND hidden != -1 AND dyndns = ''";
 +
 
 +
$sql = $db->query("SELECT * FROM records WHERE id = " . intval($record) . " AND domain_id = " . intval($id));
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$info = $sql->fetch_object();
 +
 
 +
if ($type == "URL" || $type == "IFRAME") {
 +
if ($info->hidden == 2) {
 +
$db->query("UPDATE redirects SET type = '" . ($type == "URL" ? "REDIRECT" : "FRAME") . "', target = '" . $db->real_escape_string($content) . "' WHERE hostname LIKE '" . $db->real_escape_string($info->name) . "'");
 +
} else {
 +
$content = $db->real_escape_string($this->options->ws_ipv4);
 +
$db->query("UPDATE records SET `name` = '$name', `type` = 'A', `content` = '$content', `ttl` = $ttl, `prio` = $prio, `hidden` = 2 WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . "$hidden LIMIT 1");
 +
 
 +
if (!empty($this->options->ws_ipv6) && filter_var($this->options->ws_ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
 +
$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`, `hidden`) VALUES (" . intval($id) . ", '$name', 'AAAA', '" . $db->real_escape_string($this->options->ws_ipv6) . "', $ttl, $prio, 2)");
 +
}
 +
 
 +
$db->query("INSERT INTO redirects (hostname, type, target) VALUES ('" . $db->real_escape_string($name) . "', '" . ($type == "URL" ? "REDIRECT" : "FRAME") . "', '" . $db->real_escape_string($new[2]) . "')");
 +
}
 +
 
 +
return true;
 +
} else if ($info->hidden == 2) {
 +
$db->query("DELETE FROM redirects WHERE hostname LIKE '" . $db->real_escape_string($info->name) . "'");
 +
 
 +
$db->query("DELETE FROM records WHERE name LIKE '" . $db->real_escape_string($info->name) . "' AND type = 'AAAA' AND content = '" . $db->real_escape_string($this->options->ws_ipv6) . "' AND id != " . intval($record));
 +
$db->query("DELETE FROM records WHERE name LIKE '" . $db->real_escape_string($info->name) . "' AND type = 'A' AND content = '" . $db->real_escape_string($this->options->ws_ipv4) . "' AND id != " . intval($record));
 +
 
 +
$db->query("UPDATE records SET `hidden` = 0 WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . "$hidden LIMIT 1");
 +
}
 +
 
 +
$this->refreshZone($domain);
 +
if ($db->query("UPDATE records SET `name` = '$name', `type` = '$type', `content` = '$content', `ttl` = $ttl, `prio` = $prio WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . "$hidden LIMIT 1")) {
 +
return $new;
 +
}
 +
 
 +
return false;
 +
}
 +
 
 +
public function editHidden($domain, $record, $hidden = 0) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
 
 +
if ($db->query("UPDATE records SET `hidden` = " . ($hidden ? "1" : "0") . " WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . " LIMIT 1")) {
 +
return true;
 +
}
 +
 
 +
return false;
 +
}
 +
 
 +
public function removeRecord($domain, $record, $force = 0) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
$hidden = $force ? "" : " AND hidden != -1 AND dyndns = ''";
 +
 
 +
$sql = $db->query("SELECT * FROM records WHERE id = " . intval($record) . " AND domain_id = " . intval($id));
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$info = $sql->fetch_object();
 +
 
 +
if ($info->type == "A" && $info->content == $this->options->ws_ipv4) {
 +
$db->query("DELETE FROM records WHERE name LIKE '" . $db->real_escape_string($info->name) . "' AND type = 'AAAA' AND content = '" . $db->real_escape_string($this->options->ws_ipv6) . "'");
 +
$db->query("DELETE FROM redirects WHERE hostname LIKE '" . $db->real_escape_string($info->name) . "'");
 +
} else if ($info->type == "AAAA" && $info->content == $this->options->ws_ipv6) {
 +
$db->query("DELETE FROM records WHERE name LIKE '" . $db->real_escape_string($info->name) . "' AND type = 'A' AND content = '" . $db->real_escape_string($this->options->ws_ipv4) . "'");
 +
$db->query("DELETE FROM redirects WHERE hostname LIKE '" . $db->real_escape_string($info->name) . "'");
 +
}
 +
 
 +
$this->refreshZone($domain);
 +
if ($db->query("DELETE FROM records WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . "$hidden LIMIT 1")) {
 +
return true;
 +
}
 +
 
 +
return false;
 +
}
 +
 
 +
public function removeZone($domain) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
 
 +
return $db->query("DELETE FROM records WHERE domain_id = " . intval($id)) && $db->query("DELETE FROM domains WHERE id = " . intval($id));
 +
}
 +
 
 +
public function addDynDNS($domain, $sub, $password) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
 
 +
$name = $db->real_escape_string($sub) . "." . $db->real_escape_string($domain);
 +
$type = "A";
 +
$content = "127.0.0.1";
 +
$ttl = "180";
 +
$prio = "0";
 +
$password = $db->real_escape_string($password);
 +
 
 +
if ($db->query("SELECT 1 FROM `records` WHERE `name` = '$name' AND `dyndns` != ''")->num_rows > 0) {
 +
return false;
 +
}
 +
 
 +
$db->query("INSERT INTO `records` (`name`, `type`, `content`, `ttl`, `prio`, `domain_id`, `dyndns`) VALUES ('$name', '$type', '$content', $ttl, $prio, " . intval($id) . ", '$password')");
 +
 
 +
$type = "AAAA";
 +
$content = "::1";
 +
 
 +
$db->query("INSERT INTO `records` (`name`, `type`, `content`, `ttl`, `prio`, `domain_id`, `dyndns`) VALUES ('$name', '$type', '$content', $ttl, $prio, " . intval($id) . ", '$password')");
 +
 
 +
return true;
 +
}
 +
 
 +
public function getDynDNS($domain) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
 
 +
$a = Array();
 +
$sql = $db->query("SELECT * FROM `records` WHERE `type` = 'A' AND `domain_id` = $id AND `dyndns` != ''");
 +
while ($row = $sql->fetch_object()) {
 +
array_push($a, Array($this->trimDomain($row->name, $domain), $row->content, $db->query("SELECT * FROM `records` WHERE `type` = 'AAAA' AND `domain_id` = $id AND `dyndns` != ''")->fetch_object()->content, $row->dyndns));
 +
}
 +
 
 +
return $a;
 +
}
 +
 
 +
public function delDynDNS($domain, $sub) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
 +
if ($sql->num_rows != 1) {
 +
return false;
 +
}
 +
 
 +
$id = $sql->fetch_object()->id;
 +
 
 +
$name = $db->real_escape_string($sub) . "." . $db->real_escape_string($domain);
 +
$db->query("DELETE FROM `records` WHERE `domain_id` = $id AND (`type` = 'A' OR `type` = 'AAAA') AND `dyndns` != '' AND `name` = '$name'");
 +
}
 +
 
 +
public function updateDynDNS($domain, $password, $ip, $ip6) {
 +
$domain = $this->idn($domain);
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
if (!empty($ip) && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
 +
$db->query("UPDATE `records` SET `content` = '" . $db->real_escape_string($ip) . "' WHERE `dyndns` = '" . $db->real_escape_string($password) . "' AND name = '" . $db->real_escape_string($domain) . "' AND `type` = 'A'");
 +
}
 +
 
 +
if (!empty($ip6) && filter_var($ip6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
 +
$db->query("UPDATE `records` SET `content` = '" . $db->real_escape_string($ip6) . "' WHERE `dyndns` = '" . $db->real_escape_string($password) . "' AND name = '" . $db->real_escape_string($domain) . "' AND `type` = 'AAAA'");
 +
}
 +
 
 +
}
 +
 
 +
public function pushToSlave($domain) {
 +
$db = $this->getConnection();
 +
if (!$db) {
 +
return false;
 +
}
 +
 
 +
$db->query("UPDATE domains SET notified_serial = 0 WHERE name = '" . $db->real_escape_string($domain) . "' LIMIT 1");
 +
return (bool) $db->affected_rows;
 +
}
 +
}
 +
</source>

Aktuelle Version vom 10. Oktober 2018, 08:48 Uhr

Entwickler können ein neues DNS-System an sourceDESK relativ leicht anbinden. Dazu muss ein DNS-Modul erstellt werden.

Grundaufbau

Ein DNS-Modul bekommt ein eigenes Verzeichnis unter modules/dns. In diesem Verzeichnis muss sich eine PHP-Datei mit dem gleichen Namen wie das Verzeichnis und der Endung .php befinden. In dieser PHP-Datei wird eine Klasse definiert, die von der Klasse DNSProvider erbt.

Attribute

Es werden mehrere Attribute innerhalb der erstellten Klasse benötigt, die höchstens protected sein dürfen:

  • $short gibt den Kurznamen des Moduls an, das entspricht dem Verzeichnis- bzw. Dateinamen
  • $name gibt einen Anzeigenamen für das Modul an
  • $version definiert die Version des Moduls

Methoden

Folgende Methoden können definiert werden:

  • getSettings() gibt die verfügbaren Modul-Einstellungen als Array zurück - auf sie kann nachher über das Objekt (stdClass) $this->options zugegriffen werden
  • addZone($domain, Array $ns, $defaultIp, $defaultIp6 = null) soll eine neue Zone anlegen
  • getZones() soll eine Liste der vorhandenen Zonennamen zurückliefern
  • getZone($domain, $force = false) soll die Records einer Zone abrufen
  • recordTypes($admin = false) soll die verfügbaren RR-Typen zurückgeben
  • addRecord($domain, $record, $hidden, $admin) soll einen RR anlegen
  • refreshZone($domain) soll die Zone auf allen Servern neu laden
  • editRecord($domain, $record, $new, $force) soll einen Record ändern
  • editHidden($domain, $record, $hidden) soll die Hidden-Eigenschaft für einen Record setzen
  • removeRecord($domain, $record, $force) soll einen Record löschen
  • removeZone($domain) soll eine Zone löschen
  • addDynDNS($domain, $sub, $password) soll einen DynDNS-Record erstellen
  • getDynDNS($domain) soll die DynDNS-Records einer Zone auflisten
  • delDynDNS($domain, $sub) soll einen DynDNS-Record löschen
  • updateDynDNS($domain, $password, $ip, $ip6) soll einen DynDNS-Record aktualisieren
  • pushToSlave($domain) soll eine Zone an die Slave-Server pushen

Beispiel-Code

<?php
 
class PowerDNS extends DNSProvider {
	protected $short = "powerdns";
	protected $name = "PowerDNS (MySQL/MariaDB)";
	protected $version = "1.0";
 
	public function getSettings() {
		return Array(
			"db_host" => Array("type" => "text", "name" => "Datenbank-Host"),
			"db_user" => Array("type" => "text", "name" => "Datenbank-Benutzer"),
			"db_password" => Array("type" => "password", "name" => "Datenbank-Passwort"),
			"db_name" => Array("type" => "text", "name" => "Datenbank"),
			"hint" => Array("name" => "Hinweis", "help" => "Es muss die PowerDNS-Datenbankstruktur vorhanden sein. In der Tabelle records muss noch eine Spalte 'hidden' vorhanden sein (Integer, L&auml;nge: 1, Standard: 0). Au&szlig;erdem noch eine weitere Spalte 'dyndns' vom Typ Varchar (L&auml;nge: 255, Standard: leer).", "type" => "hint"),
			"ws_ipv4" => Array("type" => "text", "name" => "IPv4-Adresse des Webservers", "placeholder" => "Nur wenn URL-Weiterleitungen konfiguriert sind (siehe Wiki)"),
			"ws_ipv6" => Array("type" => "text", "name" => "IPv6-Adresse des Webservers", "placeholder" => "Optional"),
		);
	}
 
	public function addZone($domain, Array $ns, $ip, $ip6 = null) {
		global $CFG;
 
		$domain = $this->idn($domain);
 
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		if ($ip == "8.8.8.8") {
			$ip = "5.189.157.67";
		}
 
		if (!$db->query("INSERT INTO domains (`name`, `type`) VALUES ('" . $db->real_escape_string($domain) . "', 'MASTER')")) {
			return false;
		}
 
		$zoneId = $db->insert_id;
 
		$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'SOA', '" . $ns[0] . " " . str_replace("@", ".", $CFG['PAGEMAIL']) . ". " . date("Ymd") . "01 3600 900 604800 3600', 3600, 0)");
 
		foreach ($ns as $n) {
			if (!empty($n)) {
				$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'NS', '$n', 3600, 0)");
			}
		}
 
		$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'A', '" . $db->real_escape_string($ip) . "', 3600, 0)");
		$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, 'www." . $db->real_escape_string($domain) . "', 'A', '" . $db->real_escape_string($ip) . "', 3600, 0)");
		$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '*." . $db->real_escape_string($domain) . "', 'A', '" . $db->real_escape_string($ip) . "', 3600, 0)");
 
		if (filter_var($ip6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
			$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'AAAA', '" . $db->real_escape_string($ip6) . "', 3600, 0)");
			$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, 'www." . $db->real_escape_string($domain) . "', 'AAAA', '" . $db->real_escape_string($ip6) . "', 3600, 0)");
			$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '*." . $db->real_escape_string($domain) . "', 'AAAA', '" . $db->real_escape_string($ip6) . "', 3600, 0)");
		}
 
		$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`) VALUES ($zoneId, '" . $db->real_escape_string($domain) . "', 'MX', '" . $db->real_escape_string($domain) . "', 3600, 0)");
 
		return true;
	}
 
	public function idn($domain) {
		$idn = new IdnaConvert;
		return $idn->encode($domain);
	}
 
	private function getConnection() {
		@$db = new MySQLi($this->options->db_host, $this->options->db_user, $this->options->db_password, $this->options->db_name);
		return $db->connect_errno ? false : $db;
	}
 
	public function getZones() {
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT name, id FROM domains ORDER BY name ASC");
		$arr = Array();
 
		while ($row = $sql->fetch_object()) {
			$arr[$this->idd($row->name)] = $db->query("SELECT COUNT(*) AS c FROM records WHERE domain_id = {$row->id}")->fetch_object()->c;
		}
 
		return $arr;
	}
 
	public function idd($domain) {
		$idn = new IdnaConvert;
		return $idn->decode($domain);
	}
 
	public function getZone($domain, $force = 0) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
 
		$typeOrder = $force ? "`type` = 'SOA' DESC, `type` = 'NS' DESC, " : "";
		foreach ($this->recordTypes() as $t) {
			$typeOrder .= "`type` = '" . $t . "' DESC, ";
		}
 
		$typeOrder = rtrim($typeOrder, ", ");
		$hidden = $force ? " AND hidden < 2" : " AND hidden = 0 AND dyndns = ''";
 
		$sql = $db->query("SELECT * FROM records WHERE domain_id = " . intval($id) . "$hidden ORDER BY $typeOrder, name ASC");
		$records = Array();
		while ($row = $sql->fetch_object()) {
			if (in_array($row->type, $this->recordTypes()) || $force) {
				$records[$row->id] = Array($this->trimDomain($row->name, $domain), $row->type, $this->idd($row->content), $row->ttl, $row->prio, $row->hidden, $row->dyndns);
			}
		}
 
		$sql = $db->query("SELECT * FROM redirects WHERE hostname LIKE '%" . $db->real_escape_string($domain) . "' ORDER BY hostname ASC");
		while ($row = $sql->fetch_object()) {
			$sql2 = $db->query("SELECT * FROM records WHERE domain_id = " . intval($id) . " AND hidden = 2 AND name LIKE '" . $db->real_escape_string($row->hostname) . "' LIMIT 1");
			if ($sql2->num_rows != 1) {
				continue;
			}
 
			$i = $sql2->fetch_object();
 
			$records[$i->id] = Array($this->trimDomain($row->hostname, $domain), $row->type == "REDIRECT" ? "URL" : "IFRAME", $row->target, $i->ttl, $i->prio, 0, 0);
		}
 
		return $records;
	}
 
	public function recordTypes($admin = false) {
		if (!$admin) {
			$a = Array("MX", "A", "AAAA", "CNAME", "URL", "IFRAME", "SPF", "SRV", "TXT", "AFSDB", "CERT", "DHCID", "DLV", "DNSKEY", "DS", "EUI48", "EUI64", "HINFO", "IPSECKEY", "KEY", "KX", "LOC", "MINFO", "MR", "NAPTR", "NSEC", "NSEC3", "NSEC3PARAM", "OPT", "PTR", "RKEY", "RP", "RRSIG", "SSHFP", "TLSA", "TSIG", "WKS");
		}
 
		$a = Array("SOA", "NS", "MX", "A", "AAAA", "CNAME", "URL", "IFRAME", "SPF", "SRV", "TXT", "AFSDB", "CERT", "DHCID", "DLV", "DNSKEY", "DS", "EUI48", "EUI64", "HINFO", "IPSECKEY", "KEY", "KX", "LOC", "MINFO", "MR", "NAPTR", "NSEC", "NSEC3", "NSEC3PARAM", "OPT", "PTR", "RKEY", "RP", "RRSIG", "SSHFP", "TLSA", "TSIG", "WKS");
 
		if (empty($this->options->ws_ipv4) || !filter_var($this->options->ws_ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
			unset($a[array_search("URL", $a)]);
			unset($a[array_search("IFRAME", $a)]);
		}
 
		return $a;
	}
 
	private function trimDomain($text, $domain, $r = true) {
		if (substr($text, strlen("." . $domain) / -1) == "." . $domain) {
			return substr($text, 0, strlen("." . $domain) / -1);
		}
 
		if (substr($text, strlen($domain) / -1) == $domain) {
			return substr($text, 0, strlen($domain) / -1);
		}
 
		if (!$r) {
			return $text;
		}
 
		return $this->trimDomain($text, $this->idn($domain), false);
	}
 
	public function addRecord($domain, $record, $hidden = 0, $admin = true) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
 
		$record = $this->sanitizeRecord($domain, $record, $admin);
		if (!$record) {
			return false;
		}
 
		$name = $db->real_escape_string($record[0]);
		$type = $db->real_escape_string($record[1]);
		$content = $db->real_escape_string($this->idn($record[2]));
		$ttl = $db->real_escape_string($record[3]);
		$prio = $db->real_escape_string($record[4]);
 
		if ($type == "URL" || $type == "IFRAME") {
			$type = "A";
			$hidden = 2;
			$content = $this->options->ws_ipv4;
 
			if (!empty($this->options->ws_ipv6) && filter_var($this->options->ws_ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
				$this->addRecord($domain, Array($record[0], "AAAA", $this->options->ws_ipv6, $record[3], $record[4]), 2);
			}
 
			$db->query("INSERT INTO redirects (hostname, type, target) VALUES ('" . $db->real_escape_string($record[0]) . "', '" . ($record[1] == "URL" ? "REDIRECT" : "FRAME") . "', '" . $db->real_escape_string($record[2]) . "')");
		}
 
		if ($db->query("INSERT INTO `records` (`name`, `type`, `content`, `ttl`, `prio`, `domain_id`, `hidden`) VALUES ('$name', '$type', '$content', $ttl, $prio, " . intval($id) . ", $hidden)")) {
			$record[0] = $this->trimDomain($record[0], $domain);
			$this->refreshZone($domain);
			return $record;
		}
 
		return false;
	}
 
	private function sanitizeRecord($domain, $record, $admin = false) {
		$domain = $this->idn($domain);
		$record[0] = $this->trimDomain($record[0], $domain);
 
		if (!ctype_alnum(str_replace(Array(".", "-", "_", '@', '*'), "", $record[0]) . "a")) {
			return false;
		}
 
		if ($record[0] == "@") {
			$record[0] = "";
		}
 
		if (!empty($record[0])) {
			$record[0] .= ".";
		}
 
		$record[0] .= $domain;
 
		if (!in_array($record[1], $this->recordTypes(), $admin)) {
			return false;
		}
 
		if ($record[1] == "A" && !filter_var($record[2], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
			return false;
		}
 
		if ($record[1] == "AAAA" && !filter_var($record[2], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
			return false;
		}
 
		if (!is_numeric($record[3]) || $record[3] < 180) {
			$record[3] = 3600;
		}
 
		if (!is_numeric($record[4]) || $record[4] < 0) {
			$record[4] = 0;
		}
 
		return $record;
	}
 
	private function refreshZone($domain) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT * FROM records WHERE name = '" . $db->real_escape_string($domain) . "' AND type = 'SOA'");
		while ($row = $sql->fetch_object()) {
			$ex = explode(" ", $row->content);
			$old = $ex[2];
			$soa = date("Ymd") . "01";
			while ($old >= $soa) {
				$soa++;
			}
 
			$ex[2] = $soa;
			$db->query("UPDATE records SET content = '" . $db->real_escape_string(implode(" ", $ex)) . "' WHERE id = {$row->id}");
		}
	}
 
	public function editRecord($domain, $record, $new, $force = 0) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
 
		$new = $this->sanitizeRecord($domain, $new);
		if (!$new) {
			return false;
		}
 
		$name = $db->real_escape_string($new[0]);
		$type = $db->real_escape_string($new[1]);
		$content = $db->real_escape_string($this->idn($new[2]));
		$ttl = $db->real_escape_string($new[3]);
		$prio = $db->real_escape_string($new[4]);
		$hidden = $force ? "" : " AND hidden != -1 AND dyndns = ''";
 
		$sql = $db->query("SELECT * FROM records WHERE id = " . intval($record) . " AND domain_id = " . intval($id));
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$info = $sql->fetch_object();
 
		if ($type == "URL" || $type == "IFRAME") {
			if ($info->hidden == 2) {
				$db->query("UPDATE redirects SET type = '" . ($type == "URL" ? "REDIRECT" : "FRAME") . "', target = '" . $db->real_escape_string($content) . "' WHERE hostname LIKE '" . $db->real_escape_string($info->name) . "'");
			} else {
				$content = $db->real_escape_string($this->options->ws_ipv4);
				$db->query("UPDATE records SET `name` = '$name', `type` = 'A', `content` = '$content', `ttl` = $ttl, `prio` = $prio, `hidden` = 2 WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . "$hidden LIMIT 1");
 
				if (!empty($this->options->ws_ipv6) && filter_var($this->options->ws_ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
					$db->query("INSERT INTO records (`domain_id`, `name`, `type`, `content`, `ttl`, `prio`, `hidden`) VALUES (" . intval($id) . ", '$name', 'AAAA', '" . $db->real_escape_string($this->options->ws_ipv6) . "', $ttl, $prio, 2)");
				}
 
				$db->query("INSERT INTO redirects (hostname, type, target) VALUES ('" . $db->real_escape_string($name) . "', '" . ($type == "URL" ? "REDIRECT" : "FRAME") . "', '" . $db->real_escape_string($new[2]) . "')");
			}
 
			return true;
		} else if ($info->hidden == 2) {
			$db->query("DELETE FROM redirects WHERE hostname LIKE '" . $db->real_escape_string($info->name) . "'");
 
			$db->query("DELETE FROM records WHERE name LIKE '" . $db->real_escape_string($info->name) . "' AND type = 'AAAA' AND content = '" . $db->real_escape_string($this->options->ws_ipv6) . "' AND id != " . intval($record));
			$db->query("DELETE FROM records WHERE name LIKE '" . $db->real_escape_string($info->name) . "' AND type = 'A' AND content = '" . $db->real_escape_string($this->options->ws_ipv4) . "' AND id != " . intval($record));
 
			$db->query("UPDATE records SET `hidden` = 0 WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . "$hidden LIMIT 1");
		}
 
		$this->refreshZone($domain);
		if ($db->query("UPDATE records SET `name` = '$name', `type` = '$type', `content` = '$content', `ttl` = $ttl, `prio` = $prio WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . "$hidden LIMIT 1")) {
			return $new;
		}
 
		return false;
	}
 
	public function editHidden($domain, $record, $hidden = 0) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
 
		if ($db->query("UPDATE records SET `hidden` = " . ($hidden ? "1" : "0") . " WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . " LIMIT 1")) {
			return true;
		}
 
		return false;
	}
 
	public function removeRecord($domain, $record, $force = 0) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
		$hidden = $force ? "" : " AND hidden != -1 AND dyndns = ''";
 
		$sql = $db->query("SELECT * FROM records WHERE id = " . intval($record) . " AND domain_id = " . intval($id));
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$info = $sql->fetch_object();
 
		if ($info->type == "A" && $info->content == $this->options->ws_ipv4) {
			$db->query("DELETE FROM records WHERE name LIKE '" . $db->real_escape_string($info->name) . "' AND type = 'AAAA' AND content = '" . $db->real_escape_string($this->options->ws_ipv6) . "'");
			$db->query("DELETE FROM redirects WHERE hostname LIKE '" . $db->real_escape_string($info->name) . "'");
		} else if ($info->type == "AAAA" && $info->content == $this->options->ws_ipv6) {
			$db->query("DELETE FROM records WHERE name LIKE '" . $db->real_escape_string($info->name) . "' AND type = 'A' AND content = '" . $db->real_escape_string($this->options->ws_ipv4) . "'");
			$db->query("DELETE FROM redirects WHERE hostname LIKE '" . $db->real_escape_string($info->name) . "'");
		}
 
		$this->refreshZone($domain);
		if ($db->query("DELETE FROM records WHERE id = " . intval($record) . " AND domain_id = " . intval($id) . "$hidden LIMIT 1")) {
			return true;
		}
 
		return false;
	}
 
	public function removeZone($domain) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
 
		return $db->query("DELETE FROM records WHERE domain_id = " . intval($id)) && $db->query("DELETE FROM domains WHERE id = " . intval($id));
	}
 
	public function addDynDNS($domain, $sub, $password) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
 
		$name = $db->real_escape_string($sub) . "." . $db->real_escape_string($domain);
		$type = "A";
		$content = "127.0.0.1";
		$ttl = "180";
		$prio = "0";
		$password = $db->real_escape_string($password);
 
		if ($db->query("SELECT 1 FROM `records` WHERE `name` = '$name' AND `dyndns` != ''")->num_rows > 0) {
			return false;
		}
 
		$db->query("INSERT INTO `records` (`name`, `type`, `content`, `ttl`, `prio`, `domain_id`, `dyndns`) VALUES ('$name', '$type', '$content', $ttl, $prio, " . intval($id) . ", '$password')");
 
		$type = "AAAA";
		$content = "::1";
 
		$db->query("INSERT INTO `records` (`name`, `type`, `content`, `ttl`, `prio`, `domain_id`, `dyndns`) VALUES ('$name', '$type', '$content', $ttl, $prio, " . intval($id) . ", '$password')");
 
		return true;
	}
 
	public function getDynDNS($domain) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
 
		$a = Array();
		$sql = $db->query("SELECT * FROM `records` WHERE `type` = 'A' AND `domain_id` = $id AND `dyndns` != ''");
		while ($row = $sql->fetch_object()) {
			array_push($a, Array($this->trimDomain($row->name, $domain), $row->content, $db->query("SELECT * FROM `records` WHERE `type` = 'AAAA' AND `domain_id` = $id AND `dyndns` != ''")->fetch_object()->content, $row->dyndns));
		}
 
		return $a;
	}
 
	public function delDynDNS($domain, $sub) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$sql = $db->query("SELECT id FROM domains WHERE name = '" . $db->real_escape_string($domain) . "'");
		if ($sql->num_rows != 1) {
			return false;
		}
 
		$id = $sql->fetch_object()->id;
 
		$name = $db->real_escape_string($sub) . "." . $db->real_escape_string($domain);
		$db->query("DELETE FROM `records` WHERE `domain_id` = $id AND (`type` = 'A' OR `type` = 'AAAA') AND `dyndns` != '' AND `name` = '$name'");
	}
 
	public function updateDynDNS($domain, $password, $ip, $ip6) {
		$domain = $this->idn($domain);
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		if (!empty($ip) && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
			$db->query("UPDATE `records` SET `content` = '" . $db->real_escape_string($ip) . "' WHERE `dyndns` = '" . $db->real_escape_string($password) . "' AND name = '" . $db->real_escape_string($domain) . "' AND `type` = 'A'");
		}
 
		if (!empty($ip6) && filter_var($ip6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
			$db->query("UPDATE `records` SET `content` = '" . $db->real_escape_string($ip6) . "' WHERE `dyndns` = '" . $db->real_escape_string($password) . "' AND name = '" . $db->real_escape_string($domain) . "' AND `type` = 'AAAA'");
		}
 
	}
 
	public function pushToSlave($domain) {
		$db = $this->getConnection();
		if (!$db) {
			return false;
		}
 
		$db->query("UPDATE domains SET notified_serial = 0 WHERE name = '" . $db->real_escape_string($domain) . "' LIMIT 1");
		return (bool) $db->affected_rows;
	}
}