Subversion Repositories oidplus

Rev

Rev 1375 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
941 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
1081 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
941 daniel-mar 6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
1050 daniel-mar 20
namespace ViaThinkSoft\OIDplus;
941 daniel-mar 21
 
1086 daniel-mar 22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
1189 daniel-mar 26
class OIDplusOIDIP extends OIDplusBaseClass {
941 daniel-mar 27
 
1130 daniel-mar 28
        /**
29
         * @var string
30
         */
941 daniel-mar 31
        protected $XML_SCHEMA_URN;
1130 daniel-mar 32
 
33
        /**
34
         * @var string
35
         */
941 daniel-mar 36
        protected $XML_SCHEMA_URL;
1130 daniel-mar 37
 
38
        /**
39
         * @var string
40
         */
941 daniel-mar 41
        protected $JSON_SCHEMA_URN;
1130 daniel-mar 42
 
43
        /**
44
         * @var string
45
         */
941 daniel-mar 46
        protected $JSON_SCHEMA_URL;
47
 
1116 daniel-mar 48
        /**
49
         * @throws OIDplusException
50
         */
941 daniel-mar 51
        public function __construct() {
52
                // NOTES:
53
                // - XML_SCHEMA_URN must be equal to the string in the .xsd file!
54
                // - the schema file names and draft version are also written in OIDplusPagePublicWhois.class.php
1338 daniel-mar 55
                $this->XML_SCHEMA_URN  = 'urn:ietf:id:draft-viathinksoft-oidip-06';
1375 daniel-mar 56
                $this->XML_SCHEMA_URL  = OIDplus::webpath(__DIR__,OIDplus::PATH_ABSOLUTE_CANONICAL).'whois/draft-viathinksoft-oidip-06.xsd';
1338 daniel-mar 57
                $this->JSON_SCHEMA_URN = 'urn:ietf:id:draft-viathinksoft-oidip-06';
1375 daniel-mar 58
                $this->JSON_SCHEMA_URL = OIDplus::webpath(__DIR__,OIDplus::PATH_ABSOLUTE_CANONICAL).'whois/draft-viathinksoft-oidip-06.json';
941 daniel-mar 59
        }
60
 
1116 daniel-mar 61
        /**
1130 daniel-mar 62
         * @param string $query
63
         * @return array
1116 daniel-mar 64
         * @throws OIDplusException
65
         */
1130 daniel-mar 66
        public function oidipQuery(string $query): array {
941 daniel-mar 67
 
68
                $out_type = null;
69
                $out_content = '';
70
 
71
                // Split input into query and arguments
72
                $chunks = explode('$', $query);
73
                $query = array_shift($chunks);
74
                $original_query = $query;
75
                $arguments = array();
76
                foreach ($chunks as $chunk) {
77
                        if (strtolower(substr($chunk,0,5)) !== 'auth=') $original_query .= '$'.$chunk;
78
 
79
                        if (strpos($chunk,'=') === false) continue;
80
                        $tmp = explode('=',$chunk,2);
81
 
82
                        //if (!preg_match('@^[a-z0-9]+$@', $tmp[0], $m)) continue; // be strict with the names. They must be lowercase (TODO: show "Service error" message?)
83
                        $tmp[0] = strtolower($tmp[0]); // be tolerant instead
84
 
85
                        $arguments[$tmp[0]] = $tmp[1];
86
                }
87
 
88
                $query = str_replace('oid:.', 'oid:', $query); // allow leading dot
89
 
1130 daniel-mar 90
                $format = $arguments['format'] ?? 'text';
941 daniel-mar 91
 
92
                if (isset($arguments['auth'])) {
93
                        $authTokens = explode(',', $arguments['auth']);
94
                } else {
95
                        $authTokens = array();
96
                }
97
 
98
                // $lang= is not implemented, since we don't know in which language the OID descriptions are written by the site operator
99
                /*
100
                if (isset($arguments['lang'])) {
101
                        $languages = explode(',', $arguments['lang']);
102
                } else {
103
                        $languages = array();
104
                }
105
                */
106
 
107
                $unimplemented_format = ($format != 'text') && ($format != 'json') && ($format != 'xml');
108
                if ($unimplemented_format) {
109
                        $format = 'text';
110
                }
111
 
112
                // Step 1: Collect data
113
 
114
                $out = array();
115
 
116
                // ATTENTION: THE ORDER IS IMPORTANT FOR THE XML VALIDATION!
117
                // The order of the RFC is the same as in the XSD
118
                $out[] = $this->_oidip_attr('query', $original_query);
119
 
120
                $query = OIDplus::prefilterQuery($query, false);
121
 
122
                if ($unimplemented_format) {
123
                        $out[] = $this->_oidip_attr('result', 'Service error');
124
                        $out[] = $this->_oidip_attr('message', 'Format is not implemented');
125
                        $out[] = $this->_oidip_attr('lang', 'en-US');
126
                } else {
127
 
128
                        $distance = null;
129
                        $found = null;
130
 
971 daniel-mar 131
                        $obj = OIDplusObject::parse($query);
941 daniel-mar 132
 
133
                        $only_wellknown_ids_found = false;
134
                        $continue = false;
135
 
136
                        if (!$obj) {
971 daniel-mar 137
                                // Object type not known or invalid syntax of $query
941 daniel-mar 138
                                $out[] = $this->_oidip_attr('result', 'Not found'); // DO NOT TRANSLATE!
139
                                $continue = false;
140
                        } else {
971 daniel-mar 141
 
142
                                $query = $obj->nodeId(); // normalize
143
 
941 daniel-mar 144
                                $obj = null;
145
                                $distance = 0;
146
 
147
                                $init_query = $query;
148
                                while (true) {
971 daniel-mar 149
 
150
                                        $obj_test = OIDplusObject::findFitting($query);
151
                                        if ($obj_test) {
152
                                                $obj = $obj_test;
153
                                        } else {
154
                                                $alts = OIDplusPagePublicObjects::getAlternativesForQuery($query);
155
                                                foreach ($alts as $alt) {
156
                                                        $obj_test = OIDplusObject::findFitting($alt);
157
                                                        if ($obj_test) {
158
                                                                $query = $alt;
159
                                                                $obj = $obj_test;
160
                                                        }
161
                                                }
162
                                        }
163
                                        if ($obj) {
941 daniel-mar 164
                                                if ($distance > 0) {
165
                                                        $out[] = $this->_oidip_attr('result', 'Not found; superior object found'); // DO NOT TRANSLATE!
166
                                                        $out[] = $this->_oidip_attr('distance', $distance); // DO NOT TRANSLATE
167
                                                } else {
168
                                                        $out[] = $this->_oidip_attr('result', 'Found'); // DO NOT TRANSLATE!
169
                                                }
170
                                                $continue = true;
171
                                                break;
172
                                        }
173
 
174
                                        if (substr($query,0,4) === 'oid:') {
175
                                                $query_prev = $query;
176
                                                $query = 'oid:'.oid_up(explode(':',$query,2)[1]);
177
                                                if ($query == $query_prev) break;
178
                                                $distance++;
179
                                        } else {
180
                                                // getParent() will find the parent which DOES exist in the DB.
181
                                                // It does not need to be the direct parent (like ->one_up() does)
182
                                                $obj = OIDplusObject::parse($query)->getParent(); // For objects, we assume that they are parents of each other
183
                                                if ($obj) {
184
                                                        $distance = $obj->distance($query);
185
 
186
                                                        $query = $obj->nodeId();
1414 daniel-mar 187
                                                        assert(OIDplusObject::findFitting($query));
941 daniel-mar 188
                                                }
189
 
190
                                                if ($distance > 0) {
191
                                                        $out[] = $this->_oidip_attr('result', 'Not found; superior object found'); // DO NOT TRANSLATE!
192
                                                        $out[] = $this->_oidip_attr('distance', $distance); // DO NOT TRANSLATE
193
                                                }
194
                                                $continue = true;
195
 
196
                                                break;
197
                                        }
198
                                }
199
 
200
                                if ((substr($query,0,4) === 'oid:') && (!$obj)) {
201
                                        $query = $init_query;
202
                                        $distance = 0;
203
                                        while (true) {
975 daniel-mar 204
                                                // Checks if there is any identifier (so it is a well-known OID)
205
                                                $res_test = OIDplus::db()->query("select * from ###asn1id where oid = ? union select * from ###iri where oid = ?", array($query, $query));
206
                                                if ($res_test->any()) {
941 daniel-mar 207
                                                        $obj = OIDplusObject::parse($query);
208
                                                        if ($distance > 0) {
209
                                                                $out[] = $this->_oidip_attr('result', 'Not found; superior object found'); // DO NOT TRANSLATE!
210
                                                                $out[] = $this->_oidip_attr('distance', $distance); // DO NOT TRANSLATE
211
                                                        } else {
212
                                                                $out[] = $this->_oidip_attr('result', 'Found'); // DO NOT TRANSLATE!
213
                                                        }
214
                                                        $only_wellknown_ids_found = true; // Information partially available
215
                                                        $continue = true;
216
                                                        break;
217
                                                }
218
                                                $query_prev = $query;
219
                                                $query = 'oid:'.oid_up(explode(':',$query,2)[1]);
220
                                                if ($query == $query_prev) break;
221
                                                $distance++;
222
                                        }
223
                                }
224
 
225
                                if (!$obj) {
226
                                        $out[] = $this->_oidip_attr('result', 'Not found'); // DO NOT TRANSLATE!
227
                                        $continue = false;
228
                                }
229
 
230
                                $found = $distance == 0;
231
                        }
232
 
233
                        if ($continue) {
234
                                $out[] = '';
235
 
236
                                // ATTENTION: THE ORDER IS IMPORTANT FOR THE XML VALIDATION!
237
                                // The order of the RFC is the same as in the XSD
238
                                $out[] = $this->_oidip_attr('object', $query); // DO NOT TRANSLATE!
239
                                if (!$this->allowObjectView($obj, $authTokens)) {
240
                                        $out[] = $this->_oidip_attr('status', 'Information unavailable'); // DO NOT TRANSLATE!
241
                                        $out[] = $this->_oidip_attr('attribute', 'confidential'); // DO NOT TRANSLATE!
242
                                } else {
243
                                        if ($only_wellknown_ids_found) {
244
                                                $out[] = $this->_oidip_attr('status', 'Information partially available'); // DO NOT TRANSLATE!
245
                                        } else {
246
                                                $out[] = $this->_oidip_attr('status', 'Information available'); // DO NOT TRANSLATE!
247
                                        }
248
 
249
                                        // $this->_oidip_attr('lang', ...); // not implemented (since we don't know the language of the texts written by the page operator)
250
 
975 daniel-mar 251
                                        if ($obj) {
252
                                                $out[] = $this->_oidip_attr('name', $obj->getTitle()); // DO NOT TRANSLATE!
941 daniel-mar 253
 
975 daniel-mar 254
                                                $cont = $obj->getDescription();
941 daniel-mar 255
                                                $cont = preg_replace('@<a[^>]+href\s*=\s*["\']([^\'"]+)["\'][^>]*>(.+)<\s*/\s*a\s*>@ismU', '\2 (\1)', $cont);
256
                                                $cont = preg_replace('@<br.*>@', "\n", $cont);
257
                                                $cont = preg_replace('@\\n+@', "\n", $cont);
258
                                                $out[] = $this->_oidip_attr('description', trim(html_entity_decode(strip_tags($cont)))); // DO NOT TRANSLATE!
259
                                        }
260
 
261
                                        // $this->_oidip_attr('information', ...); Not used. Contains additional information, e.g. Management Information Base (MIB) definitions.
262
 
263
                                        if ($only_wellknown_ids_found) {
264
                                                if (substr($query,0,4) === 'oid:') {
265
                                                        // Since it is well-known, oid-info.com will most likely have it described
1371 daniel-mar 266
                                                        $out[] = $this->_oidip_attr('url', 'http://oid-info.com/get/'.$obj->nodeId(false));
941 daniel-mar 267
                                                }
268
                                        } else {
1375 daniel-mar 269
                                                $out[] = $this->_oidip_attr('url', OIDplus::webpath(null,OIDplus::PATH_ABSOLUTE_CANONICAL).'?goto='.urlencode($obj->nodeId(true)));
941 daniel-mar 270
                                        }
271
 
272
                                        if (substr($query,0,4) === 'oid:') {
1116 daniel-mar 273
                                                assert($obj instanceof OIDplusOid); //assert(get_class($obj) === "ViaThinkSoft\OIDplus\OIDplusOid");
274
 
941 daniel-mar 275
                                                $out[] = $this->_oidip_attr('asn1-notation', $obj->getAsn1Notation(false)); // DO NOT TRANSLATE!
276
                                                $out[] = $this->_oidip_attr('iri-notation', $obj->getIriNotation(false)); // DO NOT TRANSLATE!
277
 
975 daniel-mar 278
                                                $res_asn = OIDplus::db()->query("select * from ###asn1id where oid = ?", array($obj->nodeId()));
279
                                                while ($row_asn = $res_asn->fetch_object()) {
280
                                                        $out[] = $this->_oidip_attr('identifier', $row_asn->name); // DO NOT TRANSLATE!
941 daniel-mar 281
                                                }
282
 
975 daniel-mar 283
                                                $res_asn = OIDplus::db()->query("select * from ###asn1id where standardized = ? and oid = ?", array(true, $obj->nodeId()));
284
                                                while ($row_asn = $res_asn->fetch_object()) {
285
                                                        $out[] = $this->_oidip_attr('standardized-id', $row_asn->name); // DO NOT TRANSLATE!
941 daniel-mar 286
                                                }
287
 
975 daniel-mar 288
                                                $res_iri = OIDplus::db()->query("select * from ###iri where oid = ?", array($obj->nodeId()));
289
                                                while ($row_iri = $res_iri->fetch_object()) {
290
                                                        $out[] = $this->_oidip_attr('unicode-label', $row_iri->name); // DO NOT TRANSLATE!
941 daniel-mar 291
                                                }
292
 
975 daniel-mar 293
                                                $res_iri = OIDplus::db()->query("select * from ###iri where longarc = ? and oid = ?", array(true, $obj->nodeId()));
294
                                                while ($row_iri = $res_iri->fetch_object()) {
295
                                                        $out[] = $this->_oidip_attr('long-arc', $row_iri->name); // DO NOT TRANSLATE!
941 daniel-mar 296
                                                }
297
                                        }
298
 
299
                                        // $this->_oidip_attr('oidip-service', ...); Not used.
300
 
1081 daniel-mar 301
                                        // $this->_oidip_attr('oidip-pubkey', ...); Not used.
302
 
1131 daniel-mar 303
                                        if ($obj instanceof INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4) {
941 daniel-mar 304
                                                // Also ask $obj for extra attributes:
305
                                                // This way we could add various additional information, e.g. IPv4/6 range analysis, interpretation of GUID, etc.
1131 daniel-mar 306
                                                $obj->whoisObjectAttributes($obj->nodeId(), $out);
941 daniel-mar 307
                                        }
308
 
1005 daniel-mar 309
                                        foreach (OIDplus::getAllPlugins() as $plugin) {
1131 daniel-mar 310
                                                if ($plugin instanceof INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4) {
311
                                                        $plugin->whoisObjectAttributes($obj->nodeId(), $out);
941 daniel-mar 312
                                                }
313
                                        }
314
 
315
                                        if ($obj->isConfidential()) { // yes, we use isConfidential() instead of $this->allowObjectView()!
316
                                                $out[] = $this->_oidip_attr('attribute', 'confidential'); // DO NOT TRANSLATE!
317
                                        }
318
 
319
                                        if (substr($query,0,4) === 'oid:') {
320
                                                $sParent = 'oid:'.oid_up(explode(':',$query,2)[1]);
321
 
322
                                                $objTest = OIDplusObject::parse($sParent);
323
                                                if ($this->allowObjectView($objTest, $authTokens)) {
324
                                                        $out[] = $this->_oidip_attr('parent', $sParent.$this->show_asn1_appendix($sParent)); // DO NOT TRANSLATE!
325
                                                } else {
326
                                                        $out[] = $this->_oidip_attr('parent', $sParent); // DO NOT TRANSLATE!
327
                                                }
975 daniel-mar 328
                                        } else {
976 daniel-mar 329
                                                $objParent = $obj->getParent();
330
                                                $sParent = $objParent ? $objParent->nodeId() : '';
331
                                                if (!empty($sParent) && (!$this->is_root($sParent))) {
975 daniel-mar 332
                                                        $out[] = $this->_oidip_attr('parent', $sParent); // DO NOT TRANSLATE!
333
                                                }
941 daniel-mar 334
                                        }
335
 
1148 daniel-mar 336
                                        $res_children = OIDplus::db()->query("select * from ###objects where parent = ?", array($obj->nodeId()));
1156 daniel-mar 337
                                        $res_children->naturalSortByField('id');
975 daniel-mar 338
                                        while ($row_children = $res_children->fetch_object()) {
339
                                                $objTest = OIDplusObject::parse($row_children->id);
941 daniel-mar 340
                                                if ($this->allowObjectView($objTest, $authTokens)) {
975 daniel-mar 341
                                                        $out[] = $this->_oidip_attr('subordinate', $row_children->id.$this->show_asn1_appendix($row_children->id)); // DO NOT TRANSLATE!
941 daniel-mar 342
                                                } else {
975 daniel-mar 343
                                                        $out[] = $this->_oidip_attr('subordinate', $row_children->id); // DO NOT TRANSLATE!
941 daniel-mar 344
                                                }
345
                                        }
346
 
975 daniel-mar 347
                                        if ($obj) {
348
                                                if ($tim = $obj->getCreatedTime()) $out[] = $this->_oidip_attr('created', date('Y-m-d H:i:s', strtotime($tim))); // DO NOT TRANSLATE!
349
                                                if ($tim = $obj->getUpdatedTime()) $out[] = $this->_oidip_attr('updated', date('Y-m-d H:i:s', strtotime($tim))); // DO NOT TRANSLATE!
941 daniel-mar 350
                                        }
351
 
352
                                        $out[] = '';
353
 
354
                                        // ATTENTION: THE ORDER IS IMPORTANT FOR THE XML VALIDATION!
355
                                        // The order of the RFC is the same as in the XSD
975 daniel-mar 356
                                        $res_ra = OIDplus::db()->query("select * from ###ra where email = ?", array($obj ? $obj->getRaMail() : ''));
357
                                        if ($row_ra = $res_ra->fetch_object()) {
358
                                                $out[] = $this->_oidip_attr('ra', (!empty($row_ra->ra_name) ? $row_ra->ra_name : (!empty($row_ra->email) ? $row_ra->email : /*_L*/('Unknown')))); // DO NOT TRANSLATE!
941 daniel-mar 359
 
975 daniel-mar 360
                                                if (!$this->allowRAView($row_ra, $authTokens)) {
941 daniel-mar 361
                                                        $out[] = $this->_oidip_attr('ra-status', 'Information partially available'); // DO NOT TRANSLATE!
362
                                                } else {
363
                                                        $out[] = $this->_oidip_attr('ra-status', 'Information available'); // DO NOT TRANSLATE!
364
                                                }
365
 
366
                                                // $this->_oidip_attr('ra-lang', ...); // not implemented (since we don't know the language of the texts written by the page operator)
367
 
368
                                                $tmp = array();
975 daniel-mar 369
                                                if (!empty($row_ra->office)) $tmp[] = $row_ra->office;
370
                                                if (!empty($row_ra->organization)) $tmp[] = $row_ra->organization;
941 daniel-mar 371
                                                $tmp = implode(', ', $tmp);
372
 
975 daniel-mar 373
                                                $out[] = $this->_oidip_attr('ra-contact-name', $row_ra->personal_name.(!empty($tmp) ? " ($tmp)" : '')); // DO NOT TRANSLATE!
374
                                                if (!$this->allowRAView($row_ra, $authTokens)) {
375
                                                        if (!empty($row_ra->street) || !empty($row_ra->zip_town) || !empty($row_ra->country)) {
941 daniel-mar 376
                                                                $out[] = $this->_oidip_attr('ra-address', /*_L*/('(redacted)')); // DO NOT TRANSLATE!
377
                                                        }
975 daniel-mar 378
                                                        $out[] = $this->_oidip_attr('ra-phone', (!empty($row_ra->phone) ? /*_L*/('(redacted)') : '')); // DO NOT TRANSLATE!
379
                                                        $out[] = $this->_oidip_attr('ra-mobile', (!empty($row_ra->mobile) ? /*_L*/('(redacted)') : '')); // DO NOT TRANSLATE!
380
                                                        $out[] = $this->_oidip_attr('ra-fax', (!empty($row_ra->fax) ? /*_L*/('(redacted)') : '')); // DO NOT TRANSLATE!
941 daniel-mar 381
                                                } else {
382
                                                        $address = array();
975 daniel-mar 383
                                                        if (!empty($row_ra->street))   $address[] = $row_ra->street; // DO NOT TRANSLATE!
384
                                                        if (!empty($row_ra->zip_town)) $address[] = $row_ra->zip_town; // DO NOT TRANSLATE!
385
                                                        if (!empty($row_ra->country))  $address[] = $row_ra->country; // DO NOT TRANSLATE!
941 daniel-mar 386
                                                        if (count($address) > 0) $out[] = $this->_oidip_attr('ra-address', implode("\n",$address));
975 daniel-mar 387
                                                        $out[] = $this->_oidip_attr('ra-phone', $row_ra->phone); // DO NOT TRANSLATE!
388
                                                        $out[] = $this->_oidip_attr('ra-mobile', $row_ra->mobile); // DO NOT TRANSLATE!
389
                                                        $out[] = $this->_oidip_attr('ra-fax', $row_ra->fax); // DO NOT TRANSLATE!
941 daniel-mar 390
                                                }
1137 daniel-mar 391
                                                $out[] = $this->_oidip_attr('ra-email', $obj->getRaMail() ?? ''); // DO NOT TRANSLATE!
941 daniel-mar 392
 
393
                                                // $this->_oidip_attr('ra-url', ...); Not used.
394
 
1137 daniel-mar 395
                                                if ($raEMail = $obj->getRaMail()) {
396
                                                        $raObj = new OIDplusRA($raEMail);
397
                                                        if ($raObj instanceof INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4) {
398
                                                                $raObj->whoisRaAttributes($raEMail, $out);
399
                                                        }
941 daniel-mar 400
 
1137 daniel-mar 401
                                                        foreach (OIDplus::getAllPlugins() as $plugin) {
402
                                                                if ($plugin instanceof INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4) {
403
                                                                        $plugin->whoisRaAttributes($raEMail, $out);
404
                                                                }
941 daniel-mar 405
                                                        }
406
                                                }
407
 
975 daniel-mar 408
                                                // yes, we use row_ra->privacy() instead of $this->allowRAView(), becuase $this->allowRAView=true if auth token is given; and we want to inform the person that they content they are viewing is confidential!
409
                                                if ($row_ra->privacy) {
941 daniel-mar 410
                                                        $out[] = $this->_oidip_attr('ra-attribute', 'confidential'); // DO NOT TRANSLATE!
411
                                                }
412
 
975 daniel-mar 413
                                                if ($row_ra->registered) $out[] = $this->_oidip_attr('ra-created', date('Y-m-d H:i:s', strtotime($row_ra->registered))); // DO NOT TRANSLATE!
414
                                                if ($row_ra->updated)    $out[] = $this->_oidip_attr('ra-updated', date('Y-m-d H:i:s', strtotime($row_ra->updated))); // DO NOT TRANSLATE!
941 daniel-mar 415
                                        } else {
1137 daniel-mar 416
                                                $ra_avail = $obj && !empty($obj->getRaMail());
417
                                                $out[] = $this->_oidip_attr('ra', $ra_avail ? $obj->getRaMail() : /*_L*/('Unknown')); // DO NOT TRANSLATE!
418
                                                if ($ra_avail) {
1005 daniel-mar 419
                                                        foreach (OIDplus::getAllPlugins() as $plugin) {
1131 daniel-mar 420
                                                                if ($plugin instanceof INTF_OID_1_3_6_1_4_1_37476_2_5_2_3_4) {
421
                                                                        $plugin->whoisRaAttributes($obj->getRaMail(), $out);
941 daniel-mar 422
                                                                }
423
                                                        }
424
                                                }
425
                                                $out[] = $this->_oidip_attr('ra-status', 'Information unavailable'); // DO NOT TRANSLATE!
426
                                        }
427
                                }
428
                        }
429
                }
430
 
431
                // Upgrade legacy $out to extended $out format
432
 
433
                $this->_oidip_newout_format($out);
434
 
435
                // Trim all values
436
 
437
                foreach ($out as &$tmp) {
438
                        $tmp['value'] = trim($tmp['value']);
439
                }
1316 daniel-mar 440
                unset($tmp);
941 daniel-mar 441
 
442
                // Step 2: Format output
443
 
444
                if ($format == 'text') {
445
                        $out_type = 'text/plain; charset=UTF-8';
446
 
447
                        $longest_key = 0;
448
                        foreach ($out as $data) {
449
                                $longest_key = max($longest_key, strlen(trim($data['name'])));
450
                        }
451
 
452
                        ob_start();
453
 
454
                        //$out_content .= '% ' . str_repeat('*', OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80)-2)."\r\n";
455
 
456
                        foreach ($out as $data) {
457
                                if ($data['name'] == '') {
458
                                        $out_content .= "\r\n";
459
                                        continue;
460
                                }
461
 
462
                                $key = $data['name'];
463
                                $value = $data['value'];
464
 
465
                                // Normalize line-breaks to \r\n, otherwise mb_wordwrap won't work correctly
466
                                $value = str_replace("\r\n", "\n", $value);
467
                                $value = str_replace("\r", "\n", $value);
468
                                $value = str_replace("\n", "\r\n", $value);
469
 
470
                                $value = mb_wordwrap($value, OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80) - $longest_key - strlen(':') - OIDplus::config()->getValue('webwhois_output_format_spacer', 2), "\r\n");
471
                                $value = str_replace("\r\n", "\r\n$key:".str_repeat(' ', $longest_key-strlen($key)) . str_repeat(' ', OIDplus::config()->getValue('webwhois_output_format_spacer', 2)), $value);
472
 
473
                                if (!empty($value)) {
474
                                        $out_content .= $key.':' . str_repeat(' ', $longest_key-strlen($key)) . str_repeat(' ', OIDplus::config()->getValue('webwhois_output_format_spacer', 2)) . $value . "\r\n";
475
                                }
476
                        }
477
 
478
                        //$out_content .= '% ' . str_repeat('*', OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80)-2)."\r\n";
479
 
480
                        $cont = ob_get_contents();
481
                        ob_end_clean();
482
 
483
                        $out_content .= $cont;
484
 
485
                        if (OIDplus::getPkiStatus()) {
486
                                $signature = '';
487
                                if (@openssl_sign($cont, $signature, OIDplus::getSystemPrivateKey())) {
488
                                        $signature = base64_encode($signature);
489
                                        $signature = mb_wordwrap($signature, OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80) - strlen('% '), "\r\n", true);
490
                                        $signature = "% -----BEGIN RSA SIGNATURE-----\r\n".
491
                                                     preg_replace('/^/m', '% ', $signature)."\r\n".
492
                                                     "% -----END RSA SIGNATURE-----\r\n";
493
                                        $out_content .= $signature;
494
                                }
495
                        }
496
                }
