Subversion Repositories oidplus

Rev

Rev 801 | Rev 867 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2022 Daniel Marschall, ViaThinkSoft
  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.  
  20. require_once __DIR__ . '/../../../../../includes/oidplus.inc.php';
  21.  
  22. //define('XML_URN', 'urn:oid:1.3.6.1.4.1.37476.2.5.2.5.1.1');
  23. define('XML_URN', 'urn:ietf:id:viathinksoft-oidip-02');
  24.  
  25. // define('XML_URN_URL', 'https://oidplus.viathinksoft.com/oidplus/plugins/publicPages/100_whois/whois/xml_schema.xsd');
  26. define('XML_URN_URL', OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'xml_schema.xsd');
  27.  
  28. OIDplus::init(true);
  29. set_exception_handler(array('OIDplusGui', 'html_exception_handler'));
  30.  
  31. if (OIDplus::baseConfig()->getValue('DISABLE_PLUGIN_OIDplusPagePublicWhois', false)) {
  32.         throw new OIDplusException(_L('This plugin was disabled by the system administrator!'));
  33. }
  34.  
  35. originHeaders();
  36.  
  37. // Step 0: Get request parameter
  38.  
  39. if (PHP_SAPI == 'cli') {
  40.         if ($_SERVER['argc'] != 2) {
  41.                 echo _L('Syntax').': '.$_SERVER['argv'][0].' <query>'."\n";
  42.                 exit(2);
  43.         }
  44.         $query = $_SERVER['argv'][1];
  45. } else {
  46.         if (!isset($_REQUEST['query'])) {
  47.                 http_response_code(400);
  48.                 die('<h1>'._L('Error').'</h1><p>'._L('Argument "%1" is missing','query').'<p>');
  49.         }
  50.         $query = $_REQUEST['query'];
  51. }
  52.  
  53. // Split input into query, authTokens and serverCommands
  54. $tokens = explode('$', $query);
  55. $query = array_shift($tokens);
  56. $authTokens[] = array();
  57. $serverCommands = array();
  58. foreach ($tokens as $token) {
  59.         if (strpos($token,'=') !== false) {
  60.                 $tmp = explode('=',$token,2);
  61.                 $serverCommands[strtolower($tmp[0])] = $tmp[1];
  62.         } else {
  63.                 $authTokens[] = $token;
  64.         }
  65. }
  66.  
  67. $query = str_replace('oid:.', 'oid:', $query); // allow leading dot
  68.  
  69. if (isset($_REQUEST['format'])) {
  70.         $format = $_REQUEST['format'];
  71. } else if (isset($serverCommands['format'])) {
  72.         $format = $serverCommands['format'];
  73. } else {
  74.         $format = 'text'; // default
  75. }
  76.  
  77. $unimplemented_format = ($format != 'text') && ($format != 'json') && ($format != 'xml');
  78. if ($unimplemented_format) {
  79.         $format = 'text';
  80. }
  81.  
  82. // Step 1: Collect data
  83.  
  84. $out = array();
  85.  
  86. $out[] = "query: $query";
  87.  
  88. $query = OIDplus::prefilterQuery($query, false);
  89.  
  90. if ($unimplemented_format) {
  91.         $out[] = 'result: Service error';
  92.         $out[] = 'message: Format is not implemented';
  93. } else {
  94.  
  95.         $distance = null;
  96.         $found = null;
  97.  
  98.         try {
  99.                 $obj = OIDplusObject::findFitting($query);
  100.                 if (!$obj) $obj = OIDplusObject::parse($query); // in case we didn't find anything fitting, we take it as it is and later use getParent() to find something else
  101.                 $query = $obj->nodeId();
  102.         } catch (Exception $e) {
  103.                 $obj = null;
  104.         }
  105.  
  106.         $only_wellknown_ids_found = false;
  107.         $continue = false;
  108.  
  109.         if (!$obj) {
  110.                 $out[] = "result: Not found"; // DO NOT TRANSLATE!
  111.                 $continue = false;
  112.                 $res = null;
  113.         } else {
  114.                 $obj = null;
  115.                 $distance = 0;
  116.  
  117.                 $init_query = $query;
  118.                 while (true) {
  119.                         $res = OIDplus::db()->query("select * from ###objects where id = ?", array($query));
  120.                         if ($res->any()) {
  121.                                 $obj = OIDplusObject::parse($query);
  122.                                 if ($distance > 0) {
  123.                                         $out[] = "result: Not found; superior object found"; // DO NOT TRANSLATE!
  124.                                         $out[] = "distance: $distance"; // DO NOT TRANSLATE
  125.                                 } else {
  126.                                         $out[] = "result: Found"; // DO NOT TRANSLATE!
  127.                                 }
  128.                                 $continue = true;
  129.                                 break;
  130.                         }
  131.  
  132.                         if (substr($query,0,4) === 'oid:') {
  133.                                 $query_prev = $query;
  134.                                 $query = 'oid:'.oid_up(explode(':',$query,2)[1]);
  135.                                 if ($query == $query_prev) break;
  136.                                 $distance++;
  137.                         } else {
  138.                                 // getParent() will find the parent which DOES exist in the DB.
  139.                                 // It does not need to be the direct parent (like ->one_up() does)
  140.                                 $obj = OIDplusObject::parse($query)->getParent(); // For objects, we assume that they are parents of each other
  141.                                 if ($obj) {
  142.                                         $res = OIDplus::db()->query("select * from ###objects where id = ?", array($obj->nodeId()));
  143.                                         $distance = $obj->distance($query);
  144.                                         assert($res->any());
  145.  
  146.                                         $query = $obj->nodeId();
  147.                                 }
  148.  
  149.                                 if ($distance > 0) {
  150.                                         $out[] = "result: Not found; superior object found"; // DO NOT TRANSLATE!
  151.                                         $out[] = "distance: $distance"; // DO NOT TRANSLATE
  152.                                 }
  153.                                 $continue = true;
  154.  
  155.                                 break;
  156.                         }
  157.                 }
  158.  
  159.                 if ((substr($query,0,4) === 'oid:') && (!$obj)) {
  160.                         $query = $init_query;
  161.                         $distance = 0;
  162.                         while (true) {
  163.                                 $res = OIDplus::db()->query("select * from ###asn1id where oid = ? union select * from ###iri where oid = ?", array($query, $query));
  164.                                 if ($res->any()) {
  165.                                         $obj = OIDplusObject::parse($query);
  166.                                         $res = null;
  167.                                         if ($distance > 0) {
  168.                                                 $out[] = "result: Not found; superior object found"; // DO NOT TRANSLATE!
  169.                                                 $out[] = "distance: $distance"; // DO NOT TRANSLATE
  170.                                         } else {
  171.                                                 $out[] = "result: Found"; // DO NOT TRANSLATE!
  172.                                         }
  173.                                         $only_wellknown_ids_found = true; // Information partially available
  174.                                         $continue = true;
  175.                                         break;
  176.                                 }
  177.                                 $query_prev = $query;
  178.                                 $query = 'oid:'.oid_up(explode(':',$query,2)[1]);
  179.                                 if ($query == $query_prev) break;
  180.                                 $distance++;
  181.                         }
  182.                 }
  183.  
  184.                 if (!$obj) {
  185.                         $out[] = "result: Not found"; // DO NOT TRANSLATE!
  186.                         $continue = false;
  187.                 }
  188.  
  189.                 $found = $distance == 0;
  190.         }
  191.  
  192.         if ($continue) {
  193.                 $out[] = "";
  194.                 $out[] = "object: $query"; // DO NOT TRANSLATE!
  195.                 if (!allowObjectView($obj, $authTokens)) {
  196.                         $out[] = "status: Information unavailable"; // DO NOT TRANSLATE!
  197.                         $out[] = "attribute: confidential"; // DO NOT TRANSLATE!
  198.                 } else {
  199.                         if ($only_wellknown_ids_found) {
  200.                                 $out[] = "status: Information partially available"; // DO NOT TRANSLATE!
  201.                         } else {
  202.                                 $out[] = "status: Information available"; // DO NOT TRANSLATE!
  203.                         }
  204.  
  205.                         $row = $res ? $res->fetch_object() : null;
  206.  
  207.                         if (!is_null($row)) {
  208.                                 $out[] = 'name: ' . $row->title; // DO NOT TRANSLATE!
  209.  
  210.                                 $cont = $row->description;
  211.                                 $cont = preg_replace('@<a[^>]+href\s*=\s*["\']([^\'"]+)["\'][^>]*>(.+)<\s*/\s*a\s*>@ismU', '\2 (\1)', $cont);
  212.                                 $cont = preg_replace('@<br.*>@', "\n", $cont);
  213.                                 $cont = preg_replace('@\\n+@', "\n", $cont);
  214.                                 $out[] = 'description: ' . trim(html_entity_decode(strip_tags($cont))); // DO NOT TRANSLATE!
  215.                         }
  216.  
  217.                         if (substr($query,0,4) === 'oid:') {
  218.                                 $out[] = 'asn1-notation: ' . $obj->getAsn1Notation(false); // DO NOT TRANSLATE!
  219.                                 $out[] = 'iri-notation: ' . $obj->getIriNotation(false); // DO NOT TRANSLATE!
  220.  
  221.                                 $res2 = OIDplus::db()->query("select * from ###asn1id where oid = ?", array($obj->nodeId()));
  222.                                 while ($row2 = $res2->fetch_object()) {
  223.                                         $out[] = 'identifier: ' . $row2->name; // DO NOT TRANSLATE!
  224.                                 }
  225.  
  226.                                 $res2 = OIDplus::db()->query("select * from ###asn1id where standardized = ? and oid = ?", array(true, $obj->nodeId()));
  227.                                 while ($row2 = $res2->fetch_object()) {
  228.                                         $out[] = 'standardized-id: ' . $row2->name; // DO NOT TRANSLATE!
  229.                                 }
  230.  
  231.                                 $res2 = OIDplus::db()->query("select * from ###iri where oid = ?", array($obj->nodeId()));
  232.                                 while ($row2 = $res2->fetch_object()) {
  233.                                         $out[] = 'unicode-label: ' . $row2->name; // DO NOT TRANSLATE!
  234.                                 }
  235.  
  236.                                 $res2 = OIDplus::db()->query("select * from ###iri where longarc = ? and oid = ?", array(true, $obj->nodeId()));
  237.                                 while ($row2 = $res2->fetch_object()) {
  238.                                         $out[] = 'long-arc: ' . $row2->name; // DO NOT TRANSLATE!
  239.                                 }
  240.                         }
  241.  
  242.                         if ($obj->isConfidential()) { // yes, we use isConfidential() instead of allowObjectView()!
  243.                                 $out[] = 'attribute: confidential'; // DO NOT TRANSLATE!
  244.                         }
  245.  
  246.                         if ($obj->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  247.                                 // Also ask $obj for extra attributes:
  248.                                 // This way we could add various additional information, e.g. IPv4/6 range analysis, interpretation of GUID, etc. (TODO)
  249.                                 $obj->whoisObjectAttributes($obj->nodeId(), $out);
  250.                         }
  251.  
  252.                         foreach (OIDplus::getPagePlugins() as $plugin) {
  253.                                 if ($plugin->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  254.                                         $plugin->whoisObjectAttributes($obj->nodeId(), $out);
  255.                                 }
  256.                         }
  257.  
  258.                         if (substr($query,0,4) === 'oid:') {
  259.                                 $sParent = 'oid:'.oid_up(explode(':',$query,2)[1]);
  260.  
  261.                                 $objTest = OIDplusObject::parse($sParent);
  262.                                 if (allowObjectView($objTest, $authTokens)) {
  263.                                         $out[] = 'parent: ' . $sParent . show_asn1_appendix($sParent); // DO NOT TRANSLATE!
  264.                                 } else {
  265.                                         $out[] = 'parent: ' . $sParent; // DO NOT TRANSLATE!
  266.                                 }
  267.                         } else if (!is_null($row) && !empty($row->parent) && (!is_root($row->parent))) {
  268.                                 $sParent = $row->parent;
  269.                                 $out[] = 'parent: ' . $row->parent; // DO NOT TRANSLATE!
  270.                         }
  271.  
  272.                         $res2 = OIDplus::db()->query("select * from ###objects where parent = ? order by ".OIDplus::db()->natOrder('id'), array($obj->nodeId()));
  273.                         while ($row2 = $res2->fetch_object()) {
  274.                                 $objTest = OIDplusObject::parse($row2->id);
  275.                                 if (allowObjectView($objTest, $authTokens)) {
  276.                                         $out[] = 'subordinate: ' . $row2->id . show_asn1_appendix($row2->id); // DO NOT TRANSLATE!
  277.                                 } else {
  278.                                         $out[] = 'subordinate: ' . $row2->id; // DO NOT TRANSLATE!
  279.                                 }
  280.                         }
  281.  
  282.                         if (!is_null($row)) {
  283.                                 if ($row->created) $out[] = 'created: ' . date('Y-m-d H:i:s', strtotime($row->created)); // DO NOT TRANSLATE!
  284.                                 if ($row->updated) $out[] = 'updated: ' . date('Y-m-d H:i:s', strtotime($row->updated)); // DO NOT TRANSLATE!
  285.                         }
  286.  
  287.                         $out[] = '';
  288.  
  289.                         $res2 = OIDplus::db()->query("select * from ###ra where email = ?", array(is_null($row) ? '' : $row->ra_email));
  290.                         if ($row2 = $res2->fetch_object()) {
  291.                                 $out[] = 'ra: '.(!empty($row2->ra_name) ? $row2->ra_name : (!empty($row2->email) ? $row2->email : _L('Unknown'))); // DO NOT TRANSLATE!
  292.                                 $out[] = 'ra-status: Information available'; // DO NOT TRANSLATE!
  293.  
  294.                                 $tmp = array();
  295.                                 if (!empty($row2->office)) $tmp[] = $row2->office;
  296.                                 if (!empty($row2->organization)) $tmp[] = $row2->organization;
  297.                                 $tmp = implode(', ', $tmp);
  298.  
  299.                                 $out[] = 'ra-contact-name: ' . $row2->personal_name.(!empty($tmp) ? " ($tmp)" : ''); // DO NOT TRANSLATE!
  300.                                 if (!allowRAView($row2, $authTokens)) {
  301.                                         if (!empty($row2->street) || !empty($row2->zip_town) || !empty($row2->country)) {
  302.                                                 $out[] = 'ra-address: '._L('(redacted)'); // DO NOT TRANSLATE!
  303.                                         }
  304.                                         $out[] = 'ra-phone: ' . (!empty($row2->phone) ? _L('(redacted)') : ''); // DO NOT TRANSLATE!
  305.                                         $out[] = 'ra-mobile: ' . (!empty($row2->mobile) ? _L('(redacted)') : ''); // DO NOT TRANSLATE!
  306.                                         $out[] = 'ra-fax: ' . (!empty($row2->fax) ? _L('(redacted)') : ''); // DO NOT TRANSLATE!
  307.                                 } else {
  308.                                         if (!empty($row2->street))   $out[] = 'ra-address: ' . $row2->street; // DO NOT TRANSLATE!
  309.                                         if (!empty($row2->zip_town)) $out[] = 'ra-address: ' . $row2->zip_town; // DO NOT TRANSLATE!
  310.                                         if (!empty($row2->country))  $out[] = 'ra-address: ' . $row2->country; // DO NOT TRANSLATE!
  311.                                         $out[] = 'ra-phone: ' . $row2->phone; // DO NOT TRANSLATE!
  312.                                         $out[] = 'ra-mobile: ' . $row2->mobile; // DO NOT TRANSLATE!
  313.                                         $out[] = 'ra-fax: ' . $row2->fax; // DO NOT TRANSLATE!
  314.                                 }
  315.                                 $out[] = 'ra-email: ' . $row->ra_email; // DO NOT TRANSLATE!
  316.  
  317.                                 $ra = new OIDplusRA($row->ra_email);
  318.                                 if ($ra->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  319.                                         $ra->whoisRaAttributes($row->ra_email, $out); /** @phpstan-ignore-line */
  320.                                 }
  321.  
  322.                                 foreach (OIDplus::getPagePlugins() as $plugin) {
  323.                                         if ($plugin->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  324.                                                 $plugin->whoisRaAttributes($row->ra_email, $out);
  325.                                         }
  326.                                 }
  327.  
  328.                                 if ($row2->privacy) { // yes, we use row2->privacy() instead of allowRAView()!
  329.                                         $out[] = 'ra-attribute: confidential'; // DO NOT TRANSLATE!
  330.                                 }
  331.  
  332.                                 if ($row2->registered) $out[] = 'ra-created: ' . date('Y-m-d H:i:s', strtotime($row2->registered)); // DO NOT TRANSLATE!
  333.                                 if ($row2->updated)    $out[] = 'ra-updated: ' . date('Y-m-d H:i:s', strtotime($row2->updated)); // DO NOT TRANSLATE!
  334.                         } else {
  335.                                 $out[] = 'ra: '.(!is_null($row) && !empty($row->ra_email) ? $row->ra_email : _L('Unknown')); // DO NOT TRANSLATE!
  336.                                 if (!is_null($row)) {
  337.                                         foreach (OIDplus::getPagePlugins() as $plugin) {
  338.                                                 if ($plugin->implementsFeature('1.3.6.1.4.1.37476.2.5.2.3.4')) {
  339.                                                         $plugin->whoisRaAttributes($row->ra_email, $out);
  340.                                                 }
  341.                                         }
  342.                                 }
  343.                                 $out[] = "ra-status: Information unavailable"; // DO NOT TRANSLATE!
  344.                         }
  345.                 }
  346.         }
  347. }
  348.  
  349. // Step 2: Format output
  350.  
  351. if ($format == 'text') {
  352.         header('Content-Type:text/plain; charset=UTF-8');
  353.  
  354.         $longest_key = 0;
  355.         foreach ($out as $line) {
  356.                 $longest_key = max($longest_key, strlen(trim(explode(':',$line,2)[0])));
  357.         }
  358.  
  359.         ob_start();
  360.  
  361.         //echo '% ' . str_repeat('*', OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80)-2)."\r\n";
  362.  
  363.         foreach ($out as $line) {
  364.                 if (trim($line) == '') {
  365.                         echo "\r\n";
  366.                         continue;
  367.                 }
  368.  
  369.                 $ary = explode(':', $line, 2);
  370.  
  371.                 $key = trim($ary[0]);
  372.  
  373.                 $value = isset($ary[1]) ? trim($ary[1]) : '';
  374.  
  375.                 // Normalize line-breaks to \r\n, otherwise mb_wordwrap won't work correctly
  376.                 $value = str_replace("\r\n", "\n", $value);
  377.                 $value = str_replace("\r", "\n", $value);
  378.                 $value = str_replace("\n", "\r\n", $value);
  379.  
  380.                 $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");
  381.                 $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);
  382.  
  383.                 if (!empty($value)) {
  384.                         echo $key.':' . str_repeat(' ', $longest_key-strlen($key)) . str_repeat(' ', OIDplus::config()->getValue('webwhois_output_format_spacer', 2)) . $value . "\r\n";
  385.                 }
  386.         }
  387.  
  388.         //echo '% ' . str_repeat('*', OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80)-2)."\r\n";
  389.  
  390.         $cont = ob_get_contents();
  391.         ob_end_clean();
  392.  
  393.         echo $cont;
  394.  
  395.         if (OIDplus::getPkiStatus()) {
  396.                 $signature = '';
  397.                 if (@openssl_sign($cont, $signature, OIDplus::getSystemPrivateKey())) {
  398.                         $signature = base64_encode($signature);
  399.                         $signature = mb_wordwrap($signature, OIDplus::config()->getValue('webwhois_output_format_max_line_length', 80) - strlen('% '), "\r\n", true);
  400.                         $signature = "% -----BEGIN RSA SIGNATURE-----\r\n".
  401.                                      preg_replace('/^/m', '% ', $signature)."\r\n".
  402.                                      "% -----END RSA SIGNATURE-----\r\n";
  403.                         echo $signature;
  404.                 }
  405.         }
  406. }
  407.  
  408. if ($format == 'json') {
  409.         $ary = array();
  410.  
  411.         $current_section = array();
  412.         $ary[] = &$current_section;
  413.  
  414.         foreach ($out as $line) {
  415.                 if ($line == '') {
  416.                         unset($current_section);
  417.                         $current_section = array();
  418.                         $ary[] = &$current_section;
  419.                 } else {
  420.                         list($key,$val) = explode(':', $line, 2);
  421.                         $val = trim($val);
  422.                         if (!empty($val)) {
  423.                                 if (!isset($current_section[$key])) {
  424.                                         $current_section[$key] = $val;
  425.                                 } elseif (is_array($current_section[$key])) {
  426.                                         $current_section[$key][] = $val;
  427.                                 } else {
  428.                                         $current_section[$key] = array($current_section[$key], $val);
  429.                                 }
  430.                         }
  431.                 }
  432.         }
  433.         $ary = array(
  434.                 // https://code.visualstudio.com/docs/languages/json#_mapping-in-the-json
  435.                 // Note that this syntax is VS Code-specific and not part of the JSON Schema specification.
  436.                 //'$schema' => 'https://oidplus.viathinksoft.com/oidplus/plugins/publicPages/100_whois/whois/json_schema.json',
  437.                 '$schema' => OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'json_schema.json',
  438.  
  439.                 // 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)
  440.                 'oidip' => $ary
  441.         );
  442.  
  443.         if (OIDplus::getPkiStatus()) {
  444.                 $cont = json_encode($ary);
  445.                 $signature = '';
  446.                 if (@openssl_sign($cont, $signature, OIDplus::getSystemPrivateKey())) {
  447.                         $signature = base64_encode($signature);
  448.                         $ary['signature'] = array('content' => $cont, 'signature' => $signature);
  449.                 }
  450.         }
  451.  
  452.         // Good JSON schema validator here: https://www.jsonschemavalidator.net
  453.         header('Content-Type:application/json; charset=UTF-8');
  454.         echo json_encode($ary);
  455. }
  456.  
  457. if ($format == 'xml') {
  458.         $xml = '<oidip><section>';
  459.         foreach ($out as $line) {
  460.                 if ($line == '') {
  461.                         $xml .= '</section><section>';
  462.                 } else {
  463.                         list($key,$val) = explode(':', $line, 2);
  464.                         $val = trim($val);
  465.                         if (!empty($val)) {
  466.                                 $xml .= "<$key>".htmlspecialchars($val)."</$key>";
  467.                         }
  468.                 }
  469.         }
  470.         $xml .= '</section></oidip>';
  471.  
  472.         $xml = preg_replace('@<section><(.+)>(.+)</section>@ismU', '<\\1Section><\\1>\\2</\\1Section>', $xml);
  473.  
  474.         if (OIDplus::getPkiStatus()) {
  475.                 $cont = $xml;
  476.                 $signature = '';
  477.                 if (@openssl_sign($cont, $signature, OIDplus::getSystemPrivateKey())) {
  478.                         $signature = base64_encode($signature);
  479.                         $cdata = '<![CDATA['.str_replace(']]>', ']]]]><![CDATA[>', $cont).']]>';
  480.                         $xml .= "<signatureSection><content>".$cdata."</content><signature>".htmlspecialchars($signature)."</signature></signatureSection>";
  481.                 }
  482.         }
  483.  
  484.         // Good XSD validator here: https://www.liquid-technologies.com/online-xsd-validator
  485.         header('Content-Type:application/xml; charset=UTF-8');
  486.         echo '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>';
  487.         echo '<root xmlns="'.XML_URN.'"';
  488.         echo '      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"';
  489.         echo '      xsi:schemaLocation="'.XML_URN.' '.XML_URN_URL.'">';
  490.         echo $xml;
  491.         echo '</root>';
  492. }
  493.  
  494. # ---
  495.  
  496. function show_asn1_appendix($id) {
  497.         if (substr($id,0,4) === 'oid:') {
  498.                 $appendix_asn1ids = array();
  499.                 $res3 = OIDplus::db()->query("select * from ###asn1id where oid = ?", array($id));
  500.                 while ($row3 = $res3->fetch_object()) {
  501.                         $appendix_asn1ids[] = $row3->name;
  502.                 }
  503.  
  504.                 $appendix = implode(', ', $appendix_asn1ids);
  505.                 if (!empty($appendix)) $appendix = " ($appendix)";
  506.         } else {
  507.                 $appendix = '';
  508.         }
  509.         return $appendix;
  510. }
  511.  
  512. function is_root($id) {
  513.         return empty(explode(':',$id,2)[1]);
  514. }
  515.  
  516. function authTokenAccepted($content, $authTokens) {
  517.         foreach ($authTokens as $token) {
  518.                 if (OIDplusPagePublicWhois::genWhoisAuthToken($content) == $token) return true;
  519.         }
  520.         return false;
  521. }
  522.  
  523. function allowObjectView($obj, $authTokens) {
  524.         // Master auth token
  525.         $authToken = trim(OIDplus::config()->getValue('whois_auth_token'));
  526.         if (empty($authToken)) $authToken = false;
  527.         if ($authToken && in_array($authToken, $authTokens)) return true;
  528.  
  529.         // Per-OID auth tokens
  530.         $curid = $obj->nodeId();
  531.         while (($res = OIDplus::db()->query("select parent, confidential from ###objects where id = ?", array($curid)))->any()) {
  532.                 $row = $res->fetch_array();
  533.                 // Example: You have an auth Token for 2.999.1.2.3
  534.                 // This allows you to view 2.999.1.2.3 and all of its children,
  535.                 // as long as they are not confidential (then you need their auth token).
  536.                 // 2, 2.999, 2.999.1 and 2.999.1.2 are visible,
  537.                 // (because their existence is now obvious).
  538.                 if ($row['confidential'] && !authTokenAccepted($curid, $authTokens)) return false;
  539.                 $curid = $row['parent'];
  540.         }
  541.  
  542.         // Allow
  543.         return true;
  544. }
  545.  
  546. function allowRAView($row, $authTokens) {
  547.         // Master auth token
  548.         $authToken = trim(OIDplus::config()->getValue('whois_auth_token'));
  549.         if (empty($authToken)) $authToken = false;
  550.         if ($authToken && in_array($authToken, $authTokens)) return true;
  551.  
  552.         // Per-RA auth tokens
  553.         if ($row->privacy && !authTokenAccepted('ra:'.$row->ra_name, $authTokens)) return false;
  554.  
  555.         // Allow
  556.         return true;
  557. }
  558.