Subversion Repositories oidplus

Rev

Rev 1399 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 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. namespace ViaThinkSoft\OIDplus;
  21.  
  22. // phpcs:disable PSR1.Files.SideEffects
  23. \defined('INSIDE_OIDPLUS') or die;
  24. // phpcs:enable PSR1.Files.SideEffects
  25.  
  26. class OIDplusX500DN extends OIDplusObject {
  27.         /**
  28.          * @var string
  29.          */
  30.         private $identifier;
  31.  
  32.         /**
  33.          * @param string $identifier
  34.          */
  35.         public function __construct(string $identifier) {
  36.                 // No syntax checks
  37.                 $this->identifier = $identifier;
  38.         }
  39.  
  40.         /**
  41.          * @param string $node_id
  42.          * @return OIDplusX500DN|null
  43.          */
  44.         public static function parse(string $node_id)/*: ?OIDplusX500DN*/ {
  45.                 @list($namespace, $identifier) = explode_with_escaping(':', $node_id, 2);
  46.                 if ($namespace !== self::ns()) return null;
  47.                 return new self($identifier);
  48.         }
  49.  
  50.         /**
  51.          * @return string
  52.          */
  53.         public static function objectTypeTitle(): string {
  54.                 return _L('X.500 Distinguished Name');
  55.         }
  56.  
  57.         /**
  58.          * @return string
  59.          */
  60.         public static function objectTypeTitleShort(): string {
  61.                 return _L('X.500 DN');
  62.         }
  63.  
  64.         /**
  65.          * @return string
  66.          */
  67.         public static function ns(): string {
  68.                 return 'x500dn';
  69.         }
  70.  
  71.         /**
  72.          * @return string
  73.          */
  74.         public static function root(): string {
  75.                 return self::ns().':';
  76.         }
  77.  
  78.         /**
  79.          * @return bool
  80.          */
  81.         public function isRoot(): bool {
  82.                 return $this->identifier == '';
  83.         }
  84.  
  85.         /**
  86.          * @param bool $with_ns
  87.          * @return string
  88.          */
  89.         public function nodeId(bool $with_ns=true): string {
  90.                 return $with_ns ? self::root().$this->identifier : $this->identifier;
  91.         }
  92.  
  93.         /**
  94.          * @return array Format: [oid => [source, englishName, [ldapNames, ...], oidName], ...]
  95.          */
  96.         public static function getKnownAttributeNames(): array {
  97.                 $ldap_attributes = [
  98.                         // Extracted from https://www.itu.int/itu-t/recommendations/rec.aspx?rec=X.520
  99.                         // Note: The most common ones are also defined in RFC 4519
  100.                         // ITU-T X.520 (10/2019), clause 6.1 System attribute types
  101.                         "2.5.4.2" => ["ITU-T X.520 (10/2019), clause 6.1.1", "Knowledge information", [], "id-at-knowledgeInformation"],
  102.                         // ITU-T X.520 (10/2019), clause 6.2 Labelling attribute types
  103.                         "2.5.4.41" => ["ITU-T X.520 (10/2019), clause 6.2.1", "Name", ["name"], "id-at-name"],
  104.                         "2.5.4.3" => ["ITU-T X.520 (10/2019), clause 6.2.2", "Common name", ["cn", "commonName"], "id-at-commonName"],
  105.                         "2.5.4.4" => ["ITU-T X.520 (10/2019), clause 6.2.3", "Surname", ["sn"], "id-at-surname"],
  106.                         "2.5.4.42" => ["ITU-T X.520 (10/2019), clause 6.2.4", "Given Name", ["givenName"], "id-at-givenName"],
  107.                         "2.5.4.43" => ["ITU-T X.520 (10/2019), clause 6.2.5", "Initials", ["initials"], "id-at-initials"],
  108.                         "2.5.4.44" => ["ITU-T X.520 (10/2019), clause 6.2.6", "Generation Qualifier", ["generationQualifier"], "id-at-generationQualifier"],
  109.                         "2.5.4.45" => ["ITU-T X.520 (10/2019), clause 6.2.7", "Unique Identifier", ["x500UniqueIdentifier"], "id-at-uniqueIdentifier"],
  110.                         "2.5.4.46" => ["ITU-T X.520 (10/2019), clause 6.2.8", "DN Qualifier", ["dnQualifier"], "id-at-dnQualifier"],
  111.                         "2.5.4.5" => ["ITU-T X.520 (10/2019), clause 6.2.9", "Serial Number", ["serialNumber"], "id-at-serialNumber"],
  112.                         "2.5.4.65" => ["ITU-T X.520 (10/2019), clause 6.2.10", "Pseudonym", [], "id-at-pseudonym"],
  113.                         "2.5.4.77" => ["ITU-T X.520 (10/2019), clause 6.2.11", "Universal Unique Identifier Pair", [], "id-at-uuidpair"],
  114.                         "2.5.4.83" => ["ITU-T X.520 (10/2019), clause 6.2.12", "URI", ["uri"], "id-at-uri"],
  115.                         "2.5.4.86" => ["ITU-T X.520 (10/2019), clause 6.2.13", "URN", ["urn"], "id-at-urn"],
  116.                         "2.5.4.87" => ["ITU-T X.520 (10/2019), clause 6.2.14", "URL", ["url"], "id-at-url"],
  117.                         "2.5.4.100" => ["ITU-T X.520 (10/2019), clause 6.2.15", "Domain name", ["DNS name"], "id-at-dnsName"],
  118.                         "2.5.4.104" => ["ITU-T X.520 (10/2019), clause 6.2.16", "Internationalized email address", ["Internationalized Email"], "id-at-intEmail"],
  119.                         "2.5.4.105" => ["ITU-T X.520 (10/2019), clause 6.2.17", "Jabber identifier", ["Jabber identifier"], "id-at-jid"],
  120.                         "2.5.4.106" => ["ITU-T X.520 (10/2019), clause 6.2.18", "Object identifier", ["Object Identifier"], "id-at-objectIdentifier"],
  121.                         // ITU-T X.520 (10/2019), clause 6.3 Geographical attribute types
  122.                         "2.5.4.6" => ["ITU-T X.520 (10/2019), clause 6.3.1", "Country Name", ["c"], "id-at-countryName"],
  123.                         "2.5.4.98" => ["ITU-T X.520 (10/2019), clause 6.3.2", "Country code with three characters", ["c3"], "id-at-countryCode3c"],
  124.                         "2.5.4.99" => ["ITU-T X.520 (10/2019), clause 6.3.3", "Numeric character country code", ["n3"], "id-at-countryCode3n"],
  125.                         "2.5.4.7" => ["ITU-T X.520 (10/2019), clause 6.3.4", "Locality Name", ["l"], "id-at-localityName"],
  126.                         "2.5.4.7.1" => ["ITU-T X.520 (10/2019), clause 6.3.4", "Collective Locality Name", ["c-l"], "id-at-collectiveLocalityName"],
  127.                         "2.5.4.8" => ["ITU-T X.520 (10/2019), clause 6.3.5", "State or Province Name", ["st"], "id-at-stateOrProvinceName"],
  128.                         "2.5.4.8.1" => ["ITU-T X.520 (10/2019), clause 6.3.5", "Collective State or Province Name", ["c-st"], "id-at-collectiveStateOrProvinceName"],
  129.                         "2.5.4.9" => ["ITU-T X.520 (10/2019), clause 6.3.6", "Street Address", ["street"], "id-at-streetAddress"],
  130.                         "2.5.4.9.1" => ["ITU-T X.520 (10/2019), clause 6.3.6", "Collective Street Address", ["c-street"], "id-at-collectiveStreetAddress"],
  131.                         "2.5.4.51" => ["ITU-T X.520 (10/2019), clause 6.3.7", "House Identifier", ["houseIdentifier"], "id-at-houseIdentifier"],
  132.                         "2.5.4.88" => ["ITU-T X.520 (10/2019), clause 6.3.8", "UTM coordinates", ["utmCoordinates"], "id-at-utmCoordinates"],
  133.                         // ITU-T X.520 (10/2019), clause 6.4 Organizational attribute types
  134.                         "2.5.4.10" => ["ITU-T X.520 (10/2019), clause 6.4.1", "Organization Name", ["o"], "id-at-organizationName"],
  135.                         "2.5.4.10.1" => ["ITU-T X.520 (10/2019), clause 6.4.1", "Collective Organization Name", ["c-o"], "id-at-collectiveOrganizationName"],
  136.                         "2.5.4.11" => ["ITU-T X.520 (10/2019), clause 6.4.2", "Organizational Unit Name", ["ou"], "id-at-organizationalUnitName"],
  137.                         "2.5.4.11.1" => ["ITU-T X.520 (10/2019), clause 6.4.2", "Collective Organizational Unit Name", ["c-ou"], "id-at-collectiveOrganizationalUnitName"],
  138.                         "2.5.4.12" => ["ITU-T X.520 (10/2019), clause 6.4.3", "Title", ["title"], "id-at-title"],
  139.                         "2.5.4.97" => ["ITU-T X.520 (10/2019), clause 6.4.4", "Organization identifier", ["organizationIdentifier"], "id-at-organizationIdentifier"],
  140.                         // ITU-T X.520 (10/2019), clause 6.5 Explanatory attribute types
  141.                         "2.5.4.13" => ["ITU-T X.520 (10/2019), clause 6.5.1", "Description", ["description"], "id-at-description"],
  142.                         "2.5.4.14" => ["ITU-T X.520 (10/2019), clause 6.5.2", "Search Guide", ["searchGuide"], "id-at-searchGuide"],
  143.                         "2.5.4.47" => ["ITU-T X.520 (10/2019), clause 6.5.3", "Enhanced Search Guide", ["enhancedSearchGuide"], "id-at-enhancedSearchGuide"],
  144.                         "2.5.4.15" => ["ITU-T X.520 (10/2019), clause 6.5.4", "Business Category", ["businessCategory"], "id-at-businessCategory"],
  145.                         // ITU-T X.520 (10/2019), clause 6.6 Postal addressing attribute types
  146.                         "2.5.4.16" => ["ITU-T X.520 (10/2019), clause 6.6.1", "Postal Address", ["postalAddress"], "id-at-postalAddress"],
  147.                         "2.5.4.16.1" => ["ITU-T X.520 (10/2019), clause 6.6.1", "Collective Postal Address", ["c-PostalAddress"], "id-at-collectivePostalAddress"],
  148.                         "2.5.4.17" => ["ITU-T X.520 (10/2019), clause 6.6.2", "Postal Code", ["postalCode"], "id-at-postalCode"],
  149.                         "2.5.4.17.1" => ["ITU-T X.520 (10/2019), clause 6.6.2", "Collective Postal Code", ["c-PostalCode"], "id-at-collectivePostalCode"],
  150.                         "2.5.4.18" => ["ITU-T X.520 (10/2019), clause 6.6.3", "Post Office Box", ["postOfficeBox"], "id-at-postOfficeBox"],
  151.                         "2.5.4.18.1" => ["ITU-T X.520 (10/2019), clause 6.6.3", "Collective Post Office Box", ["c-PostOfficeBox"], "id-at-collectivePostOfficeBox"],
  152.                         "2.5.4.19" => ["ITU-T X.520 (10/2019), clause 6.6.4", "Physical delivery office name", ["physicalDeliveryOfficeName"], "id-at-physicalDeliveryOfficeName"],
  153.                         "2.5.4.19.1" => ["ITU-T X.520 (10/2019), clause 6.6.4", "Collective Physical Delivery Office Name", ["c-PhysicalDeliveryOfficeName"], "id-at-collectivePhysicalDeliveryOfficeName"],
  154.                         // ITU-T X.520 (10/2019), clause 6.7 Telecommunications addressing attribute types
  155.                         "2.5.4.20" => ["ITU-T X.520 (10/2019), clause 6.7.1", "Telephone number", ["telephoneNumber"], "id-at-telephoneNumber"],
  156.                         "2.5.4.20.1" => ["ITU-T X.520 (10/2019), clause 6.7.1", "Collective Telephone number", ["c-TelephoneNumber"], "id-at-collectiveTelephoneNumber"],
  157.                         "2.5.4.21" => ["ITU-T X.520 (10/2019), clause 6.7.2", "Telex Number", ["telexNumber"], "id-at-telexNumber"],
  158.                         "2.5.4.21.1" => ["ITU-T X.520 (10/2019), clause 6.7.2", "Collective Telex Number", ["c-TelexNumber"], "id-at-collectiveTelexNumber"],
  159.                         "2.5.4.22" => ["ITU-T X.520 (10/2019), clause 6.7.3", "Teletex Terminal Identifier", [], "id-at-teletexTerminalIdentifier"],
  160.                         "2.5.4.22.1" => ["ITU-T X.520 (10/2019), clause 6.7.3", "Collective Teletex Terminal Identifier", [], "id-at-collectiveTeletexTerminalIdentifier"],
  161.                         "2.5.4.23" => ["ITU-T X.520 (10/2019), clause 6.7.4", "Facsimile telephone number", ["facsimileTelephoneNumber"], "id-at-facsimileTelephoneNumber"],
  162.                         "2.5.4.23.1" => ["ITU-T X.520 (10/2019), clause 6.7.4", "Collective Facsimile Telephone Number", ["c-FacsimileTelephoneNumber"], "id-at-collectiveFacsimileTelephoneNumber"],
  163.                         "2.5.4.24" => ["ITU-T X.520 (10/2019), clause 6.7.5", "X.121 Address", ["x121Address"], "id-at-x121Address"],
  164.                         "2.5.4.25" => ["ITU-T X.520 (10/2019), clause 6.7.6", "International ISDN Number", ["internationalISDNNumber"], "id-at-internationalISDNNumber"],
  165.                         "2.5.4.25.1" => ["ITU-T X.520 (10/2019), clause 6.7.6", "Collective International ISDN Number", ["c-InternationalISDNNumber"], "id-at-collectiveInternationalISDNNumber"],
  166.                         "2.5.4.26" => ["ITU-T X.520 (10/2019), clause 6.7.7", "Registered Address", ["registeredAddress"], "id-at-registeredAddress"],
  167.                         "2.5.4.27" => ["ITU-T X.520 (10/2019), clause 6.7.8", "Destination indicator", ["destinationIndicator"], "id-at-destinationIndicator"],
  168.                         "2.5.4.66" => ["ITU-T X.520 (10/2019), clause 6.7.9", "Communications Service", ["communicationsService"], "id-at-communicationsService"],
  169.                         "2.5.4.67" => ["ITU-T X.520 (10/2019), clause 6.7.10", "Communications Network", ["communicationsNetwork"], "id-at-communicationsNetwork"],
  170.                         // ITU-T X.520 (10/2019), clause 6.8 Preferences attribute types
  171.                         "2.5.4.28" => ["ITU-T X.520 (10/2019), clause 6.8.1", "Preferred Delivery Method", ["preferredDeliveryMethod"], "id-at-preferredDeliveryMethod"],
  172.                         // ITU-T X.520 (10/2019), clause 6.9 OSI application attribute types
  173.                         "2.5.4.29" => ["ITU-T X.520 (10/2019), clause 6.9.1", "Presentation Address", ["presentationAddress"], "id-at-presentationAddress"],
  174.                         "2.5.4.30" => ["ITU-T X.520 (10/2019), clause 6.9.2", "Supported Application Context", ["supportedApplicationContext"], "id-at-supportedApplicationContext"],
  175.                         "2.5.4.48" => ["ITU-T X.520 (10/2019), clause 6.9.3", "Protocol Information", [], "id-at-protocolInformation"],
  176.                         // ITU-T X.520 (10/2019), clause 6.10 Relational attribute types
  177.                         "2.5.4.49" => ["ITU-T X.520 (10/2019), clause 6.10.1", "Distinguished Name", ["distinguishedName"], "id-at-distinguishedName"],
  178.                         "2.5.4.31" => ["ITU-T X.520 (10/2019), clause 6.10.2", "Member", ["member"], "id-at-member"],
  179.                         "2.5.4.50" => ["ITU-T X.520 (10/2019), clause 6.10.3", "Unique Member", ["uniqueMember"], "id-at-uniqueMember"],
  180.                         "2.5.4.32" => ["ITU-T X.520 (10/2019), clause 6.10.4", "Owner", ["owner"], "id-at-owner"],
  181.                         "2.5.4.33" => ["ITU-T X.520 (10/2019), clause 6.10.5", "Role Occupant", ["roleOccupant"], "id-at-roleOccupant"],
  182.                         "2.5.4.34" => ["ITU-T X.520 (10/2019), clause 6.10.6", "See Also", ["seeAlso"], "id-at-seeAlso"],
  183.                         // ITU-T X.520 (10/2019), clause 6.11 Domain attribute types
  184.                         "2.5.4.54" => ["ITU-T X.520 (10/2019), clause 6.11.1", "DMD Name", [], "id-at-dmdName"],
  185.                         // ITU-T X.520 (10/2019), clause 6.12 Hierarchical attribute types
  186.                         "2.17.1.2.0" => ["ITU-T X.520 (10/2019), clause 6.12.1", "Top level object identifier arc", [], "id-oidC1"],
  187.                         "2.17.1.2.1" => ["ITU-T X.520 (10/2019), clause 6.12.2", "Second level object identifier arc", [], "id-oidC2"],
  188.                         "2.17.1.2.2" => ["ITU-T X.520 (10/2019), clause 6.12.3", "Lower level object identifier arc", [], "id-oidC"],
  189.                         "2.5.4.89" => ["ITU-T X.520 (10/2019), clause 6.12.4", "URN component", ["urnC"], "id-at-urnC"],
  190.                         // ITU-T X.520 (10/2019), clause 6.13 Attributes for applications using tag-based identification
  191.                         "2.5.4.78" => ["ITU-T X.520 (10/2019), clause 6.13.1", "Tag OID", ["tagOid"], "id-at-tagOid"],
  192.                         "2.5.4.79" => ["ITU-T X.520 (10/2019), clause 6.13.2", "UII Format", ["uiiFormat"], "id-at-uiiFormat"],
  193.                         "2.5.4.80" => ["ITU-T X.520 (10/2019), clause 6.13.3 (LDAP-NAME found in Annex A only)", "UII in URN", ["uiiInUrn"], "id-at-uiiInUrn"],
  194.                         "2.5.4.81" => ["ITU-T X.520 (10/2019), clause 6.13.4", "Content URL", ["contentUrl"], "id-at-contentUrl"],
  195.                         "2.5.4.90" => ["ITU-T X.520 (10/2019), clause 6.13.5", "UII", ["uii"], "id-at-uii"],
  196.                         "2.5.4.91" => ["ITU-T X.520 (10/2019), clause 6.13.6", "EPC", ["epc"], "id-at-epc"],
  197.                         "2.5.4.92" => ["ITU-T X.520 (10/2019), clause 6.13.7", "Tag AFI", ["tagAfi"], "id-at-tagAfi"],
  198.                         "2.5.4.93" => ["ITU-T X.520 (10/2019), clause 6.13.8", "EPC format", ["epcFormat"], "id-at-epcFormat"],
  199.                         "2.5.4.94" => ["ITU-T X.520 (10/2019), clause 6.13.9", "EPC in URN", ["epcInUrn"], "id-at-epcInUrn"],
  200.                         "2.5.4.95" => ["ITU-T X.520 (10/2019), clause 6.13.10", "LDAP URL", ["ldapUrl"], "id-at-ldapUrl"],
  201.                         "2.5.4.96" => ["ITU-T X.520 (10/2019), clause 6.13.11", "Tag location", ["tagLocation"], "id-at-tagLocation"],
  202.                         // ITU-T X.520 (10/2019), clause 6.14 Simple Authentication attributes held by object entries
  203.                         "2.5.4.35" => ["ITU-T X.520 (10/2019), clause 6.14.1 | X.509, Part 8", "Multi-valued user password", ["userPassword"], "id-at-userPassword"],
  204.                         "2.5.4.85" => ["ITU-T X.520 (10/2019), clause 6.14.2 | Annex B", "Single-valued user password", ["userPwd"], "id-at-userPwd"],
  205.                         "2.5.18.22" => ["ITU-T X.520 (10/2019), clause 6.14.3", "Password Start Time", ["pwdStartTime"], "id-oa-pwdStartTime"],
  206.                         "2.5.18.23" => ["ITU-T X.520 (10/2019), clause 6.14.4", "Password expiry time", ["pwdExpiryTime"], "id-oa-pwdExpiryTime"],
  207.                         "2.5.18.24" => ["ITU-T X.520 (10/2019), clause 6.14.5", "Password End Time", ["pwdEndTime"], "id-oa-pwdEndTime"],
  208.                         "2.5.18.25" => ["ITU-T X.520 (10/2019), clause 6.14.6", "Password fails", ["pwdFails"], "id-oa-pwdFails"],
  209.                         "2.5.18.26" => ["ITU-T X.520 (10/2019), clause 6.14.7", "Password failure time", ["pwdFailureTime"], "id-oa-pwdFailureTime"],
  210.                         "2.5.18.27" => ["ITU-T X.520 (10/2019), clause 6.14.8", "Password graces used", ["pwdGracesUsed"], "id-oa-pwdGracesUsed"],
  211.                         "2.5.18.28" => ["ITU-T X.520 (10/2019), clause 6.14.9", "User password history", [], "id-oa-userPwdHistory"],
  212.                         "2.5.18.29" => ["ITU-T X.520 (10/2019), clause 6.14.10", "User password recently expired", [], "id-oa-userPwdRecentlyExpired"],
  213.                         // ITU-T X.520 (10/2019), clause 6.15 Password policy attributes
  214.                         "2.5.18.30" => ["ITU-T X.520 (10/2019), clause 6.15.1", "Password modify entry allowed", ["pwdModifyEntryAllowed"], "id-oa-pwdModifyEntryAllowed"],
  215.                         "2.5.18.31" => ["ITU-T X.520 (10/2019), clause 6.15.2", "Password change allowed", ["pwdChangeAllowed"], "id-oa-pwdChangeAllowed"],
  216.                         "2.5.18.32" => ["ITU-T X.520 (10/2019), clause 6.15.3", "Password maximum age", ["pwdMaxAge"], "id-oa-pwdMaxAge"],
  217.                         "2.5.18.33" => ["ITU-T X.520 (10/2019), clause 6.15.4", "Password expiry age", ["pwdExpiryAge"], "id-oa-pwdExpiryAge"],
  218.                         // ITU-T X.520 (10/2019), clause 6.15.5 Password quality rule attribute types
  219.                         "2.5.18.34" => ["ITU-T X.520 (10/2019), clause 6.15.5.1", "Password minimum length", ["pwdMinLength"], "id-oa-pwdMinLength"],
  220.                         "2.5.18.35" => ["ITU-T X.520 (10/2019), clause 6.15.5.2", "Password vocabulary", ["pwdVocabulary"], "id-oa-pwdVocabulary"],
  221.                         "2.5.18.36" => ["ITU-T X.520 (10/2019), clause 6.15.5.3", "Password alphabet", ["pwdAlphabet"], "id-oa-pwdAlphabet"],
  222.                         "2.5.18.37" => ["ITU-T X.520 (10/2019), clause 6.15.5.4", "Password dictionaries", ["pwdDictionaries"], "id-oa-pwdDictionaries"],
  223.                         "2.5.18.38" => ["ITU-T X.520 (10/2019), clause 6.15.6", "Password expiry warning", ["pwdExpiryWarning"], "id-oa-pwdExpiryWarning"],
  224.                         "2.5.18.39" => ["ITU-T X.520 (10/2019), clause 6.15.7", "Password graces", ["pwdGraces"], "id-oa-pwdGraces"],
  225.                         "2.5.18.40" => ["ITU-T X.520 (10/2019), clause 6.15.8", "Password failure duration", ["pwdFailureDuration"], "id-oa-pwdFailureDuration"],
  226.                         "2.5.18.41" => ["ITU-T X.520 (10/2019), clause 6.15.9", "Password lockout duration", ["pwdLockoutDuration"], "id-oa-pwdLockoutDuration"],
  227.                         "2.5.18.42" => ["ITU-T X.520 (10/2019), clause 6.15.10", "Password maximum failures", ["pwdMaxFailures"], "id-oa-pwdMaxFailures"],
  228.                         "2.5.18.43" => ["ITU-T X.520 (10/2019), clause 6.15.11", "Password maximum time in history", ["pwdMaxTimeInHistory"], "id-oa-pwdMaxTimeInHistory"],
  229.                         "2.5.18.44" => ["ITU-T X.520 (10/2019), clause 6.15.12", "Password minimum time in history", ["pwdMinTimeInHistory"], "id-oa-pwdMinTimeInHistory"],
  230.                         "2.5.18.45" => ["ITU-T X.520 (10/2019), clause 6.15.13", "Password history slots", ["pwdHistorySlots"], "id-oa-pwdHistorySlots"],
  231.                         "2.5.18.46" => ["ITU-T X.520 (10/2019), clause 6.15.14", "Password recently expired duration", ["pwdRecentlyExpiredDuration"], "id-oa-pwdRecentlyExpiredDuration"],
  232.                         "2.5.18.47" => ["ITU-T X.520 (10/2019), clause 6.15.15", "Password encryption algorithm", ["pwdEncAlg"], "id-oa-pwdEncAlg"],
  233.                         // ITU-T X.520 (10/2019), clause 6.16 Notification attributes
  234.                         // ITU-T X.520 (10/2019), clause 6.16.1 DSA problem
  235.                         // ITU-T X.520 (10/2019), clause 6.16.2 Search service problem
  236.                         // ITU-T X.520 (10/2019), clause 6.16.3 Service-type
  237.                         // ITU-T X.520 (10/2019), clause 6.16.4 Attribute type list
  238.                         // ITU-T X.520 (10/2019), clause 6.16.5 Matching rule list
  239.                         // ITU-T X.520 (10/2019), clause 6.16.6 Filter item
  240.                         // ITU-T X.520 (10/2019), clause 6.16.7 Attribute combinations
  241.                         // ITU-T X.520 (10/2019), clause 6.16.8 Context type list
  242.                         // ITU-T X.520 (10/2019), clause 6.16.9 Context list
  243.                         // ITU-T X.520 (10/2019), clause 6.16.10 Context combinations
  244.                         // ITU-T X.520 (10/2019), clause 6.16.11 Hierarchy select list
  245.                         // ITU-T X.520 (10/2019), clause 6.16.12 Search control options list
  246.                         // ITU-T X.520 (10/2019), clause 6.16.13 Service Control Options List
  247.                         // ITU-T X.520 (10/2019), clause 6.16.14 Multiple matching localities
  248.                         // ITU-T X.520 (10/2019), clause 6.16.15 Proposed relaxation
  249.                         // ITU-T X.520 (10/2019), clause 6.16.16 Applied relaxation
  250.                         // ITU-T X.520 (10/2019), clause 6.16.17 Password response
  251.                         // ITU-T X.520 (10/2019), clause 6.16.18 LDAP diagnostic message
  252.                         // ITU-T X.520 (10/2019), clause 6.17 LDAP defined attribute types
  253.                         "0.9.2342.19200300.100.1.1" => ["ITU-T X.520 (10/2019), clause 6.17.1", "User ID", ["uid"], "id-coat-uid"],
  254.                         "0.9.2342.19200300.100.1.25" => ["ITU-T X.520 (10/2019), clause 6.17.2", "Domain component", ["dc"], "id-coat-dc"],
  255.                         "0.9.2342.19200300.100.1.3" => ["ITU-T X.520 (10/2019), clause 6.17.3", "Mail", ["mail"], "id-coat-mail"],
  256.  
  257.                         // Extracted from https://www.itu.int/rec/T-REC-X.509-201910-I/en
  258.                         "2.5.4.36" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.1", "X.509 user certificate", ["userCertificate"], "id-at-userCertificate"],
  259.                         "2.5.4.37" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.2", "X.509 CA certificate", ["cACertificate"], "id-at-cAcertificate"],
  260.                         "2.5.4.40" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.3", "X.509 cross certificate pair", ["crossCertificatePair"], "id-at-crossCertificatePair"],
  261.                         "2.5.4.39" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.4", "X.509 certificate revocation list", ["certificateRevocationList"], "id-at-certificateRevocationList"],
  262.                         "2.5.4.101" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.5", "X.509 EEPK certificate revocation list", ["eepkCertificateRevocationList"], "id-at-eepkCertificateRevocationList"],
  263.                         "2.5.4.38" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.6", "X.509 CA revocation list", ["authorityRevocationList"], "id-at-authorityRevocationList"],
  264.                         "2.5.4.53" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.7", "X.509 delta revocation list", ["deltaRevocationList"], "id-at-deltaRevocationList"],
  265.                         "2.5.4.52" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.8", "X.509 support algorithms", ["supportedAlgorithms"], "id-at-supportedAlgorithms"],
  266.                         "2.5.4.68" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.9", "Certification practice statement", [], "id-at-certificationPracticeStmt"],
  267.                         "2.5.4.69" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.10", "Certificate policy", [], "id-at-certificatePolicy"],
  268.                         "2.5.4.70" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.11", "PKI path", [], "id-at-pkiPath"],
  269.                         "2.5.4.103" => ["Rec. ITU-T X.509 (10/2019), clause 13.2.12", "X.509 supported publiv key algorithms", ["supportedPublicKeyAlgorithms"], "id-at-supportedPublicKeyAlgorithms"],
  270.                         // Rec. ITU-T X.509 (10/2019), clause 19.2 PMI directory attributes
  271.                         "2.5.4.58" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.1", "Attribute certificate", [], "id-at-attributeCertificate"],
  272.                         "2.5.4.61" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.2", "AA certificate", [], "id-at-aACertificate"],
  273.                         "2.5.4.62" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.3", "Attribute descriptor certificate", [], "id-at-attributeDescriptorCertificate"],
  274.                         "2.5.4.59" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.4", "X.509 Attr certificate revocation list", ["AttrCertificateRevocationList"], "id-at-attributeCertificateRevocationList"],
  275.                         "2.5.4.102" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.5", "X.509 EEAttr certificate revocation list", ["EEAttrCertificateRevocationList"], "id-at-eeAttrCertificateRevocationList"],
  276.                         "2.5.4.63" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.6", "X.509 AA certificate revocation list", ["AACertificateRevocationList"], "id-at-attributeAuthorityRevocationList"],
  277.                         "2.5.4.73" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.7", "Delegation path", [], "id-at-delegationPath"],
  278.                         "2.5.4.71" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.8", "Privilege policy", [], "id-at-privPolicy"],
  279.                         "2.5.4.74" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.9", "Protected privilege policy", [], "id-at-protPrivPolicy"],
  280.                         "2.5.4.76" => ["Rec. ITU-T X.509 (10/2019), clause 19.2.10", "XML Protected privilege policy", [], "id-at-xmlPrivPolicy"],
  281.                         "2.5.4.72" => ["Rec. ITU-T X.509 (10/2019), clause 16.5.1", "Role", [], "id-at-role"],
  282.                         "2.5.4.75" => ["Rec. ITU-T X.509 (10/2019), clause 16.7", "XML privilege information", [], "id-at-xMLPrivilegeInfo"],
  283.                         "2.5.4.82" => ["Rec. ITU-T X.509 (10/2019), clause 16.8.1 ", "Permission", [], "id-at-permission"],
  284.  
  285.                         // Extracted from https://www.itu.int/rec/T-REC-X.501-201910-I/en
  286.                         "2.5.4.0"  => ["Rec. ITU-T X.501 (10/2019), clause 13.4.8", "Object Class", ["objectClass"], "id-at-objectClass"],
  287.                         "2.5.4.1"  => ["Rec. ITU-T X.501 (10/2019), clause 13.4.8", "Aliased Object Name", ["aliasedObjectName"], "id-at-aliasedEntryName"],
  288.                         "2.5.4.84" => ["Rec. ITU-T X.501 (10/2019), clause 14.9", "Password attribute", ["pwdAttribute"], "id-at-pwdAttribute"],
  289.                         "2.5.4.55" => ["Rec. ITU-T X.501 (10/2019), clause 19.5", "Clearance", [], "id-at-clearance"],
  290.                         "2.5.4.56" => ["Rec. ITU-T X.501, obsolete", "Default Dir Qop", [], "id-at-defaultDirQop"],
  291.                         "2.5.4.57" => ["Rec. ITU-T X.501 (10/2019), clause 20.2", "Attribute integrity info", [], "id-at-attributeIntegrityInfo"],
  292.                         "2.5.4.60" => ["Rec. ITU-T X.501, obsolete", "Conf key info", [], "id-at-confKeyInfo"],
  293.  
  294.                         // Extracted from https://www.itu.int/rec/T-REC-X.511-201910-I/en
  295.                         "2.5.4.64" => ["Rec. ITU-T X.511 (10/2019), clause 7.7.2", "Family information in entry information", [], "id-at-family-information"],
  296.  
  297.                         // Extracted from https://www.rfc-editor.org/rfc/RFC 4524.html
  298.                         // IETF RFC 4524, clause 2 (COSINE Attribute Types)
  299.                         "0.9.2342.19200300.100.1.37" => ["IETF RFC 4524, clause 2.1", "Associated Domain", ["associatedDomain"], "associatedDomain"],
  300.                         "0.9.2342.19200300.100.1.38" => ["IETF RFC 4524, clause 2.2", "Associated Name", ["associatedName"], "associatedName"],
  301.                         "0.9.2342.19200300.100.1.48" => ["IETF RFC 4524, clause 2.3", "Building Name", ["buildingName"], "buildingName"],
  302.                         "0.9.2342.19200300.100.1.43" => ["IETF RFC 4524, clause 2.4", "Friendly Country Name", ["co", "friendCountryName"], "friendCountryName"],
  303.                         "0.9.2342.19200300.100.1.14" => ["IETF RFC 4524, clause 2.5", "Document Author", ["documentAuthor"], "documentAuthor"],
  304.                         "0.9.2342.19200300.100.1.11" => ["IETF RFC 4524, clause 2.6", "Document Identifier", ["documentIdentifier"], "documentIdentifier"],
  305.                         "0.9.2342.19200300.100.1.15" => ["IETF RFC 4524, clause 2.7", "Document Location", ["documentLocation"], "documentLocation"],
  306.                         "0.9.2342.19200300.100.1.56" => ["IETF RFC 4524, clause 2.8", "Document Publisher", ["documentPublisher"], "documentPublisher"],
  307.                         "0.9.2342.19200300.100.1.12" => ["IETF RFC 4524, clause 2.9", "Document Title", ["documentTitle"], "documentTitle"],
  308.                         "0.9.2342.19200300.100.1.13" => ["IETF RFC 4524, clause 2.10", "Document Version", ["documentVersion"], "documentVersion"],
  309.                         "0.9.2342.19200300.100.1.5" => ["IETF RFC 4524, clause 2.11", "Favorite Drink", ["drink", "favoriteDrink", "favouriteDrink"], "drink"],
  310.                         "0.9.2342.19200300.100.1.20" => ["IETF RFC 4524, clause 2.12", "Home Phone", ["homeTelephoneNumber", "homePhone"], "homePhone"],
  311.                         "0.9.2342.19200300.100.1.39" => ["IETF RFC 4524, clause 2.13", "Home Postal Address", ["homePostalAddress"], "homePostalAddress"],
  312.                         "0.9.2342.19200300.100.1.9" => ["IETF RFC 4524, clause 2.14", "Host", ["host"], "host"],
  313.                         "0.9.2342.19200300.100.1.4" => ["IETF RFC 4524, clause 2.15", "Info", ["info"], "info"],
  314.                         //(already defined in X.520) "0.9.2342.19200300.100.1.3" => ["IETF RFC 4524, clause 2.16", "Mail", ["rfc822Mailbox", "mail"], "mail"],
  315.                         "0.9.2342.19200300.100.1.10" => ["IETF RFC 4524, clause 2.17", "Manager", ["manager"], "manager"],
  316.                         "0.9.2342.19200300.100.1.41" => ["IETF RFC 4524, clause 2.18", "Mobile", ["mobileTelephoneNumber", "mobile"], "mobile"],
  317.                         "0.9.2342.19200300.100.1.45" => ["IETF RFC 4524, clause 2.19", "Organizational Status", ["organizationalStatus"], "organizationalStatus"],
  318.                         "0.9.2342.19200300.100.1.42" => ["IETF RFC 4524, clause 2.20", "Pager", ["pagerTelephoneNumber", "pager"], "pager"],
  319.                         "0.9.2342.19200300.100.1.40" => ["IETF RFC 4524, clause 2.21", "Personal Title", ["personalTitle"], "personalTitle"],
  320.                         "0.9.2342.19200300.100.1.6" => ["IETF RFC 4524, clause 2.22", "Room Number", ["roomNumber"], "roomNumber"],
  321.                         "0.9.2342.19200300.100.1.21" => ["IETF RFC 4524, clause 2.23", "secretary", ["secretary"], "secretary"],
  322.                         "0.9.2342.19200300.100.1.44" => ["IETF RFC 4524, clause 2.24", "Unique Identifier", ["uniqueIdentifier"], "uniqueIdentifier"],
  323.                         "0.9.2342.19200300.100.1.8" => ["IETF RFC 4524, clause 2.25", "User Class", ["userClass"], "userClass"],
  324.  
  325.                         // Extracted from https://www.rfc-editor.org/rfc/rfc1274.html (only the ones which don't exist above)
  326.                         "0.9.2342.19200300.100.1.2" => ["IETF RFC 1274, clause 9.3.2", "Text Encoded O/R Address", ["textEncodedORAddress"], "textEncodedORAddress"],
  327.                         "0.9.2342.19200300.100.1.7" => ["IETF RFC 1274, clause 9.3.7", "Photo", ["photo"], "photo"],
  328.                         "0.9.2342.19200300.100.1.22" => ["IETF RFC 1274, clause 9.3.18", "Other Mailbox", ["otherMailbox"], "otherMailbox"],
  329.                         "0.9.2342.19200300.100.1.23" => ["IETF RFC 1274, clause 9.3.19", "Last Modified Time", ["lastModifiedTime"], "lastModifiedTime"],
  330.                         "0.9.2342.19200300.100.1.24" => ["IETF RFC 1274, clause 9.3.20", "Last Modified By", ["lastModifiedBy"], "lastModifiedBy"],
  331.                         "0.9.2342.19200300.100.1.26" => ["IETF RFC 1274, clause 9.3.22", "DNS ARecord", ["aRecord"], "aRecord"],
  332.                         "0.9.2342.19200300.100.1.27" => ["IETF RFC 1274 ???", "MD Record", ["mDRecord"], "mDRecord"],
  333.                         "0.9.2342.19200300.100.1.28" => ["IETF RFC 1274, clause 9.3.23", "MX Record", ["mXRecord"], "mXRecord"],
  334.                         "0.9.2342.19200300.100.1.29" => ["IETF RFC 1274, clause 9.3.24", "NS Record", ["nSRecord"], "nSRecord"],
  335.                         "0.9.2342.19200300.100.1.30" => ["IETF RFC 1274, clause 9.3.25", "SOA Record", ["sOARecord"], "sOARecord"],
  336.                         "0.9.2342.19200300.100.1.31" => ["IETF RFC 1274, clause 9.3.26", "CNAME Record", ["cNAMERecord"], "cNAMERecord"],
  337.                         "0.9.2342.19200300.100.1.46" => ["IETF RFC 1274, clause 9.3.36", "Janet Mailbox", ["janetMailbox"], "janetMailbox"],
  338.                         "0.9.2342.19200300.100.1.47" => ["IETF RFC 1274, clause 9.3.37", "Mail Preference Option", ["mailPreferenceOption"], "mailPreferenceOption"],
  339.                         "0.9.2342.19200300.100.1.49" => ["IETF RFC 1274, clause 9.3.39", "DSA Quality", ["dSAQuality"], "dSAQuality"],
  340.                         "0.9.2342.19200300.100.1.50" => ["IETF RFC 1274, clause 9.3.40", "Single Level Quality", ["singleLevelQuality"], "singleLevelQuality"],
  341.                         "0.9.2342.19200300.100.1.51" => ["IETF RFC 1274, clause 9.3.41", "Subtree Minimum Quality", ["subtreeMinimumQuality"], "subtreeMinimumQuality"],
  342.                         "0.9.2342.19200300.100.1.52" => ["IETF RFC 1274, clause 9.3.42", "Subtree Maximum Quality", ["subtreeMaximumQuality"], "subtreeMaximumQuality"],
  343.                         "0.9.2342.19200300.100.1.53" => ["IETF RFC 1274, clause 9.3.43", "Personal Signature", ["personalSignature"], "personalSignature"],
  344.                         "0.9.2342.19200300.100.1.54" => ["IETF RFC 1274, clause 9.3.44", "DIT Redirect", ["dITRedirect"], "dITRedirect"],
  345.                         "0.9.2342.19200300.100.1.55" => ["IETF RFC 1274, clause 9.3.45", "Audio", ["audio"], "audio"],
  346.  
  347.                         // Extracted from https://www.rfc-editor.org/rfc/rfc2798.html
  348.                         // IETF RFC 2798, clause 2 Attribute Types Used in the inetOrgPerson Object Class
  349.                         "2.16.840.1.113730.3.1.1" => ["IETF RFC 2798, clause 2.1", "Vehicle license or registration plate", ["carLicense"], "carLicense"],
  350.                         "2.16.840.1.113730.3.1.2" => ["IETF RFC 2798, clause 2.2", "Department number", ["departmentNumber"], "departmentNumber"],
  351.                         "2.16.840.1.113730.3.1.241" => ["IETF RFC 2798, clause 2.3", "Display Name", ["displayName"], "displayName"],
  352.                         "2.16.840.1.113730.3.1.3" => ["IETF RFC 2798, clause 2.4", "Employee Number", ["employeeNumber"], "employeeNumber"],
  353.                         "2.16.840.1.113730.3.1.4" => ["IETF RFC 2798, clause 2.5", "Employee Type", ["employeeType"], "employeeType"],
  354.                         "0.9.2342.19200300.100.1.60" => ["IETF RFC 2798, clause 2.6", "JPEG Photograph", ["jpegPhoto"], "jpegPhoto"],
  355.                         "2.16.840.1.113730.3.1.39" => ["IETF RFC 2798, clause 2.7", "Preferred Language", ["preferredLanguage"], "preferredLanguage"],
  356.                         "2.16.840.1.113730.3.1.40" => ["IETF RFC 2798, clause 2.8", "User S/MIME Certificate", ["userSMIMECertificate"], "userSMIMECertificate"],
  357.                         "2.16.840.1.113730.3.1.216" => ["IETF RFC 2798, clause 2.9", "User PKCS #12", ["userPKCS12"], "userPKCS12"],
  358.  
  359.                 ];
  360.  
  361.                 // Additional identifiers found at https://www.ibm.com/docs/en/zos/2.2.0?topic=SSLTBW_2.2.0/com.ibm.tcp.ipsec.ipsec.help.doc/com/ibm/tcp/ipsec/nss/NssImageServerPs.RB_X500.htm
  362.                 $ldap_attributes["1.2.840.113549.1.9.1"] = ["???", "E-mail address", ["E", "EMAIL", "EMAILADDRESS"], "pkcs-9-at-emailAddress"]; //(preferred: EMAIL)
  363.                 $ldap_attributes["2.5.4.17"][2][] = "PC"; // Postal code
  364.                 $ldap_attributes["2.5.4.8"][2][] = "S"; // State or province
  365.                 $ldap_attributes["2.5.4.8"][2][] = "SP"; // State or province
  366.                 $ldap_attributes["2.5.4.12"][2][] = "T"; // Title
  367.  
  368.                 // Additional identifiers found at https://www.cryptosys.net/pki/manpki/pki_distnames.html
  369.                 $ldap_attributes["2.5.4.42"][2][] = "G"; // Given name
  370.                 $ldap_attributes["2.5.4.42"][2][] = "GN"; // Given name
  371.  
  372.                 // Found in OpenSSL https://github.com/openssl/openssl/blob/a2608e4bc430d6216bbf36f50a29278e8759103a/include/openssl/obj_mac.h
  373.                 $ldap_attributes["2.5.4.100"][2][] = "dnsName"; // X.520 says "DNS Name"
  374.                 $ldap_attributes["2.5.4.72"][2][] = "role";
  375.  
  376.                 // Merge identifiers from X.520 and RFC 4524
  377.                 $ldap_attributes["0.9.2342.19200300.100.1.3"][2][] = "rfc822Mailbox";
  378.  
  379.                 // IETF RFC 1274 uses the identifier "userid" instead of "uid".
  380.                 $ldap_attributes["0.9.2342.19200300.100.1.1"][2][] = "userId";
  381.  
  382.                 // Additional identifiers by Daniel Marschall (these attributes don't have a LDAP-NAME property in X.520, so we set something based on the ASN.1 alphanumeric identifier)
  383.                 $ldap_attributes["2.5.4.2"][2][] = "knowledgeInformation";
  384.                 $ldap_attributes["2.5.4.65"][2][] = "pseudonym";
  385.                 $ldap_attributes["2.5.4.77"][2][] = "uuidpair";
  386.                 $ldap_attributes["2.5.4.22"][2][] = "teletexTerminalIdentifier";
  387.                 $ldap_attributes["2.5.4.22.1"][2][] = "collectiveTeletexTerminalIdentifier";
  388.                 $ldap_attributes["2.5.4.22.1"][2][] = "c-teletexTerminalIdentifier";
  389.                 $ldap_attributes["2.5.4.48"][2][] = "protocolInformation";
  390.                 $ldap_attributes["2.5.4.54"][2][] = "dmdName";
  391.                 $ldap_attributes["2.17.1.2.0"][2][] = "oidC1";
  392.                 $ldap_attributes["2.17.1.2.1"][2][] = "oidC2";
  393.                 $ldap_attributes["2.17.1.2.2"][2][] = "oidC";
  394.                 $ldap_attributes["2.5.18.28"][2][] = "userPwdHistory";
  395.                 $ldap_attributes["2.5.18.29"][2][] = "userPwdRecentlyExpired";
  396.  
  397.                 // Vendor specific stuff
  398.                 $ldap_attributes["1.3.6.1.4.1.37476.2.5.2.9.4.1"] = ["ViaThinkSoft: OidplusAttributeTypes ASN.1 Module, Version 1", "OIDplus System", ["OidplusSystemId"], "system-id"];
  399.                 $ldap_attributes["1.3.6.1.4.1.37476.2.5.2.9.4.2"] = ["ViaThinkSoft: OidplusAttributeTypes ASN.1 Module, Version 1", "OIDplus Object Hash", ["OidplusObjectHash"], "object-hash"];
  400.  
  401.                 // Test data
  402.                 /*
  403.                 for ($i=0; $i<=106; $i++) {
  404.                         if (!isset($ldap_attributes["2.5.4.$i"])) echo "WARNING: 2.5.4.$i MISSING<br>\n";
  405.                 }
  406.                 for ($i=1; $i<=60; $i++) {
  407.                         if (($i>15) && ($i<20)) continue;
  408.                         if (($i>31) && ($i<37)) continue;
  409.                         if (($i>56) && ($i<60)) continue;
  410.                         if (!isset($ldap_attributes["0.9.2342.19200300.100.1.$i"])) echo "WARNING: 0.9.2342.19200300.100.1.$i MISSING<br>\n";
  411.                 }
  412.                 foreach($ldap_attributes as $a) {
  413.                         if (count($a) !== 4) echo "WARNING: ".print_r($a,true)."<br>\n";
  414.                 }
  415.                 */
  416.  
  417.                 return $ldap_attributes;
  418.         }
  419.  
  420.         /**
  421.          * @param string $val
  422.          * @param bool $escape_equal_sign
  423.          * @param bool $escape_backslash
  424.          * @param bool $allow_ber
  425.          * @return string
  426.          */
  427.         protected static function escapeAttributeValue(string $val, bool $escape_equal_sign, bool $escape_backslash, bool $allow_ber): string {
  428.                 // Escaping required by https://datatracker.ietf.org/doc/html/rfc2253#section-2.4
  429.  
  430.                 $val = trim($val); // we don't escape whitespaces. It is very unlikely that someone wants whitespaces at the beginning or end (it is rather a copy-paste error)
  431.  
  432.                 if ($escape_backslash) $val = str_replace('\\', '\\\\', $val); // important: do this first
  433.  
  434.                 $chars_to_escape = array(',', '+', '"', '<', '>', ';'); // listed in RFC 2253
  435.                 $chars_to_escape[] = '/'; // defined by us (OIDplus)
  436.                 if ($escape_equal_sign) $chars_to_escape[] = '='; // defined by us (OIDplus)
  437.  
  438.                 foreach ($chars_to_escape as $char) {
  439.                         $dummy = find_nonexisting_substr($val);
  440.                         if (!$escape_backslash) $val = str_replace('\\'.$char, $dummy, $val);
  441.                         $val = str_replace($char, '\\'.$char, $val);
  442.                         if (!$escape_backslash) $val = str_replace($dummy, '\\'.$char, $val);
  443.                 }
  444.  
  445.                 if (!$allow_ber) {
  446.                         if (substr($val, 0, 1) == '#') {
  447.                                 $val = '\\' . $val;
  448.                         }
  449.                 }
  450.  
  451.                 return $val;
  452.         }
  453.  
  454.         /**
  455.          * @param string &$arc A RDN (Relative Distinguished Name), e.g. C=DE, CN=test, or 2.999=example.
  456.          *                     It *might* be auto-corrected (adding escape values).
  457.          * @param bool $allow_multival Are multi-valued arcs (e.g. "uid=4711+cn=John Doe") allowed?
  458.          * @return bool
  459.          */
  460.         protected static function isValidArc(string &$arc, bool $allow_multival=true): bool {
  461.                 if ($allow_multival) {
  462.                         // We allow unescaped "+" and try to escape it, but at the same time we try to allow multi-valued names
  463.                         // Example:
  464.                         // "/cn=A+B Consulting"  will get corrected to  "/cn=A\+B Consulting"
  465.                         // "/cn=X+cn=Y" stays the same (multi-valued)
  466.                         // "/cn=X+cn=A+B Consulting"  will get corrected to  "/cn=X+cn=A\+B Consulting"
  467.                         // But we will also accept escape sequences by the user!
  468.                         // "/cn=X\+cn=A\+B Consulting" stays the same (not multi-valued)
  469.  
  470.                         $values = explode_with_escaping('+', $arc);
  471.  
  472.                         $corrected_identifier = '';
  473.                         foreach ($values as $v) {
  474.                                 $dummy = find_nonexisting_substr($v);
  475.                                 $v = str_replace('\\=', $dummy, $v);
  476.                                 $is_rdn = strpos($v, '=');
  477.                                 $v = str_replace($dummy, '\\=', $v);
  478.  
  479.                                 if ($is_rdn) {
  480.                                         if (!self::isValidArc($v, false)) return false; // Note: isValidArc() also corrects the escaping of $v
  481.                                 } else {
  482.                                         $v = self::escapeAttributeValue($v, /*$escape_equal_sign=*/false, /*$escape_backslash=*/false, /*$allow_ber=*/true);
  483.                                 }
  484.  
  485.                                 if ($corrected_identifier == '') { // 1st value
  486.                                         if ($is_rdn) {
  487.                                                 // "cn=hello" (values = ["cn=hello"]) is valid
  488.                                                 $corrected_identifier = $v;
  489.                                         } else {
  490.                                                 // "hello+cn=world" (values = ["hello", "cn=world"]) is always invalid
  491.                                                 return false;
  492.                                         }
  493.                                 } else { // 2nd, 3rd, ... value
  494.                                         if ($is_rdn) {
  495.                                                 // "cn=hello+cn=world" (values = ["cn=hello", "cn=world"]) stays "cn=hello+cn=world"
  496.                                                 $corrected_identifier .= '+' . $v;
  497.                                         } else {
  498.                                                 // "cn=hello+world" (values = ["cn=hello", "world"]) becomes "cn=hello\+world"
  499.                                                 $corrected_identifier .= '\\+' . $v;
  500.                                         }
  501.                                 }
  502.                         }
  503.                         $arc = $corrected_identifier; // return the auto-corrected identifier
  504.                         return true;
  505.                 } else {
  506.                         $ary = explode_with_escaping('=', $arc, 2);
  507.                         if (count($ary) !== 2) return false;
  508.                         if ($ary[0] == "") return false;
  509.                         if ($ary[1] == "") return false;
  510.  
  511.                         $ary[0] = self::escapeAttributeValue($ary[0], /*$escape_equal_sign=*/false, /*$escape_backslash=*/false, /*$allow_ber=*/true);
  512.                         $ary[1] = self::escapeAttributeValue($ary[1], /*$escape_equal_sign=*/true,  /*$escape_backslash=*/false, /*$allow_ber=*/true);
  513.  
  514.                         if (oid_valid_dotnotation($ary[0], false, false, 1)) {
  515.                                 $arc = $ary[0] . '=' . $ary[1]; // return the auto-corrected identifier
  516.                                 return true;
  517.                         }
  518.  
  519.                         if (substr($ary[1],0,1) == '#') {
  520.                                 $hex_code = substr($ary[1],1);
  521.                                 $is_valid_hexstr = preg_match("/^[a-f0-9]{2,}$/i", $hex_code) && !(strlen($hex_code) & 1);
  522.                                 if (!$is_valid_hexstr) {
  523.                                         throw new OIDplusException(_L('"%1" is not a valid hex string. Note: In case you want a string starting with a hashtag, you need to add a backslash in front of it.', $ary[1]));
  524.                                 }
  525.  
  526.                                 // TODO: Theoretically, we should also check if the hex string is valid BER code... but that is a very hard task
  527.                                 //       Also, if we go even a step further, then we could also check if the data is valid (correct ASN.1 type).
  528.  
  529.                         }
  530.  
  531.                         $known_attr_names = self::getKnownAttributeNames();
  532.                         foreach ($known_attr_names as $oid => list($source, $englishName, $ldapNames, $oidName)) {
  533.                                 foreach ($ldapNames as $abbr) {
  534.                                         if (strtolower($abbr) === strtolower($ary[0])) {
  535.                                                 $arc = $ary[0] . '=' . $ary[1]; // return the auto-corrected identifier
  536.                                                 return true;
  537.                                         }
  538.                                 }
  539.                         }
  540.  
  541.                         return false;
  542.                 }
  543.         }
  544.  
  545.         /**
  546.          * @param string $str
  547.          * @return string
  548.          */
  549.         public function addString(string $str): string {
  550.                 if (substr($str,0,1) == '/') $str = substr($str, 1);
  551.  
  552.                 $new_arcs = explode_with_escaping('/', $str);
  553.                 foreach ($new_arcs as $n => &$test_arc) {
  554.                         if (!self::isValidArc($test_arc, true)) {
  555.                                 throw new OIDplusException(_L("Arc %1 (%2) is not a valid Relative Distinguished Name (RDN).", $n+1, $test_arc));
  556.                         }
  557.                 }
  558.                 unset($test_arc);
  559.                 $str = implode('/', $new_arcs); // correct escaping which was auto-corrected by isValidArc()
  560.  
  561.                 if ($this->isRoot()) {
  562.                         if (substr($str,0,1) != '/') $str = '/'.$str;
  563.                         return self::root() . $str;
  564.                 } else {
  565.                         if (strpos($str,'/') !== false) throw new OIDplusException(_L('Please only submit one arc.'));
  566.                         return $this->nodeId() . '/' . $str;
  567.                 }
  568.         }
  569.  
  570.         /**
  571.          * @param OIDplusObject $parent
  572.          * @return string
  573.          */
  574.         public function crudShowId(OIDplusObject $parent): string {
  575.                 if ($parent->isRoot()) {
  576.                         return substr($this->nodeId(), strlen($parent->nodeId()));
  577.                 } else {
  578.                         return substr($this->nodeId(), strlen($parent->nodeId())+1);
  579.                 }
  580.         }
  581.  
  582.         /**
  583.          * @param OIDplusObject|null $parent
  584.          * @return string
  585.          */
  586.         public function jsTreeNodeName(OIDplusObject $parent = null): string {
  587.                 if ($parent == null) return $this->objectTypeTitle();
  588.                 if ($parent->isRoot()) {
  589.                         return substr($this->nodeId(), strlen($parent->nodeId()));
  590.                 } else {
  591.                         return substr($this->nodeId(), strlen($parent->nodeId())+1);
  592.                 }
  593.         }
  594.  
  595.         /**
  596.          * @return string
  597.          */
  598.         public function defaultTitle(): string {
  599.                 return $this->identifier;
  600.         }
  601.  
  602.         /**
  603.          * @return bool
  604.          */
  605.         public function isLeafNode(): bool {
  606.                 return false;
  607.         }
  608.  
  609.         /**
  610.          * @return string[]
  611.          * @throws OIDplusException
  612.          */
  613.         private function getTechInfo(): array {
  614.                 $tech_info = array();
  615.  
  616.                 $known_attr_names = self::getKnownAttributeNames();
  617.  
  618.                 // Note: There are some notation rules if names contain things like backslashes, see https://www.cryptosys.net/pki/manpki/pki_distnames.html
  619.                 // We currently do not fully implement these! (TODO)
  620.  
  621.                 $html_dce_ad_notation = '';
  622.                 $html_ldap_notation = '';
  623.                 $html_encoded_string_notation = '';
  624.  
  625.                 $arcs = explode_with_escaping('/', ltrim($this->identifier,'/'));
  626.                 foreach ($arcs as $arc) {
  627.                         $ary = explode_with_escaping('=', $arc, 2);
  628.  
  629.                         $found_oid = '';
  630.                         $found_hf_name = _L('Unknown attribute');
  631.  
  632.                         foreach ($known_attr_names as $oid => list($source, $englishName, $ldapNames, $oidName)) {
  633.                                 if ($ary[0] == $oid) {
  634.                                         $found_oid = $oid;
  635.                                         $found_hf_name = $englishName;
  636.                                         break;
  637.                                 }
  638.                                 foreach ($ldapNames as $abbr) {
  639.                                         if (strtolower($abbr) == strtolower($ary[0])) {
  640.                                                 $found_oid = $oid;
  641.                                                 $found_hf_name = $englishName;
  642.                                                 break 2;
  643.                                         }
  644.                                 }
  645.                         }
  646.  
  647.                         $html_dce_ad_notation .= '/<abbr title="'.htmlentities($found_hf_name).'">'.htmlentities(strtoupper($ary[0])).'</abbr>='.htmlentities($ary[1]);
  648.                         $html_ldap_notation = '<abbr title="'.htmlentities($found_hf_name).'">'.htmlentities(strtoupper($ary[0])).'</abbr>='.htmlentities(str_replace(',','\\,',$ary[1])) . ($html_ldap_notation == '' ? '' : ', ' . $html_ldap_notation);
  649.  
  650.                         // TODO: how are multi-valued values handled?
  651.                         // TODO: We cannot simply encode everything to UTF8String, because some attributes need to be encoded as binary, integer, datetime, etc.!
  652.                         $html_encoded_str = '#<abbr title="'._L('ASN.1: UTF8String').'">'.sprintf('%02s', strtoupper(dechex(0x0C/*UTF8String*/))).'</abbr>';
  653.                         if (substr($ary[1],0,1) == '#') {
  654.                                 $html_encoded_str = htmlentities(strtoupper($ary[1]));
  655.                         } else {
  656.                                 $utf8 = vts_utf8_encode($ary[1]);
  657.                                 $html_encoded_str .= '<abbr title="'._L('Length').'">'.sprintf('%02s', strtoupper(dechex(strlen($utf8)))).'</abbr>'; // TODO: This length does only work for length <= 0x7F! The correct implementation is described here: https://misc.daniel-marschall.de/asn.1/oid_facts.html#chap1_2
  658.                                 $html_encoded_str .= '<abbr title="'.htmlentities($ary[1]).'">';
  659.                                 for ($i=0; $i<strlen($utf8); $i++) {
  660.                                         $char = substr($utf8, $i, 1);
  661.                                         $html_encoded_str .= sprintf('%02s', strtoupper(dechex(ord($char))));
  662.                                 }
  663.                                 $html_encoded_str .= '</abbr>';
  664.                         }
  665.                         if ($ary[0] == $found_oid) {
  666.                                 $html_encoded_string_notation = '<abbr title="'.htmlentities($found_hf_name).'">'.htmlentities($found_oid).'</abbr>='.$html_encoded_str . ($html_encoded_string_notation == '' ? '' : ',' . $html_encoded_string_notation);
  667.                         } else {
  668.                                 $html_encoded_string_notation = '<abbr title="'.htmlentities(strtoupper($ary[0]) . ' = ' . $found_hf_name).'">'.htmlentities($found_oid).'</abbr>='.$html_encoded_str . ($html_encoded_string_notation == '' ? '' : ',' . $html_encoded_string_notation);
  669.                         }
  670.                 }
  671.  
  672.                 $tmp = _L('DCE/MSAD notation');
  673.                 $tmp = str_replace('DCE', '<abbr title="'._L('Distributed Computing Environment').'">DCE</abbr>', $tmp);
  674.                 $tmp = str_replace('MSAD', '<abbr title="'._L('Microsoft ActiveDirectory').'">MSAD</abbr>', $tmp);
  675.                 $tech_info[$tmp] = $html_dce_ad_notation;
  676.  
  677.                 $tmp = _L('LDAP notation');
  678.                 $tmp = str_replace('LDAP', '<abbr title="'._L('Lightweight Directory Access Protocol').'">LDAP</abbr>', $tmp);
  679.                 $tech_info[$tmp] = $html_ldap_notation;
  680.  
  681.                 $tmp = _L('Encoded string notation');
  682.                 $tech_info[$tmp] = $html_encoded_string_notation;
  683.  
  684.                 return $tech_info;
  685.         }
  686.  
  687.         /**
  688.          * @param string $title
  689.          * @param string $content
  690.          * @param string $icon
  691.          * @return void
  692.          * @throws OIDplusException
  693.          */
  694.         public function getContentPage(string &$title, string &$content, string &$icon) {
  695.                 $icon = file_exists(__DIR__.'/img/main_icon.png') ? OIDplus::webpath(__DIR__,OIDplus::PATH_RELATIVE).'img/main_icon.png' : '';
  696.  
  697.                 if ($this->isRoot()) {
  698.                         $title = OIDplusX500DN::objectTypeTitle();
  699.  
  700.                         $res = OIDplus::db()->query("select * from ###objects where parent = ?", array(self::root()));
  701.                         if ($res->any()) {
  702.                                 $content  = '<p>'._L('Please select an object in the tree view at the left to show its contents.').'</p>';
  703.                         } else {
  704.                                 $content  = '<p>'._L('Currently, no X.500 Distinguished Names are registered in the system.').'</p>';
  705.                         }
  706.  
  707.                         if (!$this->isLeafNode()) {
  708.                                 if (OIDplus::authUtils()->isAdminLoggedIn()) {
  709.                                         $content .= '<h2>'._L('Manage root objects').'</h2>';
  710.                                 } else {
  711.                                         $content .= '<h2>'._L('Available objects').'</h2>';
  712.                                 }
  713.                                 $content .= '%%CRUD%%';
  714.                         }
  715.                 } else {
  716.                         $title = $this->getTitle();
  717.  
  718.                         $tech_info = $this->getTechInfo();
  719.                         $tech_info_html = '';
  720.                         if (count($tech_info) > 0) {
  721.                                 $tech_info_html .= '<h2>'._L('Technical information').'</h2>';
  722.                                 $tech_info_html .= '<div style="overflow:auto"><table border="0">';
  723.                                 foreach ($tech_info as $key => $value) {
  724.                                         $tech_info_html .= '<tr><td valign="top" style="white-space: nowrap;">'.$key.': </td><td><code>'.$value.'</code></td></tr>';
  725.                                 }
  726.                                 $tech_info_html .= '</table></div>';
  727.                         }
  728.  
  729.                         $content = $tech_info_html;
  730.  
  731.                         $content .= '<h2>'._L('Description').'</h2>%%DESC%%';
  732.  
  733.                         if (!$this->isLeafNode()) {
  734.                                 if ($this->userHasWriteRights()) {
  735.                                         $content .= '<h2>'._L('Create or change subordinate objects').'</h2>';
  736.                                 } else {
  737.                                         $content .= '<h2>'._L('Subordinate objects').'</h2>';
  738.                                 }
  739.                                 $content .= '%%CRUD%%';
  740.                         }
  741.                 }
  742.         }
  743.  
  744.         /**
  745.          * @return OIDplusX500DN|null
  746.          */
  747.         public function one_up()/*: ?OIDplusX500DN*/ {
  748.                 $oid = $this->identifier;
  749.  
  750.                 $p = strrpos($oid, '/');
  751.                 if ($p === false) return self::parse($oid);
  752.                 if ($p == 0) return self::parse('/');
  753.  
  754.                 $oid_up = substr($oid, 0, $p);
  755.  
  756.                 return self::parse(self::ns().':'.$oid_up);
  757.         }
  758.  
  759.         /**
  760.          * @param OIDplusObject|string $to
  761.          * @return int|null
  762.          */
  763.         public function distance($to) {
  764.                 if (!is_object($to)) $to = OIDplusObject::parse($to);
  765.                 if (!$to) return null;
  766.                 if (!($to instanceof $this)) return null;
  767.  
  768.                 $a = $to->identifier;
  769.                 $b = $this->identifier;
  770.  
  771.                 if (substr($a,0,1) == '/') $a = substr($a,1);
  772.                 if (substr($b,0,1) == '/') $b = substr($b,1);
  773.  
  774.                 $ary = explode_with_escaping('/', $a);
  775.                 $bry = explode_with_escaping('/', $b);
  776.  
  777.                 $min_len = min(count($ary), count($bry));
  778.  
  779.                 for ($i=0; $i<$min_len; $i++) {
  780.                         if ($ary[$i] != $bry[$i]) return null;
  781.                 }
  782.  
  783.                 return count($ary) - count($bry);
  784.         }
  785.  
  786.         /**
  787.          * @return OIDplusAltId[]
  788.          * @throws OIDplusException
  789.          */
  790.         public function getAltIds(): array {
  791.                 if ($this->isRoot()) return array();
  792.                 $ids = parent::getAltIds();
  793.  
  794.                 // Note: The payload for the namebased UUID can be binary (DER) oder text-based. But there is no definition about the format of the text (LDAP or DCE notation; with or without whitespace, etc.)
  795.                 $ids[] = new OIDplusAltId('guid', gen_uuid_md5_namebased(UUID_NAMEBASED_NS_X500_DN, $this->nodeId(false)), _L('Name based version 3 / MD5 UUID with namespace %1','UUID_NAMEBASED_NS_X500_DN'));
  796.                 $ids[] = new OIDplusAltId('guid', gen_uuid_sha1_namebased(UUID_NAMEBASED_NS_X500_DN, $this->nodeId(false)), _L('Name based version 5 / SHA1 UUID with namespace %1','UUID_NAMEBASED_NS_X500_DN'));
  797.  
  798.                 return $ids;
  799.         }
  800.  
  801.         /**
  802.          * @return string
  803.          */
  804.         public function getDirectoryName(): string {
  805.                 if ($this->isRoot()) return $this->ns();
  806.                 return $this->ns().'_'.md5($this->nodeId(false));
  807.         }
  808.  
  809.         /**
  810.          * @param string $mode
  811.          * @return string
  812.          */
  813.         public static function treeIconFilename(string $mode): string {
  814.                 return 'img/'.$mode.'_icon16.png';
  815.         }
  816. }
  817.