497
 
498
                if ($format == 'json') {
499
                        $ary = array();
500
 
501
                        $current_section = array();
502
                        $ary[] = &$current_section;
503
 
504
                        foreach ($out as $data) {
505
                                if ($data['name'] == '') {
506
                                        unset($current_section);
507
                                        $current_section = array();
508
                                        $ary[] = &$current_section;
509
                                } else {
510
                                        $key = $data['name'];
511
                                        $val = trim($data['value']);
512
                                        if (!empty($val)) {
513
                                                if (!isset($current_section[$key])) {
514
                                                        $current_section[$key] = $val;
515
                                                } elseif (is_array($current_section[$key])) {
516
                                                        $current_section[$key][] = $val;
517
                                                } else {
518
                                                        $current_section[$key] = array($current_section[$key], $val);
519
                                                }
520
                                        }
521
                                }
522
                        }
523
 
524
                        // Change $ary=[["query",...], ["object",...], ["ra",...]]
525
                        // to     $bry=["querySection"=>["query",...], "objectsection"=>["object",...], "raSection"=>["ra",...]]
526
                        $bry = array();
527
                        foreach ($ary as $cry) {
528
                                $dry = array_keys($cry);
529
                                if (count($dry) == 0) continue;
530
                                $bry[$dry[0].'Section'] = $cry; /** @phpstan-ignore-line */ // PHPStan thinks that count($dry) is always 0
531
                        }
532
 
533
                        // Remove 'ra-', 'ra1-', ... field prefixes from JSON (the prefix is only for the text view)
534
                        foreach ($bry as $section_name => &$cry) { /** @phpstan-ignore-line */ // PHPStan thinks that $bry is empty
535
                                if (preg_match('@^(ra\d*)\\Section@', $section_name, $m)) {
536
                                        $ra_id = str_replace('Section', '', $section_name);
537
                                        $dry = array();
538
                                        foreach ($cry as $name => $val) {
539
                                                if ($name == $ra_id) $name = 'ra'; // First field is always 'ra' even in 'ra1Section' etc.
540
                                                $name = preg_replace('@^('.preg_quote($ra_id,'@').')\\-@', '', $name);
541
                                                $dry[$name] = $val;
542
                                        }
543
                                        $cry = $dry;
544
                                }
545
                        }
546
 
547
                        $ary = array(
548
                                // We use the URN here, because $id of the schema also uses the URN
549
                                '$schema' => $this->JSON_SCHEMA_URN,
550
 
551
                                // we need this NAMED root, otherwise PHP will name the sections "0", "1", "2" if the array is not sequencial (e.g. because "signature" is added)
552
                                'oidip' => $bry
553
                        );
554
 
555
                        $json = json_encode($ary);
556
 
557
                        if (OIDplus::getPkiStatus()) {
558
                                try {
559
                                        require_once __DIR__.'/whois/json/security.inc.php';
560
                                        $json = oidplus_json_sign($json, OIDplus::getSystemPrivateKey(), OIDplus::getSystemPublicKey());
1050 daniel-mar 561
                                } catch (\Exception $e) {
941 daniel-mar 562
                                        // die($e->getMessage());
563
                                }
564
                        }
565
 
566
                        // Good JSON schema validator here: https://www.jsonschemavalidator.net
567
                        $out_type = 'application/json; charset=UTF-8';
568
                        $out_content .= $json;
569
                }
