Subversion Repositories oidplus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
226 daniel-mar 1
<?php
2
 
3
# todo: "recursive" functions private machen, damit params nicht manipuliert werden können...
4
# todo: return false -> exception
5
 
6
# TODO false vs null ausgaben
7
# TODO signed openssl output
8
 
9
# TODO: oid definieren für name based
10
# QUE: geht extention von macros?
11
 
12
# TODO: "server time" am ende von Âquery() anzeigen?
13
 
14
require_once __DIR__ . '/../includes/uuid_utils.inc.php';
15
require_once __DIR__ . '/../includes/oid_utils.inc.php';
16
 
17
define('UUID_NAMEBASED_NS_OidPlusMisc',   'ad1654e6-7e15-11e4-9ef6-78e3b5fc7f22');
18
define('UUID_NAMEBASED_NS_OidPlusNSOnly', '0943e3ce-4b79-11e5-b742-78e3b5fc7f22');
19
 
20
define('GENERATION_ROOT_DEFAULT', '.2.25.<SYSID>');
21
 
22
class VolcanoDB {
23
        protected $macro_data = array(); // TODO: use with caution!
24
        protected $oid_data = array(); // TODO: use with caution!
25
        protected $authTokens = array();
26
        protected $configuration = array();
27
        protected $configuration_file = null;
28
        protected $configuration_may_create = false;
29
 
30
        protected $redactedMessage = '# Information redacted. Please append a correct auth token with your request.';
31
#       protected $redactedMessage = null;
32
 
33
        public function filterRedactedEntries($output) {
34
                $out = '';
35
 
36
                $lines = explode("\n", $output);
37
                foreach ($lines as $line) {
38
                        $line = trim($line);
39
                        if ($line == '') continue;
40
                        $name_val = explode(':', $line, 2);
41
                        if (count($name_val) != 2) continue;
42
                        if (trim($name_val[1]) != trim($this->redactedMessage)) {
43
                                $out .= $line."\n";
44
                        }
45
 
46
                }
47
 
48
                return $out;
49
        }
50
 
51
        public function getOIDData() {
52
                return $this->oid_data;
53
        }
54
 
55
        public function getMacroData() {
56
                return $this->macro_data;
57
        }
58
 
59
        public function __construct($conf_file, $may_create) {
60
                $this->configuration_file = $conf_file;
61
                $this->configuration_may_create = $may_create;
62
                $this->readConfiguration();
63
        }
64
 
65
        public function getConfigValue($name) {
66
                if (!isset($this->configuration[$name])) return false;
67
                return $this->configuration[$name];
68
        }
69
 
70
        public function setConfigValue($name, $value) {
71
                $this->configuration[$name] = $value;
72
                $this->saveConfiguration();
73
        }
74
 
75
        protected function readConfiguration() {
76
                if (is_null($this->configuration_file)) return;
77
                $this->configuration = array();
78
                if (!file_exists($this->configuration_file)) {
79
                        if (!$this->configuration_may_create) {
80
                                $metadata = array();
81
                                throw new VolcanoException("Configuration file '$conf_file' not found", $metadata);
82
                        }
83
                } else {
84
                        $lines = file($this->configuration_file);
85
                        foreach ($lines as &$line) {
86
                                $line = trim($line);
87
                                if (empty($line)) continue;
88
                                if ($line[0] == '#') continue;
89
                                $ary = explode('=', $line, 2);
90
                                if (count($ary) < 2) continue;
91
                                $this->configuration[$ary[0]] = $ary[1];
92
                        }
93
                }
94
 
95
                if ($this->getConfigValue('system_unique_id') === false) {
96
                        $this->setConfigValue('system_unique_id', gen_uuid());
97
                }
98
        }
99
 
100
        public function getSystemID() {
101
                return $this->getConfigValue('system_unique_id');
102
        }
103
 
104
        public function getSystemIDInteger() {
105
                $val = uuid_numeric_value($this->getSystemID());
106
                if (!$val) {
107
                        $metadata = array();
108
                        throw new VolcanoException("system_unique_id is not a valid UUID. Please check db/local.conf", $metadata);
109
                }
110
                return $val;
111
        }
112
 
113
        protected function saveConfiguration() {
114
                if (is_null($this->configuration_file)) return;
115
                file_put_contents($this->configuration_file, '');
116
                foreach ($this->configuration as $name => $val) {
117
                        file_put_contents($this->configuration_file, $name.'='.$val."\n", FILE_APPEND);
118
                }
119
        }
120
 
121
        public function setRedactedMessage($msg) {
122
                $this->redactedMessage = $msg;
123
        }
124
 
125
        public function getRedactedMessage() {
126
                return $this->redactedMessage;
127
        }
128
 
129
        public function disableRedactedMessage() {
130
                $this->redactedMessage = null;
131
        }
132
 
133
        public function isRedactedMessageEnabled() {
134
                return !is_null($this->redactedMessage);
135
        }
136
 
137
        public function addAuthToken($token) {
138
                if (!in_array($token, $this->authTokens)) {
139
                        $this->authTokens[] = $token;
140
                        $this->clearCaches();
141
                }
142
        }
143
 
144
        public function clearAuthTokens() {
145
                $this->authTokens = array();
146
                $this->clearCaches();
147
        }
148
 
149
        public function addDir($dir, $recursive=true) {
150
                if (!is_dir($dir)) {
151
                        throw new VolcanoException("Directory not found: $dir");
152
                }
153
                $ary = glob($dir . '/*');
154
                if (count($ary) == 0) return;
155
                if ($ary === false) return;
156
                sort($ary);
157
                foreach ($ary as &$a) {
158
                        if ($a[0] == '.') continue; // e.g. '.', '..', or '.htaccess'
159
                        if (self::endsWith($a, '~')) continue; // recycled files
160
                        if (($recursive) && (is_dir($a))) {
161
                                $this->addDir($a, $recursive);
162
                                continue;
163
                        }
164
                        if (!is_file($a)) continue;
165
                        if (!self::endsWith($a, '.db')) continue;
166
                        $this->addFile($a);
167
                }
168
                unset($a);
169
 
170
#echo '<!--';
171
#print_r($this->oid_data);
172
#print_r($this->macro_data);
173
#echo '-->';
174
 
175
        }
176
 
177
        public function addFile($file) {
178
                $h = fopen($file, 'r');
179
 
180
                $lineno = 1;
181
                $header = fgets($h);
182
                if (trim($header) != '['.$this->getFileformatIdentifier().']') {
183
                        $metadata = array();
184
                        $metadata['source'] = "$file:0";
185
                        throw new VolcanoException("Header of file is invalid", $metadata);
186
                }
187
 
188
                while (!feof($h)) { // TODO OK?
189
                        $line = fgets($h);
190
                        $lineno++;
191
                        $this->addLine($line, $file.':'.$lineno);
192
                }
193
                fclose($h);
194
        }
195
 
196
# todo: wie das problem bzgl allowed field-override lösen? (superior RA verbietet "identifier" change)
197
 
198
        protected function isSpecialInvisibleField($data) {
199
                assert(isset($data['attrib_name']));
200
 
201
                $field_name = $data['attrib_name'];
202
 
203
                if (empty($field_name)) return false;
204
 
205
                if ($field_name == '*read-auth')  return true;
206
        #       if ($field_name == '*write-auth') return true;
207
                if ($field_name == '*invisible')  return true;
208
 
209
                if ($field_name[0] == '*') {
210
                        throw new VolcanoException("Invalid system command '$field_name'", $data);
211
                }
212
 
213
                return false;
214
        }
215
 
216
        public function isAuthentificated($oid) {
217
                $readAuths = $this->getDatasets($oid, '*read-auth', false); # todo: [co] erzwingen?
218
                foreach ($readAuths as &$ra) {
219
                        $nid = isset($ra['attrib_params'][0]) ? $ra['attrib_params'][0] : '';
220
                        $found_valid_authprovider = false;
221
                        foreach (self::$authProvs as &$ap) {
222
                                if ($ap->checkId($nid)) {
223
                                        $found_valid_authprovider = true;
224
                                        if ($ap->checkAuth($ra['value'], null)) return true; // first, check with empty/null authToken, e.g. for IP-Authentification
225
                                        foreach ($this->authTokens as &$token) {
226
                                                if ($ap->checkAuth($ra['value'], $token)) return true;
227
                                        }
228
                                        unset($token);
229
                                }
230
                        }
231
                        if (!$found_valid_authprovider) {
232
                                # return false;
233
                                throw new VolcanoException("No authentification provider found", $ra);
234
                        }
235
                        unset($ap);
236
                }
237
                unset($ra);
238
                return false;
239
        }
240
 
241
        protected static function showSource($source) {
242
                if (strpos($source, ':') === false) return $source;
243
                preg_match('@^(.+):(\\d+)$@', $source, $m);
244
                $file = $m[1];
245
                $line = $m[2];
246
                return "$file at line $line";
247
        }
248
 
249
        protected function clearCaches() { # TODO: aufsplitten in caches, die die auth betreffen und die, die es nicht tun?
250
                $this->all_cache = null;
251
                $this->cache_recdatasets = array();
252
                $this->cache_listOIDs = array();
253
        }
254
 
255
        protected function getIndexGenerationRoot() {
256
                $val = $this->getConfigValue('local_index_generation_root');
257
                if (!$val) {
258
                        $val = GENERATION_ROOT_DEFAULT;
259
                        $this->setConfigValue('local_index_generation_root', $val);
260
                }
261
 
262
                return $this->extendOID($val, true);
263
        }
264
 
265
        public function extendOID($identifier, $no_gen_root_replacement=false) {
266
                $identifier = str_replace('<SYSID>', $this->getSystemIDInteger(), $identifier);
267
                if (!$no_gen_root_replacement) {
268
                        // Avoids an endless recursion with getIndexGenerationRoot
269
                        $identifier = str_replace('<GENROOT>', $this->getIndexGenerationRoot(), $identifier);
270
                }
271
                $identifier = sanitizeOID($identifier, true);
272
                return $identifier;
273
        }
274
 
275
        public function addLine($line, $source='(Direct Input):0') {
276
                $line = trim($line);
277
                $line = str_replace("\t", ' ', $line);
278
                $line = str_replace(array("\n", "\r"), '', $line);
279
 
280
                $line_expl = explode(' ', $line, 2);
281
 
282
                if (empty($line) || ($line[0] == '#')) {
283
                        return;
284
                } else if (strpos($line_expl[0], ':') !== false) {
285
                        $this->clearCaches();
286
 
287
                        $expl2 = explode(':', $line_expl[0], 2);
288
                        $namespace  = trim(strtolower($expl2[0]));
289
                        $identifier = trim($expl2[1]);
290
 
291
                        if (empty($namespace)) {
292
                                $metadata = array();
293
                                $metadata['source'] = $source;
294
                                throw new VolcanoException("The namespace may not be empty", $metadata);
295
                        }
296
 
297
                        if (empty($identifier)) {
298
                                $metadata = array();
299
                                $metadata['source'] = $source;
300
                                throw new VolcanoException("The identifier may not be empty", $metadata);
301
                        }
302
 
303
                        $is_oid      = $namespace == 'oid';
304
                        $is_macro    = $namespace == '*macro';
305
                        $is_external = $namespace == '*external';
306
                        $is_other    = (!$is_oid) && (!$is_macro) && (!$is_external);
307
 
308
                        # TODO: isSpecialNamespace($namespace, $source) ?
309
                        if (($namespace[0] == '*') && (!$is_macro) && (!$is_external)) {
310
                                $metadata = array();
311
                                $metadata['source'] = $source;
312
                                throw new VolcanoException("Invalid system namespace '$namepsace'", $metadata);
313
                        }
314
 
315
                        if ($is_external) {
316
                                # TODO: wenn lokal und mit .php endend, dann eval()
317
                                $url = $identifier;
318
                                $this->addFile($url);
319
                                return;
320
                        }
321
 
322
                        if ($is_macro) {
323
                                $identifier = strtoupper($identifier);
324
                        }
325
 
326
                        if ($is_other) {
327
                                # TODO!!! diese handlers als plugins auslagern!
328
                                # QUE: sollte sowas nicht OID+ und nicht Volcano sein?
329
 
330
                                $flags = '';
331
                                if (strpos($identifier, '[co]') !== false) {
332
                                        $identifier = str_replace('[co]', '', $identifier);
333
                                        $flags .= '[co]';
334
                                }
335
                                $original_identifier = $identifier;
336
                                # TODO: check for unknown attrib flags
337
 
338
/*
339
                                if (($namespace == 'guid') || ($namespace == 'uuid')) {
340
                                        $guid = $identifier;
341
                                } else {
342
                                        $guid = gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OidPlusMisc, $namespace.':'.$identifier);
343
                                }
344
                                $identifier = '.'.uuid_to_oid($guid); # TODO: parameter, ob mit leading dot oder nicht
345
*/
346
 
347
                                $is_guid_ns = ($namespace == 'guid') || ($namespace == 'uuid');
348
 
349
                                if ($is_guid_ns && (!$this->getConfigValue('uuid_indexes_in_genroot'))) {
350
                                        # uuid_indexes_in_genroot wird verwendet, sonst würde bei einer sammlung von MS COM clsids würde sonst die root zone platzen
351
                                        $guid = $identifier;
352
                                        $identifier = '.'.uuid_to_oid($guid); # TODO: parameter, ob mit leading dot oder nicht
353
                                } else {
354
                                        # Es wird ein weltweit einzigartiger Root angelegt, anstelle 2.25 für jeden einzelnen index zu verwenden.
355
                                        # Grund: Indexes sollen (alleine schon aus Performancegründen bei der Root-Anzeige im Webinterface)
356
                                        # in einer Wurzel zusammengefasst werden. Diese Wurzel sollte aber unter der legalen Kontrolle des
357
                                        # Eigentümers stehen. "2.25" kann nicht als Wurzel definiert werden, da der Benutzer kein Recht hat,
358
                                        # "2.25" sein Eigen zu nennen.
359
                                        # PROBLEM: aber dann kann man von den einzelnen items nicht das "uuid" feld ablesen, um an die
360
                                        # per UUID_NAMEBASED_NS_OidPlusMisc erstellte UUID ranzukommen, da das "uuid" feld nur kindsknoten von 2.25
361
                                        # konvertiert.
362
                                        $gen_root = $this->getIndexGenerationRoot();
363
 
364
                                        if ($namespace == 'doi') {
365
                                                if (!self::beginswith($identifier, '10.')) {
366
                                                        $metadata = array();
367
                                                        $metadata['source'] = $source;
368
                                                        throw new VolcanoException("Invalid DOI '$identifier'. Must start with '10.'", $metadata);
369
                                                }
370
                                                $x = substr($identifier, 3/* strlen('10.') */);
371
                                                $orgid = explode('/', $x, 2)[0];
372
 
373
                                                # doi(10) <orgid> <doi:identifier>
374
                                                $identifier_ns = $gen_root.'.10.'.$orgid;
375
 
376
                                                if (!$this->oidDescribed($identifier_ns, false)) {
377
                                                        $this->addLine("oid:$identifier_ns description: Organisation $orgid", $source);
378
                                                }
379
 
380
                                                $guid_item = gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OidPlusMisc, 'doi:'.$identifier);
381
                                        } else if ($is_guid_ns) {
382
                                                # uuid(25) <numeric_uuid>
383
                                                $identifier_ns = $gen_root.'.25';
384
 
385
                                                # TODO: im web frontend trotzdem die originale index uuid zum klicken abieten (action=uuid_info, nicht nur action=show_index!)
386
                                                $guid_item = $identifier;
387
                                        } else {
388
                                                $guid_ns = gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OidPlusNSOnly, $namespace);
389
 
390
                                                # ns(0) <ns> <ns:identifier>
391
                                                $identifier_ns = $gen_root.'.0.'.uuid_numeric_value($guid_ns);
392
 
393
                                                if (!$this->oidDescribed($identifier_ns, false)) {
394
                                                        $this->addLine("oid:$identifier_ns description: Automatically generated arc for namespace \"$namespace\"", $source);
395
                                                        if (oid_id_is_valid($namespace)) {
396
                                                                $this->addLine("oid:$identifier_ns identifier:$namespace", $source);
397
                                                        }
398
                                                }
399
 
400
                                                $guid_item = gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_OidPlusMisc, $namespace.':'.$identifier);
401
                                        }
402
 
403
                                        $identifier = $identifier_ns.'.'.uuid_numeric_value($guid_item);
404
                                }
405
                                if (!$this->oidDescribed($identifier, false)) { # TODO: WARUM? man kann den index doch auch so anlegen??? !!
406
                                        $this->addLine('oid:'.$identifier." ${flags}index($namespace):$original_identifier", $source);
407
                                }
408
                                $is_other = false;
409
                                $is_oid   = true;
410
                        }
