ok, so here's the problem:

I do a search for the userparameters attribute with ldap_search. I needed to get a couple of values out, being "CtxWFHomeDirDrive", "CtxWFHomeDir" and "CtxWFProfilePath"

The string I got was complete jibberish, so after an entire day of trying out every single character encoding conversion I could find, this one did half the trick:

$pUserParams = iconv('UTF-8', 'UTF-32', $entry_value)

For some reason the individual hex numbers following the 3 values I needed to extract were inversed (so 5C, which is backslash, came out as C5. Don't ask how I figured that one out :-P )

Knowing this, I could convert the hex values to display the actual citrix homedirdrive, profilepath, etc.

However, I still need to filter out a lot of unneeded characters from the final result string, but the following always returns false for some reason:

if (strpos($pUserParams,"CtxWFProfilePath") !== false) {}

When I echo the $pUserParams variable, it does display the whole string with the above three ctx parameters in it.

I suspected it must have something to do with special characters in the result string, so I tried removing line breaks, EOL's, unserializing the string (which produces an error at offset 0), etc etc

Nothing seems to work... Does anyone have an idea?

Thanks, Vincent

original string run through hex2bin:

20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202050071a080143747843666750726573656e74e394b5e694b1e688b0e381a2180801437478436667466c61677331e380b0e381a6e380b2e380b9120801437478536861646f77e384b0e380b0e380b0e380b02a02014374784d696e456e6372797074696f6e4c6576656ce384b02206014374785746486f6d654469724472697665e3a0b4e684b3e380b018c282014374785746486f6d65446972e68cb5e68cb5e394b7e38cb7e39cb6e388b6e394b6e398b6e3a4b6e68cb6e394b6e380b3e380b3e384b3e694b2e394b7e38cb7e39cb6e380b7e394b6e698b6e380b7e68cb6e394b6e388b6e394b6e694b2e3a4b6e694b6e390b7e68cb5e394b7e38cb7e394b6e388b7e3a0b6e698b6e690b6e394b6e390b6e388b7e3a4b6e398b7e394b6e390b2e68cb5e38cb7e390b7e3a4b6e684b6e694b6e694b2e688b6e698b6e688b6e688b6e394b6e68cb6e394b6e694b6e388b6e394b6e388b7e39cb6e380b020c28001437478574650726f66696c6550617468e68cb5e68cb5e384b6e38cb7e380b7e694b6e394b6e390b7e384b6e380b7e380b7e380b3e380b3e384b3e694b2e384b6e38cb7e380b7e694b2e3a4b6e694b6e390b7e68cb5e380b7e388b7e698b6e398b6e3a4b6e68cb6e394b6e38cb7e698b5e388b6e394b6e68cb6e39cb6e3a4b6e394b7e690b6e390b2e68cb5e38cb5e38cb5e38cb4e68cb5e38cb7e390b7e3a4b6e684b6e694b6e694b2e688b6e698b6e688b6e688b6e394b6e68cb6e394b6e694b6e388b6e394b6e388b7e39cb6e380b0e380b0
�� 
有帮助吗?

解决方案 3

removing all special characters with

preg_replace('/[^a-zA-Z0-9_ %[].()%&-]/s', '', $piConverted);

solved it :-)

Now, if I could only find a more elegant conversion so I don't have to "manually" reverse the hex code

其他提示

To build on the work already done by Tenzian, I ended up creating a few self-contained classes that can be used to safely decode/encode a userParameters blob to extract and modify/view/create all TS properties. This does make a few assumptions:

  • PHP 5.6+ (Feel free to edit to suit your needs/work with a lower version)
  • Assumes all of the classes are defined in the same namespace.
  • For multi-byte string support you have the mbstring extension loaded.
  • Any TS property for time is represented in terms of minutes.

Anyway, these are the classes you need:

TSPropertyArray

