Subversion Repositories oidplus

Rev

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