411
 
412
                        # Muss als letztes stehen, da $is_other zu $is_oid konvertiert werden kann
413
                        if ($is_oid) {
414
                                $bak_oid = $identifier;
415
                                $identifier = $this->extendOID($identifier);
416
 
417
                                if ($identifier === false) {
418
                                        $metadata = array();
419
                                        $metadata['source'] = $source;
420
                                        $metadata['oid']    = $bak_oid;
421
                                        throw new VolcanoException("Illegal OID or dot notation with leading dot not recognized", $metadata);
422
                                }
423
                        }
424
 
425
                        if (!isset($line_expl[1])) {
426
                                $metadata = array();
427
                                $metadata['source'] = $source;
428
                                throw new VolcanoException("Syntax error in DB: line contains no data", $metadata);
429
                        }
430
 
431
                        $data = isset($line_expl[1]) ? $line_expl[1] : '';
432
 
433
                        $bry = explode(':', $data, 2);
434
                        $attrib_name = (isset($bry[0])) ? $bry[0] : '';
435
                        $value       = (isset($bry[1])) ? $bry[1] : '';
436
 
437
                        // <attrib_name>(<attrib_params>)
438
                        if (preg_match("@^(.*)\((.+)\)(.*)$@isU", $attrib_name, $m)) {
439
                                $attrib_name   = $m[1].$m[3];
440
                                $attrib_params = explode(',', $m[2]);
441
                        } else {
442
                                $attrib_params = array();
443
                        }
