changeset 1:caa68b502313 draft

Added the MARC DNS server (and small fixes in marcus and anoclaims)
author Ivo Smits <Ivo@UCIS.nl>
date Thu, 13 Nov 2014 17:22:12 +0100
parents 3ac7bd7495fd
children f6954b464d2f
files anoclaims.php marcus.php marns.php marns_import.php
diffstat 4 files changed, 492 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/anoclaims.php	Sat Nov 08 22:22:42 2014 +0100
+++ b/anoclaims.php	Thu Nov 13 17:22:12 2014 +0100
@@ -21,6 +21,7 @@
 		$label = argtolabel($argv, $argi);
 		$resource = $database->GetResource($label);
 		if (!$resource) $resource = array('label' => $label, 'value' => array());
+		else $resource = $resource->ToArray();
 		if (!$database->UpdateResource($resource, $key)) throw new Exception('Could not update resource');
 		break;
 	case 'SETNS':
--- a/marcus.php	Sat Nov 08 22:22:42 2014 +0100
+++ b/marcus.php	Thu Nov 13 17:22:12 2014 +0100
@@ -262,9 +262,9 @@
 		case 'DOM':
 		case 'DOMAIN': return chr(4).strtolower(trim($argv[$argi++], '.'));
 		default:
-			if (preg_match('/^AS[0-9]{1-9}$/', $t)) return chr(3).marc_decode_int32be(substr($argv[$argi++], 2));
-			if (preg_match('_^[0-9]{1-3}\.[0-9]{1-3}\.[0-9]{1-3}\.[0-9]{1-3}/[0-9]{1-2}$_', $t)) return ipv4tolabel($t);
-			if (preg_match('_^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})\z/[0-9]{1-3}_i', $t)) return ipv6tolabel($t);
+			if (preg_match('/^AS[0-9]{1-9}$/', $t)) return chr(3).marc_decode_int32be(substr($t, 2));
+			if (preg_match('_^[0-9]{1-3}\.[0-9]{1-3}\.[0-9]{1-3}\.[0-9]{1-3}/[0-9]{1-2}$_', $t)) return ipnettolabel($t);
+			if (preg_match('_^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})\z/[0-9]{1-3}_i', $t)) return ipnettolabel($t);
 			if (preg_match('/^[a-f0-9]{64}$/i', $t)) return chr(0).hex2bin($t);
 			if (preg_match('/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/i', $t)) return chr(4).strtolower(trim($t, '.'));
 			throw new Exception('Could not detect label type for '.$t);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/marns.php	Thu Nov 13 17:22:12 2014 +0100
