0
|
1 <?php |
|
2 require_once './marccore.php'; |
|
3 |
|
4 error_reporting(E_ALL); |
|
5 |
|
6 if (!isset($argv)) $argv = $_SERVER['argv']; |
|
7 $argi = 1; |
|
8 |
|
9 class FilteredResourceIterator implements Iterator { |
|
10 private $source, $filtertype, $filtervalue; |
|
11 public function __construct($source, $filtertype, $filtervalue) { |
|
12 $this->source = $source; |
|
13 $this->filtertype = $filtertype; |
|
14 $this->filtervalue = $filtervalue; |
|
15 } |
|
16 public function current() { $r = $this->source->current(); return $r; } |
|
17 public function key() { return $this->source->key(); } |
|
18 public function valid() { return $this->source->valid(); } |
|
19 public function next() { $this->source->next(); $this->findnext(); } |
|
20 public function rewind() { $this->source->rewind(); $this->findnext(); } |
|
21 private function findnext() { |
|
22 while ($this->source->valid()) { |
|
23 $c = $this->source->current(); |
|
24 if ($this->filter($c)) break; |
|
25 $this->source->next(); |
|
26 } |
|
27 } |
|
28 private function filter($c) { |
|
29 switch ($this->filtertype) { |
|
30 case 'OWNER': return isset($c['value']['owner']) && is_scalar($c['value']['owner']) && strcasecmp($c['value']['owner'], $this->filtervalue) == 0; |
|
31 case 'KEY': return $c['key'] == hex2bin($this->filtervalue); |
|
32 case 'DOMEXT': return ord($c['label'][0]) == 4 && substr_compare($c['label'], $this->filtervalue, -strlen($this->filtervalue), strlen($this->filtervalue), TRUE) == 0; |
|
33 default: return FALSE; |
|
34 } |
|
35 } |
|
36 } |
|
37 |
|
38 $database = new MARCDatabaseFlatFile(); |
|
39 $key = NULL; |
|
40 $resource = NULL; |
|
41 $reschanged = FALSE; |
|
42 |
|
43 while ($argi < count($argv)) { |
|
44 switch (strtoupper($argv[$argi++])) { |
|
45 case 'OPEN': |
|
46 if ($reschanged) echo "Warning: selected resource has not been updated.\n"; |
|
47 if ($database->IsChanged()) echo "Warning: database has unsaved changes.\n"; |
|
48 $database->Open($argv[$argi++]); |
|
49 $reschanged = FALSE; |
|
50 break; |
|
51 case 'OPENSQLITE': |
|
52 $database = new MARCDatabaseSQLite($argv[$argi++]); |
|
53 break; |
|
54 case 'OPENDBA': |
|
55 $database = new MARCDatabaseDBA($argv[$argi++]); |
|
56 break; |
|
57 case 'SAVE': |
|
58 $database->Save(); |
|
59 break; |
|
60 case 'SAVEAS': |
|
61 $database->SaveAs($argv[$argi]); |
|
62 break; |
|
63 case 'SYNC': |
|
64 $database->SyncHTTP($argv[$argi++]); |
|
65 break; |
|
66 case 'KEY': |
|
67 switch (strtoupper($argv[$argi++])) { |
|
68 case 'CREATE': |
|
69 $key = array('store' => TRUE); |
|
70 $key['pk'] = nacl_crypto_sign_ed25519_keypair($key['sk'], randombytes(32)); |
|
71 $dbchanged = TRUE; |
|
72 echo 'Created public key '.bin2hex($key['pk'])."\n"; |
|
73 break; |
|
74 case 'FORGET': |
|
75 $key['store'] = FALSE; |
|
76 $dbchanged = TRUE; |
|
77 break; |
|
78 case 'STORE': |
|
79 $key['store'] = TRUE; |
|
80 $dbchanged = TRUE; |
|
81 break; |
|
82 case 'USE': |
|
83 $key = array('store' => FALSE, 'pk' => $resource['key']); |
|
84 if (isset($resource['value']['seckey'])) $key['sk'] = $resource['value']['seckey']; |
|
85 if (isset($resource['value']['seckeyenc'])) $key['locked'] = $resource['value']['seckeyenc']; |
|
86 break; |
|
87 case 'IMPORT': |
|
88 $key = array('store' => FALSE); |
|
89 $key['pk'] = nacl_crypto_sign_ed25519_keypair($key['sk'], hex2bin($argv[$argi++])); |
|
90 $dbchanged = TRUE; |
|
91 break; |
|
92 case 'UNLOCK': |
|
93 if (!isset($key['locked'])) throw new Exception('The key is not locked'); |
|
94 $ret = hash('sha512', $key['pk'].$argv[$argi++], TRUE); |
|
95 $key['sk'] = ''; |
|
96 for ($i = 0; $i < 32; $i++) $key['sk'] .= chr(ord($key['locked'][$i]) ^ ord($ret[$i])); |
|
97 $ret = nacl_crypto_sign_ed25519_keypair($key['sk'], $key['sk']); |
|
98 if ($ret != $key['pk']) throw new Exception('Key password is not valid'); |
|
99 echo 'Unlocked public key '.bin2hex($key['pk'])."\n"; |
|
100 break; |
|
101 default: |
|
102 throw new Exception('Unknown key operation '.$argv[$argi-1]); |
|
103 } |
|
104 break; |
|
105 case 'LIST': |
|
106 foreach ($database->GetResources() as $ret) echo labeltoname($ret['label'])."\n"; |
|
107 break; |
|
108 case 'FIND': |
|
109 foreach (filterresources($database->GetResources(), $argv, $argi) as $ret) echo labeltoname($ret['label'])."\n"; |
|
110 break; |
|
111 case 'DELETE': |
|
112 $database->DeleteResource($resource['label']); |
|
113 break; |
|
114 case 'DUMP': |
|
115 dumpresource($resource); |
|
116 break; |
|
117 case 'UPDATE': |
|
118 if (!isset($resource['key'])) $resource['key'] = $key['pk']; |
|
119 unset($resource['serial']); |
|
120 $res = $database->UpdateResource($resource, $key['sk']); |
|
121 if (!$res) throw new Exception('Could not update resource'); |
|
122 $resource = $res->ToArray(); |
|
123 $reschanged = FALSE; |
|
124 break; |
|
125 case 'SET': |
|
126 switch (strtoupper($argv[$argi++])) { |
|
127 case 'OWNER': |
|
128 if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array(); |
|
129 $resource['value']['owner'] = $argv[$argi++]; |
|
130 $reschanged = TRUE; |
|
131 break; |
|
132 case 'DESC': |
|
133 case 'DESCR': |
|
134 case 'DESCRIPTION': |
|
135 if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array(); |
|
136 $resource['value']['descr'] = $argv[$argi++]; |
|
137 $reschanged = TRUE; |
|
138 break; |
|
139 case 'PWAUTH': |
|
140 if (!isset($key['sk'])) throw new Exception('The key is not available'); |
|
141 $ret = hash('sha512', $key['pk'].$argv[$argi++], TRUE); |
|
142 $key['locked'] = ''; |
|
143 for ($i = 0; $i < 32; $i++) $key['locked'] .= chr(ord($key['sk'][$i]) ^ ord($ret[$i])); |
|
144 if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array(); |
|
145 $resource['value']['seckeyenc'] = $key['locked']; |
|
146 $reschanged = TRUE; |
|
147 break; |
|
148 case 'TRANSFER': |
|
149 $ret = $argv[$argi++]; |
|
150 $resource['value']['transfer'] = (strtolower($ret) == 'any') ? '' : hex2bin($ret); |
|
151 $reschanged = TRUE; |
|
152 break; |
|
153 default: |
|
154 throw new Exception('Unknown set operation '.$argv[$argi-1]); |
|
155 } |
|
156 break; |
|
157 case 'ADD': |
|
158 switch (strtoupper($argv[$argi++])) { |
|
159 case 'NS': |
|
160 if (!isset($resource['value']) || !is_array($resource['value'])) $resource['value'] = array(); |
|
161 if (!isset($resource['value']['ns']) || !is_array($resource['value']['ns'])) $resource['value']['ns'] = array(); |
|
162 $nsname = $argv[$argi++]; |
|
163 $nsglue = (strlen($nsname) && $nsname[strlen($nsname)-1] != '.') ? $argv[$argi++] : NULL; |
|
164 if (!isset($resource['value']['ns'][$nsname]) || !is_array($resource['value']['ns'][$nsname])) $resource['value']['ns'][$nsname] = array(); |
|
165 if ($nsglue !== NULL) $resource['value']['ns'][$nsname][] = $nsglue; |
|
166 $reschanged = TRUE; |
|
167 break; |
|
168 default: |
|
169 throw new Exception('Unknown add operation '.$argv[$argi-1]); |
|
170 } |
|
171 break; |
|
172 case 'RESET': |
|
173 switch (strtoupper($argv[$argi++])) { |
|
174 case 'OWNER': |
|
175 if (!is_array($resource['value'])) $resource['value'] = array(); |
|
176 unset($resource['value']['owner']); |
|
177 $reschanged = TRUE; |
|
178 break; |
|
179 case 'DESC': |
|
180 case 'DESCR': |
|
181 case 'DESCRIPTION': |
|
182 if (!is_array($resource['value'])) $resource['value'] = array(); |
|
183 unset($resource['value']['descr']); |
|
184 $reschanged = TRUE; |
|
185 break; |
|
186 case 'PWAUTH': |
|
187 if (!is_array($resource['value'])) $resource['value'] = array(); |
|
188 unset($resource['value']['seckeyenc']); |
|
189 $reschanged = TRUE; |
|
190 break; |
|
191 case 'NS': |
|
192 if (!is_array($resource['value'])) $resource['value'] = array(); |
|
193 unset($resource['value']['ns']); |
|
194 $reschanged = TRUE; |
|
195 break; |
|
196 case 'VALUE': |
|
197 $resource['value'] = array(); |
|
198 $reschanged = TRUE; |
|
199 break; |
|
200 case 'TRANSFER': |
|
201 unset($resource['transfer']); |
|
202 $reschanged = TRUE; |
|
203 break; |
|
204 case 'EXPIRATION': |
|
205 unset($resource['expiration']); |
|
206 $reschanged = TRUE; |
|
207 break; |
|
208 default: |
|
209 throw new Exception('Unknown reset operation '.$argv[$argi-1]); |
|
210 } |
|
211 break; |
|
212 case 'CREATE': |
|
213 if ($reschanged) echo "Warning: selected resource has not been updated.\n"; |
|
214 $reschanged = TRUE; |
|
215 $resource = array('label' => argtolabel($argv, $argi)); |
|
216 break; |
|
217 case 'SELECT': |
|
218 if ($reschanged) echo "Warning: selected resource has not been updated.\n"; |
|
219 $reschanged = FALSE; |
|
220 $label = argtolabel($argv, $argi); |
|
221 $resource = $database->GetResource($label); |
|
222 if (!$resource) echo "Warning: resource ".labeltoname($label)." does not exist.\n"; |
|
223 else $resource = $resource->ToArray(); |
|
224 break; |
|
225 case 'HELP': |
|
226 print_help(); |
|
227 break; |
|
228 default: |
|
229 throw new Exception('Unknown operation '.$argv[$argi-1]); |
|
230 } |
|
231 } |
|
232 if ($reschanged) echo "Warning: selected resource has not been updated.\n"; |
|
233 if ($database->IsChanged()) echo "Warning: database has unsaved changes.\n"; |
|
234 $database->Close(); |
|
235 |
|
236 function filterresources($iterator, $argv, &$argi) { |
|
237 $filtertype = strtoupper($argv[$argi++]); |
|
238 switch ($filtertype) { |
|
239 case 'OWNER': |
|
240 case 'KEY': |
|
241 case 'DOMEXT': |
|
242 $filtervalue = $argv[$argi++]; |
|
243 break; |
|
244 default: |
|
245 throw new Exception('Unknown filter type '.$t); |
|
246 } |
|
247 return new FilteredResourceIterator($iterator, $filtertype, $filtervalue); |
|
248 } |
|
249 function argtolabel($argv, &$argi) { |
|
250 $t = $argv[$argi++]; |
|
251 switch (strtoupper($t)) { |
|
252 case 'LABEL': return hex2bin($argv[$argi++]); |
|
253 case 'CURRENTKEY': return chr(0).$GLOBALS['key']['pk']; |
|
254 case 'RESOURCEKEY': return chr(0).$GLOBALS['resource']['key']; |
|
255 case 'KEY': return chr(0).hex2bin($argv[$argi++]); |
|
256 case 'IP': |
|
257 case 'IP4': |
|
258 case 'IPV4': |
|
259 case 'IP6': |
|
260 case 'IPV6': return ipnettolabel($argv[$argi++]); |
|
261 case 'AS': return chr(3).marc_decode_int32be($argv[$argi++]); |
|
262 case 'DOM': |
|
263 case 'DOMAIN': return chr(4).strtolower(trim($argv[$argi++], '.')); |
|
264 default: |
|
265 if (preg_match('/^AS[0-9]{1-9}$/', $t)) return chr(3).marc_decode_int32be(substr($argv[$argi++], 2)); |
|
266 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); |
|
267 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); |
|
268 if (preg_match('/^[a-f0-9]{64}$/i', $t)) return chr(0).hex2bin($t); |
|
269 if (preg_match('/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$/i', $t)) return chr(4).strtolower(trim($t, '.')); |
|
270 throw new Exception('Could not detect label type for '.$t); |
|
271 } |
|
272 } |
|
273 function ipnettolabel($s) { |
|
274 $ip = inet_pton(strtok($s, '/')); |
|
275 $pl = intval(strtok('/')); |
|
276 if ($pl == 0) throw new Exception('Invalid IP network specified'); |
|
277 if (strlen($ip) == 4) return chr(1).$ip.chr($pl); |
|
278 if (strlen($ip) == 16) return chr(2).$ip.chr($pl); |
|
279 } |
|
280 function labeltoname($l) { |
|
281 switch (ord($l)) { |
|
282 case 0: return 'KEY '.bin2hex(substr($l, 1)); |
|
283 case 1: if (strlen($l) == 6) return 'IPv4 '.inet_ntop(substr($l, 1, 4)).'/'.ord($l[5]); else return 'LABEL '.bin2hex($l); |
|
284 case 2: if (strlen($l) == 18) return 'IPv6 '.inet_ntop(substr($l, 1, 16)).'/'.ord($l[17]); else return 'LABEL '.bin2hex($l); |
|
285 case 3: if (strlen($l) == 5) return 'AS '.marc_decode_int32be(substr($l, 1, 4)); else return 'LABEL '.bin2hex($l); |
|
286 case 4: if (strlen($l) > 1) return 'DOM '.substr($l, 1); else return 'LABEL '.bin2hex($l); |
|
287 default: return 'LABEL '.bin2hex($l); |
|
288 } |
|
289 } |
|
290 function dumpresource($r, $p = '') { |
|
291 if (is_null($r)) { |
|
292 echo "NULL\n"; |
|
293 } else if (is_string($r)) { |
|
294 $bin = FALSE; |
|
295 for ($i = 0; $i < strlen($r); $i++) $bin |= ord($r[$i]) < 32 || ord($r[$i]) > 126; |
|
296 if ($bin) echo '0x'.bin2hex($r)."\n"; |
|
297 else echo '"'.$r.'"'."\n"; |
|
298 } else if (is_scalar($r)) { |
|
299 echo $r."\n"; |
|
300 } else if (is_array($r)) { |
|
301 echo "array(\n"; |
|
302 foreach ($r as $key => $value) { |
|
303 echo $p.' ['.$key.'] => '; |
|
304 dumpresource($value, $p.' '); |
|
305 } |
|
306 echo $p." )\n"; |
|
307 } else { |
|
308 print_r($r); |
|
309 } |
|
310 } |
|
311 function randombytes($n) { |
|
312 $b = ''; |
|
313 $file = fopen('/dev/urandom', 'r'); |
|
314 for ($i = 0; $i < $n; $i++) $b .= fgetc($file); |
|
315 fclose($file); |
|
316 return $b; |
|
317 } |
|
318 |
|
319 function print_help() { |
|
320 echo 'Usage: marcus.php [operation] [arguments] [operation] [arguments]... |
|
321 open [filename.mdb] - opens the specified database file |
|
322 save - saves the current database file |
|
323 saveas [filename.mdb] - saves the current data to a new databse file |
|
324 sync [url] - synchronize database with remote server |
|
325 key create - create a new key pair |
|
326 key forget - do not store the current key pair in the local database |
|
327 key store - store the current key pair in the local database |
|
328 key use - use the key pair from the currently selected resource |
|
329 key import [secretkey] - import the key pair defined by the given secret key |
|
330 key unlock [password] - unlock a password protected key pair |
|
331 list - list registered resources |
|
332 find [type] [value] - list registered sources matching filter (type=OWNER|KEY|DOMEXT) |
|
333 create [identifier] - create given resource |
|
334 create currentkey - create resource for current key pair |
|
335 create ip|ip4|ipv4 [ipv4network] - create resource for IPv4 network |
|
336 create ip|ip6|ipv6 [ipv6network] - create resource for IPv6 network |
|
337 create dom|domain [ipv6network] - create resource for domain name |
|
338 select [identifier] - select resource given by identifier |
|
339 select currentkey - select key resource for current key pair |
|
340 select resourcekey - select key resource for the key that signed the currently selected resource |
|
341 select label [identifier] - select resource by hexadecimal label |
|
342 select key [publickey] - select key resource (hexadecimal) |
|
343 select ip|ip4|ipv4 [ipv4network] - select resource for IPv4 network |
|
344 select ip|ip6|ipv6 [ipv6network] - select resource for IPv6 network |
|
345 select dom|domain [ipv6network] - select resource for domain name |
|
346 delete - delete currently selected resource |
|
347 dump - display currently selected resource |
|
348 update - update selected resource in the local database |
|
349 set owner [name] - set the owner name for the selected resource |
|
350 set descr|desc|description [text] - set the description for the selected resource |
|
351 set pwauth [password] - store the current key pair in the selected resource for password authentication |
|
352 set transfer any - allow anyone to take over the resource |
|
353 set transfer [key] - transfer the resource to given key |
|
354 TODO: set expiration [value] |
|
355 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) |
|
356 add ns [name]. - add an external nameserver |
|
357 reset owner - clear the owner |
|
358 reset descr|desc|description - clear the description |
|
359 reset pwauth - remove the key pair from the recourse, disabling password authentication |
|
360 reset ns - clear the nameserver records |
|
361 reset transfer - disable resource transfers |
|
362 reset expiration - disable explicit expiration |
|
363 reset value - clear the owner, description, password authentication and nameserver records |
|
364 |
|
365 Examples: |
|
366 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 |
|
367 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 |
|
368 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 |
|
369 OPEN marc.mdb SYNC http://marc.ucis.ano/ SAVE |
|
370 '; |
|
371 } |