444
 
445
                        // Process params
446
                        // [co]<attrib_name> marks the line as confidential (read-auth necessary)
447
                        $attrib_name = str_replace('[co]', '', $attrib_name, $cnt);
448
                        $flag_confidential = $cnt > 0;
449
 
450
                        $attrib_name = str_replace('[xt]', '', $attrib_name, $cnt);
451
                        $flag_extend = $cnt > 0;
452
 
453
                        $attrib_name = str_replace('[add]', '', $attrib_name, $cnt);
454
                        $flag_add = $cnt > 0;
455
 
456
                        $attrib_name = str_replace('[del]', '', $attrib_name, $cnt);
457
                        $flag_del = $cnt > 0;
458
 
459
                        $attrib_name = str_replace('[in]', '', $attrib_name, $cnt);
460
                ##      $flag_inherit = $cnt > 0;
461
                        # Wir speichern hier keinen boolean, sondern den $attrib_name .
462
                        # Dadurch wird sichergestellt, dass ein [xt]-Block im ganzen überschrieben wird, und nicht jede Zeile einzeln.
463
                        # Jede Zeile enthält also im flag_inherit den Namen ihres [xt]-Blocks.
464
                        $flag_inherit = ($cnt > 0) ? $attrib_name : false; // attrib_name muss ohne restliche flags sein, deswegen [in] zuletzt parsen