570
 
571
                if ($format == 'xml') {
572
                        $xml_oidip = '<oidip><section>';
573
                        foreach ($out as $data) {
574
                                if ($data['name'] == '') {
575
                                        $xml_oidip .= '</section><section>';
576
                                } else {
577
                                        if ($data['xmlns'] != '') {
578
                                                $key = $data['xmlns'].':'.$data['name'];
579
                                        } else {
580
                                                $key = $data['name'];
581
                                        }
582
                                        $val = trim($data['value']);
583
                                        if (!empty($val)) {
584
                                                if (strpos($val,"\n") !== false) {
585
                                                        $val = str_replace(']]>', ']]]]><![CDATA[>', $val); // Escape ']]>'
586
                                                        $xml_oidip .= "<$key><![CDATA[$val]]></$key>";
587
                                                } else {
588
                                                        $xml_oidip .= "<$key>".htmlspecialchars($val, ENT_XML1)."</$key>";
589
                                                }
590
                                        }
591
                                }
592
                        }
593
                        $xml_oidip .= '</section></oidip>';
594
 
595
                        $xml_oidip = preg_replace('@<section><(.+)>(.+)</section>@ismU', '<\\1Section><\\1>\\2</\\1Section>', $xml_oidip);
596
 
597
                        // Remove 'ra-', 'ra1-', ... field prefixes from XML (the prefix is only for the text view)
598
                        $xml_oidip = preg_replace('@<(/{0,1})ra\\d*-@', '<\\1', $xml_oidip);
599
 
600
                        // <ra1Section><ra1>...</ra1> => <ra1Section><ra>...</ra>
601
                        $xml_oidip = preg_replace('@<ra(\\d+)Section><ra\\1>(.+)</ra\\1>@U', '<ra\\1Section><ra>\\2</ra>', $xml_oidip);
602
 
603
                        /* Debug:
604
                        $out_type = 'application/xml; charset=UTF-8';
605
                        $out_content .= $xml_oidip;
606
                        die();
607
                        */
608
 
609
                        // Very good XSD validator here: https://www.liquid-technologies.com/online-xsd-validator
610
                        // Also good: https://www.utilities-online.info/xsdvalidation (but does not accept &amp; or &quot; results in "Premature end of data in tag description line ...")
611
                        // Note:
612
                        // - These do not support XSD 1.1
613
                        // - You need to host http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd yourself, otherwise there will be a timeout in the validation!!!
614
 
615
                        $extra_schemas = array();
616
                        foreach ($out as $data) {
617
                                if (isset($data['xmlns']) && ($data['xmlns'] != '') && !isset($extra_schemas[$data['xmlns']])) {
618
                                        $extra_schemas[$data['xmlns']] = array($data['xmlschema'], $data['xmlschemauri']);
619
                                }
620
                        }
621
 
622
                        $xml  = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?'.'>'."\n";
623
                        $xml .= '<root xmlns="'.$this->XML_SCHEMA_URN.'"'."\n";
624
                        $xml .= '      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'."\n";
625
                        foreach ($extra_schemas as $xmlns => list($schema,$schemauri)) {
626
                                $xml .= '      xmlns:'.$xmlns.'="'.$schema.'"'."\n";
627
                        }
628
                        $xml .= '      xsi:schemaLocation="'.$this->XML_SCHEMA_URN.' '.$this->XML_SCHEMA_URL;
629
                        foreach ($extra_schemas as $xmlns => list($schema,$schemauri)) {
630
                                $xml .= ' '.$schema.' '.$schemauri;
631
                        }
632
                        $xml .= '">'."\n";
633
                        $xml .= $xml_oidip."\n";
634
                        $xml .= '</root>';
635
 
636
                        if (!OIDplus::getPkiStatus()) {
637
                                $xml .= '<!-- Cannot add signature: OIDplus PKI is not initialized (OpenSSL missing?) -->';
638
                        } else if (!class_exists('DOMDocument')) {
639
                                $xml .= '<!-- Cannot add signature: "PHP-XML" extension not installed -->';
640
                        } else {
641
                                try {
642
                                        require_once __DIR__.'/whois/xml/security.inc.php';
643
                                        $xml = oidplus_xml_sign($xml, OIDplus::getSystemPrivateKey(), OIDplus::getSystemPublicKey());
1050 daniel-mar 644
                                } catch (\Exception $e) {
941 daniel-mar 645
                                        $xml .= '<!-- Cannot add signature: '.$e.' -->';
646
                                }
647
                        }
648
 
649
                        $out_type = 'application/xml; charset=UTF-8';
650
                        $out_content .= $xml;
651
                }