@@ -0,0 +1,360 @@
+<?php
+require_once './marccore.php';
+
+$dns_opcode = array(0 => 'QUERY', 1 => 'IQUERY', 2 => 'STATUS');
+$dns_responsecode = array(0 => 'NOERROR', 1 => 'FORMERR', 2 => 'SERVFAIL', 3 => 'NXDOMAIN', 4 => 'NOTIMPL', 5 => 'REFUSED');
+$dns_rr_class = array(1 => 'IN', 2 => 'CS', 3 => 'CH', 4 => 'HS', 255 => 'ANY');
+$dns_rr_type = array(1 => 'A', 2 => 'NS', 5 => 'CNAME', 6 => 'SOA', 10 => 'NULL', 12 => 'PTR', 15 => 'MX', 16 => 'TXT', 28 => 'AAAA', 252 => 'AXFR', 255 => 'ANY');
+$dns_opcode_rev = array_flip($dns_opcode);
+$dns_responsecode_rev = array_flip($dns_responsecode);
+$dns_rr_class_rev = array_flip($dns_rr_class);
+$dns_rr_type_rev = array_flip($dns_rr_type);
+
+error_reporting(E_ALL);
+
+if (!isset($argv)) $argv = $_SERVER['argv'];
+if (count($argv) < 3) {
+	echo "Usage: ".$argv[0]." [marc-flatfile-database-filename] [listen-ip-address] [listen-port-number]\n";
+	die();
+}
+
+$dbfile = $argv[1];
+$listenaddress = $argv[2];
+$listenport = $argv[3];
+
+$database = new MARCDatabaseFlatFile();
+
+function dbreload() {
+	global $database, $dbfile;
+	$database->Open($dbfile);
+	$database->Close();
+}
+
+dbreload();
+
+$socket = socket_create(strpos($listenaddress, ':') === FALSE ? AF_INET : AF_INET6, SOCK_DGRAM, SOL_UDP);
+socket_bind($socket, $listenaddress, $listenport);
+
+while (true) {
+	$buffer = $remoteaddr = $remoteport = NULL;
+	$readlen = socket_recvfrom($socket, $buffer, 0xFFFF, 0, $remoteaddr, $remoteport);
+	if ($readlen === FALSE) continue;
+	$buffer = dns_process($buffer, $remoteaddr);
+	if ($buffer === FALSE) continue;
+	socket_sendto($socket, $buffer, strlen($buffer), 0, $remoteaddr, $remoteport);
+}
+
+function dns_process($reqbuffer, $remoteaddr) {
+	$req = dns_decode($reqbuffer);
+	if ($req['isresponse']) return;
+	$res = array('id' => $req['id'], 'isresponse' => true, 'opcode' => $req['opcode'], 'question' => $req['question'], 'responsecode' => 'NOERROR', 'answer' => array(), 'authority' => array(), 'additional' => array());
+	if ($req['opcode'] != 'QUERY') {
+		$res['responsecode'] = 'NOTIMPL';
+	} else {
+		foreach ($req['question'] as $q) {
+			if ($q['class'] == 'IN') {
+				if (!dns_get_response($res, $q)) $res['responsecode'] = 'NXDOMAIN';
+			} else if ($q['class'] == 'CH' && $q['type'] == 'TXT') {
+				switch ($q['name']) {
+					case 'version.bind': $res['answer'][] = array('name' => $q['name'], 'class' => $q['class'], 'type' => $q['type'], 'value' => 'UCIS MARNS - http://www.ucis.nl'); break;
+					case 'reload.marns':
+						if ($remoteaddr == '127.0.0.1' || $remoteaddr == '::1' || $remoteaddr == $GLOBALS['listenaddress']) {
+							dbreload();
+							$res['answer'][] = array('name' => $q['name'], 'class' => $q['class'], 'type' => $q['type'], 'value' => 'RELOAD OK');
+						} else {
+							$res['responsecode'] = 'REFUSED';
+						}
+						break;
+					default: $res['responsecode'] = 'NOTIMPL'; break;
+				}
+			} else {
+				$res['responsecode'] = 'NOTIMPL';
+			}
+		}
+	}
+	return dns_encode($res);
+}
+
+function dns_find_zone(&$name, &$zone) {
+	global $database;
+	$zone = strtolower(rtrim($name, '.'));
+	$name = '';
+	while (strlen($zone)) {
+		$dbzone = $database->GetResource(chr(4).$zone);
+		if ($dbzone != NULL) return $dbzone;
+		$i = strpos($zone, '.');
+		if ($i === FALSE) break;
+		if (strlen($name)) $name .= '.';
+		$name .= substr($zone, 0, $i);
+		$zone = substr($zone, $i + 1);
+	}
+	return NULL;
+}
+function dns_convert_record($item, $realname, $zonename) {
+		if (isset($item['class']) && strcasecmp($item['class'], 'IN')) return FALSE;
+		if (!is_array($item) || !isset($item['type'])) return FALSE;
+		if (strlen($realname)) $realname .= '.';
+		$realname .= $zonename;
+		$type = strtoupper($item['type']);
+		$rr = array('name' => $realname, 'type' => $type);
+		if (($type == 'A' || $type == 'AAAA') && isset($item['target'])) $rr['address'] = $item['target'];
+		elseif (($type == 'CNAME' || $type == 'NS' || $type == 'PTR') && isset($item['target'])) $rr['target'] = $item['target'];
+		elseif ($type == 'MX' && isset($item['target'])) { $rr['target'] = $item['target']; if (isset($item['priority'])) $rr['priority'] = $item['priority']; }
+		else return FALSE;
+		if (isset($rr['target'])) {
+			if (!strlen($rr['target'])) $rr['target'] = $zonename;
+			elseif ($rr['target'][strlen($rr['target']) - 1] != '.') $rr['target'] .= '.'.$zonename;
+		}
+		$rr['ttl'] = isset($item['ttl']) ? $item['ttl'] : 3600;
+		return $rr;
+}
+function dns_find_records_real(&$response, $dbzone, $findname, $realname, $zonename, $qtype) {
+	if (!isset($dbzone[$findname])) return FALSE;
+	foreach ($dbzone[$findname] as $item) {
+		if (!is_array($item) || !isset($item['type'])) continue;
+		if (isset($item['class']) && strcasecmp($item['class'], 'IN')) continue;
+		if (strcasecmp($item['type'], $qtype) && strcasecmp($qtype, 'ANY') && strcasecmp($item['type'], 'CNAME')) continue;
+		$record = dns_convert_record($item, $realname, $zonename);
+		if ($record === FALSE) continue;
+		$response[] = $record;
+	}
+	return TRUE;
+}
+function dns_find_ns_records(&$res, $dbzone, $findname, $realname, $zonename) {
+	if (!isset($dbzone[$findname])) return FALSE;
+	$foundns = FALSE;
+	foreach ($dbzone[$findname] as $item) {
+		if (!is_array($item) || !isset($item['type'])) continue;
+		if (isset($item['class']) && strcasecmp($item['class'], 'IN')) continue;
+		if (strcasecmp($item['type'], 'NS')) continue;
+		$record = dns_convert_record($item, $realname, $zonename);
+		if ($record === FALSE) continue;
+		$res['authority'][] = $record;
+		if (strcasecmp(substr($record['target'], -strlen($zonename)), $zonename) == 0) dns_find_records_realwild($res['additional'], $dbzone, rtrim(substr($record['target'], 0, -strlen($zonename)), '.'), $zonename, 'ANY');
+		$foundns = TRUE;
+	}
+	return $foundns;
+}
+function dns_find_records_realwild(&$res, $dbzone, $name, $zonename, $qtype) {
+	if (dns_find_records_real($res, $dbzone, $name, $name, $zonename, $qtype)) return TRUE;
+	$nameparts = strlen($name) ? explode('.', strtolower($name)) : array();
+	for ($i = 1; $i < count($nameparts); $i++) {
+		if (dns_find_records_real($res, $dbzone, '*.'.implode('.', array_slice($nameparts, $i)), $name, $zonename, $qtype)) return TRUE;
+	}
+	if (dns_find_records_real($res, $dbzone, '*', $name, $zonename, $qtype)) return TRUE;
+	return FALSE;
+}
+function dns_find_records(&$res, $dbzone, $name, $zone, $qtype) {
+	if (!isset($dbzone->value['dnsrecords']) || !is_array($dbzone->value['dnsrecords'])) return FALSE;
+	$dbzone = $dbzone->value['dnsrecords'];
+	$nameparts = strlen($name) ? explode('.', strtolower($name)) : array();
+	for ($i = count($nameparts) - 1; $i >= 0; $i--) {
+		$realname = implode('.', array_slice($nameparts, $i));
+		if (dns_find_ns_records($res, $dbzone, $realname, $realname, $zone)) return TRUE;
+		if (dns_find_ns_records($res, $dbzone, '*.'.implode('.', array_slice($nameparts, $i + 1)), $realname, $zone)) return TRUE;
+	}
+	return dns_find_records_realwild($res['answer'], $dbzone, $name, $zone, $qtype);
+}
+function dns_find_authority(&$res, $dbzone, $zone, $nsquery = FALSE) {
+	if (!isset($dbzone->value['ns']) || !is_array($dbzone->value['ns'])) return;
+	foreach ($dbzone->value['ns'] as $nsname => $nsglues) {
+		$rr = array('name' => $zone, 'type' => 'NS', 'ttl' => 3600, 'target' => (strlen($nsname) && $nsname[strlen($nsname) - 1] != '.') ? $nsname.'.'.$zone : $nsname);
+		if ($nsquery) $res['answer'][] = $rr;
+		else $res['authority'][] = $rr;
+		if (is_array($nsglues)) {
+			foreach ($nsglues as $glue) {
+				$glue = inet_pton($glue);
+				if (!strlen($glue)) continue;
+				$res['additional'][] = array('name' => $rr['target'], 'ttl' => 3600, 'type' => strlen($glue) == 4 ? 'A' : 'AAAA', 'data' => $glue);
+			}
+		}
+	}
+}
+
+function dns_get_response(&$res, $q) {
+	$name = $q['name'];
+	$zone = '';
+	$dbzone = dns_find_zone($name, $zone);
+	if ($dbzone == NULL) return;
+	dns_find_records($res, $dbzone, $name, $zone, $q['type']);
+	//dns_find_authority($res, $dbzone, $zone, $q['type'] == 'NS');
+}
+
+function dns_decode_uint16be($data, $i = 0) { return (ord($data[$i+0]) << 8) | ord($data[$i+1]); }
+function dns_decode_uint32be($data, $i = 0) { return (ord($data[$i]) << 24) | (ord($data[$i+1]) << 16) | (ord($data[$i+2]) << 8) | ord($data[$i+3]); }
+function dns_encode_uint32be($v) { return pack("N", $v); }
+function dns_encode_uint16be($v) { return pack("n", $v); }
+
+function dns_decode($packet) {
+	global $dns_opcode, $dns_responsecode;
+	$dp = array(
+		'id' => dns_decode_uint16be($packet, 0),
+		'isresponse' => ($packet[2] & 128) != 0,
+		'opcode' => ($packet[2] >> 3) & 0x0F,
+		'authoritative' => ($packet[2] & 4) != 0,
+		'truncation' => ($packet[2] & 2) != 0,
+		'recursiondesired' => ($packet[2] & 1) != 0,
+		'recursionavailable' => ($packet[3] & 128) != 0,
+		'responsecode' => ($packet[3] & 0x0F),
+		'qdcount' => dns_decode_uint16be($packet, 4),
+		'ancount' => dns_decode_uint16be($packet, 6),
+		'nscount' => dns_decode_uint16be($packet, 8),
+		'arcount' => dns_decode_uint16be($packet, 10),
+	);
+	if (isset($dns_opcode[$dp['opcode']])) $dp['opcode'] = $dns_opcode[$dp['opcode']];
+	if (isset($dns_responsecode[$dp['responsecode']])) $dp['responsecode'] = $dns_responsecode[$dp['responsecode']];
+	$ptr = 12;
+	$dp['question'] = dns_decode_rrs($packet, $ptr, false, $dp['qdcount']);
+	$dp['answer'] = dns_decode_rrs($packet, $ptr, true, $dp['ancount']);
+	$dp['authority'] = dns_decode_rrs($packet, $ptr, true, $dp['nscount']);
+	$dp['additional'] = dns_decode_rrs($packet, $ptr, true, $dp['arcount']);
+	return $dp;
+}
+function dns_decode_rrs($packet, &$ptr, $isanswer, $count) {
+	$ret = array();
+	for ($i = 0; $i < $count; $i++) $ret[] = dns_decode_rr($packet, $ptr, $isanswer);
+	return $ret;
+}
+function dns_decode_rr($packet, &$ptr, $isanswer) {
+	global $dns_rr_class, $dns_rr_type;
+	$rr = array();
+	$rr['name'] = dns_decode_name($packet, $ptr);
+	$rr['type'] = dns_decode_uint16be($packet, $ptr); $ptr += 2;
+	$rr['class'] = dns_decode_uint16be($packet, $ptr); $ptr += 2;
+	if (isset($dns_rr_class[$rr['class']])) $rr['class'] = $dns_rr_class[$rr['class']];
+	if (isset($dns_rr_type[$rr['type']])) $rr['type'] = $dns_rr_type[$rr['type']];
+	if ($isanswer) {
+		$rr['ttl'] = dns_decode_uint32be($packet, $ptr); $ptr += 4;
+		$rrdatalength = dns_decode_uint16be($packet, $ptr); $ptr += 2;
+		$rr['data'] = substr($packet, $ptr, $rrdatalength);
+		$dataptr = $ptr;
+		$ptr += $rrdatalength;
+		if ($rr['class'] == 'IN') {
+			switch ($rr['type']) {
+				case 'A':
+				case 'AAAA':
+					$rr['address'] = inet_ntop($rr['data']);
+					break;
+				case 'CNAME':
+				case 'NS':
+				case 'PTR':
+					$rr['target'] = dns_decode_name($packet, $dataptr);
+					break;
+				case 'MX':
+					$rr['priority'] = dns_decode_uint16be($packet, $dataptr); $dataptr += 2;
+					$rr['target'] = dns_decode_name($packet, $dataptr);
+					break;
+				case 'TXT':
+					$rr['value'] = dns_decode_name($packet, $dataptr);
+					break;
+			}
+		}
+	}
+	return $rr;
+}
+function dns_decode_name($packet, &$ptr) {
+	$name = '';
+	$canrecurse = 64;
+	while ($ptr < strlen($packet)) {
+		$l = ord($packet[$ptr++]);
+		if ($l == 0) break;
+		if (($l & 0xC0) == 0) {
+			$name .= substr($packet, $ptr, $l).'.';
+			$ptr += $l;
+		} else if (($l & 0xC0) == 0xC0 && $canrecurse > 0) {
+			$newptr = (($l & 0x3F) << 8) | ord($packet[$ptr++]);
+			$ptr = &$newptr;
+			$canrecurse--;
+		} else {
+			return FALSE;
+		}
+	}
+	return substr($name, 0, -1);
+}
+
+function dns_encode($dp) {
+	global $dns_opcode_rev, $dns_responsecode_rev;
+	$data = '';
+	$qdcount = isset($dp['question']) ? count($dp['question']) : 0;
+	$ancount = isset($dp['answer']) ? count($dp['answer']) : 0;
+	$nscount = isset($dp['authority']) ? count($dp['authority']) : 0;
+	$arcount = isset($dp['additional']) ? count($dp['additional']) : 0;
+	$opcode = isset($dp['opcode']) ? $dp['opcode'] : 0;
+	$responsecode = isset($dp['responsecode']) ? $dp['responsecode'] : 0;
+	if (isset($dns_opcode_rev[$opcode])) $opcode = $dns_opcode_rev[$opcode];
+	if (isset($dns_responsecode_rev[$responsecode])) $responsecode = $dns_responsecode_rev[$responsecode];
+	$isresponse = $ancount || $nscount || $arcount || $responsecode != 0 || (isset($dp['isresponse']) && $dp['isresponse']);
+	$data .= dns_encode_uint16be($dp['id']);
+	$data .= chr(($isresponse ? 128 : 0) | (($opcode & 0x0F) << 3) | ((isset($dp['authoritative']) && $dp['authoritative']) ? 4 : 0) | ((isset($dp['truncation']) && $dp['truncation']) ? 2 : 0) | ((isset($dp['recursiondesired']) && $dp['recursiondesired']) ? 1 : 0));
+	$data .= chr(((isset($dp['recursionavailable']) && $dp['recursionavailable']) ? 128 : 0) | ($responsecode & 0xF));
+	$data .= dns_encode_uint16be($qdcount);
+	$data .= dns_encode_uint16be($ancount);
+	$data .= dns_encode_uint16be($nscount);
+	$data .= dns_encode_uint16be($arcount);
+	$labels = array();
+	if ($qdcount) dns_encode_rrs($data, $dp['question'], false, $labels);
+	if ($ancount) dns_encode_rrs($data, $dp['answer'], true, $labels);
+	if ($nscount) dns_encode_rrs($data, $dp['authority'], true, $labels);
+	if ($arcount) dns_encode_rrs($data, $dp['additional'], true, $labels);
+	return $data;
+}
+function dns_encode_rrs(&$data, $rrs, $isanswer, &$labels) {
+	global $dns_rr_type_rev, $dns_rr_class_rev;
+	foreach ($rrs as $rr) {
+		$rrtype = $rr['type'];
+		$rrclass = isset($rr['class']) ? $rr['class'] : 1;
+		if (isset($dns_rr_type_rev[$rrtype])) $rrtype = $dns_rr_type_rev[$rrtype];
+		if (isset($dns_rr_class_rev[$rrclass])) $rrclass = $dns_rr_class_rev[$rrclass];
+		$data .= dns_encode_name($rr['name'], $labels, strlen($data));
+		$data .= dns_encode_uint16be($rrtype);
+		$data .= dns_encode_uint16be($rrclass);
+		if ($isanswer) {
+			$rrdata = isset($rr['data']) ? $rr['data'] : '';
+			if (($rrtype == 5 || $rrtype == 2 || $rrtype == 12) && isset($rr['target'])) $rrdata = dns_encode_name($rr['target'], $labels, strlen($data) + 6);
+			elseif (($rrtype == 16) && isset($rr['value'])) $rrdata = dns_encode_txt($rr['value']);
+			elseif ($rrtype == 15 && isset($rr['target'])) $rrdata = dns_encode_uint16be(isset($rr['priority']) ? $rr['priority'] : 0).dns_encode_name($rr['target'], $labels, strlen($data) + 8);
+			elseif ($rrclass == 1) {
+				if (($rrtype == 1 || $rrtype == 28) && isset($rr['address'])) $rrdata = inet_pton($rr['address']);
+			}
+			$rrdata = (string)$rrdata;
+			$data .= dns_encode_uint32be(isset($rr['ttl']) ? $rr['ttl'] : 0);
+			$data .= dns_encode_uint16be(strlen($rrdata));
+			$data .= $rrdata;
+		}
+	}
+}
+function dns_encode_name($name, &$labels, $offset = FALSE) {
+	$name = rtrim($name, '.');
+	$data = '';
+	while (strlen($name)) {
+		$lname = strtolower($name);
+		if (isset($labels[$lname])) {
+			$ptr = $labels[$lname];
+			return $data.chr(0xC0 | (($ptr >> 8) & 0x3F)).chr($ptr & 0xFF);
+		} else {
+			$dot = strpos($name, '.');
+			if ($dot === FALSE) {
+				$part = $name;
+				$name = '';
+			} else {
+				$part = substr($name, 0, $dot);
+				$name = substr($name, $dot + 1);
+			}
+			if (strlen($part) > 63) $part = substr($part, 0, 63);
+			if ($offset !== FALSE && $offset + strlen($data) <= 0x3FFF) $labels[$lname] = strlen($data) + $offset;
+			$data .= chr(strlen($part)).$part;
+		}
+	}
+	return $data.chr(0);
+}
+function dns_encode_txt($parts) {
+	$data = '';
+	if (!is_array($parts)) $parts = array($parts);
+	foreach ($parts as $part) {
+		$part = (string)$part;
+		if (strlen($part) > 63) $part = substr($part, 0, 63);
+		$data .= chr(strlen($part)).$part;
+	}
+	return $data;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/marns_import.php	Thu Nov 13 17:22:12 2014 +0100
@@ -0,0 +1,128 @@
+<?php
+require_once './marccore.php';
+
+if (!isset($argv)) $argv = $_SERVER['argv'];
+if (count($argv) < 4) {
+	echo "Usage: ".$argv[0]." [marc-flatfile-database-filename] [domain-name-or-IP-network] [secret-key-or-file] < [zone-file]\n";
+	die();
+}
+
+$marclabel = argtolabel($argv[2]);
+switch (ord($marclabel[0])) {
+	case 1:
+		$bits = ord($marclabel[5]);
+		$orgzonename = 'in-addr.arpa';
+		if (($bits % 8) != 0) throw new Exception('Invalid prefix length for IPv4 network');
+		for ($i = 0; $i < $bits / 8; $i++) $orgzonename = ord($marclabel[$i + 1]).'.'.$orgzonename;
+		break;
+	case 2:
+		$bits = ord($marclabel[17]);
+		$hex = bin2hex(substr($marclabel, 1, 16));
+		$orgzonename = 'ip6.arpa';
+		if (($bits % 4) != 0) throw new Exception('Invalid prefix length for IPv6 network');
+		for ($i = 0; $i < $bits / 4; $i++) $orgzonename = $hex[$i].'.'.$orgzonename;
+		break;
+	case 4:
+		$orgzonename = substr($marclabel, 1);
+		break;
+	default:
+		throw new Exception('Unsupported zone type');
+}
+$orgzonename .= '.';
+
+$stdin = fopen('php://stdin', 'r');
+$dnsrecords = array();
+while (($line = fgets($stdin)) !== FALSE) {
+	$line = rtrim($line, "\r\n");
+	if (!strlen($line) || $line[0] == ';') continue;
+	$token = strtok($line, " \t");
+	if ($line[0] != ' ' && $line[0] != "\t") {
+		if (!strlen($token) || $token[0] == ';') continue;
+		if ($token == '@' || strcasecmp($token, $orgzonename) == 0) $name = '';
+		elseif (strlen($token) > strlen($orgzonename) && strcasecmp(substr($token, -strlen($orgzonename)-1), '.'.$orgzonename) == 0) $name = substr($token, 0, -strlen($orgzonename)-1);
+		elseif (strlen($token) && $token[strlen($token) - 1] == '.') continue;
+		else $name = $token;
+		$name = strtolower($name);
+		$token = strtok(" \t");
+	}
+	$rr = array();
+	if (is_numeric($token)) {
+		$rr['ttl'] = intval($token);
+		$token = strtok(" \t");
+		if ($token == 'IN') {
+			$rr['class'] = $token;
+			$token = strtok(" \t");
+		}
+	} else if ($token == 'IN') {
+		$rr['class'] = $token;
+		$token = strtok(" \t");
+		if (is_numeric($token)) {
+			$rr['ttl'] = intval($token);
+			$token = strtok(" \t");
+		}
+	}
+	$rr['type'] = strtoupper($token);
+	switch ($rr['type']) {
+		case 'A':
+		case 'AAAA':
+			$rr['target'] = strtok(" \t");
+			break;
+		case 'CNAME':
+		case 'NS':
+		case 'PTR':
+			$rr['target'] = strtok(" \t");
+			break;
+		case 'MX':
+			$rr['priority'] = strtok(" \t");
+			$rr['target'] = strtok(" \t");
+			break;
+		default:
+			$rr = NULL;
+	}
+	if ($rr === NULL) continue;
+	if (!isset($dnsrecords[$name])) $dnsrecords[$name] = array();
+	$dnsrecords[$name][] = $rr;
+}
+fclose($stdin);
+
+print_r($dnsrecords);
+
+$skey = $argv[3];
+if (file_exists($skey)) {
+	$skey = file_get_contents($skey);
+} else if (strlen($skey) == 64) {
+	$skey = bin2hex($skey);
+} else if ($skey == '-') {
+	nacl_crypto_sign_ed25519_keypair($skey);
+}
+if (strlen($skey) != 32 && strlen($skey) != 64) throw new Exception('Invalid signing key specified');
+
+error_reporting(E_ALL);
+$database = new MARCDatabaseFlatFile();
+$database->Open($argv[1]);
+$label = chr(4).'ucis-test.ano';
+$resource = $database->GetResource($marclabel);
+if ($resource) {
+	$resource = $resource->ToArray();
+	unset($resource['key']);
+} else {
+	$resource = array('label' => $marclabel, 'value' => array(), 'transfer' => '', 'expiration' => time() + 3600 * 24 * 7);
+}
+$resource['value']['dnsrecords'] = $dnsrecords;
+if (!$database->UpdateResource($resource, $skey)) throw new Exception('Could not update resource');
+$database->Save();
+$database->Close();
+
+function argtolabel($t) {
+	if (preg_match('_^[0-9]{1-3}\.[0-9]{1-3}\.[0-9]{1-3}\.[0-9]{1-3}/[0-9]{1-2}$_', $t)) return ipnettolabel($t);
+	if (preg_match('_^(((?=.*(::))(?!.*\3.+\3))\3?|([\dA-F]{1,4}(\3|:\b|$)|\2))(?4){5}((?4){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})\z/[0-9]{1-3}_i', $t)) return ipnettolabel($t);
+	if (preg_match('/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/i', $t)) return chr(4).strtolower(trim($t, '.'));
+	throw new Exception('Could not detect label type for '.$t);
+}
+function ipnettolabel($s) {
+	$ip = inet_pton(strtok($s, '/'));
+	$pl = intval(strtok('/'));
+	if ($pl == 0) throw new Exception('Invalid IP network specified');
+	if (strlen($ip) == 4) return chr(1).$ip.chr($pl);
+	if (strlen($ip) == 16) return chr(2).$ip.chr($pl);
+}