465
 
466
                        # No! We just leave inherited so we can use it later
467
                        /*
468
                        if (($flag_inherit) && ($is_macro)) {
469
                                $metadata = array();
470
                                $metadata['source'] = $source;
471
                                throw new VolcanoException("Macro attributes cannot be inherited", $metadata);
472
                        }
473
                        */
474
 
475
                        if (preg_match('@\[(.*)\]@isU', $attrib_name, $m)) {
476
                                $metadata = array();
477
                                $unkn_flag = $m[1];
478
                                $metadata['source'] = $source;
479
                                throw new VolcanoException("Flag [$unkn_flag] unknown", $metadata);
480
                        }
481
 
482
                        $attrib_name = trim($attrib_name);
483
 
484
                        // Add data
485
                        if ($flag_extend) {
486
                                $value = trim($value);
487
 
488
                                // Allow multiple whitespaces as separator for macro params
489
                                $value = preg_replace('@(\s+)@', ' ', $value);
490
 
491
                                # $macro_params = explode(' ', $value); // <macroname> <macroparam_1> <...>
492
                                $macro_params = str_getcsv($value, ' '); // requires PHP 5.3, see http://stackoverflow.com/questions/2202435/php-explode-the-string-but-treat-words-in-quotes-as-a-single-word for alternatives
493
                                $macroname = $macro_params[0];
494
                                if (!isset($this->macro_data[strtoupper($macroname)])) {
495
                                        $metadata = array();
496
                                        $metadata['source'] = $source;
497
 
498
                                        # TODO: zuerst alle anderen *.db files durchprobieren ob es auftaucht?
499
                                        throw new VolcanoException("Macro '$macroname' not found", $metadata);
500
                                }
501
 
502
                                $x = $this->macro_data[strtoupper($macroname)];
503
 
504
                                foreach ($x as $y) { // no &$y
505
                                #       $y['source'] bleibt erhalten
506
 
507
                                        // Replace macro params in value, attrib_name and attrib_params
508
                                        foreach ($macro_params as $mp_n => &$mp_x) {
509
                                                $y['value']         = str_replace('__'.$mp_n.'__', $mp_x, $y['value']);
510
                                                $y['attrib_name']   = str_replace('__'.$mp_n.'__', $mp_x, $y['attrib_name']);
511
                                                foreach ($y['attrib_params'] as &$ap_val) {
512
                                                        $ap_val = str_replace('__'.$mp_n.'__', $mp_x, $ap_val);
513
                                                }
514
                                        }
515
                                        // Remove unused macro param place holders
516
                                        // NO! Otherwise we cannot use parametrized macros which are using parametrized macros itself
517
                                        /*
518
                                        $y['value']         = preg_replace('@__(\\d+)__@sU', '', $y['value']);
519
                                        $y['attrib_name']   = preg_replace('@__(\\d+)__@sU', '', $y['attrib_name']);
520
                                        foreach ($y['attrib_params'] as &$ap_val) {
521
                                                $ap_val = preg_replace('@__(\\d+)__@sU', '', $ap_val);
522
                                        }
523
                                        */
524
 
525
                                        // $y['attrib_params']     = $attrib_params;
526
                                        // Nein, attrib_params lieber direkt nach ?? einfügen:
527
                                        if (count($attrib_params) > 0) {
528
                                                $attr_ext = '('.implode(',', $attrib_params).')';
529
                                        } else {
530
                                                $attr_ext = '';
531
                                        }
532
                                        # NG: '??' auch ersetzen in attrib_param, value ?
533
                                        $y['attrib_name']       = str_replace('??', $attrib_name.$attr_ext, $y['attrib_name']);
534
                                        // QUE: wie verhalten sich die params im falle einer vererbung? kann man params missbrauchen um z.B. ra(1), ra(2) etc zu kennzeichnen?
535
 
536
                                        $y['flag_confidential'] = $flag_confidential || $y['flag_confidential'];
537
                                        # todo: read-auth im macro-bereich?
538
                                #       $y['flag_extend']       = true; // TODO?
539
                                        $y['flag_inherit']      = trim($flag_inherit); # dieser flag_inherit geht von der ursprungs-oid über alle stufen der macros und submacros hinweg
540
                                        $y['flag_add']          = $flag_add;
541
 
542
                                        if ($is_macro) {
543
                                                $y['macro'] = $identifier;
544
                                        } else {
545
                                                $y['oid']   = $identifier;
546
                                        }
547
 
548
                                        // Check if the identifier is valid to the ASN.1 standards
549
                                        $flag_extend = strpos($y['attrib_name'], '[xt]') !== false;
550
                                        if ((!$flag_extend) && (!$is_macro) && (strtolower($y['attrib_name']) == 'identifier') && (!oid_id_is_valid($y['value']))) {
551
                                                throw new VolcanoException("Identifier '".$y['value']."' is not a valid ASN.1 identifier", $y);
552
                                        }
553
 
554
                                        if ((!$flag_extend) && (!$is_macro) && (strtolower($y['attrib_name']) == 'iri') && (!iri_valid($y['value']))) {
555
                                                throw new VolcanoException("Identifier '".$y['value']."' is not a valid IRI identifier", $y);
556
                                        }
557
 
558
                                        if ($is_macro) {
559
                                                $this->macro_data[$identifier][] = $y;
560
                                        } else {
561
                                                $this->oid_data[$identifier][] = $y;
562
                                        }
563
                                }
564
                        } else {
565
                                // $value = trim($value); // TODO: oder doch lieber? z.b. descriptions einrücken?
566
 
567
                                # NG: '??' auch ersetzen in attrib_param, value ?
568
                                if ((!$is_macro) && (strpos($attrib_name, '??') !== false)) {
569
                                        $metadata = array();
570
                                        $metadata['source'] = $source;
571
                                        throw new VolcanoException("'??' is only allowed inside a macro", $metadata);
572
                                }
573
 
574
                                $y = array(
575
                                        'source'            => $source,
576
                                        'attrib_name'       => $attrib_name,
577
                                        'attrib_params'     => $attrib_params,
578
                                        'value'             => $value,
579
                                        'flag_confidential' => $flag_confidential,
580
                                #       'flag_extend'       => $flag_extend,
581
                                        'flag_inherit'      => trim($flag_inherit),
582
                                        'flag_add'          => $flag_add,
583
                                        'flag_del'          => $flag_del
584
                                );
585
 
586
                                if ($is_macro) {
587
                                        $y['macro'] = $identifier;
588
                                } else {
589
                                        $y['oid']   = $identifier;
590
                                }
591
 
592
                                // Check if the identifier is valid to the ASN.1 standards
593
                                if ((!$flag_extend) && (!$is_macro) && (strtolower($y['attrib_name']) == 'identifier') && (!oid_id_is_valid($y['value']))) {
594
                                        throw new VolcanoException("Identifier '".$y['value']."' is not a valid ASN.1 identifier", $y);
595
                                }
596
 
597
                                if ($is_macro) {
598
                                        $this->macro_data[$identifier][] = $y;
599
                                } else {
600
                                        $this->oid_data[$identifier][] = $y;
601
                                }
602
                        }