652
 
653
                return array($out_content, $out_type);
654
        }
655
 
1116 daniel-mar 656
        /**
1130 daniel-mar 657
         * @param string $id
1116 daniel-mar 658
         * @return string
659
         * @throws OIDplusException
660
         */
1130 daniel-mar 661
        protected function show_asn1_appendix(string $id): string {
941 daniel-mar 662
                if (substr($id,0,4) === 'oid:') {
663
                        $appendix_asn1ids = array();
975 daniel-mar 664
                        $res_asn = OIDplus::db()->query("select * from ###asn1id where oid = ?", array($id));
665
                        while ($row_asn = $res_asn->fetch_object()) {
666
                                $appendix_asn1ids[] = $row_asn->name;
941 daniel-mar 667
                        }
668
 
669
                        $appendix = implode(', ', $appendix_asn1ids);
670
                        if (!empty($appendix)) $appendix = " ($appendix)";
671
                } else {
672
                        $appendix = '';
673
                }
674
                return $appendix;
675
        }
676
 
1116 daniel-mar 677
        /**
1130 daniel-mar 678
         * @param string $id
1116 daniel-mar 679
         * @return bool
680
         */
1130 daniel-mar 681
        protected function is_root(string $id): bool {
941 daniel-mar 682
                return empty(explode(':',$id,2)[1]);
683
        }