/**
 * Represents TSPropertyArray data that contains individual TSProperty structures in a userParameters value.
 *
 * @see https://msdn.microsoft.com/en-us/library/ff635189.aspx
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class TSPropertyArray
{
    /**
     * Represents that the TSPropertyArray data is valid.
     */
    const VALID_SIGNATURE = 'P';

    /**
     * @var array The default values for the 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 The default data that occurs before the TSPropertyArray (CtxCfgPresent with a bunch of spaces...?)
     */
    protected $defaultPreBinary = '43747843666750726573656e742020202020202020202020202020202020202020202020202020202020202020202020';

    /**
     * @var TSProperty[]
     */
    protected $tsProperty = [];

    /**
     * @var string
     */
    protected $signature = self::VALID_SIGNATURE;

    /**
     * @var string Binary data that occurs before the TSPropertyArray data in userParameters.
     */
    protected $preBinary = '';

    /**
     * @var string Binary data that occurs after the TSPropertyArray data in userParameters.
     */
    protected $postBinary = '';

    /**
     * Construct in one of the following ways:
     *
     *   - Pass an array of TSProperty key => value pairs (See DEFAULTS constant).
     *   - Pass the userParameters binary value. The object representation of that will be decoded and constructed.
     *   - Pass nothing and a default set of TSProperty key => value pairs will be 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 as $key => $value) {
                $tsProperty = new TSProperty();
                $tsProperty->setName($key);
                $tsProperty->setValue($value);
                $this->tsProperty[$key] = $tsProperty;
            }
        } else {
            $this->decodeUserParameters($tsPropertyArray);
        }
    }

    /**
     * Check if a specific TSProperty exists by its property name.
     * 
     * @param string $propName
     * @return bool
     */
    public function has($propName)
    {
        return array_key_exists(strtolower($propName), array_change_key_case($this->tsProperty));
    }

    /**
     * Get a TSProperty object by its property name (ie. CtxWFProfilePath).
     *
     * @param string $propName
     * @return TSProperty
     */
    public function get($propName)
    {
        $this->validateProp($propName);

        return $this->getTsPropObj($propName)->getValue();
    }

    /**
     * Add a TSProperty object. If it already exists, it will be overwritten.
     *
     * @param TSProperty $tsProperty
     * @return $this
     */
    public function add(TSProperty $tsProperty)
    {
        $this->tsProperty[$tsProperty->getName()] = $tsProperty;

        return $this;
    }

    /**
     * Remove a TSProperty by its property name (ie. CtxMinEncryptionLevel).
     * 
     * @param string $propName
     * @return $this
     */
    public function remove($propName)
    {
        foreach (array_keys($this->tsProperty) as $property) {
            if (strtolower($propName) == strtolower($property)) {
                unset($this->tsProperty[$property]);
            }
        }

        return $this;
    }

    /**
     * Set the value for a specific TSProperty by its name.
     * 
     * @param string $propName
     * @param mixed $propValue
     * @return $this
     */
    public function set($propName, $propValue)
    {
        $this->validateProp($propName);
        $this->getTsPropObj($propName)->setValue($propValue);

        return $this;
    }

    /**
     * Get the full binary representation of the userParameters containing the 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 as $tsProperty) {
            $binary .= $tsProperty->toBinary();
        }

        return $binary.$this->postBinary;
    }

    /**
     * Get a simple associative array containing of all TSProperty names and values.
     * 
     * @return array
     */
    public function toArray()
    {
        $userParameters = [];

        foreach ($this->tsProperty as $property => $tsPropObj) {
            $userParameters[$property] = $tsPropObj->getValue();
        }

        return $userParameters;
    }

    /**
     * Get all 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 for "%s" does not exist.', $propName));
        }    
    }

    /**
     * @param string $propName
     * @return TSProperty
     */
    protected function getTsPropObj($propName)
    {
        return array_change_key_case($this->tsProperty)[strtolower($propName)];
    }

    /**
     * Get an associative array with all of the userParameters property names and values.
     *
     * @param string $userParameters
     * @return array
     */
    protected function decodeUserParameters($userParameters)
    {
        $userParameters = bin2hex($userParameters);

        // Save the 96-byte array of reserved data, so as to not ruin anything that may be stored there.
        $this->preBinary = hex2bin(substr($userParameters, 0, 96));
        // The signature is a 2-byte unicode character at the front
        $this->signature = MBString::chr(hexdec(substr($userParameters, 96, 2)));
        // This asserts the validity of the tsPropertyArray data. For some reason 'P' means valid...
        if ($this->signature != self::VALID_SIGNATURE) {
            throw new \InvalidArgumentException('Invalid TSPropertyArray data');
        }

        // The property count is a 2-byte unsigned integer indicating the number of elements for the tsPropertyArray
        // It starts at position 98. The actual variable data begins at position 100.
        $length = $this->addTSPropData(substr($userParameters, 100), hexdec(substr($userParameters, 98, 2)));

        // Reserved data length + (count and sig length == 4) + the added lengths of the TSPropertyArray
        // This saves anything after that variable TSPropertyArray data, so as to not squash anything stored there
        if (strlen($userParameters) > (96 + 4 + $length)) {
            $this->postBinary = hex2bin(substr($userParameters, (96 + 4 + $length)));
        }
    }

    /**
     * Given the start of TSPropertyArray hex data, and the count for the number of TSProperty structures in contains,
     * parse and split out the individual TSProperty structures. Return the full length of the TSPropertyArray data.
     *
     * @param string $tsPropertyArray
     * @param int $tsPropCount
     * @return int The length of the data in the TSPropertyArray
     */
    protected function addTSPropData($tsPropertyArray, $tsPropCount)
    {
        $length = 0;

        for ($i = 0; $i < $tsPropCount; $i++) {
            // Prop length = name length + value length + type length + the space for the 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 a TSProperty structure in a TSPropertyArray of a 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. The first value for each is if the nibble is <= 9, otherwise the second value is used.
     */
    const NIBBLE_CONTROL = [
        'X' => ['001011', '011010'],
        'Y' => ['001110', '011010'],
    ];

    /**
     * The nibble header.
     */
    const NIBBLE_HEADER = '1110';

    /**
     * Conversion factor needed for time values in the TSPropertyArray (stored in microseconds).
     */
    const TIME_CONVERSION = 60 * 1000;

    /**
     * A simple map to help determine how the property needs to be decoded/encoded from/to its binary value.
     *
     * There are some names that are simple repeats but have 'W' at the end. Not sure as to what that signifies. I
     * cannot find any information on them in Microsoft documentation. However, their values appear to stay in sync with
     * their non 'W' counterparts. But not doing so when manipulating the data manually does not seem to affect anything.
     * This probably 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 The property name.
     */
    protected $name;

    /**
     * @var string|int The property value.
     */
    protected $value;

    /**
     * @var int The property value type.
     */
    protected $valueType = 1;

    /**
     * @param string|null $value Pass binary TSProperty data to construct its object representation.
     */
    public function __construct($value = null)
    {
        if ($value) {
            $this->decode(bin2hex($value));
        }
    }

    /**
     * Set the name for the TSProperty.
     *
     * @param string $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Get the name for the TSProperty.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set the value for the TSProperty.
     *
     * @param string|int $value
     */
    public function setValue($value)
    {
        $this->value = $value;
    }

    /**
     * Get the value for the TSProperty.
     *
     * @return string|int
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * Convert the TSProperty name/value back to its binary representation for the 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 a TSProperty blob, decode the name/value/type/etc.
     *
     * @param string $tsProperty
     */
    protected function decode($tsProperty)
    {
        $nameLength = hexdec(substr($tsProperty, 0, 2));
        # 1 data byte is 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 the property name/value in question, get its 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 are null terminated. Unsure if this is needed or simply a product of how ADUC does stuff?
            $value = $this->encodePropValue($propValue."\0", true);
        } elseif (in_array($propName, $this->propTypes['time'])) {
            # Needs to be in microseconds (assuming it is in minute format)...
            $value = $this->encodePropValue($propValue * self::TIME_CONVERSION);
        } else {
            $value = $this->encodePropValue($propValue);
        }

        return $value;
    }

    /**
     * Based on the property name in question, get its actual value from the 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. I think this should be desired, otherwise it just ends in confusion.
            $value = str_replace("\0", '', $this->decodePropValue($propValue, true));
        } elseif (in_array($propName, $this->propTypes['time'])) {
            // Convert from microseconds to minutes (how ADUC displays it anyway, and seems the most 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 the property by inspecting the nibbles of each blob, checking the control, and adding up the results into
     * a final value.
     *
     * @param string $hex
     * @param bool $string Whether or not this is simple string data.
     * @return string
     */
    protected function decodePropValue($hex, $string = false)
    {
        $decodePropValue = '';
        $blobs = str_split($hex, 6);

        foreach ($blobs as $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;
    }

    /**
     * Get the encoded property value as a binary blob.
     *
     * @param string $value
     * @param bool $string
     * @return string
     */
    protected function encodePropValue($value, $string = false)
    {
        // An int must be properly padded. (then split and reversed). For a string, we just split the chars. This seems
        // to be the easiest way to handle UTF-8 characters instead of trying to work with their hex values.
        $chars = $string ? MBString::str_split($value) : array_reverse(str_split($this->dec2hex($value, 8), 2));

        $encoded = '';
        foreach ($chars as $char) {
            // Get the bits for the char. Using this method to ensure it is fully padded.
            $bits = sprintf('%08b', $string ? MBString::ord($char) : hexdec($char));
            $nibbleX = substr($bits, 0, 4);
            $nibbleY = substr($bits, 4, 4);

            // Construct the value with the header, high nibble, then low nibble.
            $value = self::NIBBLE_HEADER;
            foreach (['Y' => $nibbleY, 'X' => $nibbleX] as $nibbleType => $nibble) {
                $value .= $this->getNibbleWithControl($nibbleType, $nibble);
            }

            // Convert it back to a binary bit stream
            foreach ([0, 8, 16] as $start) {
                $encoded .= $this->packBitString(substr($value, $start, 8), 8);
            }
        }

        return $encoded;
    }

    /**
     * PHP's pack() function has no 'b' or 'B' template. This is a workaround that turns a literal bit-string into a
     * packed byte-string with 8 bits per byte.
     *
     * @param string $bits
     * @param bool $len
     * @return string
     */
    protected function packBitString($bits, $len)
    {
        $bits = substr($bits, 0, $len);
        // Pad input with zeros to next multiple of 4 above $len
        $bits = str_pad($bits, 4 * (int) (($len + 3) / 4), '0');

        // Split input into chunks of 4 bits, convert each to hex and pack them
        $nibbles = str_split($bits, 4);
        foreach ($nibbles as $i => $nibble) {
            $nibbles[$i] = base_convert($nibble, 2, 16);
        }

        return pack('H*', implode('', $nibbles));
    }

    /**
     * Based on the control, adjust the nibble accordingly.
     *
     * @param string $nibble
     * @param string $control
     * @return string
     */
    protected function nibbleControl($nibble, $control)
    {
        // This control stays constant for the low/high nibbles, so it doesn't matter which we compare to
        if ($control == self::NIBBLE_CONTROL['X'][1]) {
            $dec = bindec($nibble);
            $dec += 9;
            $nibble = str_pad(decbin($dec), 4, '0', STR_PAD_LEFT);
        }

        return $nibble;
    }

    /**
     * Get the nibble value with the control prefixed.
     *
     * If the nibble dec is <= 9, the control X equals 001011 and Y equals 001110, otherwise if the nibble dec is > 9
     * the control for X or Y equals 011010. Additionally, if the dec value of the nibble is > 9, then the nibble value
     * must be subtracted by 9 before the final value is 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 to make sure hex values are always an even length, so pad as needed.
     *
     * @param int $int
     * @param int $padLength The hex string must be padded to this length (with zeros).
     * @return string
     */
    protected function dec2hex($int, $padLength = 2)
    {
        return str_pad(dechex($int), $padLength, 0, STR_PAD_LEFT);
    }
}

MBString

/**
 * Some utility functions to handle multi-byte strings properly, as support is lacking/inconsistent for most PHP string
 * functions. This provides a wrapper for various workarounds and falls back to normal functions if needed.
 *
 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
 */
class MBString
{
    /**
     * Get the integer value of a 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);
    }

    /**
     * Get the character for a 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 a string into its individual characters and return it as an array.
     * 
     * @param string $value
     * @return string[]
     */
    public static function str_split($value)
    {
        return preg_split('/(?<!^)(?!$)/u', $value);
    }

    /**
     * Simple check for the mbstring extension.
     * 
     * @return bool
     */
    protected static function isMbstringLoaded()
    {
        return extension_loaded('mbstring');
    }
}

Displaying userParameters Values

// Assuming $value is the binary value from userParameters.
$tsPropArray = new TSPropertyArray($value);

// Prints out each TS property name and value for the user.
foreach($tsPropArray->toArray() as $prop => $value) {
    echo "$prop => $value".PHP_EOL;
}

// Print a single value
echo $tsPropArray->get('CtxWFProfilePath');

Modifying/Creating userParameters Values

// Creates a new set of values for userParameters (default values).
$tsPropArray = new TSPropertyArray();
// Set a max session idle time of 30 minutes.
$tsPropArray->set('CtxMaxIdleTime', 30);
// Get the binary value to save to LDAP
$binary = $tsPropArray->toBinary();

// Load binary userParameters data from a user
$tsPropArray = new TSPropertyArray($binary);
// Replace their user profile location...
$tsPropArray->set('CtxWFProfilePath', '\\some\path');
// Get the new binary value, then save it as needed back to LDAP...
$binary = $tsPropArray->toBinary();

A Few Additional Notes

The code above will handle multi-byte chars, so if there are UTF8 chars in a value it should be fine. It also respects other binary data within the userParameters binary blob. So it is not destructive, that data will be preserved when you are modifying an existing value.

I also noticed that there are some TS properties in userParameters that end in 'W' and are duplicates of other properties (even their values are duplicated). I could not find any information on this in MSDN or elsewhere, so I'm not sure what their significance is.

I know it's been a while since the original question was asked, but this is the only page that comes up in a search for "CtxWFProfilePath" and PHP, and it's where I started from when I was trying to work out how to get the values out of userParameters.

It turns out that the userParameters blob has possibly the most arcane and unnecessary encoding ever invented. I have no idea what Microsoft were thinking when this was dreamt up, but they fact that a CtxCfgPresent value of 0xB00B1E55 indicates valid data may go some way to explaining it...

(Big thanks to http://daduke.org/linux/userparameters.html for figuring out the encoding of the TSProperty structure.)

Here's my solution:

<?php
function userParameters($userParameters){
  /*
    userParameters data structure described at: http://msdn.microsoft.com/en-us/library/ff635189.aspx
    TSProperty data structure described at: http://msdn.microsoft.com/en-us/library/ff635169.aspx

    Input:  userParameters blob returned from ldap_search
    Output: associative array of user parameters
  */
  $parameters = array();
  $userParameters = bin2hex($userParameters);
  $userParameters = substr($userParameters,96);
  $Signature = chr(hexdec(substr($userParameters,0,2)));
  $userParameters = substr($userParameters,2);
  if ($Signature != 'P'){
    return false;
  }
  $TSPropertyCount = hexdec(substr($userParameters,0,2));
  $userParameters = substr($userParameters,2);
  for ($i = 0; $i < $TSPropertyCount; $i++){
    $NameLength = hexdec(substr($userParameters,0,2));
    $userParameters = substr($userParameters,2);
    $ValueLength = hexdec(substr($userParameters,0,2)) * 3; // 1 data byte = 3 encoded bytes
    $userParameters = substr($userParameters,2);
    $Type = substr($userParameters,0,2);
    $userParameters = substr($userParameters,2);
    $PropName = substr($userParameters,0,$NameLength);
    $PropName = hex2str($PropName);
    $userParameters = substr($userParameters,$NameLength);
    $PropValue = substr($userParameters,0,$ValueLength);
    $userParameters = substr($userParameters,$ValueLength);
    switch ($PropName) {
      case 'CtxWFHomeDir':
      case 'CtxWFHomeDirDrive':
      case 'CtxInitialProgram':
      case 'CtxWFProfilePath':
      case 'CtxWorkDirectory':
      case 'CtxCallbackNumber':
        $parameters[$PropName] = decode_PropValue($PropValue,true);
        break;
      case 'CtxCfgFlags1':
        $parameters[$PropName] = parse_CtxCfgFlags1(decode_PropValue($PropValue));
        break;
      case 'CtxShadow':
        $parameters[$PropName] = parse_CtxShadow(decode_PropValue($PropValue));
        break;
      default:
        $parameters[$PropName] = decode_PropValue($PropValue);
    }
  }
  return $parameters;
}

function hex2str($hex) {
  $str = '';
  for($i = 0; $i < strlen($hex); $i += 2){
    $str .= chr(hexdec(substr($hex,$i,2)));
  }
  return $str;
}

function decode_PropValue($hex,$ascii=false){
  /*
    Encoding described at: http://daduke.org/linux/userparameters.html
    for each character you want to encode, do:
      - split the character's byte into nibbles xxxx and yyyy
      - have a look at xxxx. If it's <= 9, control x (XXXXXX) equals 001011, otherwise it's 011010
      - have a look at yyyy. Here the bit patterns for control y (YYYYYY) are 001110 (yyyy <= 9), 011010 otherwise
      - if xxxx > 9: xxxx -= 9
      - if yyyy > 9: yyyy -= 9
      - take the prefix (1110), control y, yyyy, control x and xxxx and glue them all together to yield a 24 bit string
      - convert this bit stream to three bytes: 1110 YYYY YYyy yyXX XXXX xxxx
  */
  $decode_PropValue = '';
  $blobs = str_split($hex,6);
  foreach ($blobs as $blob){
    $bin = decbin(hexdec($blob));
    $control_y = substr($bin,4,6);
    $nibble_y = substr($bin,10,4);
    $control_x = substr($bin,14,6);
    $nibble_x = substr($bin,20,4);
    $byte = nibble_control($nibble_x,$control_x).nibble_control($nibble_y,$control_y);
    if ($ascii){
      $decode_PropValue .= chr(bindec($byte));
    }
    else {
      $decode_PropValue = str_pad(dechex(bindec($byte)),2,'0',STR_PAD_LEFT).$decode_PropValue;
    }
  }
  return $decode_PropValue;
}

function nibble_control($nibble,$control){
  if ($control == '011010'){
    $dec = bindec($nibble);
    $dec += 9;
    return str_pad(decbin($dec),4,'0',STR_PAD_LEFT);
  }
  return $nibble;
}

function parse_CtxCfgFlags1($CtxCfgFlags1) {
  /* Flag bit mask values from: http://msdn.microsoft.com/en-us/library/ff635169.aspx */
  $parse_CtxCfgFlags1 = array();
  $CtxCfgFlags1 = hexdec($CtxCfgFlags1);
  $flags = array(
    'F1MSK_INHERITINITIALPROGRAM'       => 268435456,
    'F1MSK_INHERITCALLBACK'             => 134217728,
    'F1MSK_INHERITCALLBACKNUMBER'       =>  67108864,
    'F1MSK_INHERITSHADOW'               =>  33554432,
    'F1MSK_INHERITMAXSESSIONTIME'       =>  16777216,
    'F1MSK_INHERITMAXDISCONNECTIONTIME' =>   8388608,
    'F1MSK_INHERITMAXIDLETIME'          =>   4194304,
    'F1MSK_INHERITAUTOCLIENT'           =>   2097152,
    'F1MSK_INHERITSECURITY'             =>   1048576,
    'F1MSK_PROMPTFORPASSWORD'           =>    524288,
    'F1MSK_RESETBROKEN'                 =>    262144,
    'F1MSK_RECONNECTSAME'               =>    131072,
    'F1MSK_LOGONDISABLED'               =>     65536,
    'F1MSK_AUTOCLIENTDRIVES'            =>     32768,
    'F1MSK_AUTOCLIENTLPTS'              =>     16384,
    'F1MSK_FORCECLIENTLPTDEF'           =>      8192,
    'F1MSK_DISABLEENCRYPTION'           =>      4096,
    'F1MSK_HOMEDIRECTORYMAPROOT'        =>      2048,
    'F1MSK_USEDEFAULTGINA'              =>      1024,
    'F1MSK_DISABLECPM'                  =>       512,
    'F1MSK_DISABLECDM'                  =>       256,
    'F1MSK_DISABLECCM'                  =>       128,
    'F1MSK_DISABLELPT'                  =>        64,
    'F1MSK_DISABLECLIP'                 =>        32,
    'F1MSK_DISABLEEXE'                  =>        16,
    'F1MSK_WALLPAPERDISABLED'           =>         8,
    'F1MSK_DISABLECAM'                  =>         4
  );
  foreach($flags as $flag => $bit) {
    if ($CtxCfgFlags1 & $bit) {
      $parse_CtxCfgFlags1[] = $flag;
    }
  }
  return($parse_CtxCfgFlags1);
}

function parse_CtxShadow($CtxShadow) {
  /* Flag values from: http://msdn.microsoft.com/en-us/library/ff635169.aspx */
  $CtxShadow = hexdec($CtxShadow);
  $flags = array('Disable','EnableInputNotify','EnableInputNoNotify','EnableNoInputNotify','EnableNoInputNoNotify');
  if ($CtxShadow < 0 || $CtxShadow > 4) {
    return false;
  }
  return $flags[$CtxShadow];
}
?>
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top