Hү)=¡uiK>grmcHLу\T cMPPdYK-״$gW.a63؟*p3QfA!D>Fͦ. }(pEt5^'ȇ\K_ ;zrk)1B2U%n\uK:$2wMdۥ0e G۲]EQͰk QV񌞷> |NY*@G-S)GmˇRTsNQQ_r(MD/i\*d鱎8p3sV'qΔnt'խ鉝۵2[.څ4BY@A_z\Geqݨi6F' -LX2@(̡*?LeWձ%Dy`^<"6.VhO 4$ eW3M‘H Wӆ3W$`ykl@pbszC6 a<"k3gc]ނɱ[ujۢi5n6ű~J05 U QTg D_LO:{G6KO"H>6Q4m1Φ&S͹{]q;h> *>>]J1(Eyhú6ʶB"ʛ+9 7LnyliG=oyp$e(2qgBQVrY&|ڡ!tYtj\Nr#JnK-LlOΊ_:+X4X&C6,V.&!.r<\RW 1YI-)"\63uZ=#YkFuuz?͈!ۑFV+|\NFu;[.%MT5M` Әy8 ӝML e K 0i 6¯7[g*J:>auD_qUL~b*˴i.`3 Yi+CEC} OpqEzeImh%b6(ꀝf ,4'Mt//|ǔdO؅Y{6n15U4hj#n# ں)|8t ,2_u:096JՄ^F -Eg)eBH"WmN@7gssyBK/5>"N uG}JBd FЪF*B'cj[jBxNz* O]-MTq,% M{n܀27s}i,EO֐*4FV~W(h dS)Vɦs#dp⻁TqTyPkWqe^Y΋D1}H0~=8UF?/ d: hc7a7I}C٭=c(A_Bn%*w;"&JlfWq~J]4aX!x(Gp_xIK|!7[{MSmrCp[Y5h{EOW,CVK4h.F#8GʡTeEф30IyXp}p)~v,Xƈ#@ q;wĊ9`)]qYHUd9;jS?{HL[`e"^VWja8Bz\ټ]X:Wten1E2UkNy$ﬞӿ>T;"Wɵ}JZx!%ShBj!ߣi|RCZx[M2$4{ɲ^uw$֛H|DZ BOQw[Sq~VAVY_7[AtJc .Bjk+>Q1rgP[/|[.ŕ|Ks a23>$EGu2C.R.7X ,/8Oρ>ORYڲr3[ thC- ~_Keǒm:Y Vʅ~"pFLy|2\@ h bcY+lTJ+wfs'5t|Y>1YS-`?8&Gh5M0rk8ޘwkQ?Gv~)_51\ip@aҩ{lKͥll3 ssbc3bl갓yC,GӆCeZ-:3na)y}wws@,Mt窓Kk2-d&tOnt|Z揞6LK!IJipߔruG2²rd/Bڥ f4s{zO2ʼn>y x0mȿs\k{rV"|60dzXl?Hos):ޭІCi%]|}ҿ(ApQ+>WIUPN7?@;$R04b9Ւ&ڶ> 6.9419dĢk 9RcL}қC9Juݼw׮TuIi`O;]khEʹUk[+KTGjqdJG>.r=O&!5^Mc8ϨJ1#mͧ3Z^-}֦U,xK=|xcзkDBʀ_3q <}-`O:.- GMvTb۰[I09+ȶeH{| Xs ΕA0ð~MCg'8k{rV"|60[6Hব~T]牐4w,24P"ibU W~_OKau6"i?X&`p%t-=ΗF.Ͽ%/9SE=nٖ 5m]İ*9MYqi$h%t̡Dn%#?RM FZ*W63!'SNJ 1e簦fh[D=hthADғ0efcOسd|Hw/02TJRNy [dps'W\jKOj*4T#qdb//kH `/6W"6)msgcƂnR52>{|xHyuFwWɧ8sf |z˦ǒp g.{^2HB/.Hʿ&N0Fb#+b&ot$w] 5P1_{,Z>1UE!ʍȀNyN$*>.9 $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::correct_dn_splitting($dn_array, ','); // 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::unescape_dn_value($value); $rdns = self::split_rdn_multival($value_u[0]); if (count($rdns) > 1) { // MV RDN! foreach ($rdns as $subrdn_k => $subrdn_v) { // Casefolding if ($options['casefold'] == 'upper') { $subrdn_v = preg_replace_callback( "/^\w+=/", function ($matches) { return strtoupper($matches[0]); }, $subrdn_v ); } else if ($options['casefold'] == 'lower') { $subrdn_v = preg_replace_callback( "/^\w+=/", function ($matches) { return strtolower($matches[0]); }, $subrdn_v ); } if ($options['onlyvalues']) { preg_match('/(.+?)(?", ";", "#", "=" 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 array $values An array containing the DN values that should be escaped * * @static * @return array The array $values, but escaped */ public static function escape_dn_value($values = array()) { // Parameter validation if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // Escaping of filter meta characters $val = str_replace('\\', '\\\\', $val); $val = str_replace(',', '\,', $val); $val = str_replace('+', '\+', $val); $val = str_replace('"', '\"', $val); $val = str_replace('<', '\<', $val); $val = str_replace('>', '\>', $val); $val = str_replace(';', '\;', $val); $val = str_replace('#', '\#', $val); $val = str_replace('=', '\=', $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 = $matches[2]; for ($i = 0; $i < strlen($matches[1]); $i++) { $val = '\20'.$val; } for ($i = 0; $i < strlen($matches[3]); $i++) { $val = $val.'\20'; } } if (null === $val) $val = '\0'; // apply escaped "null" if string is empty $values[$key] = $val; } return $values; } /** * Undoes the conversion done by escape_dn_value(). * * Any escape sequence starting with a baskslash - hexpair or special character - * will be transformed back to the corresponding character. * * @param array $values Array of DN Values * * @return array Same as $values, but unescaped * @static */ public static function unescape_dn_value($values = array()) { // Parameter validation if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // strip slashes from special chars $val = str_replace('\\\\', '\\', $val); $val = str_replace('\,', ',', $val); $val = str_replace('\+', '+', $val); $val = str_replace('\"', '"', $val); $val = str_replace('\<', '<', $val); $val = str_replace('\>', '>', $val); $val = str_replace('\;', ';', $val); $val = str_replace('\#', '#', $val); $val = str_replace('\=', '=', $val); // Translate hex code into ascii $values[$key] = self::hex2asc($val); } return $values; } /** * Returns the given DN in a canonical form * * Returns false if DN is not a valid Distinguished Name. * DN can either be a string or an array * as returned by ldap_explode_dn, 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. This is the default. * none Do not change attribute type names. * [NOT IMPLEMENTED] mbcescape If TRUE, characters that are encoded as a multi-octet UTF-8 sequence will be escaped as \(hexpair){2,*}. * reverse If TRUE, the RDN sequence is reversed. * separator Separator to use between RDNs. Defaults to comma (','). * * Note: 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 * * @static * @return false|string The canonical DN or FALSE * @todo implement option mbcescape */ public static function canonical_dn($dn, $options = array('casefold' => 'upper', 'separator' => ',')) { if ($dn === '') return $dn; // empty DN is valid! // options check if (!isset($options['reverse'])) { $options['reverse'] = false; } else { $options['reverse'] = true; } if (!isset($options['casefold'])) $options['casefold'] = 'upper'; if (!isset($options['separator'])) $options['separator'] = ','; 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('/(?<=[^\\\\])'.$options['separator'].'/', $dn); // clear wrong splitting (possibly we have split too much) $dn = self::correct_dn_splitting($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; } } // convert to indexed, if associative array detected if ($assoc) { $newdn = array(); foreach ($dn as $dn_key => $dn_part) { if (is_array($dn_part)) { ksort($dn_part, SORT_STRING); // we assume here, that the rdn parts are also associative $newdn[] = $dn_part; // copy array as-is, so we can resolve it later } else { $newdn[] = $dn_key.'='.$dn_part; } } $dn =& $newdn; } } // Escaping and casefolding foreach ($dn as $pos => $dnval) { if (is_array($dnval)) { // subarray detected, this means very surely, 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::canonical_dn($subval); if (false === $subval_processed) return false; $dnval_new .= $subval_processed.'+'; } $dn[$pos] = substr($dnval_new, 0, -1); // store RDN part, strip last plus } else { // try to split multivalued RDNS into array $rdns = self::split_rdn_multival($dnval); if (count($rdns) > 1) { // Multivalued RDN was detected! // The RDN value is expected to be correctly split by split_rdn_multival(). // It's time to sort the RDN and build the DN! $rdn_string = ''; sort($rdns, SORT_STRING); // Sort RDN keys alphabetically foreach ($rdns as $rdn) { $subval_processed = self::canonical_dn($rdn); if (false === $subval_processed) return false; $rdn_string .= $subval_processed.'+'; } $dn[$pos] = substr($rdn_string, 0, -1); // store RDN part, strip last plus } else { // no multivalued RDN! // split at first unescaped "=" $dn_comp = preg_split('/(?<=[^\\\\])=/', $rdns[0], 2); $ocl = ltrim($dn_comp[0]); // trim left whitespaces 'cause of "cn=foo, l=bar" syntax (whitespace after comma) $val = $dn_comp[1]; // strip 'OID.', otherwise apply casefolding and escaping if (substr(strtolower($ocl), 0, 4) == 'oid.') { $ocl = substr($ocl, 4); } else { if ($options['casefold'] == 'upper') $ocl = strtoupper($ocl); if ($options['casefold'] == 'lower') $ocl = strtolower($ocl); $ocl = self::escape_dn_value(array($ocl)); $ocl = $ocl[0]; } // escaping of dn-value $val = self::escape_dn_value(array($val)); $val = str_replace('/', '\\2f', $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 Array of values to escape * * @static * @return array Array $values, but escaped */ public static function escape_filter_value($values = array()) { // Parameter validation if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // Escaping of filter meta characters $val = str_replace('\\', '\5c', $val); $val = str_replace('*', '\2a', $val); $val = str_replace('(', '\28', $val); $val = str_replace(')', '\29', $val); // ASCII < 32 escaping $val = self::asc2hex32($val); if (null === $val) $val = '\0'; // apply escaped "null" if string is empty $values[$key] = $val; } return $values; } /** * Undoes the conversion done by {@link escape_filter_value()}. * * Converts any sequences of a backslash followed by two hex digits into the corresponding character. * * @param array $values Array of values to escape * * @static * @return array Array $values, but unescaped */ public static function unescape_filter_value($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 * * @static * @return string */ public static function asc2hex32($string) { for ($i = 0; $i < strlen($string); $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 Hex expressions ("\HEX") to their original ASCII characters * * @param string $string String to convert * * @static * @author beni@php.net, heavily based on work from DavidSmith@byu.net * @return string */ public static function hex2asc($string) { $string = preg_replace_callback( "/\\\[0-9A-Fa-f]{2}/", function ($matches) { return chr(hexdec($matches[0])); }, $string ); return $string; } /** * Split an multivalued RDN value into an Array * * A RDN can contain multiple values, spearated by a plus sign. * This function 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: * array([0] => 'OU=Sales', [1] => 'CN=J. Smith') * * The method trys to be smart if it encounters unescaped "+" characters, but may fail, * so ensure escaped "+"es in attr names and attr 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 value of the first pair instead as attr name of the second pair. * To prevent this, escape correctly. * * @param string $rdn Part of an (multivalued) escaped RDN (eg. ou=foo OR ou=foo+cn=bar) * * @static * @return array Array with the components of the multivalued RDN or Error */ public static function split_rdn_multival($rdn) { $rdns = preg_split('/(?, <, >=, <=, ~=). * * @param string $attr Attribute and Value Syntax ("foo=bar") * @param boolean $extended If set to true, also filter-assertion delimeter will be matched * @param boolean $withDelim If set to true, the return array contains the delimeter at index 1, putting the value to index 2 * * @return array Indexed array: 0=attribute name, 1=attribute value OR ($withDelim=true): 0=attr, 1=delimeter, 2=value */ public static function split_attribute_string($attr, $extended=false, $withDelim=false) { if ($withDelim) $withDelim = PREG_SPLIT_DELIM_CAPTURE; if (!$extended) { return preg_split('/(?=|<=|>|<|~=|=)/', $attr, 2, $withDelim); } } /** * Corrects splitting of dn parts * * @param array $dn Raw DN array * @param array $separator Separator that was used when splitting * * @return array Corrected array * @access protected */ protected static function correct_dn_splitting($dn = array(), $separator = ',') { foreach ($dn as $key => $dn_value) { $dn_value = $dn[$key]; // refresh value (foreach caches!) // if the dn_value is not in attr=value format, then 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. Keep in mind, that UTIL class // must remain independent from the other classes or connections. if (!preg_match('/.+(?