603
                } else {
604
                        $metadata = array();
605
                        $metadata['source'] = $source;
606
                        throw new VolcanoException("Syntax error in database (not beginning with 'oid:' or '*macro:' or a valid identifier like 'guid:<uuid>')", $metadata);
607
                }
608
        }
609
 
610
        private $all_cache = array();
611
        # TODO: auch $parent_oid parameter
612
        public function getAllOIDs($check_auth=true) {
613
                $vvv = $check_auth;
614
                if (isset($this->all_cache[$vvv])) return $this->all_cache[$vvv];
615
 
616
                // TODO: $with_macros per default false. ausblenden. xxx
617
 
618
                $ary = array();
619
                foreach ($this->oid_data as $b => &$data) {
620
                        if ($check_auth) {
621
                                if ($this->oidDescribed($b, $check_auth)) $ary[] = $b;
622
                        } else {
623
                                $ary[] = $b;
624
                        }
625
                }
626
 
627
                $this->all_cache[$vvv] = $ary;
628
                return $ary;
629
        }
630
 
631
        // TODO: -> functions.inc.php
632
        protected static function getShortestString(&$ary) {
633
                $minlen = PHP_INT_MAX;
634
                $out = false;
635
                foreach ($ary as &$a) {
636
                        if ($a === null) continue;
637
                        $len = strlen($a);
638
                        if ($len < $minlen) {
639
                                $out = $a;
640
                                $minlen = $len;
641
                        }
642
                }
643
                unset($a);
644
                return $out;
645
        }
646
 
647
        // TODO: -> oid_utils.inc.php ?
648
        protected static function strikeRoot(&$ary, $oid) {
649
                $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
650
                if ($oid === false) return false;
651
 
652
                $dotstop = self::appendDot($oid);
653
                foreach ($ary as &$a) {
654
                        if (($a == $oid) || (self::beginsWith($a, $dotstop))) {
655
                                $a = null;
656
                        }
657
                }
658
                unset($a);
659
        }
660
 
661
        public function findRoots($check_auth=true) {
662
                $out = array();
663
 
664
                $ary = $this->getAllOIDs($check_auth);
665
 
666
                while (($oid = $this->getShortestString($ary)) !== false) {
667
                        $out[] = $oid;
668
                        $this->strikeRoot($ary, $oid);
669
                }
670
 
671
                oidSort($out);
672
 
673
                return $out;
674
        }
675
 
676
        public static function findSearchProvider($nid) {
677
                $found_prov = null;
678
                foreach (self::$searchProvs as &$searchprov) {
679
                        if ($searchprov->checkId($nid)) {
680
                                $found_prov = $searchprov;
681
                                break;
682
                        }
683
                }
684
                unset($searchprov);
685
 
686
                return $found_prov;
687
        }