684
 
1116 daniel-mar 685
        /**
1130 daniel-mar 686
         * @param string $id
687
         * @param array $authTokens
1116 daniel-mar 688
         * @return bool
1130 daniel-mar 689
         * @throws OIDplusException
1116 daniel-mar 690
         */
1130 daniel-mar 691
        protected function authTokenAccepted(string $id, array $authTokens): bool {
692
                return in_array(OIDplusPagePublicWhois::genWhoisAuthToken($id), $authTokens);
941 daniel-mar 693
        }
694
 
1116 daniel-mar 695
        /**
1130 daniel-mar 696
         * @param OIDplusObject $obj
697
         * @param array $authTokens
1116 daniel-mar 698
         * @return bool
699
         * @throws OIDplusException
700
         */
1130 daniel-mar 701
        protected function allowObjectView(OIDplusObject $obj, array $authTokens): bool {
941 daniel-mar 702
                // Master auth token (TODO: Have an object-master-token and a ra-master-token?)
703
                $authToken = trim(OIDplus::config()->getValue('whois_auth_token'));
704
                if (empty($authToken)) $authToken = false;
705
                if ($authToken && in_array($authToken, $authTokens)) return true;
706
 
707
                // Per-OID auth tokens
975 daniel-mar 708
 
941 daniel-mar 709
                $curid = $obj->nodeId();
975 daniel-mar 710
                while ($test_obj = OIDplusObject::findFitting($curid)) {
941 daniel-mar 711
                        // Example: You have an auth Token for 2.999.1.2.3
712
                        // This allows you to view 2.999.1.2.3 and all of its children,
713
                        // as long as they are not confidential (then you need their auth token).
714
                        // 2, 2.999, 2.999.1 and 2.999.1.2 are visible,
715
                        // (because their existence is now obvious).
975 daniel-mar 716
                        if ($test_obj->isConfidential() && !$this->authTokenAccepted($curid, $authTokens)) return false;
976 daniel-mar 717
                        $objParentTest = $test_obj->getParent();
718
                        if (!$objParentTest) break;
719
                        $curid = $objParentTest->nodeId();
941 daniel-mar 720
                }
721
 
722
                // Allow
723
                return true;
724
        }
