git subrepo commit (merge) mailcow/src/mailcow-dockerized
subrepo: subdir: "mailcow/src/mailcow-dockerized"
merged: "02ae5285"
upstream: origin: "https://github.com/mailcow/mailcow-dockerized.git"
branch: "master"
commit: "649a5c01"
git-subrepo: version: "0.4.3"
origin: "???"
commit: "???"
Change-Id: I870ad468fba026cc5abf3c5699ed1e12ff28b32b
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php
new file mode 100644
index 0000000..ad56aa1
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSProperty.php
@@ -0,0 +1,396 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+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',
+ ],
+ ];
+
+ /**
+ * The property name.
+ *
+ * @var string
+ */
+ protected $name;
+
+ /**
+ * The property value.
+ *
+ * @var string|int
+ */
+ protected $value;
+
+ /**
+ * The property value type.
+ *
+ * @var int
+ */
+ protected $valueType = 1;
+
+ /**
+ * Pass binary TSProperty data to construct its object representation.
+ *
+ * @param string|null $value
+ */
+ public function __construct($value = null)
+ {
+ if ($value) {
+ $this->decode(bin2hex($value));
+ }
+ }
+
+ /**
+ * Set the name for the TSProperty.
+ *
+ * @param string $name
+ *
+ * @return TSProperty
+ */
+ public function setName($name)
+ {
+ $this->name = $name;
+
+ return $this;
+ }
+
+ /**
+ * Get the name for the TSProperty.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set the value for the TSProperty.
+ *
+ * @param string|int $value
+ *
+ * @return TSProperty
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * 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::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 string $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);
+ }
+}