688
 
689
        public function findOID(&$indexname, $check_auth=true) {
690
                $lowest_oid  = null;
691
                $lowest_dist = PHP_INT_MAX;
692
                $lowest_nid  = null;
693
                $oids = $this->getAllOIDs($check_auth);
694
 
695
                $ary = explode(':', $indexname, 2);
696
                if (count($ary) == 2) {
697
                        $suggested_nid = $ary[0]; // This does not neccessarily be a searchprovider NID, since it could also be a part of an IPv6 address
698
                        $found_prov = self::findSearchProvider($suggested_nid);
699
                        if (!is_null($found_prov)) {
700
                                $indexname = $ary[1];
701
                        } else {
702
                                // No search provider was found. We don't strip the text before ':', because it
703
                                // could be actually a part of the index name, e.g. for an IPv6
704
                                # $indexname = $ary[1];
705
                        }
706
                } else {
707
                        $suggested_nid = '';
708
                        $found_prov = null;
709
                }
710
 
711
                $has_preferred_prov = !is_null($found_prov);
712
 
713
                foreach ($oids as &$oid) {
714
                        $search_data = $this->getDatasets($oid, 'index'); # todo: wird [co] beachtet?
715
                        foreach ($search_data as &$data) {
716
                                $nid   = $data['attrib_params'][0];
717
                                $value = $data['value'];
718
 
719
 
720
                                if (!$has_preferred_prov) $found_prov = self::findSearchProvider($nid);
721
 
722
                                if (!is_null($found_prov)) {
723
                                        $cur_distance = $found_prov->calcDistance($value, $indexname);
724
                                        if ($cur_distance === false) continue;
725
                                        if ($cur_distance < 0) continue; // item is too specific for the request
726
                                        // else if ($cur_distance == 0) return array($oid, 0, $nid);
727
                                        else if ($cur_distance < $lowest_dist) {
728
                                                $lowest_dist = $cur_distance;
729
                                                $lowest_oid  = $oid;
730
                                                $lowest_nid  = $nid;
731
                                                if ($cur_distance == 0) break 2;
732
                                        }
733
                                } else {
734
                                        // We don't have a Searchprovider, but we can look for an index which matches exactly
735
                                        if ($suggested_nid == $nid) {
736
                                                $indexname = isset($ary[1]) ? $ary[1] : '';
737
                                        }
738
                                        if ($value == $indexname) {
739
                                                $lowest_dist = 0;
740
                                                $lowest_oid = $oid;
741
                                                $lowest_nid = $nid;
742
                                                break 2;
743
                                        }
744
                                }
745
                        }
746
                        unset($data);
747
                }
748
                unset($oid);
749
 
750
                if (is_null($lowest_oid)) return null;
751
                return array($lowest_oid, $lowest_dist, $lowest_nid);
752
        }
753
 
754
        private $cache_listOIDs = array();
755
        public function listOIDs($parent_oid, $absolute=true, $depth=-1, $check_auth=true) {
756
                $parent_oid = sanitizeOID($parent_oid, substr($parent_oid,0,1) == '.');
757
                if ($parent_oid === false) return false;
758
 
759
                $vvv = ($absolute ? 'T' : 'F').($check_auth ? 'T' : 'F').$depth.'/'.$parent_oid;
760
                if (isset($this->cache_listOIDs[$vvv])) return $this->cache_listOIDs[$vvv];
761
 
762
                $dotstop = self::appendDot($parent_oid);
763
 
764
                $oids = $this->getAllOIDs($check_auth);
765
 
766
                $out = array();
767
                foreach ($oids as &$oid) {
768
                        if (self::beginsWith($oid, $dotstop)) {
769
                                if ($depth >= 0) {
770
                                        if (substr_count($oid, '.')-substr_count($dotstop, '.') > $depth-1) continue;
771
                                }
772
                                if ($absolute) {
773
                                        $out[] = $oid;
774
                                } else {
775
                                        $out[] = substr($oid, strlen($dotstop));
776
                                }
777
                        }
778
                }
779
                unset($oid);
780
 
781
                $this->cache_listOIDs[$vvv] = $out;
782
                return $out;
783
        }
784
 
785
        public function listChildren($parent_oid, $levels=-1, $check_auth=true) {
786
                if ($levels == 0) return false;
787
 
788
                $parent_oid = sanitizeOID($parent_oid, substr($parent_oid,0,1) == '.');
789
                if ($parent_oid === false) return false;
790
 
791
                $deepChildrenSearch = $this->listOIDs($parent_oid, false, -1, $check_auth);
792
                if (count($deepChildrenSearch) == 0) return array();
793
 
794
                $firstarcs = array();
795
                foreach ($deepChildrenSearch as &$child) {
796
                        $ary = explode('.', $child, 2);
797
                        $firstarc = $ary[0];
798
                        if ($firstarc == '') continue; # Achtung: Darf nicht empty() sein, da empty(0)===true
799
 
800
#                       if (!in_array($firstarc, $firstarcs)) $firstarcs[] = $firstarc; # slow
801
                        if (!isset($firstarcs[$firstarc])) $firstarcs[$firstarc] = true;
802
                }
803
                unset($child);
804
#               sort($firstarcs);
805
                ksort($firstarcs);
806
 
807
                $dotstop = self::appendDot($parent_oid);
808
 
809
                $out = array();
810
#               foreach ($firstarcs as &$firstarc) {
811
                foreach ($firstarcs as $firstarc => &$dummy) {
812
 
813
 
814
/*      This doesn't work with orphan OIDs
815
                $firstarcs = $this->listOIDs($parent_oid, false, 1, $check_auth);
816
                if (count($firstarcs) == 0) return array();
817
 
818
                sort($firstarcs);
819
 
820
                $dotstop = self::appendDot($parent_oid);
821
 
822
                $out = array();
823
                foreach ($firstarcs as &$firstarc) {
824
*/
825
                        $cur_oid = $dotstop.$firstarc;
826
 
827
                        $out[$firstarc] = array(
828
                                'children'      => $this->listChildren($cur_oid, $levels-1, $check_auth),
829
                                'described'     => $this->oidDescribed($cur_oid, $check_auth),
830
                                'identifiers'   => $this->getIdentifiers($cur_oid, $check_auth),
831
                                'unicodelabels' => $this->getUnicodeLabels($cur_oid, $check_auth),
832
                                'attributes'    => $this->getOIDAttribs($cur_oid, $check_auth)
833
                        );
834
                }
835
 
836
                return $out;
837
        }
