DNS-Module erstellen

Aus sourceDESK Wiki
Version vom 10. Oktober 2018, 10:48 Uhr von Richard Reiber (Diskussion | Beiträge)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
Wechseln zu: Navigation, Suche

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;
	}
}