725
 
1116 daniel-mar 726
        /**
1130 daniel-mar 727
         * @param \stdClass $row_ra
728
         * @param array $authTokens
1116 daniel-mar 729
         * @return bool
730
         * @throws OIDplusException
731
         */
1130 daniel-mar 732
        protected function allowRAView(\stdClass $row_ra, array $authTokens): bool {
941 daniel-mar 733
                // Master auth token (TODO: Have an object-master-token and a ra-master-token?)
734
                $authToken = trim(OIDplus::config()->getValue('whois_auth_token'));
735
                if (empty($authToken)) $authToken = false;
736
                if ($authToken && in_array($authToken, $authTokens)) return true;
737
 
738
                // Per-RA auth tokens
975 daniel-mar 739
                if ($row_ra->privacy && !$this->authTokenAccepted('ra:'.$row_ra->ra_name, $authTokens)) return false;
941 daniel-mar 740
 
741
                // Allow
742
                return true;
743
        }
744
 
1116 daniel-mar 745
        /**
1130 daniel-mar 746
         * @param string $name
747
         * @param mixed $value
1116 daniel-mar 748
         * @return array
749
         */
1130 daniel-mar 750
        protected function _oidip_attr(string $name, $value): array {
941 daniel-mar 751
                return array(
752
                        'xmlns' => '',
753
                        'xmlschema' => '',
754
                        'xmlschemauri' => '',
755
                        'name' => $name,
756
                        'value' => $value
757
                );
758
        }
