diff marcus.php @ 0:3ac7bd7495fd draft

Initial commit
author Ivo Smits <Ivo@UCIS.nl>
date Sat, 08 Nov 2014 22:22:42 +0100
parents
children caa68b502313
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/marcus.php	Sat Nov 08 22:22:42 2014 +0100
@@ -0,0 +1,371 @@
+<?php
+require_once './marccore.php';
+
+error_reporting(E_ALL);
+
+if (!isset($argv)) $argv = $_SERVER['argv'];
+$argi = 1;
+
+class FilteredResourceIterator implements Iterator {
+	private $source, $filtertype, $filtervalue;
+	public function __construct($source, $filtertype, $filtervalue) {
+		$this->source = $source;
+		$this->filtertype = $filtertype;
+		$this->filtervalue = $filtervalue;
+	}
+	public function current() { $r = $this->source->current(); return $r; }
+	public function key() { return $this->source->key(); }
+	public function valid() { return $this->source->valid(); }
+	public function next() { $this->source->next(); $this->findnext(); }
+	public function rewind() { $this->source->rewind(); $this->findnext(); }
+	private function findnext() {
+		while ($this->source->valid()) {
+			$c = $this->source->current();
+			if ($this->filter($c)) break;
+			$this->source->next();
+		}
+	}
+	private function filter($c) {
+		switch ($this->filtertype) {
+			case 'OWNER': return isset($c['value']['owner']) && is_scalar($c['value']['owner']) && strcasecmp($c['value']['owner'], $this->filtervalue) == 0;
+			case 'KEY': return $c['key'] == hex2bin($this->filtervalue);
+			case 'DOMEXT': return ord($c['label'][0]) == 4 && substr_compare($c['label'], $this->filtervalue, -strlen($this->filtervalue), strlen($this->filtervalue), TRUE) == 0;
+			default: return FALSE;
+		}
+	}
+}
+
+$database = new MARCDatabaseFlatFile();
+$key = NULL;
+$resource = NULL;
+$reschanged = FALSE;
+
+while ($argi < count($argv)) {
+	switch (strtoupper($argv[$argi++])) {
+		case 'OPEN':
+			if ($reschanged) echo "Warning: selected resource has not been updated.\n";
+			if ($database->IsChanged()) echo "Warning: database has unsaved changes.\n";
+			$database->Open($argv[$argi++]);
+			$reschanged = FALSE;
+			break;
+		case 'OPENSQLITE':
+			$database = new MARCDatabaseSQLite($argv[$argi++]);
+			break;
+		case 'OPENDBA':
+			$database = new MARCDatabaseDBA($argv[$argi++]);
+			break;
+		case 'SAVE':
+			$database->Save();
+			break;
+		case 'SAVEAS':
+			$database->SaveAs($argv[$argi]);
+			break;
+		case 'SYNC':
+			$database->SyncHTTP($argv[$argi++]);
+			break;
+		case 'KEY':
+			switch (strtoupper($argv[$argi++])) {
+				case 'CREATE':
+					$key = array('store' => TRUE);
+					$key['pk'] = nacl_crypto_sign_ed25519_keypair($key['sk'], randombytes(32));
+					$dbchanged = TRUE;
+					echo 'Created public key '.bin2hex($key['pk'])."\n";
+					break;
+				case 'FORGET':
+					$key['store'] = FALSE;
+					$dbchanged = TRUE;
+					break;
+				case 'STORE':
+					$key['store'] = TRUE;
+					$dbchanged = TRUE;
+					break;
+				case 'USE':
+					$key = array('store' => FALSE, 'pk' => $resource['key']);
+					if (isset($resource['value']['seckey'])) $key['sk'] = $resource['value']['seckey'];
+					if (isset($resource['value']['seckeyenc'])) $key['locked'] = $resource['value']['seckeyenc'];
+					break;
+				case 'IMPORT':
+					$key = array('store' => FALSE);
+					$key['pk'] = nacl_crypto_sign_ed25519_keypair($key['sk'], hex2bin($argv[$argi++]));
+					$dbchanged = TRUE;
+					break;
+				case 'UNLOCK':
+					if (!isset($key['locked'])) throw new Exception('The key is not locked');
+					$ret = hash('sha512', $key['pk'].$argv[$argi++], TRUE);
+					$key['sk'] = '';
+					for ($i = 0; $i < 32; $i++) $key['sk'] .= chr(ord($key['locked'][$i]) ^ ord($ret[$i]));
+					$ret = nacl_crypto_sign_ed25519_keypair($key['sk'], $key['sk']);
+					if ($ret != $key['pk']) throw new Exception('Key password is not valid');
+					echo 'Unlocked public key '.bin2hex($key['pk'])."\n";
+					break;
+				default:
+					throw new Exception('Unknown key operation '.$argv[$argi-1]);
+			}
+			break;
+		case 'LIST':
+			foreach ($database->GetResources() as $ret) echo labeltoname($ret['label'])."\n";
+			break;
+		case 'FIND':
+			foreach (filterresources($database->GetResources(), $argv, $argi) as $ret) echo labeltoname($ret['label'])."\n";
+			break;
+		case 'DELETE':
+			$database->DeleteResource($resource['label']);
+			break;
+		case 'DUMP':
+			dumpresource($resource);
+			break;
+		case 'UPDATE':
+			if (!isset($resource['key'])) $resource['key'] = $key['pk'];
+			unset($resource['serial']);
+			$res = $database->UpdateResource($resource, $key['sk']);
+			if (!$res) throw new Exception('Could not update resource');
+			$resource = $res->ToArray();
+			$reschanged = FALSE;
+			break;
+		case 'SET':
+			switch (strtoupper($argv[$argi++])) {
+				case 'OWNER':
+					if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array();
+					$resource['value']['owner'] = $argv[$argi++];
+					$reschanged = TRUE;
+					break;
+				case 'DESC':
+				case 'DESCR':
+				case 'DESCRIPTION':
+					if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array();
+					$resource['value']['descr'] = $argv[$argi++];
+					$reschanged = TRUE;
+					break;
+				case 'PWAUTH':
+					if (!isset($key['sk'])) throw new Exception('The key is not available');
+					$ret = hash('sha512', $key['pk'].$argv[$argi++], TRUE);
+					$key['locked'] = '';
+					for ($i = 0; $i < 32; $i++) $key['locked'] .= chr(ord($key['sk'][$i]) ^ ord($ret[$i]));
+					if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array();
+					$resource['value']['seckeyenc'] = $key['locked'];
+					$reschanged = TRUE;
+					break;
+				case 'TRANSFER':
+					$ret = $argv[$argi++];
+					$resource['value']['transfer'] = (strtolower($ret) == 'any') ? '' : hex2bin($ret);
+					$reschanged = TRUE;
+					break;
+				default:
+					throw new Exception('Unknown set operation '.$argv[$argi-1]);
+			}
+			break;
+		case 'ADD':
+			switch (strtoupper($argv[$argi++])) {
+				case 'NS':
+					if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array();
+					if (!isset($resource['value']['ns']) || !is_array($resource['value']['ns'])) $resource['value']['ns'] = array();
+					$nsname = $argv[$argi++];
+					$nsglue = (strlen($nsname) && $nsname[strlen($nsname)-1] != '.') ? $argv[$argi++] : NULL;
+					if (!isset($resource['value']['ns'][$nsname]) || !is_array($resource['value']['ns'][$nsname])) $resource['value']['ns'][$nsname] = array();
+					if ($nsglue !== NULL) $resource['value']['ns'][$nsname][] = $nsglue;
+					$reschanged = TRUE;
+					break;
+				default:
+					throw new Exception('Unknown add operation '.$argv[$argi-1]);
+			}
+			break;
+		case 'RESET':
+			switch (strtoupper($argv[$argi++])) {
+				case 'OWNER':
+					if (!is_array($resource['value'])) $resource['value'] = array();
+					unset($resource['value']['owner']);
+					$reschanged = TRUE;
+					break;
+				case 'DESC':
+				case 'DESCR':
+				case 'DESCRIPTION':
+					if (!is_array($resource['value'])) $resource['value'] = array();
+					unset($resource['value']['descr']);
+					$reschanged = TRUE;
+					break;
+				case 'PWAUTH':
+					if (!is_array($resource['value'])) $resource['value'] = array();
+					unset($resource['value']['seckeyenc']);
+					$reschanged = TRUE;
+					break;
+				case 'NS':
+					if (!is_array($resource['value'])) $resource['value'] = array();
+					unset($resource['value']['ns']);
+					$reschanged = TRUE;
+					break;
+				case 'VALUE':
+					$resource['value'] = array();
+					$reschanged = TRUE;
+					break;
+				case 'TRANSFER':
+					unset($resource['transfer']);
+					$reschanged = TRUE;
+					break;
+				case 'EXPIRATION':
+					unset($resource['expiration']);
+					$reschanged = TRUE;
+					break;
+				default:
+					throw new Exception('Unknown reset operation '.$argv[$argi-1]);
+			}
+			break;
+		case 'CREATE':
+			if ($reschanged) echo "Warning: selected resource has not been updated.\n";
+			$reschanged = TRUE;
+			$resource = array('label' => argtolabel($argv, $argi));
+			break;
+		case 'SELECT':
+			if ($reschanged) echo "Warning: selected resource has not been updated.\n";
+			$reschanged = FALSE;
+			$label = argtolabel($argv, $argi);
+			$resource = $database->GetResource($label);
+			if (!$resource) echo "Warning: resource ".labeltoname($label)." does not exist.\n";
+			else $resource = $resource->ToArray();
+			break;
+		case 'HELP':
+			print_help();
+			break;
+		default:
+			throw new Exception('Unknown operation '.$argv[$argi-1]);
+	}
+}
+if ($reschanged) echo "Warning: selected resource has not been updated.\n";
+if ($database->IsChanged()) echo "Warning: database has unsaved changes.\n";
+$database->Close();
+
+function filterresources($iterator, $argv, &$argi) {
+	$filtertype = strtoupper($argv[$argi++]);
+	switch ($filtertype) {
+		case 'OWNER':
+		case 'KEY':
+		case 'DOMEXT':
+			$filtervalue = $argv[$argi++];
+			break;
+		default:
+			throw new Exception('Unknown filter type '.$t);
+	}
+	return new FilteredResourceIterator($iterator, $filtertype, $filtervalue);
+}
+function argtolabel($argv, &$argi) {
+	$t = $argv[$argi++];
+	switch (strtoupper($t)) {
+		case 'LABEL': return hex2bin($argv[$argi++]);
+		case 'CURRENTKEY': return chr(0).$GLOBALS['key']['pk'];
+		case 'RESOURCEKEY': return chr(0).$GLOBALS['resource']['key'];
+		case 'KEY': return chr(0).hex2bin($argv[$argi++]);
+		case 'IP':
+		case 'IP4':
+		case 'IPV4':
+		case 'IP6':
+		case 'IPV6': return ipnettolabel($argv[$argi++]);
+		case 'AS': return chr(3).marc_decode_int32be($argv[$argi++]);
+		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('/^[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);
+	}
+}
+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);
+}
+function labeltoname($l) {
+	switch (ord($l)) {
+		case 0: return 'KEY '.bin2hex(substr($l, 1));
+		case 1: if (strlen($l) == 6) return 'IPv4 '.inet_ntop(substr($l, 1, 4)).'/'.ord($l[5]); else return 'LABEL '.bin2hex($l);
+		case 2: if (strlen($l) == 18) return 'IPv6 '.inet_ntop(substr($l, 1, 16)).'/'.ord($l[17]); else return 'LABEL '.bin2hex($l);
+		case 3: if (strlen($l) == 5) return 'AS '.marc_decode_int32be(substr($l, 1, 4)); else return 'LABEL '.bin2hex($l);
+		case 4: if (strlen($l) > 1) return 'DOM '.substr($l, 1); else return 'LABEL '.bin2hex($l);
+		default: return 'LABEL '.bin2hex($l);
+	}
+}
+function dumpresource($r, $p = '') {
+	if (is_null($r)) {
+		echo "NULL\n";
+	} else if (is_string($r)) {
+		$bin = FALSE;
+		for ($i = 0; $i < strlen($r); $i++) $bin |= ord($r[$i]) < 32 || ord($r[$i]) > 126;
+		if ($bin) echo '0x'.bin2hex($r)."\n";
+		else echo '"'.$r.'"'."\n";
+	} else if (is_scalar($r)) {
+		echo $r."\n";
+	} else if (is_array($r)) {
+		echo "array(\n";
+		foreach ($r as $key => $value) {
+			echo $p.'    ['.$key.'] => ';
+			dumpresource($value, $p.'    ');
+		}
+		echo $p."  )\n";
+	} else {
+		print_r($r);
+	}
+}
+function randombytes($n) {
+	$b = '';
+	$file = fopen('/dev/urandom', 'r');
+	for ($i = 0; $i < $n; $i++) $b .= fgetc($file);
+	fclose($file);
+	return $b;
+}
+
+function print_help() {
+	echo 'Usage: marcus.php [operation] [arguments] [operation] [arguments]...
+open [filename.mdb] - opens the specified database file
+save - saves the current database file
+saveas [filename.mdb] - saves the current data to a new databse file
+sync [url] - synchronize database with remote server
+key create - create a new key pair
+key forget - do not store the current key pair in the local database
+key store - store the current key pair in the local database
+key use - use the key pair from the currently selected resource
+key import [secretkey] - import the key pair defined by the given secret key
+key unlock [password] - unlock a password protected key pair
+list - list registered resources
+find [type] [value] - list registered sources matching filter (type=OWNER|KEY|DOMEXT)
+create [identifier] - create given resource
+create currentkey - create resource for current key pair
+create ip|ip4|ipv4 [ipv4network] - create resource for IPv4 network
+create ip|ip6|ipv6 [ipv6network] - create resource for IPv6 network
+create dom|domain [ipv6network] - create resource for domain name
+select [identifier] - select resource given by identifier
+select currentkey - select key resource for current key pair
+select resourcekey - select key resource for the key that signed the currently selected resource
+select label [identifier] - select resource by hexadecimal label
+select key [publickey] - select key resource (hexadecimal)
+select ip|ip4|ipv4 [ipv4network] - select resource for IPv4 network
+select ip|ip6|ipv6 [ipv6network] - select resource for IPv6 network
+select dom|domain [ipv6network] - select resource for domain name
+delete - delete currently selected resource
+dump - display currently selected resource
+update - update selected resource in the local database
+set owner [name] - set the owner name for the selected resource
+set descr|desc|description [text] - set the description for the selected resource
+set pwauth [password] - store the current key pair in the selected resource for password authentication
+set transfer any - allow anyone to take over the resource
+set transfer [key] - transfer the resource to given key
+TODO: set expiration [value]
+add ns [name] [glue] - add in-zone nameserver with glue record (name is the part of the nameserver name before the domain name, glue is an IPv4 or IPv6 address)
+add ns [name]. - add an external nameserver
+reset owner - clear the owner
+reset descr|desc|description - clear the description
+reset pwauth - remove the key pair from the recourse, disabling password authentication
+reset ns - clear the nameserver records
+reset transfer - disable resource transfers
+reset expiration - disable explicit expiration
+reset value - clear the owner, description, password authentication and nameserver records
+
+Examples:
+OPEN marc.mdb KEY create CREATE currentkey SET owner "Your name" SET pwauth yourpassword UPDATE KEY forget CREATE yourdomain.ano SET owner "Your name" UPDATE SAVE
+OPEN marc.mdb SELECT yourdomain.ano SELECT resourcekey KEY use KEY unlock yourpassword SELECT yourdomain.ano ADD ns ns1 1.2.3.4 UPDATE SAVE
+OPEN marc.mdb SELECT yourdomain.ano SELECT resourcekey KEY use KEY unlock yourpassword CREATE 1.2.3.0/24 SET owner "Your name" ADD ns ns1.yourdomain.ano. UPDATE SAVE
+OPEN marc.mdb SYNC http://marc.ucis.ano/ SAVE
+';
+}