838
 
839
        /* protected */ public function getIdentifiers($oid, $check_auth=true) {
840
                // TODO: value sanity check
841
                return $this->getValuesOf($oid, 'identifier', $check_auth);
842
        }
843
 
844
        /* protected */ public function getUnicodeLabels($oid, $check_auth=true) {
845
                // TODO: value sanity check
846
                return $this->getValuesOf($oid, 'unicodelabel', $check_auth);
847
        }
848
 
849
        /* protected */ public function getOIDAttribs($oid, $check_auth=true) {
850
                return $this->getValuesOf($oid, 'attribute', $check_auth, 1);
851
        }
852
 
853
        // case 0 = preserve            TODO: als konstanten auslagern
854
        // case 1 = upper case
855
        // case 2 = lower case
856
        /* protected */ public function getValuesOf($oid, $attrib_name, $unique=true, $case=0) {
857
                $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
858
                if ($oid === false) return false;
859
 
860
                $identifiers = array();
861
                $identifiers_raw = $this->getDatasets($oid, $attrib_name);
862
                foreach ($identifiers_raw as &$idd) {
863
                        $val = $idd['value'];
864
                        if ($case == 1) $val = strtoupper($val);
865
                        elseif ($case == 2) $val = strtolower($val);
866
                        $identifiers[] = $val;
867
                }
868
                unset($idd);
869
 
870
                if ($unique) $identifiers = array_unique($identifiers);
871
 
872
                return $identifiers;
873
        }
874
 
875
        /* protected */ public function getValues($oid, $attribute_name) {
876
                $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
877
                if ($oid === false) return false;
878
 
879
                $values = array();
880
                $values_raw = $this->getDatasets($oid, $attribute_name);
881
                foreach ($values_raw as &$idd) {
882
                        $values[] = $idd['value'];
883
                }
884
                unset($idd);
885
                return $values;
886
        }
887
 
888
        public function oidDescribed($oid, $check_auth=true) {
889
                $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
890
                if ($oid === false) return false;
891
 
892
                if ($check_auth) {
893
                        $m = $this->getDatasets($oid, '', $check_auth);
894
                        $c = 0;
895
                        foreach ($m as &$data) {
896
                                if ($this->isSpecialInvisibleField($data)) continue;
897
                                $c++;
898
                        }
899
                        if ($c == 0) return false; // secret OID or not available OID
900
                }
901
 
902
                return isset($this->oid_data[$oid]); # TODO: was ist wenn OID secret?
903
        }
904
 
905
        public function getDatasets($oid, $attrib_name='', $check_auth=true) { // TODO filter nach params, value?
906
                $out = array();
907
                $ary = $this->rec_getDatasets($oid, $attrib_name, $check_auth);
908
                foreach ($ary as &$d) {
909
                        if ((!empty($attrib_name)) && ($d['attrib_name'] != $attrib_name)) continue;
910
                        $out[] = $d;
911
                }
912
                return $out;
913
        }
914
 
915
        public function stripAttribs($oid, $attrib_name, $limit=-1) {
916
                $count = 0;
917
                foreach ($this->oid_data[$oid] as $n => $data) { // no &$data
918
                        if (empty($attrib_name) || ($data['attrib_name'] == $attrib_name)) {
919
                                $count++;
920
                                if (($limit >= 0) && ($count > $limit)) break;
921
                                unset($this->oid_data[$oid][$n]);
922
                        }
923
                }
924
        }
925
 
926
        private $cache_recdatasets = array();
927
 
928
        # FUT QUE: return by reference?
