Subversion Repositories oidplus

Rev

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