⚝
One Hat Cyber Team
⚝
Your IP:
216.73.216.78
Server IP:
41.128.143.86
Server:
Linux host.raqmix.cloud 6.8.0-1025-azure #30~22.04.1-Ubuntu SMP Wed Mar 12 15:28:20 UTC 2025 x86_64
Server Software:
Apache
PHP Version:
8.3.23
Buat File
|
Buat Folder
Eksekusi
Dir :
~
/
usr
/
share
/
psa-pear
/
pear
/
php
/
Horde
/
Ldap
/
Edit File: Util.php
<?php /** * Utility Class for Horde_Ldap * * This class servers some functionality to the other classes of Horde_Ldap but * most of the methods can be used separately as well. * * Copyright 2009 Benedikt Hallinger * Copyright 2010-2017 Horde LLC (http://www.horde.org/) * * @category Horde * @package Ldap * @author Benedikt Hallinger <beni@php.net> * @author Jan Schneider <jan@horde.org> * @license http://www.gnu.org/licenses/lgpl-3.0.html LGPL-3.0 */ class Horde_Ldap_Util { /** * Explodes the given DN into its elements * * {@link http://www.ietf.org/rfc/rfc2253.txt RFC 2253} says, a * Distinguished Name is a sequence of Relative Distinguished Names (RDNs), * which themselves are sets of Attributes. For each RDN a array is * constructed where the RDN part is stored. * * For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded * to: * <code> * array(array('OU=Sales', 'CN=J. Smith'), * 'DC=example', * 'DC=net') * </code> * * [NOT IMPLEMENTED] DNs might also contain values, which are the bytes of * the BER encoding of the X.500 AttributeValue rather than some LDAP * string syntax. These values are hex-encoded and prefixed with a #. To * distinguish such BER values, explodeDN uses references to the * actual values, e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is * exploded to: * <code> * array(array('1.3.6.1.4.1.1466.0' => "\004\002Hi"), * array('DC' => 'example', * array('DC' => 'com')) * <code> * See {@link http://www.vijaymukhi.com/vmis/berldap.htm} for more * information on BER. * * It also performs the following operations on the given DN: * - Unescape "\" followed by ",", "+", """, "\", "<", ">", ";", "#", "=", * " ", or a hexpair and strings beginning with "#". * - Removes the leading 'OID.' characters if the type is an OID instead of * a name. * - If an RDN contains multiple parts, the parts are re-ordered so that * the attribute type names are in alphabetical order. * * $options is a list of name/value pairs, valid options are: * - casefold: Controls case folding of attribute types names. * Attribute values are not affected by this option. * The default is to uppercase. Valid values are: * - lower: Lowercase attribute types names. * - upper: Uppercase attribute type names. This is the * default. * - none: Do not change attribute type names. * - reverse: If true, the RDN sequence is reversed. * - onlyvalues: If true, then only attributes values are returned ('foo' * instead of 'cn=foo') * * @todo implement BER * @todo replace preg_replace() callbacks. * * @param string $dn The DN that should be exploded. * @param array $options Options to use. * * @return array Parts of the exploded DN. */ public static function explodeDN($dn, array $options = array()) { $options = array_merge( array( 'casefold' => 'upper', 'onlyvalues' => false, 'reverse' => false, ), $options ); // Escaping of DN and stripping of "OID.". $dn = self::canonicalDN($dn, array('casefold' => $options['casefold'])); // Splitting the DN. $dn_array = preg_split('/(?<!\\\\),/', $dn); // Clear wrong splitting (possibly we have split too much). // Not clear, if this is neccessary here: //$dn_array = self::_correctDNSplitting($dn_array, ','); $callback_upper = function($value) { return Horde_String::upper($value[1]); }; $callback_lower = function($value) { return Horde_String::lower($value[1]); }; // Construct subarrays for multivalued RDNs and unescape DN value, also // convert to output format and apply casefolding. foreach ($dn_array as $key => $value) { $value_u = self::unescapeDNValue($value); $rdns = self::splitRDNMultivalue($value_u[0]); // TODO: nuke code duplication if (count($rdns) > 1) { // Multivalued RDN! foreach ($rdns as $subrdn_k => $subrdn_v) { // Casefolding. if ($options['casefold'] == 'upper') { $subrdn_v = preg_replace_callback('/^(\w+=)/', $callback_upper, $subrdn_v); } if ($options['casefold'] == 'lower') { $subrdn_v = preg_replace_callback('/^(\w+=)/', $callback_lower, $subrdn_v); } if ($options['onlyvalues']) { preg_match('/(.+?)(?<!\\\\)=(.+)/', $subrdn_v, $matches); $rdn_val = $matches[2]; $unescaped = self::unescapeDNValue($rdn_val); $rdns[$subrdn_k] = $unescaped[0]; } else { $unescaped = self::unescapeDNValue($subrdn_v); $rdns[$subrdn_k] = $unescaped[0]; } } $dn_array[$key] = $rdns; } else { // Singlevalued RDN. // Casefolding. if ($options['casefold'] == 'upper') { $value = preg_replace_callback('/^(\w+=)/', $callback_upper, $value); } if ($options['casefold'] == 'lower') { $value = preg_replace_callback('/^(\w+=)/', $callback_lower, $value); } if ($options['onlyvalues']) { preg_match('/(.+?)(?<!\\\\)=(.+)/', $value, $matches); $dn_val = $matches[2]; $unescaped = self::unescapeDNValue($dn_val); $dn_array[$key] = $unescaped[0]; } else { $unescaped = self::unescapeDNValue($value); $dn_array[$key] = $unescaped[0]; } } } if ($options['reverse']) { return array_reverse($dn_array); } return $dn_array; } /** * Escapes DN values according to RFC 2253. * * Escapes the given VALUES according to RFC 2253 so that they can be * safely used in LDAP DNs. The characters ",", "+", """, "\", "<", ">", * ";", "#", "=" with a special meaning in RFC 2252 are preceeded by ba * backslash. Control characters with an ASCII code < 32 are represented as * \hexpair. Finally all leading and trailing spaces are converted to * sequences of \20. * * @param string|array $values DN values that should be escaped. * * @return array The escaped values. */ public static function escapeDNValue($values) { // Parameter validation. if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // Escaping of filter meta characters. $val = addcslashes($val, '\\,+"<>;#='); // ASCII < 32 escaping. $val = self::asc2hex32($val); // Convert all leading and trailing spaces to sequences of \20. if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) { $val = str_repeat('\20', strlen($matches[1])) . $matches[2] . str_repeat('\20', strlen($matches[3])); } if (null === $val) { // Apply escaped "null" if string is empty. $val = '\0'; } $values[$key] = $val; } return $values; } /** * Unescapes DN values according to RFC 2253. * * Reverts the conversion done by escapeDNValue(). * * Any escape sequence starting with a baskslash - hexpair or special * character - will be transformed back to the corresponding character. * * @param array $values DN values. * * @return array Unescaped DN values. */ public static function unescapeDNValue($values) { // Parameter validation. if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // Strip slashes from special chars. $val = str_replace( array('\\\\', '\,', '\+', '\"', '\<', '\>', '\;', '\#', '\='), array('\\', ',', '+', '"', '<', '>', ';', '#', '='), $val); // Translate hex code into ascii. $values[$key] = self::hex2asc($val); } return $values; } /** * Converts a DN into a canonical form. * * DN can either be a string or an array as returned by explodeDN(), * which is useful when constructing a DN. The DN array may have be * indexed (each array value is a OCL=VALUE pair) or associative (array key * is OCL and value is VALUE). * * It performs the following operations on the given DN: * - Removes the leading 'OID.' characters if the type is an OID instead of * a name. * - Escapes all RFC 2253 special characters (",", "+", """, "\", "<", ">", * ";", "#", "="), slashes ("/"), and any other character where the ASCII * code is < 32 as \hexpair. * - Converts all leading and trailing spaces in values to be \20. * - If an RDN contains multiple parts, the parts are re-ordered so that * the attribute type names are in alphabetical order. * * $options is a list of name/value pairs, valid options are: * * - casefold: Controls case folding of attribute type names. Attribute * values are not affected by this option. The default is to * uppercase. Valid values are: * - lower: Lowercase attribute type names. * - upper: Uppercase attribute type names. * - none: Do not change attribute type names. * - reverse: If true, the RDN sequence is reversed. * - separator: Separator to use between RDNs. Defaults to comma (','). * * The empty string "" is a valid DN, so be sure not to do a "$can_dn == * false" test, because an empty string evaluates to false. Use the "===" * operator instead. * * @param array|string $dn The DN. * @param array $options Options to use. * * @return boolean|string The canonical DN or false if the DN is not valid. */ public static function canonicalDN($dn, array $options = array()) { if ($dn === '') { // Empty DN is valid. return $dn; } // Options check. $options = array_merge( array( 'casefold' => 'upper', 'reverse' => false, 'separator' => ',', ), $options ); if (!is_array($dn)) { // It is not clear to me if the perl implementation splits by the // user defined separator or if it just uses this separator to // construct the new DN. $dn = preg_split('/(?<!\\\\)' . preg_quote($options['separator']) . '/', $dn); // Clear wrong splitting (possibly we have split too much). $dn = self::_correctDNSplitting($dn, $options['separator']); } else { // Is array, check if the array is indexed or associative. $assoc = false; foreach ($dn as $dn_key => $dn_part) { if (!is_int($dn_key)) { $assoc = true; break; } } // Convert to indexed, if associative array detected. if ($assoc) { $newdn = array(); foreach ($dn as $dn_key => $dn_part) { if (is_array($dn_part)) { // We assume here that the RDN parts are also // associative. ksort($dn_part, SORT_STRING); // Copy array as-is, so we can resolve it later. $newdn[] = $dn_part; } else { $newdn[] = $dn_key . '=' . $dn_part; } } $dn =& $newdn; } } // Escaping and casefolding. foreach ($dn as $pos => $dnval) { if (is_array($dnval)) { // Subarray detected, this means most probably that we had a // multivalued DN part, which must be resolved. $dnval_new = ''; foreach ($dnval as $subkey => $subval) { // Build RDN part. if (!is_int($subkey)) { $subval = $subkey . '=' . $subval; } $subval_processed = self::canonicalDN($subval, $options); if (false === $subval_processed) { return false; } $dnval_new .= $subval_processed . '+'; } // Store RDN part, strip last plus. $dn[$pos] = substr($dnval_new, 0, -1); } else { // Try to split multivalued RDNs into array. $rdns = self::splitRDNMultivalue($dnval); if (count($rdns) > 1) { // Multivalued RDN was detected. The RDN value is expected // to be correctly split by splitRDNMultivalue(). It's time // to sort the RDN and build the DN. $rdn_string = ''; // Sort RDN keys alphabetically. sort($rdns, SORT_STRING); foreach ($rdns as $rdn) { $subval_processed = self::canonicalDN($rdn, $options); if (false === $subval_processed) { return false; } $rdn_string .= $subval_processed . '+'; } // Store RDN part, strip last plus. $dn[$pos] = substr($rdn_string, 0, -1); } else { // No multivalued RDN. Split at first unescaped "=". $dn_comp = self::splitAttributeString($rdns[0]); if (count($dn_comp) != 2) { throw new Horde_Ldap_Exception('Invalid RDN: ' . $rdns[0]); } // Trim left whitespaces because of "cn=foo, l=bar" syntax // (whitespace after comma). $ocl = ltrim($dn_comp[0]); $val = $dn_comp[1]; // Strip 'OID.', otherwise apply casefolding and escaping. if (substr(Horde_String::lower($ocl), 0, 4) == 'oid.') { $ocl = substr($ocl, 4); } else { if ($options['casefold'] == 'upper') { $ocl = Horde_String::upper($ocl); } if ($options['casefold'] == 'lower') { $ocl = Horde_String::lower($ocl); } $ocl = self::escapeDNValue(array($ocl)); $ocl = $ocl[0]; } // Escaping of DN value. // TODO: if the value is already correctly escaped, we get // double escaping. $val = self::escapeDNValue(array($val)); $val = str_replace('/', '\/', $val[0]); $dn[$pos] = $ocl . '=' . $val; } } } if ($options['reverse']) { $dn = array_reverse($dn); } return implode($options['separator'], $dn); } /** * Escapes the given values according to RFC 2254 so that they can be * safely used in LDAP filters. * * Any control characters with an ACII code < 32 as well as the characters * with special meaning in LDAP filters "*", "(", ")", and "\" (the * backslash) are converted into the representation of a backslash followed * by two hex digits representing the hexadecimal value of the character. * * @param array $values Values to escape. * * @return array Escaped values. */ public static function escapeFilterValue($values) { // Parameter validation. if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // Escaping of filter meta characters. $val = str_replace(array('\\', '*', '(', ')'), array('\5c', '\2a', '\28', '\29'), $val); // ASCII < 32 escaping. $val = self::asc2hex32($val); if (null === $val) { // Apply escaped "null" if string is empty. $val = '\0'; } $values[$key] = $val; } return $values; } /** * Unescapes the given values according to RFC 2254. * * Reverses the conversion done by {@link escapeFilterValue()}. * * Converts any sequences of a backslash followed by two hex digits into * the corresponding character. * * @param array $values Values to unescape. * * @return array Unescaped values. */ public static function unescapeFilterValue($values = array()) { // Parameter validation. if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $value) { // Translate hex code into ascii. $values[$key] = self::hex2asc($value); } return $values; } /** * Converts all ASCII chars < 32 to "\HEX". * * @param string $string String to convert. * * @return string Hexadecimal representation of $string. */ public static function asc2hex32($string) { for ($i = 0, $len = strlen($string); $i < $len; $i++) { $char = substr($string, $i, 1); if (ord($char) < 32) { $hex = dechex(ord($char)); if (strlen($hex) == 1) { $hex = '0' . $hex; } $string = str_replace($char, '\\' . $hex, $string); } } return $string; } /** * Converts all hexadecimal expressions ("\HEX") to their original ASCII * characters. * * @author beni@php.net, heavily based on work from DavidSmith@byu.net * * @param string $string String to convert. * * @return string ASCII representation of $string. */ public static function hex2asc($string) { return preg_replace_callback( '/\\\([0-9A-Fa-f]{2})/', function($hex) { return chr(hexdec($hex[1])); }, $string); } /** * Splits a multivalued RDN value into an array. * * A RDN can contain multiple values, spearated by a plus sign. This method * returns each separate ocl=value pair of the RDN part. * * If no multivalued RDN is detected, an array containing only the original * RDN part is returned. * * For example, the multivalued RDN 'OU=Sales+CN=J. Smith' is exploded to: * <kbd>array([0] => 'OU=Sales', [1] => 'CN=J. Smith')</kbd> * * The method tries to be smart if it encounters unescaped "+" characters, * but may fail, so better ensure escaped "+" in attribute names and * values. * * [BUG] If you have a multivalued RDN with unescaped plus characters and * there is a unescaped plus sign at the end of an value followed by * an attribute name containing an unescaped plus, then you will get * wrong splitting: * $rdn = 'OU=Sales+C+N=J. Smith'; * returns: * array('OU=Sales+C', 'N=J. Smith'); * The "C+" is treaten as the value of the first pair instead of as * the attribute name of the second pair. To prevent this, escape * correctly. * * @param string $rdn Part of a (multivalued) escaped RDN (e.g. ou=foo or * ou=foo+cn=bar) * * @return array The components of the multivalued RDN. */ public static function splitRDNMultivalue($rdn) { $rdns = preg_split('/(?<!\\\\)\+/', $rdn); $rdns = self::_correctDNSplitting($rdns, '+'); return array_values($rdns); } /** * Splits a attribute=value syntax into an array. * * The split will occur at the first unescaped '=' character. * * @param string $attr An attribute-value string. * * @return array Indexed array: 0=attribute name, 1=attribute value. */ public static function splitAttributeString($attr) { return preg_split('/(?<!\\\\)=/', $attr, 2); } /** * Corrects splitting of DN parts. * * @param array $dn Raw DN array. * @param array $separator Separator that was used when splitting. * * @return array Corrected array. */ protected static function _correctDNSplitting($dn = array(), $separator = ',') { foreach ($dn as $key => $dn_value) { // Refresh value (foreach caches!) $dn_value = $dn[$key]; // If $dn_value is not in attr=value format, we had an unescaped // separator character inside the attr name or the value. We assume // that it was the attribute value. // TODO: To solve this, we might ask the schema. The // Horde_Ldap_Util class must remain independent from the // other classes or connections though. if (!preg_match('/.+(?<!\\\\)=.+/', $dn_value)) { unset($dn[$key]); if (array_key_exists($key - 1, $dn)) { // Append to previous attribute value. $dn[$key - 1] = $dn[$key - 1] . $separator . $dn_value; } elseif (array_key_exists($key + 1, $dn)) { // First element: prepend to next attribute name. $dn[$key + 1] = $dn_value . $separator . $dn[$key + 1]; } else { $dn[$key] = $dn_value; } } } return array_values($dn); } }
Simpan