929
        protected function rec_getDatasets($oid, $attrib_name='', $check_auth=true) { // TODO filter nach params, value?
930
                $oid = sanitizeOID($oid, substr($oid,0,1) == '.');
931
                if ($oid === false) {
932
                        throw new VolcanoException("'$oid' is not a valid OID."); # TODO: exception source ("stacktrace") metadata -- idee: global $source variable?
933
                }
934
 
935
                $vvv = $oid.'/'.$attrib_name.'/'.($check_auth ? 'T' : 'F');
936
                if (isset($this->cache_recdatasets[$vvv])) return $this->cache_recdatasets[$vvv];
937
 
938
                if (!$this->oidDescribed($oid, false)) return array();
939
 
940
                $out = array();
941
                foreach ($this->oid_data[$oid] as $data) { // no &$data
942
                        if ($check_auth) {
943
                                # Anstelle *invisible lieber ein "attribute:" ? ist leider schwieriger zu bedienen , z.b. wenn man es wieder weghaben möchte
944
                                if (($data['attrib_name'] == '*invisible') && ($data['value'] == '1')) {
945
                                        if ($this->isAuthentificated($oid)) { # TODO: parameter, für was man sich authentifizieren möchte (read,write, etc).
946
                                                $dd = $data; // TODO: OK?
947
                                                $dd['attrib_name']   = 'attribute';
948
                                                $dd['value']         = 'INVISIBLE';
949
                                                $dd['flag_inherit']  = $data['flag_inherit'];
950
                                                $dd['flag_add']      = $data['flag_add']; # TODO: ok?
951
                                                $dd['attrib_params'] = array();
952
                                                $dd['source']        = $data['source'];
953
                                                # TODO: weitere dinge in $dd notwendig?
954
                                                $out[] = $dd;
955
                                        } else {
956
                                                $this->cache_recdatasets[$vvv] = array($data);
957
                                                return array($data);
958
                                        }
959
                                }
960
 
961
                                if (($data['flag_confidential']) && (!$this->isAuthentificated($oid))) {
962
                                        if (!$this->isRedactedMessageEnabled()) {
963
                                                continue; // hide complete line
964
                                        } else {
965
                                                $data['value'] = $this->redactedMessage;
966
                                        }
967
                                }
968
                        }
969
 
970
                        if ((!empty($attrib_name)) && ($data['attrib_name'] != $attrib_name)) continue;
971
 
972
                        // Add it to output
973
                        if (!$data['flag_del']) $out[] = $data;
974
                }
975
                unset($data);
976
 
977
                // Get the inherited fields
978
                if ($oid != '.') {
979
                        $ori_oid = $oid;
980
                        while ($oid != '.') {
981
                                $oid = oid_up($oid);
982
                                if ($this->oidDescribed($oid, false)) break;
983
                        }
984
                        $ds = $this->rec_getDatasets($oid, $attrib_name, $check_auth);
985
                        foreach ($ds as &$d) {
986
                                if ($d['flag_inherit']) {
987
                                        // Only inherit if it isn't overwritten
988
                                        $is_overwritten = false;
989
 
990
                                        foreach ($this->oid_data[$ori_oid] as &$x) { // $d = diese oid ; $x = vater oid
991
#/*
992
                                                // compare single fields to each other (may also include fields that were extended from a macro)
993
                                                if ($x['attrib_name'] == $d['attrib_name']) { # todo: case sensitive?
994
                                                        $is_overwritten = true;
995
                                                        break;
996
                                                }
997
#*/
998
 
999
                                                if ($x['attrib_name'] == $d['flag_inherit']) { # todo: case sensitive?
1000
                                                        $is_overwritten = true;
1001
                                                        break;
1002
                                                }
1003
 
1004
                                                // ein ganzer [xt] block wird inherited, z.B. "ra". nicht die einzelnen felder einzeln behandeln.
1005
                                                if ($x['flag_inherit'] == $d['flag_inherit']) { # todo: case sensitive?
1006
                                                        $is_overwritten = true;
1007
                                                        break;
1008
                                                }
1009
                                        }
1010
 
1011
                                        if ($x['flag_add']) {
1012
                                                $is_overwritten = false;
1013
                                        }
1014
 
1015
                                        if ($is_overwritten) continue;
1016
 
1017
                                        if ($check_auth) {
1018
                                                if (($d['attrib_name'] == '*invisible') && ($d['value'] == '1')) {
1019
                                                        if ($this->isAuthentificated($oid)) {
1020
                                                                $dd['attrib_name']   = 'attribute';
1021
                                                                $dd['value']         = 'INVISIBLE';
1022
                                                                $dd['flag_inherit']  = $d['flag_inherit'];
1023
                                                                $dd['flag_add']      = $d['flag_add']; # TODO: ok?
1024
                                                                $dd['attrib_params'] = array();
1025
                                                        #       $out[] = $dd;
1026
                                                        } else {
1027
                                                                $this->cache_recdatasets[$vvv] = array($d);
1028
                                                                return array($d);
1029
                                                        }
1030
                                                }
1031
                                        }
1032
 
1033
                                        // Add to output
1034
                                        if (!$d['flag_del']) $out[] = $d;
1035
                                }
1036
                        }
1037
                }
1038
 
1039
                $this->cache_recdatasets[$vvv] = $out;
1040
                return $out;
1041
        }
1042
 
1043
        // TODO: iso.org auch aufloesen und standardized
1044
        public function resolveIdentifiers($term) {
1045
                $ary = explode('.', $term);
1046
                $cd = $this->listChildren('.');
1047
 
1048
                foreach ($ary as $n => &$a) {
1049
                        if ($n == 0) continue;
1050
                        if (!is_numeric($a[0])) {
1051
                                if (is_null($cd)) return false; // TODO FUT: loessen durch referenzierende whois server (forward)?
1052
                                $found = false;
1053
                                foreach ($cd as $arc => &$data) {
1054
                                        foreach ($data['identifiers'] as &$idc) {
1055
                                                if ($idc == $a) {
1056
                                                        $a = $arc;
1057
                                                        $found = true;
1058
                                                        break 2;
1059
                                                }
1060
                                        }
1061
                                }
1062
                                unset($data);
1063
                                if (!$found) return false;
1064
                        }
1065
                        $cd = (!isset($cd[$a]['children'])) ? null : $cd[$a]['children'];
1066
                }
1067
                unset($a);
1068
 
1069
                return implode('.', $ary);
1070
        }
1071
 
1072
        // --------- STATIC
1073
 
1074
        protected static $searchProvs = array();
1075
        protected static $authProvs = array();
1076
 
1077
        public static function getFileformatIdentifier() {
1078
                return '1.3.6.1.4.1.37476.2.5.1.1.2';
1079
        }
1080
 
1081
        public static function registerSearchProvider($prov) {
1082
                self::$searchProvs[] = $prov;
1083
        }
1084
 
1085
        public static function registerAuthProvider($prov) {
1086
                self::$authProvs[] = $prov;
1087
        }
1088
 
1089
        protected static function appendDot($oid) {
1090
                return ($oid == '.') ? '.' : $oid . '.';
1091
        }
1092
 
1093
        // TODO: functions.inc.php
1094
        protected static function beginsWith($haystack, $needle) {
1095
                # http://maettig.com/code/php/php-performance-benchmarks.php
1096
                return strpos($haystack, $needle) === 0;
1097
        }
1098
 
1099
        // TODO: functions.inc.php
1100
        protected static function endsWith($haystack, $needle) {
1101
                # $length = strlen($needle);
1102
                # if ($length == 0) return true;
1103
                # return (substr($haystack, -$length) === $needle);
1104
                return substr($haystack, -strlen($needle)) === $needle;
1105
        }
1106
}