759
 
1116 daniel-mar 760
        /**
1130 daniel-mar 761
         * @param array $out
1116 daniel-mar 762
         * @return void
763
         */
1130 daniel-mar 764
        protected function _oidip_newout_format(array &$out) {
941 daniel-mar 765
                foreach ($out as &$line) {
766
                        if (is_string($line)) {
767
                                $ary = explode(':', $line, 2);
768
                                $key = trim($ary[0]);
769
                                $value = isset($ary[1]) ? trim($ary[1]) : '';
770
 
771
                                $line = array(
772
                                        'xmlns' => '',
773
                                        'xmlschema' => '',
774
                                        'xmlschemauri' => '',
775
                                        'name' => $key,
776
                                        'value' => $value
777
                                );
778
                        } else if (is_array($line)) {
779
                                if (!isset($line['xmlns']))        $line['xmlns'] = '';
780
                                if (!isset($line['xmlschema']))    $line['xmlschema'] = '';
781
                                if (!isset($line['xmlschemauri'])) $line['xmlschemauri'] = '';
782
                                if (!isset($line['name']))         $line['name'] = '';
783
                                if (!isset($line['value']))        $line['value'] = '';
784
                        } else {
785
                                assert(false);
786
                        }
787
                }
1316 daniel-mar 788
                unset($line);
941 daniel-mar 789
        }
790
 
791
}