ldap - PHP strpos on string returned from ldap_search -
ok, here's problem:
i search userparameters attribute ldap_search. needed couple of values out, being "ctxwfhomedirdrive", "ctxwfhomedir" , "ctxwfprofilepath"
the string got complete jibberish, after entire day of trying out every single character encoding conversion find, 1 did half trick:
$puserparams = iconv('utf-8', 'utf-32', $entry_value) for reason individual hex numbers following 3 values needed extract inversed (so 5c, backslash, came out c5. don't ask how figured 1 out :-p )
knowing this, convert hex values display actual citrix homedirdrive, profilepath, etc.
however, still need filter out lot of unneeded characters final result string, following returns false reason:
if (strpos($puserparams,"ctxwfprofilepath") !== false) {} when echo $puserparams variable, display whole string above 3 ctx parameters in it.
i suspected must have special characters in result string, tried removing line breaks, eol's, unserializing string (which produces error @ offset 0), etc etc
nothing seems work... have idea?
thanks, vincent
original string run through hex2bin:
20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202050071a080143747843666750726573656e74e394b5e694b1e688b0e381a2180801437478436667466c61677331e380b0e381a6e380b2e380b9120801437478536861646f77e384b0e380b0e380b0e380b02a02014374784d696e456e6372797074696f6e4c6576656ce384b02206014374785746486f6d654469724472697665e3a0b4e684b3e380b018c282014374785746486f6d65446972e68cb5e68cb5e394b7e38cb7e39cb6e388b6e394b6e398b6e3a4b6e68cb6e394b6e380b3e380b3e384b3e694b2e394b7e38cb7e39cb6e380b7e394b6e698b6e380b7e68cb6e394b6e388b6e394b6e694b2e3a4b6e694b6e390b7e68cb5e394b7e38cb7e394b6e388b7e3a0b6e698b6e690b6e394b6e390b6e388b7e3a4b6e398b7e394b6e390b2e68cb5e38cb7e390b7e3a4b6e684b6e694b6e694b2e688b6e698b6e688b6e688b6e394b6e68cb6e394b6e694b6e388b6e394b6e388b7e39cb6e380b020c28001437478574650726f66696c6550617468e68cb5e68cb5e384b6e38cb7e380b7e694b6e394b6e390b7e384b6e380b7e380b7e380b3e380b3e384b3e694b2e384b6e38cb7e380b7e694b2e3a4b6e694b6e390b7e68cb5e380b7e388b7e698b6e398b6e3a4b6e68cb6e394b6e38cb7e698b5e388b6e394b6e68cb6e39cb6e3a4b6e394b7e690b6e390b2e68cb5e38cb5e38cb5e38cb4e68cb5e38cb7e390b7e3a4b6e684b6e694b6e694b2e688b6e698b6e688b6e688b6e394b6e68cb6e394b6e694b6e388b6e394b6e388b7e39cb6e380b0e380b0 ��
to build on work done tenzian, ended creating few self-contained classes can used safely decode/encode userparameters blob extract , modify/view/create ts properties. make few assumptions:
- php 5.6+ (feel free edit suit needs/work lower version)
- assumes of classes defined in same namespace.
- for multi-byte string support have mbstring extension loaded.
- any ts property time represented in terms of minutes.
anyway, these classes need:
tspropertyarray
/** * represents tspropertyarray data contains individual tsproperty structures in userparameters value. * * @see https://msdn.microsoft.com/en-us/library/ff635189.aspx * @author chad sikorra <chad.sikorra@gmail.com> */ class tspropertyarray { /** * represents tspropertyarray data valid. */ const valid_signature = 'p'; /** * @var array default values tspropertyarray structure. */ const defaults = [ 'ctxcfgpresent' => 2953518677, 'ctxwfprofilepath' => '', 'ctxwfprofilepathw' => '', 'ctxwfhomedir' => '', 'ctxwfhomedirw' => '', 'ctxwfhomedirdrive' => '', 'ctxwfhomedirdrivew' => '', 'ctxshadow' => 1, 'ctxmaxdisconnectiontime' => 0, 'ctxmaxconnectiontime' => 0, 'ctxmaxidletime' => 0, 'ctxworkdirectory' => '', 'ctxworkdirectoryw' => '', 'ctxcfgflags1' => 2418077696, 'ctxinitialprogram' => '', 'ctxinitialprogramw' => '', ]; /** * @var string default data occurs before tspropertyarray (ctxcfgpresent bunch of spaces...?) */ protected $defaultprebinary = '43747843666750726573656e742020202020202020202020202020202020202020202020202020202020202020202020'; /** * @var tsproperty[] */ protected $tsproperty = []; /** * @var string */ protected $signature = self::valid_signature; /** * @var string binary data occurs before tspropertyarray data in userparameters. */ protected $prebinary = ''; /** * @var string binary data occurs after tspropertyarray data in userparameters. */ protected $postbinary = ''; /** * construct in 1 of following ways: * * - pass array of tsproperty key => value pairs (see defaults constant). * - pass userparameters binary value. object representation of decoded , constructed. * - pass nothing , default set of tsproperty key => value pairs used (see defaults constant). * * @param mixed $tspropertyarray */ public function __construct($tspropertyarray = null) { $this->prebinary = hex2bin($this->defaultprebinary); if (is_null($tspropertyarray) || is_array($tspropertyarray)) { $tspropertyarray = $tspropertyarray ?: self::defaults; foreach ($tspropertyarray $key => $value) { $tsproperty = new tsproperty(); $tsproperty->setname($key); $tsproperty->setvalue($value); $this->tsproperty[$key] = $tsproperty; } } else { $this->decodeuserparameters($tspropertyarray); } } /** * check if specific tsproperty exists property name. * * @param string $propname * @return bool */ public function has($propname) { return array_key_exists(strtolower($propname), array_change_key_case($this->tsproperty)); } /** * tsproperty object property name (ie. ctxwfprofilepath). * * @param string $propname * @return tsproperty */ public function get($propname) { $this->validateprop($propname); return $this->gettspropobj($propname)->getvalue(); } /** * add tsproperty object. if exists, overwritten. * * @param tsproperty $tsproperty * @return $this */ public function add(tsproperty $tsproperty) { $this->tsproperty[$tsproperty->getname()] = $tsproperty; return $this; } /** * remove tsproperty property name (ie. ctxminencryptionlevel). * * @param string $propname * @return $this */ public function remove($propname) { foreach (array_keys($this->tsproperty) $property) { if (strtolower($propname) == strtolower($property)) { unset($this->tsproperty[$property]); } } return $this; } /** * set value specific tsproperty name. * * @param string $propname * @param mixed $propvalue * @return $this */ public function set($propname, $propvalue) { $this->validateprop($propname); $this->gettspropobj($propname)->setvalue($propvalue); return $this; } /** * full binary representation of userparameters containing tspropertyarray data. * * @return string */ public function tobinary() { $binary = $this->prebinary; $binary .= hex2bin(str_pad(dechex(mbstring::ord($this->signature)), 2, 0, str_pad_left)); $binary .= hex2bin(str_pad(dechex(count($this->tsproperty)), 2, 0, str_pad_left)); foreach ($this->tsproperty $tsproperty) { $binary .= $tsproperty->tobinary(); } return $binary.$this->postbinary; } /** * simple associative array containing of tsproperty names , values. * * @return array */ public function toarray() { $userparameters = []; foreach ($this->tsproperty $property => $tspropobj) { $userparameters[$property] = $tspropobj->getvalue(); } return $userparameters; } /** * tsproperty objects. * * @return tsproperty[] */ public function gettsproperties() { return $this->tsproperty; } /** * @param string $propname */ protected function validateprop($propname) { if (!$this->has($propname)) { throw new \invalidargumentexception(sprintf('tsproperty "%s" not exist.', $propname)); } } /** * @param string $propname * @return tsproperty */ protected function gettspropobj($propname) { return array_change_key_case($this->tsproperty)[strtolower($propname)]; } /** * associative array of userparameters property names , values. * * @param string $userparameters * @return array */ protected function decodeuserparameters($userparameters) { $userparameters = bin2hex($userparameters); // save 96-byte array of reserved data, not ruin may stored there. $this->prebinary = hex2bin(substr($userparameters, 0, 96)); // signature 2-byte unicode character @ front $this->signature = mbstring::chr(hexdec(substr($userparameters, 96, 2))); // asserts validity of tspropertyarray data. reason 'p' means valid... if ($this->signature != self::valid_signature) { throw new \invalidargumentexception('invalid tspropertyarray data'); } // property count 2-byte unsigned integer indicating number of elements tspropertyarray // starts @ position 98. actual variable data begins @ position 100. $length = $this->addtspropdata(substr($userparameters, 100), hexdec(substr($userparameters, 98, 2))); // reserved data length + (count , sig length == 4) + added lengths of tspropertyarray // saves after variable tspropertyarray data, not squash stored there if (strlen($userparameters) > (96 + 4 + $length)) { $this->postbinary = hex2bin(substr($userparameters, (96 + 4 + $length))); } } /** * given start of tspropertyarray hex data, , count number of tsproperty structures in contains, * parse , split out individual tsproperty structures. return full length of tspropertyarray data. * * @param string $tspropertyarray * @param int $tspropcount * @return int length of data in tspropertyarray */ protected function addtspropdata($tspropertyarray, $tspropcount) { $length = 0; ($i = 0; $i < $tspropcount; $i++) { // prop length = name length + value length + type length + space length data. $proplength = hexdec(substr($tspropertyarray, $length, 2)) + (hexdec(substr($tspropertyarray, $length + 2, 2)) * 3) + 6; $tsproperty = new tsproperty(hex2bin(substr($tspropertyarray, $length, $proplength))); $this->tsproperty[$tsproperty->getname()] = $tsproperty; $length += $proplength; } return $length; } } tsproperty
/** * represents tsproperty structure in tspropertyarray of userparameters binary value. * * @see https://msdn.microsoft.com/en-us/library/ff635169.aspx * @see http://daduke.org/linux/userparameters.html * @author chad sikorra <chad.sikorra@gmail.com> */ class tsproperty { /** * nibble control values. first value each if nibble <= 9, otherwise second value used. */ const nibble_control = [ 'x' => ['001011', '011010'], 'y' => ['001110', '011010'], ]; /** * nibble header. */ const nibble_header = '1110'; /** * conversion factor needed time values in tspropertyarray (stored in microseconds). */ const time_conversion = 60 * 1000; /** * simple map determine how property needs decoded/encoded from/to binary value. * * there names simple repeats have 'w' @ end. not sure signifies. * cannot find information on them in microsoft documentation. however, values appear stay in sync * non 'w' counterparts. not doing when manipulating data manually not seem affect anything. * needs more investigation. * * @var array */ protected $proptypes = [ 'string' => [ 'ctxwfhomedir', 'ctxwfhomedirw', 'ctxwfhomedirdrive', 'ctxwfhomedirdrivew', 'ctxinitialprogram', 'ctxinitialprogramw', 'ctxwfprofilepath', 'ctxwfprofilepathw', 'ctxworkdirectory', 'ctxworkdirectoryw', 'ctxcallbacknumber', ], 'time' => [ 'ctxmaxdisconnectiontime', 'ctxmaxconnectiontime', 'ctxmaxidletime', ], 'int' => [ 'ctxcfgflags1', 'ctxcfgpresent', 'ctxkeyboardlayout', 'ctxminencryptionlevel', 'ctxnwlogonserver', 'ctxshadow', ], ]; /** * @var string property name. */ protected $name; /** * @var string|int property value. */ protected $value; /** * @var int property value type. */ protected $valuetype = 1; /** * @param string|null $value pass binary tsproperty data construct object representation. */ public function __construct($value = null) { if ($value) { $this->decode(bin2hex($value)); } } /** * set name tsproperty. * * @param string $name */ public function setname($name) { $this->name = $name; } /** * name tsproperty. * * @return string */ public function getname() { return $this->name; } /** * set value tsproperty. * * @param string|int $value */ public function setvalue($value) { $this->value = $value; } /** * value tsproperty. * * @return string|int */ public function getvalue() { return $this->value; } /** * convert tsproperty name/value binary representation userparameters blob. * * @return string */ public function tobinary() { $name = bin2hex($this->name); $binvalue = $this->getencodedvalueforprop($this->name, $this->value); $valuelen = strlen(bin2hex($binvalue)) / 3; $binary = hex2bin( $this->dec2hex(strlen($name)) .$this->dec2hex($valuelen) .$this->dec2hex($this->valuetype) .$name ); return $binary.$binvalue; } /** * given tsproperty blob, decode name/value/type/etc. * * @param string $tsproperty */ protected function decode($tsproperty) { $namelength = hexdec(substr($tsproperty, 0, 2)); # 1 data byte 3 encoded bytes $valuelength = hexdec(substr($tsproperty, 2, 2)) * 3; $this->valuetype = hexdec(substr($tsproperty, 4, 2)); $this->name = pack('h*', substr($tsproperty, 6, $namelength)); $this->value = $this->getdecodedvalueforprop($this->name, substr($tsproperty, 6 + $namelength, $valuelength)); } /** * based on property name/value in question, encoded form. * * @param string $propname * @param string|int $propvalue * @return string */ protected function getencodedvalueforprop($propname, $propvalue) { if (in_array($propname, $this->proptypes['string'])) { # simple strings null terminated. unsure if needed or product of how aduc stuff? $value = $this->encodepropvalue($propvalue."\0", true); } elseif (in_array($propname, $this->proptypes['time'])) { # needs in microseconds (assuming in minute format)... $value = $this->encodepropvalue($propvalue * self::time_conversion); } else { $value = $this->encodepropvalue($propvalue); } return $value; } /** * based on property name in question, actual value binary blob value. * * @param string $propname * @param string $propvalue * @return string|int */ protected function getdecodedvalueforprop($propname, $propvalue) { if (in_array($propname, $this->proptypes['string'])) { // strip away null terminators. think should desired, otherwise ends in confusion. $value = str_replace("\0", '', $this->decodepropvalue($propvalue, true)); } elseif (in_array($propname, $this->proptypes['time'])) { // convert microseconds minutes (how aduc displays anyway, , seems practical). $value = hexdec($this->decodepropvalue($propvalue)) / self::time_conversion; } elseif (in_array($propname, $this->proptypes['int'])) { $value = hexdec($this->decodepropvalue($propvalue)); } else { $value = $this->decodepropvalue($propvalue); } return $value; } /** * decode property inspecting nibbles of each blob, checking control, , adding results * final value. * * @param string $hex * @param bool $string whether or not simple string data. * @return string */ protected function decodepropvalue($hex, $string = false) { $decodepropvalue = ''; $blobs = str_split($hex, 6); foreach ($blobs $blob) { $bin = decbin(hexdec($blob)); $controly = substr($bin, 4, 6); $nibbley = substr($bin, 10, 4); $controlx = substr($bin, 14, 6); $nibblex = substr($bin, 20, 4); $byte = $this->nibblecontrol($nibblex, $controlx).$this->nibblecontrol($nibbley, $controly); if ($string) { $decodepropvalue .= mbstring::chr(bindec($byte)); } else { $decodepropvalue = $this->dec2hex(bindec($byte)).$decodepropvalue; } } return $decodepropvalue; } /** * encoded property value binary blob. * * @param string $value * @param bool $string * @return string */ protected function encodepropvalue($value, $string = false) { // int must padded. (then split , reversed). string, split chars. seems // easiest way handle utf-8 characters instead of trying work hex values. $chars = $string ? mbstring::str_split($value) : array_reverse(str_split($this->dec2hex($value, 8), 2)); $encoded = ''; foreach ($chars $char) { // bits char. using method ensure padded. $bits = sprintf('%08b', $string ? mbstring::ord($char) : hexdec($char)); $nibblex = substr($bits, 0, 4); $nibbley = substr($bits, 4, 4); // construct value header, high nibble, low nibble. $value = self::nibble_header; foreach (['y' => $nibbley, 'x' => $nibblex] $nibbletype => $nibble) { $value .= $this->getnibblewithcontrol($nibbletype, $nibble); } // convert binary bit stream foreach ([0, 8, 16] $start) { $encoded .= $this->packbitstring(substr($value, $start, 8), 8); } } return $encoded; } /** * php's pack() function has no 'b' or 'b' template. workaround turns literal bit-string * packed byte-string 8 bits per byte. * * @param string $bits * @param bool $len * @return string */ protected function packbitstring($bits, $len) { $bits = substr($bits, 0, $len); // pad input zeros next multiple of 4 above $len $bits = str_pad($bits, 4 * (int) (($len + 3) / 4), '0'); // split input chunks of 4 bits, convert each hex , pack them $nibbles = str_split($bits, 4); foreach ($nibbles $i => $nibble) { $nibbles[$i] = base_convert($nibble, 2, 16); } return pack('h*', implode('', $nibbles)); } /** * based on control, adjust nibble accordingly. * * @param string $nibble * @param string $control * @return string */ protected function nibblecontrol($nibble, $control) { // control stays constant low/high nibbles, doesn't matter compare if ($control == self::nibble_control['x'][1]) { $dec = bindec($nibble); $dec += 9; $nibble = str_pad(decbin($dec), 4, '0', str_pad_left); } return $nibble; } /** * nibble value control prefixed. * * if nibble dec <= 9, control x equals 001011 , y equals 001110, otherwise if nibble dec > 9 * control x or y equals 011010. additionally, if dec value of nibble > 9, nibble value * must subtracted 9 before final value constructed. * * @param string $nibbletype either x or y * @param $nibble * @return string */ protected function getnibblewithcontrol($nibbletype, $nibble) { $dec = bindec($nibble); if ($dec > 9) { $dec -= 9; $control = self::nibble_control[$nibbletype][1]; } else { $control = self::nibble_control[$nibbletype][0]; } return $control.sprintf('%04d', decbin($dec)); } /** * need make sure hex values length, pad needed. * * @param int $int * @param int $padlength hex string must padded length (with zeros). * @return string */ protected function dec2hex($int, $padlength = 2) { return str_pad(dechex($int), $padlength, 0, str_pad_left); } } mbstring
/** * utility functions handle multi-byte strings properly, support lacking/inconsistent php string * functions. provides wrapper various workarounds , falls normal functions if needed. * * @author chad sikorra <chad.sikorra@gmail.com> */ class mbstring { /** * integer value of specific character. * * @param $string * @return int */ public static function ord($string) { if (self::ismbstringloaded()) { $result = unpack('n', mb_convert_encoding($string, 'ucs-4be', 'utf-8')); if (is_array($result) === true) { return $result[1]; } } return ord($string); } /** * character specific integer value. * * @param $int * @return string */ public static function chr($int) { if (self::ismbstringloaded()) { return mb_convert_encoding(pack('n', $int), 'utf-8', 'utf-16be'); } return chr($int); } /** * split string individual characters , return array. * * @param string $value * @return string[] */ public static function str_split($value) { return preg_split('/(?<!^)(?!$)/u', $value); } /** * simple check mbstring extension. * * @return bool */ protected static function ismbstringloaded() { return extension_loaded('mbstring'); } } displaying userparameters values
// assuming $value binary value userparameters. $tsproparray = new tspropertyarray($value); // prints out each ts property name , value user. foreach($tsproparray->toarray() $prop => $value) { echo "$prop => $value".php_eol; } // print single value echo $tsproparray->get('ctxwfprofilepath'); modifying/creating userparameters values
// creates new set of values userparameters (default values). $tsproparray = new tspropertyarray(); // set max session idle time of 30 minutes. $tsproparray->set('ctxmaxidletime', 30); // binary value save ldap $binary = $tsproparray->tobinary(); // load binary userparameters data user $tsproparray = new tspropertyarray($binary); // replace user profile location... $tsproparray->set('ctxwfprofilepath', '\\some\path'); // new binary value, save needed ldap... $binary = $tsproparray->tobinary(); a few additional notes
the code above handle multi-byte chars, if there utf8 chars in value should fine. respects other binary data within userparameters binary blob. not destructive, data preserved when modifying existing value.
i noticed there ts properties in userparameters end in 'w' , duplicates of other properties (even values duplicated). not find information on in msdn or elsewhere, i'm not sure significance is.
Comments
Post a Comment