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/AccountControl.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/AccountControl.php
new file mode 100644
index 0000000..9c6240b
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/AccountControl.php
@@ -0,0 +1,502 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+use ReflectionClass;
+
+class AccountControl
+{
+ const SCRIPT = 1;
+
+ const ACCOUNTDISABLE = 2;
+
+ const HOMEDIR_REQUIRED = 8;
+
+ const LOCKOUT = 16;
+
+ const PASSWD_NOTREQD = 32;
+
+ const PASSWD_CANT_CHANGE = 64;
+
+ const ENCRYPTED_TEXT_PWD_ALLOWED = 128;
+
+ const TEMP_DUPLICATE_ACCOUNT = 256;
+
+ const NORMAL_ACCOUNT = 512;
+
+ const INTERDOMAIN_TRUST_ACCOUNT = 2048;
+
+ const WORKSTATION_TRUST_ACCOUNT = 4096;
+
+ const SERVER_TRUST_ACCOUNT = 8192;
+
+ const DONT_EXPIRE_PASSWORD = 65536;
+
+ const MNS_LOGON_ACCOUNT = 131072;
+
+ const SMARTCARD_REQUIRED = 262144;
+
+ const TRUSTED_FOR_DELEGATION = 524288;
+
+ const NOT_DELEGATED = 1048576;
+
+ const USE_DES_KEY_ONLY = 2097152;
+
+ const DONT_REQ_PREAUTH = 4194304;
+
+ const PASSWORD_EXPIRED = 8388608;
+
+ const TRUSTED_TO_AUTH_FOR_DELEGATION = 16777216;
+
+ const PARTIAL_SECRETS_ACCOUNT = 67108864;
+
+ /**
+ * The account control flag values.
+ *
+ * @var array
+ */
+ protected $values = [];
+
+ /**
+ * Constructor.
+ *
+ * @param int $flag
+ */
+ public function __construct($flag = null)
+ {
+ if (! is_null($flag)) {
+ $this->apply($flag);
+ }
+ }
+
+ /**
+ * Get the value when casted to string.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->getValue();
+ }
+
+ /**
+ * Get the value when casted to int.
+ *
+ * @return int
+ */
+ public function __toInt()
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * Add the flag to the account control values.
+ *
+ * @param int $flag
+ *
+ * @return $this
+ */
+ public function add($flag)
+ {
+ // Use the value as a key so if the same value
+ // is used, it will always be overwritten
+ $this->values[$flag] = $flag;
+
+ return $this;
+ }
+
+ /**
+ * Remove the flag from the account control.
+ *
+ * @param int $flag
+ *
+ * @return $this
+ */
+ public function remove($flag)
+ {
+ unset($this->values[$flag]);
+
+ return $this;
+ }
+
+ /**
+ * Extract and apply the flag.
+ *
+ * @param int $flag
+ *
+ * @return void
+ */
+ public function apply($flag)
+ {
+ $this->setValues($this->extractFlags($flag));
+ }
+
+ /**
+ * Determine if the account control contains the given UAC flag(s).
+ *
+ * @param int $flag
+ *
+ * @return bool
+ */
+ public function has($flag)
+ {
+ // Here we will extract the given flag into an array
+ // of possible flags. This will allow us to see if
+ // our AccountControl object contains any of them.
+ $flagsUsed = array_intersect(
+ $this->extractFlags($flag),
+ $this->values
+ );
+
+ return in_array($flag, $flagsUsed);
+ }
+
+ /**
+ * Determine if the account control does not contain the given UAC flag(s).
+ *
+ * @param int $flag
+ *
+ * @return bool
+ */
+ public function doesntHave($flag)
+ {
+ return ! $this->has($flag);
+ }
+
+ /**
+ * Generate an LDAP filter based on the current value.
+ *
+ * @return string
+ */
+ public function filter()
+ {
+ return sprintf('(UserAccountControl:1.2.840.113556.1.4.803:=%s)', $this->getValue());
+ }
+
+ /**
+ * The logon script will be run.
+ *
+ * @return $this
+ */
+ public function runLoginScript()
+ {
+ return $this->add(static::SCRIPT);
+ }
+
+ /**
+ * The user account is locked.
+ *
+ * @return $this
+ */
+ public function accountIsLocked()
+ {
+ return $this->add(static::LOCKOUT);
+ }
+
+ /**
+ * The user account is disabled.
+ *
+ * @return $this
+ */
+ public function accountIsDisabled()
+ {
+ return $this->add(static::ACCOUNTDISABLE);
+ }
+
+ /**
+ * This is an account for users whose primary account is in another domain.
+ *
+ * This account provides user access to this domain, but not to any domain that
+ * trusts this domain. This is sometimes referred to as a local user account.
+ *
+ * @return $this
+ */
+ public function accountIsTemporary()
+ {
+ return $this->add(static::TEMP_DUPLICATE_ACCOUNT);
+ }
+
+ /**
+ * This is a default account type that represents a typical user.
+ *
+ * @return $this
+ */
+ public function accountIsNormal()
+ {
+ return $this->add(static::NORMAL_ACCOUNT);
+ }
+
+ /**
+ * This is a permit to trust an account for a system domain that trusts other domains.
+ *
+ * @return $this
+ */
+ public function accountIsForInterdomain()
+ {
+ return $this->add(static::INTERDOMAIN_TRUST_ACCOUNT);
+ }
+
+ /**
+ * This is a computer account for a computer that is running Microsoft
+ * Windows NT 4.0 Workstation, Microsoft Windows NT 4.0 Server, Microsoft
+ * Windows 2000 Professional, or Windows 2000 Server and is a member of this domain.
+ *
+ * @return $this
+ */
+ public function accountIsForWorkstation()
+ {
+ return $this->add(static::WORKSTATION_TRUST_ACCOUNT);
+ }
+
+ /**
+ * This is a computer account for a domain controller that is a member of this domain.
+ *
+ * @return $this
+ */
+ public function accountIsForServer()
+ {
+ return $this->add(static::SERVER_TRUST_ACCOUNT);
+ }
+
+ /**
+ * This is an MNS logon account.
+ *
+ * @return $this
+ */
+ public function accountIsMnsLogon()
+ {
+ return $this->add(static::MNS_LOGON_ACCOUNT);
+ }
+
+ /**
+ * (Windows 2000/Windows Server 2003) This account does
+ * not require Kerberos pre-authentication for logging on.
+ *
+ * @return $this
+ */
+ public function accountDoesNotRequirePreAuth()
+ {
+ return $this->add(static::DONT_REQ_PREAUTH);
+ }
+
+ /**
+ * When this flag is set, it forces the user to log on by using a smart card.
+ *
+ * @return $this
+ */
+ public function accountRequiresSmartCard()
+ {
+ return $this->add(static::SMARTCARD_REQUIRED);
+ }
+
+ /**
+ * (Windows Server 2008/Windows Server 2008 R2) The account is a read-only domain controller (RODC).
+ *
+ * This is a security-sensitive setting. Removing this setting from an RODC compromises security on that server.
+ *
+ * @return $this
+ */
+ public function accountIsReadOnly()
+ {
+ return $this->add(static::PARTIAL_SECRETS_ACCOUNT);
+ }
+
+ /**
+ * The home folder is required.
+ *
+ * @return $this
+ */
+ public function homeFolderIsRequired()
+ {
+ return $this->add(static::HOMEDIR_REQUIRED);
+ }
+
+ /**
+ * No password is required.
+ *
+ * @return $this
+ */
+ public function passwordIsNotRequired()
+ {
+ return $this->add(static::PASSWD_NOTREQD);
+ }
+
+ /**
+ * The user cannot change the password. This is a permission on the user's object.
+ *
+ * For information about how to programmatically set this permission, visit the following link:
+ *
+ * @see http://msdn2.microsoft.com/en-us/library/aa746398.aspx
+ *
+ * @return $this
+ */
+ public function passwordCannotBeChanged()
+ {
+ return $this->add(static::PASSWD_CANT_CHANGE);
+ }
+
+ /**
+ * Represents the password, which should never expire on the account.
+ *
+ * @return $this
+ */
+ public function passwordDoesNotExpire()
+ {
+ return $this->add(static::DONT_EXPIRE_PASSWORD);
+ }
+
+ /**
+ * (Windows 2000/Windows Server 2003) The user's password has expired.
+ *
+ * @return $this
+ */
+ public function passwordIsExpired()
+ {
+ return $this->add(static::PASSWORD_EXPIRED);
+ }
+
+ /**
+ * The user can send an encrypted password.
+ *
+ * @return $this
+ */
+ public function allowEncryptedTextPassword()
+ {
+ return $this->add(static::ENCRYPTED_TEXT_PWD_ALLOWED);
+ }
+
+ /**
+ * When this flag is set, the service account (the user or computer account)
+ * under which a service runs is trusted for Kerberos delegation.
+ *
+ * Any such service can impersonate a client requesting the service.
+ *
+ * To enable a service for Kerberos delegation, you must set this
+ * flag on the userAccountControl property of the service account.
+ *
+ * @return $this
+ */
+ public function trustForDelegation()
+ {
+ return $this->add(static::TRUSTED_FOR_DELEGATION);
+ }
+
+ /**
+ * (Windows 2000/Windows Server 2003) The account is enabled for delegation.
+ *
+ * This is a security-sensitive setting. Accounts that have this option enabled
+ * should be tightly controlled. This setting lets a service that runs under the
+ * account assume a client's identity and authenticate as that user to other remote
+ * servers on the network.
+ *
+ * @return $this
+ */
+ public function trustToAuthForDelegation()
+ {
+ return $this->add(static::TRUSTED_TO_AUTH_FOR_DELEGATION);
+ }
+
+ /**
+ * When this flag is set, the security context of the user is not delegated to a
+ * service even if the service account is set as trusted for Kerberos delegation.
+ *
+ * @return $this
+ */
+ public function doNotTrustForDelegation()
+ {
+ return $this->add(static::NOT_DELEGATED);
+ }
+
+ /**
+ * (Windows 2000/Windows Server 2003) Restrict this principal to
+ * use only Data Encryption Standard (DES) encryption types for keys.
+ *
+ * @return $this
+ */
+ public function useDesKeyOnly()
+ {
+ return $this->add(static::USE_DES_KEY_ONLY);
+ }
+
+ /**
+ * Get the account control value.
+ *
+ * @return int
+ */
+ public function getValue()
+ {
+ return array_sum($this->values);
+ }
+
+ /**
+ * Get the account control flag values.
+ *
+ * @return array
+ */
+ public function getValues()
+ {
+ return $this->values;
+ }
+
+ /**
+ * Set the account control values.
+ *
+ * @param array $flags
+ *
+ * @return void
+ */
+ public function setValues(array $flags)
+ {
+ $this->values = $flags;
+ }
+
+ /**
+ * Get all flags that are currently applied to the value.
+ *
+ * @return array
+ */
+ public function getAppliedFlags()
+ {
+ $flags = $this->getAllFlags();
+
+ $exists = [];
+
+ foreach ($flags as $name => $flag) {
+ if ($this->has($flag)) {
+ $exists[$name] = $flag;
+ }
+ }
+
+ return $exists;
+ }
+
+ /**
+ * Get all possible account control flags.
+ *
+ * @return array
+ */
+ public function getAllFlags()
+ {
+ return (new ReflectionClass(__CLASS__))->getConstants();
+ }
+
+ /**
+ * Extracts the given flag into an array of flags used.
+ *
+ * @param int $flag
+ *
+ * @return array
+ */
+ public function extractFlags($flag)
+ {
+ $flags = [];
+
+ for ($i = 0; $i <= 26; $i++) {
+ if ((int) $flag & (1 << $i)) {
+ $flags[1 << $i] = 1 << $i;
+ }
+ }
+
+ return $flags;
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedName.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedName.php
new file mode 100644
index 0000000..c092173
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedName.php
@@ -0,0 +1,419 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+use LdapRecord\EscapesValues;
+use LdapRecord\Support\Arr;
+
+class DistinguishedName
+{
+ use EscapesValues;
+
+ /**
+ * The underlying raw value.
+ *
+ * @var string|null
+ */
+ protected $value;
+
+ /**
+ * Constructor.
+ *
+ * @param string|null $value
+ */
+ public function __construct($value = null)
+ {
+ $this->value = trim($value);
+ }
+
+ /**
+ * Get the distinguished name value.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->value;
+ }
+
+ /**
+ * Alias of the "build" method.
+ *
+ * @param string|null $value
+ *
+ * @return DistinguishedNameBuilder
+ */
+ public static function of($value = null)
+ {
+ return static::build($value);
+ }
+
+ /**
+ * Get a new DN builder object from the given DN.
+ *
+ * @param string|null $value
+ *
+ * @return DistinguishedNameBuilder
+ */
+ public static function build($value = null)
+ {
+ return new DistinguishedNameBuilder($value);
+ }
+
+ /**
+ * Make a new distinguished name instance.
+ *
+ * @param string|null $value
+ *
+ * @return static
+ */
+ public static function make($value = null)
+ {
+ return new static($value);
+ }
+
+ /**
+ * Explode a distinguished name into relative distinguished names.
+ *
+ * @param string $dn
+ *
+ * @return array
+ */
+ public static function explode($dn)
+ {
+ $dn = ldap_explode_dn($dn, $withoutAttributes = false);
+
+ if (! is_array($dn)) {
+ return [];
+ }
+
+ if (! array_key_exists('count', $dn)) {
+ return [];
+ }
+
+ unset($dn['count']);
+
+ return $dn;
+ }
+
+ /**
+ * Un-escapes a hexadecimal string into its original string representation.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public static function unescape($value)
+ {
+ return preg_replace_callback('/\\\([0-9A-Fa-f]{2})/', function ($matches) {
+ return chr(hexdec($matches[1]));
+ }, $value);
+ }
+
+ /**
+ * Explode the RDN into an attribute and value.
+ *
+ * @param string $rdn
+ *
+ * @return array
+ */
+ public static function explodeRdn($rdn)
+ {
+ return explode('=', $rdn, $limit = 2);
+ }
+
+ /**
+ * Implode the component attribute and value into an RDN.
+ *
+ * @param string $rdn
+ *
+ * @return string
+ */
+ public static function makeRdn(array $component)
+ {
+ return implode('=', $component);
+ }
+
+ /**
+ * Get the underlying value.
+ *
+ * @return string|null
+ */
+ public function get()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set the underlying value.
+ *
+ * @param string|null $value
+ *
+ * @return $this
+ */
+ public function set($value)
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get the distinguished name values without attributes.
+ *
+ * @return array
+ */
+ public function values()
+ {
+ $values = [];
+
+ foreach ($this->multi() as [, $value]) {
+ $values[] = static::unescape($value);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Get the distinguished name attributes without values.
+ *
+ * @return array
+ */
+ public function attributes()
+ {
+ $attributes = [];
+
+ foreach ($this->multi() as [$attribute]) {
+ $attributes[] = $attribute;
+ }
+
+ return $attributes;
+ }
+
+ /**
+ * Get the distinguished name components with attributes.
+ *
+ * @return array
+ */
+ public function components()
+ {
+ $components = [];
+
+ foreach ($this->multi() as [$attribute, $value]) {
+ // When a distinguished name is exploded, the values are automatically
+ // escaped. This cannot be opted out of. Here we will unescape
+ // the attribute value, then re-escape it to its original
+ // representation from the server using the "dn" flag.
+ $value = $this->escape(static::unescape($value))->dn();
+
+ $components[] = static::makeRdn([$attribute, $value]);
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert the distinguished name into an associative array.
+ *
+ * @return array
+ */
+ public function assoc()
+ {
+ $map = [];
+
+ foreach ($this->multi() as [$attribute, $value]) {
+ $attribute = $this->normalize($attribute);
+
+ array_key_exists($attribute, $map)
+ ? $map[$attribute][] = $value
+ : $map[$attribute] = [$value];
+ }
+
+ return $map;
+ }
+
+ /**
+ * Split the RDNs into a multi-dimensional array.
+ *
+ * @return array
+ */
+ public function multi()
+ {
+ return array_map(function ($rdn) {
+ return static::explodeRdn($rdn);
+ }, $this->rdns());
+ }
+
+ /**
+ * Split the distinguished name into an array of unescaped RDN's.
+ *
+ * @return array
+ */
+ public function rdns()
+ {
+ return static::explode($this->value);
+ }
+
+ /**
+ * Get the first RDNs value.
+ *
+ * @return string|null
+ */
+ public function name()
+ {
+ return Arr::first($this->values());
+ }
+
+ /**
+ * Get the first RDNs attribute.
+ *
+ * @return string|null
+ */
+ public function head()
+ {
+ return Arr::first($this->attributes());
+ }
+
+ /**
+ * Get the relative distinguished name.
+ *
+ * @return string|null
+ */
+ public function relative()
+ {
+ return Arr::first($this->components());
+ }
+
+ /**
+ * Alias of relative().
+ *
+ * Get the first RDN from the distinguished name.
+ *
+ * @return string|null
+ */
+ public function first()
+ {
+ return $this->relative();
+ }
+
+ /**
+ * Get the parent distinguished name.
+ *
+ * @return string|null
+ */
+ public function parent()
+ {
+ $components = $this->components();
+
+ array_shift($components);
+
+ return implode(',', $components) ?: null;
+ }
+
+ /**
+ * Determine if the current distinguished name is a parent of the given child.
+ *
+ * @param DistinguishedName $child
+ *
+ * @return bool
+ */
+ public function isParentOf(self $child)
+ {
+ return $child->isChildOf($this);
+ }
+
+ /**
+ * Determine if the current distinguished name is a child of the given parent.
+ *
+ * @param DistinguishedName $parent
+ *
+ * @return bool
+ */
+ public function isChildOf(self $parent)
+ {
+ if (
+ empty($components = $this->components()) ||
+ empty($parentComponents = $parent->components())
+ ) {
+ return false;
+ }
+
+ array_shift($components);
+
+ return $this->compare($components, $parentComponents);
+ }
+
+ /**
+ * Determine if the current distinguished name is an ancestor of the descendant.
+ *
+ * @param DistinguishedName $descendant
+ *
+ * @return bool
+ */
+ public function isAncestorOf(self $descendant)
+ {
+ return $descendant->isDescendantOf($this);
+ }
+
+ /**
+ * Determine if the current distinguished name is a descendant of the ancestor.
+ *
+ * @param DistinguishedName $ancestor
+ *
+ * @return bool
+ */
+ public function isDescendantOf(self $ancestor)
+ {
+ if (
+ empty($components = $this->components()) ||
+ empty($ancestorComponents = $ancestor->components())
+ ) {
+ return false;
+ }
+
+ if (! $length = count($components) - count($ancestorComponents)) {
+ return false;
+ }
+
+ array_splice($components, $offset = 0, $length);
+
+ return $this->compare($components, $ancestorComponents);
+ }
+
+ /**
+ * Compare whether the two distinguished name values are equal.
+ *
+ * @param array $values
+ * @param array $other
+ *
+ * @return bool
+ */
+ protected function compare(array $values, array $other)
+ {
+ return $this->recase($values) == $this->recase($other);
+ }
+
+ /**
+ * Recase the array values.
+ *
+ * @param array $values
+ *
+ * @return array
+ */
+ protected function recase(array $values)
+ {
+ return array_map([$this, 'normalize'], $values);
+ }
+
+ /**
+ * Normalize the string value.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ protected function normalize($value)
+ {
+ return strtolower($value);
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedNameBuilder.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedNameBuilder.php
new file mode 100644
index 0000000..83dfe71
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/DistinguishedNameBuilder.php
@@ -0,0 +1,251 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+use LdapRecord\EscapesValues;
+use LdapRecord\Support\Arr;
+
+class DistinguishedNameBuilder
+{
+ use EscapesValues;
+
+ /**
+ * The components of the DN.
+ *
+ * @var array
+ */
+ protected $components = [];
+
+ /**
+ * Whether to output the DN in reverse.
+ *
+ * @var bool
+ */
+ protected $reverse = false;
+
+ /**
+ * Constructor.
+ *
+ * @param string|null $value
+ */
+ public function __construct($dn = null)
+ {
+ $this->components = array_map(function ($rdn) {
+ return DistinguishedName::explodeRdn($rdn);
+ }, DistinguishedName::make($dn)->components());
+ }
+
+ /**
+ * Forward missing method calls onto the Distinguished Name object.
+ *
+ * @param string $method
+ * @param array $args
+ *
+ * @return mixed
+ */
+ public function __call($method, $args)
+ {
+ return $this->get()->{$method}(...$args);
+ }
+
+ /**
+ * Get the distinguished name value.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->get();
+ }
+
+ /**
+ * Prepend an RDN onto the DN.
+ *
+ * @param string|array $attribute
+ * @param string|null $value
+ *
+ * @return $this
+ */
+ public function prepend($attribute, $value = null)
+ {
+ array_unshift(
+ $this->components,
+ ...$this->componentize($attribute, $value)
+ );
+
+ return $this;
+ }
+
+ /**
+ * Append an RDN onto the DN.
+ *
+ * @param string|array $attribute
+ * @param string|null $value
+ *
+ * @return $this
+ */
+ public function append($attribute, $value = null)
+ {
+ array_push(
+ $this->components,
+ ...$this->componentize($attribute, $value)
+ );
+
+ return $this;
+ }
+
+ /**
+ * Componentize the attribute and value.
+ *
+ * @param string|array $attribute
+ * @param string|null $value
+ *
+ * @return array
+ */
+ protected function componentize($attribute, $value = null)
+ {
+ // Here we will make the assumption that an array of
+ // RDN's have been given if the value is null, and
+ // attempt to break them into their components.
+ if (is_null($value)) {
+ $attributes = Arr::wrap($attribute);
+
+ $components = array_map([$this, 'makeComponentizedArray'], $attributes);
+ } else {
+ $components = [[$attribute, $value]];
+ }
+
+ return array_map(function ($component) {
+ [$attribute, $value] = $component;
+
+ return $this->makeAppendableComponent($attribute, $value);
+ }, $components);
+ }
+
+ /**
+ * Make a componentized array by exploding the value if it's a string.
+ *
+ * @param string $value
+ *
+ * @return array
+ */
+ protected function makeComponentizedArray($value)
+ {
+ return is_array($value) ? $value : DistinguishedName::explodeRdn($value);
+ }
+
+ /**
+ * Make an appendable component array from the attribute and value.
+ *
+ * @param string|array $attribute
+ * @param string|null $value
+ *
+ * @return array
+ */
+ protected function makeAppendableComponent($attribute, $value = null)
+ {
+ return [trim($attribute), $this->escape(trim($value))->dn()];
+ }
+
+ /**
+ * Pop an RDN off of the end of the DN.
+ *
+ * @param int $amount
+ * @param array $removed
+ *
+ * @return $this
+ */
+ public function pop($amount = 1, &$removed = [])
+ {
+ $removed = array_map(function ($component) {
+ return DistinguishedName::makeRdn($component);
+ }, array_splice($this->components, -$amount, $amount));
+
+ return $this;
+ }
+
+ /**
+ * Shift an RDN off of the beginning of the DN.
+ *
+ * @param int $amount
+ * @param array $removed
+ *
+ * @return $this
+ */
+ public function shift($amount = 1, &$removed = [])
+ {
+ $removed = array_map(function ($component) {
+ return DistinguishedName::makeRdn($component);
+ }, array_splice($this->components, 0, $amount));
+
+ return $this;
+ }
+
+ /**
+ * Whether to output the DN in reverse.
+ *
+ * @return $this
+ */
+ public function reverse()
+ {
+ $this->reverse = true;
+
+ return $this;
+ }
+
+ /**
+ * Get the components of the DN.
+ *
+ * @param null|string $type
+ *
+ * @return array
+ */
+ public function components($type = null)
+ {
+ return is_null($type)
+ ? $this->components
+ : $this->componentsOfType($type);
+ }
+
+ /**
+ * Get the components of a particular type.
+ *
+ * @param string $type
+ *
+ * @return array
+ */
+ protected function componentsOfType($type)
+ {
+ $components = array_filter($this->components, function ($component) use ($type) {
+ return ([$name] = $component) && strtolower($name) === strtolower($type);
+ });
+
+ return array_values($components);
+ }
+
+ /**
+ * Get the fully qualified DN.
+ *
+ * @return DistinguishedName
+ */
+ public function get()
+ {
+ return new DistinguishedName($this->build());
+ }
+
+ /**
+ * Build the distinguished name from the components.
+ *
+ * @return $this
+ */
+ protected function build()
+ {
+ $components = $this->reverse
+ ? array_reverse($this->components)
+ : $this->components;
+
+ return implode(',', array_map(function ($component) {
+ return DistinguishedName::makeRdn($component);
+ }, $components));
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/EscapedValue.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/EscapedValue.php
new file mode 100644
index 0000000..cc04a67
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/EscapedValue.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+class EscapedValue
+{
+ /**
+ * The value to be escaped.
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * The characters to ignore when escaping.
+ *
+ * @var string
+ */
+ protected $ignore;
+
+ /**
+ * The escape flags.
+ *
+ * @var int
+ */
+ protected $flags;
+
+ /**
+ * Constructor.
+ *
+ * @param string $value
+ * @param string $ignore
+ * @param int $flags
+ */
+ public function __construct($value, $ignore = '', $flags = 0)
+ {
+ $this->value = $value;
+ $this->ignore = $ignore;
+ $this->flags = $flags;
+ }
+
+ /**
+ * Get the escaped value.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->get();
+ }
+
+ /**
+ * Get the escaped value.
+ *
+ * @return mixed
+ */
+ public function get()
+ {
+ return ldap_escape($this->value, $this->ignore, $this->flags);
+ }
+
+ /**
+ * Set the characters to exclude from being escaped.
+ *
+ * @param string $characters
+ *
+ * @return $this
+ */
+ public function ignore($characters)
+ {
+ $this->ignore = $characters;
+
+ return $this;
+ }
+
+ /**
+ * Prepare the value to be escaped for use in a distinguished name.
+ *
+ * @return $this
+ */
+ public function dn()
+ {
+ $this->flags = LDAP_ESCAPE_DN;
+
+ return $this;
+ }
+
+ /**
+ * Prepare the value to be escaped for use in a filter.
+ *
+ * @return $this
+ */
+ public function filter()
+ {
+ $this->flags = LDAP_ESCAPE_FILTER;
+
+ return $this;
+ }
+
+ /**
+ * Prepare the value to be escaped for use in a distinguished name and filter.
+ *
+ * @return $this
+ */
+ public function both()
+ {
+ $this->flags = LDAP_ESCAPE_FILTER + LDAP_ESCAPE_DN;
+
+ return $this;
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Guid.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Guid.php
new file mode 100644
index 0000000..d139f5f
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Guid.php
@@ -0,0 +1,167 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+use InvalidArgumentException;
+use LdapRecord\Utilities;
+
+class Guid
+{
+ /**
+ * The string GUID value.
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * The guid structure in order by section to parse using substr().
+ *
+ * @author Chad Sikorra <Chad.Sikorra@gmail.com>
+ *
+ * @see https://github.com/ldaptools/ldaptools
+ *
+ * @var array
+ */
+ protected $guidSections = [
+ [[-26, 2], [-28, 2], [-30, 2], [-32, 2]],
+ [[-22, 2], [-24, 2]],
+ [[-18, 2], [-20, 2]],
+ [[-16, 4]],
+ [[-12, 12]],
+ ];
+
+ /**
+ * The hexadecimal octet order based on string position.
+ *
+ * @author Chad Sikorra <Chad.Sikorra@gmail.com>
+ *
+ * @see https://github.com/ldaptools/ldaptools
+ *
+ * @var array
+ */
+ protected $octetSections = [
+ [6, 4, 2, 0],
+ [10, 8],
+ [14, 12],
+ [16, 18, 20, 22, 24, 26, 28, 30],
+ ];
+
+ /**
+ * Determines if the specified GUID is valid.
+ *
+ * @param string $guid
+ *
+ * @return bool
+ */
+ public static function isValid($guid)
+ {
+ return Utilities::isValidGuid($guid);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $value
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($value)
+ {
+ if (static::isValid($value)) {
+ $this->value = $value;
+ } elseif ($value = $this->binaryGuidToString($value)) {
+ $this->value = $value;
+ } else {
+ throw new InvalidArgumentException('Invalid Binary / String GUID.');
+ }
+ }
+
+ /**
+ * Returns the string value of the GUID.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * Returns the string value of the SID.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Get the binary representation of the GUID string.
+ *
+ * @return string
+ */
+ public function getBinary()
+ {
+ return hex2bin($this->getHex());
+ }
+
+ /**
+ * Get the hexadecimal representation of the GUID string.
+ *
+ * @return string
+ */
+ public function getHex()
+ {
+ $data = '';
+
+ $guid = str_replace('-', '', $this->value);
+
+ foreach ($this->octetSections as $section) {
+ $data .= $this->parseSection($guid, $section, $octet = true);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Returns the string variant of a binary GUID.
+ *
+ * @param string $binary
+ *
+ * @return string|null
+ */
+ protected function binaryGuidToString($binary)
+ {
+ return Utilities::binaryGuidToString($binary);
+ }
+
+ /**
+ * Return the specified section of the hexadecimal string.
+ *
+ * @author Chad Sikorra <Chad.Sikorra@gmail.com>
+ *
+ * @see https://github.com/ldaptools/ldaptools
+ *
+ * @param string $hex The full hex string.
+ * @param array $sections An array of start and length (unless octet is true, then length is always 2).
+ * @param bool $octet Whether this is for octet string form.
+ *
+ * @return string The concatenated sections in upper-case.
+ */
+ protected function parseSection($hex, array $sections, $octet = false)
+ {
+ $parsedString = '';
+
+ foreach ($sections as $section) {
+ $start = $octet ? $section : $section[0];
+
+ $length = $octet ? 2 : $section[1];
+
+ $parsedString .= substr($hex, $start, $length);
+ }
+
+ return $parsedString;
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/MbString.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/MbString.php
new file mode 100644
index 0000000..672e60d
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/MbString.php
@@ -0,0 +1,80 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+class MbString
+{
+ /**
+ * Get the integer value of a specific character.
+ *
+ * @param $string
+ *
+ * @return int
+ */
+ public static function ord($string)
+ {
+ if (static::isLoaded()) {
+ $result = unpack('N', mb_convert_encoding($string, 'UCS-4BE', 'UTF-8'));
+
+ if (is_array($result)) {
+ return $result[1];
+ }
+ }
+
+ return ord($string);
+ }
+
+ /**
+ * Get the character for a specific integer value.
+ *
+ * @param $int
+ *
+ * @return string
+ */
+ public static function chr($int)
+ {
+ if (static::isLoaded()) {
+ 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 split($value)
+ {
+ return preg_split('/(?<!^)(?!$)/u', $value);
+ }
+
+ /**
+ * Detects if the given string is UTF 8.
+ *
+ * @param $string
+ *
+ * @return string|false
+ */
+ public static function isUtf8($string)
+ {
+ if (static::isLoaded()) {
+ return mb_detect_encoding($string, 'UTF-8', $strict = true);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Checks if the mbstring extension is enabled in PHP.
+ *
+ * @return bool
+ */
+ public static function isLoaded()
+ {
+ return extension_loaded('mbstring');
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Password.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Password.php
new file mode 100644
index 0000000..7f0b412
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Password.php
@@ -0,0 +1,340 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+use InvalidArgumentException;
+use LdapRecord\LdapRecordException;
+use ReflectionMethod;
+
+class Password
+{
+ const CRYPT_SALT_TYPE_MD5 = 1;
+ const CRYPT_SALT_TYPE_SHA256 = 5;
+ const CRYPT_SALT_TYPE_SHA512 = 6;
+
+ /**
+ * Make an encoded password for transmission over LDAP.
+ *
+ * @param string $password
+ *
+ * @return string
+ */
+ public static function encode($password)
+ {
+ return iconv('UTF-8', 'UTF-16LE', '"'.$password.'"');
+ }
+
+ /**
+ * Make a salted md5 password.
+ *
+ * @param string $password
+ * @param null|string $salt
+ *
+ * @return string
+ */
+ public static function smd5($password, $salt = null)
+ {
+ return '{SMD5}'.static::makeHash($password, 'md5', null, $salt ?? random_bytes(4));
+ }
+
+ /**
+ * Make a salted SHA password.
+ *
+ * @param string $password
+ * @param null|string $salt
+ *
+ * @return string
+ */
+ public static function ssha($password, $salt = null)
+ {
+ return '{SSHA}'.static::makeHash($password, 'sha1', null, $salt ?? random_bytes(4));
+ }
+
+ /**
+ * Make a salted SSHA256 password.
+ *
+ * @param string $password
+ * @param null|string $salt
+ *
+ * @return string
+ */
+ public static function ssha256($password, $salt = null)
+ {
+ return '{SSHA256}'.static::makeHash($password, 'hash', 'sha256', $salt ?? random_bytes(4));
+ }
+
+ /**
+ * Make a salted SSHA384 password.
+ *
+ * @param string $password
+ * @param null|string $salt
+ *
+ * @return string
+ */
+ public static function ssha384($password, $salt = null)
+ {
+ return '{SSHA384}'.static::makeHash($password, 'hash', 'sha384', $salt ?? random_bytes(4));
+ }
+
+ /**
+ * Make a salted SSHA512 password.
+ *
+ * @param string $password
+ * @param null|string $salt
+ *
+ * @return string
+ */
+ public static function ssha512($password, $salt = null)
+ {
+ return '{SSHA512}'.static::makeHash($password, 'hash', 'sha512', $salt ?? random_bytes(4));
+ }
+
+ /**
+ * Make a non-salted SHA password.
+ *
+ * @param string $password
+ *
+ * @return string
+ */
+ public static function sha($password)
+ {
+ return '{SHA}'.static::makeHash($password, 'sha1');
+ }
+
+ /**
+ * Make a non-salted SHA256 password.
+ *
+ * @param string $password
+ *
+ * @return string
+ */
+ public static function sha256($password)
+ {
+ return '{SHA256}'.static::makeHash($password, 'hash', 'sha256');
+ }
+
+ /**
+ * Make a non-salted SHA384 password.
+ *
+ * @param string $password
+ *
+ * @return string
+ */
+ public static function sha384($password)
+ {
+ return '{SHA384}'.static::makeHash($password, 'hash', 'sha384');
+ }
+
+ /**
+ * Make a non-salted SHA512 password.
+ *
+ * @param string $password
+ *
+ * @return string
+ */
+ public static function sha512($password)
+ {
+ return '{SHA512}'.static::makeHash($password, 'hash', 'sha512');
+ }
+
+ /**
+ * Make a non-salted md5 password.
+ *
+ * @param string $password
+ *
+ * @return string
+ */
+ public static function md5($password)
+ {
+ return '{MD5}'.static::makeHash($password, 'md5');
+ }
+
+ /**
+ * Crypt password with an MD5 salt.
+ *
+ * @param string $password
+ * @param string $salt
+ *
+ * @return string
+ */
+ public static function md5Crypt($password, $salt = null)
+ {
+ return '{CRYPT}'.static::makeCrypt($password, static::CRYPT_SALT_TYPE_MD5, $salt);
+ }
+
+ /**
+ * Crypt password with a SHA256 salt.
+ *
+ * @param string $password
+ * @param string $salt
+ *
+ * @return string
+ */
+ public static function sha256Crypt($password, $salt = null)
+ {
+ return '{CRYPT}'.static::makeCrypt($password, static::CRYPT_SALT_TYPE_SHA256, $salt);
+ }
+
+ /**
+ * Crypt a password with a SHA512 salt.
+ *
+ * @param string $password
+ * @param string $salt
+ *
+ * @return string
+ */
+ public static function sha512Crypt($password, $salt = null)
+ {
+ return '{CRYPT}'.static::makeCrypt($password, static::CRYPT_SALT_TYPE_SHA512, $salt);
+ }
+
+ /**
+ * Make a new password hash.
+ *
+ * @param string $password The password to make a hash of.
+ * @param string $method The hash function to use.
+ * @param string|null $algo The algorithm to use for hashing.
+ * @param string|null $salt The salt to append onto the hash.
+ *
+ * @return string
+ */
+ protected static function makeHash($password, $method, $algo = null, $salt = null)
+ {
+ $params = $algo ? [$algo, $password.$salt] : [$password.$salt];
+
+ return base64_encode(pack('H*', call_user_func($method, ...$params)).$salt);
+ }
+
+ /**
+ * Make a hashed password.
+ *
+ * @param string $password
+ * @param int $type
+ * @param null|string $salt
+ *
+ * @return string
+ */
+ protected static function makeCrypt($password, $type, $salt = null)
+ {
+ return crypt($password, $salt ?? static::makeCryptSalt($type));
+ }
+
+ /**
+ * Make a salt for the crypt() method using the given type.
+ *
+ * @param int $type
+ *
+ * @return string
+ */
+ protected static function makeCryptSalt($type)
+ {
+ [$prefix, $length] = static::makeCryptPrefixAndLength($type);
+
+ $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+ while (strlen($prefix) < $length) {
+ $prefix .= substr($chars, random_int(0, strlen($chars) - 1), 1);
+ }
+
+ return $prefix;
+ }
+
+ /**
+ * Determine the crypt prefix and length.
+ *
+ * @param int $type
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return array
+ */
+ protected static function makeCryptPrefixAndLength($type)
+ {
+ switch ($type) {
+ case static::CRYPT_SALT_TYPE_MD5:
+ return ['$1$', 12];
+ case static::CRYPT_SALT_TYPE_SHA256:
+ return ['$5$', 16];
+ case static::CRYPT_SALT_TYPE_SHA512:
+ return ['$6$', 16];
+ default:
+ throw new InvalidArgumentException("Invalid crypt type [$type].");
+ }
+ }
+
+ /**
+ * Attempt to retrieve the hash method used for the password.
+ *
+ * @param string $password
+ *
+ * @return string|void
+ */
+ public static function getHashMethod($password)
+ {
+ if (! preg_match('/^\{(\w+)\}/', $password, $matches)) {
+ return;
+ }
+
+ return $matches[1];
+ }
+
+ /**
+ * Attempt to retrieve the hash method and algorithm used for the password.
+ *
+ * @param string $password
+ *
+ * @return array|void
+ */
+ public static function getHashMethodAndAlgo($password)
+ {
+ if (! preg_match('/^\{(\w+)\}\$([0-9a-z]{1})\$/', $password, $matches)) {
+ return;
+ }
+
+ return [$matches[1], $matches[2]];
+ }
+
+ /**
+ * Attempt to retrieve a salt from the encrypted password.
+ *
+ * @throws LdapRecordException
+ *
+ * @return string
+ */
+ public static function getSalt($encryptedPassword)
+ {
+ // crypt() methods.
+ if (preg_match('/^\{(\w+)\}(\$.*\$).*$/', $encryptedPassword, $matches)) {
+ return $matches[2];
+ }
+
+ // All other methods.
+ if (preg_match('/{([^}]+)}(.*)/', $encryptedPassword, $matches)) {
+ return substr(base64_decode($matches[2]), -4);
+ }
+
+ throw new LdapRecordException('Could not extract salt from encrypted password.');
+ }
+
+ /**
+ * Determine if the hash method requires a salt to be given.
+ *
+ * @param string $method
+ *
+ * @throws \ReflectionException
+ *
+ * @return bool
+ */
+ public static function hashMethodRequiresSalt($method): bool
+ {
+ $parameters = (new ReflectionMethod(static::class, $method))->getParameters();
+
+ foreach ($parameters as $parameter) {
+ if ($parameter->name === 'salt') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Sid.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Sid.php
new file mode 100644
index 0000000..4ec46ea
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Sid.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+use InvalidArgumentException;
+use LdapRecord\Utilities;
+
+class Sid
+{
+ /**
+ * The string SID value.
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * Determines if the specified SID is valid.
+ *
+ * @param string $sid
+ *
+ * @return bool
+ */
+ public static function isValid($sid)
+ {
+ return Utilities::isValidSid($sid);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param mixed $value
+ *
+ * @throws InvalidArgumentException
+ */
+ public function __construct($value)
+ {
+ if (static::isValid($value)) {
+ $this->value = $value;
+ } elseif ($value = $this->binarySidToString($value)) {
+ $this->value = $value;
+ } else {
+ throw new InvalidArgumentException('Invalid Binary / String SID.');
+ }
+ }
+
+ /**
+ * Returns the string value of the SID.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getValue();
+ }
+
+ /**
+ * Returns the string value of the SID.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Returns the binary variant of the SID.
+ *
+ * @return string
+ */
+ public function getBinary()
+ {
+ $sid = explode('-', ltrim($this->value, 'S-'));
+
+ $level = (int) array_shift($sid);
+
+ $authority = (int) array_shift($sid);
+
+ $subAuthorities = array_map('intval', $sid);
+
+ $params = array_merge(
+ ['C2xxNV*', $level, count($subAuthorities), $authority],
+ $subAuthorities
+ );
+
+ return call_user_func_array('pack', $params);
+ }
+
+ /**
+ * Returns the string variant of a binary SID.
+ *
+ * @param string $binary
+ *
+ * @return string|null
+ */
+ protected function binarySidToString($binary)
+ {
+ return Utilities::binarySidToString($binary);
+ }
+}
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);
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSPropertyArray.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSPropertyArray.php
new file mode 100644
index 0000000..1831688
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/TSPropertyArray.php
@@ -0,0 +1,295 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+use InvalidArgumentException;
+
+class TSPropertyArray
+{
+ /**
+ * Represents that the TSPropertyArray data is valid.
+ */
+ const VALID_SIGNATURE = 'P';
+
+ /**
+ * The default values for the TSPropertyArray structure.
+ *
+ * @var array
+ */
+ 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;
+
+ /**
+ * Binary data that occurs before the TSPropertyArray data in userParameters.
+ *
+ * @var string
+ */
+ protected $preBinary = '';
+
+ /**
+ * Binary data that occurs after the TSPropertyArray data in userParameters.
+ *
+ * @var string
+ */
+ 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();
+
+ $this->tsProperty[$key] = $tsProperty->setName($key)->setValue($value);
+ }
+ } 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);
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Validates that the given property name exists.
+ *
+ * @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 void
+ */
+ 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;
+ }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Timestamp.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Timestamp.php
new file mode 100644
index 0000000..abd656c
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/directorytree/ldaprecord/src/Models/Attributes/Timestamp.php
@@ -0,0 +1,244 @@
+<?php
+
+namespace LdapRecord\Models\Attributes;
+
+use Carbon\Carbon;
+use Carbon\CarbonInterface;
+use DateTime;
+use LdapRecord\LdapRecordException;
+use LdapRecord\Utilities;
+
+class Timestamp
+{
+ /**
+ * The current timestamp type.
+ *
+ * @var string
+ */
+ protected $type;
+
+ /**
+ * The available timestamp types.
+ *
+ * @var array
+ */
+ protected $types = [
+ 'ldap',
+ 'windows',
+ 'windows-int',
+ ];
+
+ /**
+ * Constructor.
+ *
+ * @param string $type
+ *
+ * @throws LdapRecordException
+ */
+ public function __construct($type)
+ {
+ $this->setType($type);
+ }
+
+ /**
+ * Set the type of timestamp to convert from / to.
+ *
+ * @param string $type
+ *
+ * @throws LdapRecordException
+ */
+ public function setType($type)
+ {
+ if (! in_array($type, $this->types)) {
+ throw new LdapRecordException("Unrecognized LDAP date type [$type]");
+ }
+
+ $this->type = $type;
+ }
+
+ /**
+ * Converts the value to an LDAP date string.
+ *
+ * @param mixed $value
+ *
+ * @throws LdapRecordException
+ *
+ * @return float|string
+ */
+ public function fromDateTime($value)
+ {
+ $value = is_array($value) ? reset($value) : $value;
+
+ // If the value is being converted to a windows integer format but it
+ // is already in that format, we will simply return the value back.
+ if ($this->type == 'windows-int' && $this->valueIsWindowsIntegerType($value)) {
+ return $value;
+ }
+ // If the value is numeric, we will assume it's a UNIX timestamp.
+ elseif (is_numeric($value)) {
+ $value = Carbon::createFromTimestamp($value);
+ }
+ // If a string is given, we will pass it into a new carbon instance.
+ elseif (is_string($value)) {
+ $value = Carbon::parse($value);
+ }
+ // If a date object is given, we will convert it to a carbon instance.
+ elseif ($value instanceof DateTime) {
+ $value = Carbon::instance($value);
+ }
+
+ switch ($this->type) {
+ case 'ldap':
+ $value = $this->convertDateTimeToLdapTime($value);
+ break;
+ case 'windows':
+ $value = $this->convertDateTimeToWindows($value);
+ break;
+ case 'windows-int':
+ $value = $this->convertDateTimeToWindowsInteger($value);
+ break;
+ default:
+ throw new LdapRecordException("Unrecognized date type [{$this->type}]");
+ }
+
+ return $value;
+ }
+
+ /**
+ * Determine if the value given is in Windows Integer (NTFS Filetime) format.
+ *
+ * @param int|string $value
+ *
+ * @return bool
+ */
+ protected function valueIsWindowsIntegerType($value)
+ {
+ return is_numeric($value) && strlen((string) $value) === 18;
+ }
+
+ /**
+ * Converts the LDAP timestamp value to a Carbon instance.
+ *
+ * @param mixed $value
+ *
+ * @throws LdapRecordException
+ *
+ * @return Carbon|false
+ */
+ public function toDateTime($value)
+ {
+ $value = is_array($value) ? reset($value) : $value;
+
+ if ($value instanceof CarbonInterface || $value instanceof DateTime) {
+ return Carbon::instance($value);
+ }
+
+ switch ($this->type) {
+ case 'ldap':
+ $value = $this->convertLdapTimeToDateTime($value);
+ break;
+ case 'windows':
+ $value = $this->convertWindowsTimeToDateTime($value);
+ break;
+ case 'windows-int':
+ $value = $this->convertWindowsIntegerTimeToDateTime($value);
+ break;
+ default:
+ throw new LdapRecordException("Unrecognized date type [{$this->type}]");
+ }
+
+ return $value instanceof DateTime ? Carbon::instance($value) : $value;
+ }
+
+ /**
+ * Converts standard LDAP timestamps to a date time object.
+ *
+ * @param string $value
+ *
+ * @return DateTime|bool
+ */
+ protected function convertLdapTimeToDateTime($value)
+ {
+ return DateTime::createFromFormat(
+ strpos($value, 'Z') !== false ? 'YmdHis\Z' : 'YmdHisT',
+ $value
+ );
+ }
+
+ /**
+ * Converts date objects to a standard LDAP timestamp.
+ *
+ * @param DateTime $date
+ *
+ * @return string
+ */
+ protected function convertDateTimeToLdapTime(DateTime $date)
+ {
+ return $date->format(
+ $date->getOffset() == 0 ? 'YmdHis\Z' : 'YmdHisO'
+ );
+ }
+
+ /**
+ * Converts standard windows timestamps to a date time object.
+ *
+ * @param string $value
+ *
+ * @return DateTime|bool
+ */
+ protected function convertWindowsTimeToDateTime($value)
+ {
+ return DateTime::createFromFormat(
+ strpos($value, '0Z') !== false ? 'YmdHis.0\Z' : 'YmdHis.0T',
+ $value
+ );
+ }
+
+ /**
+ * Converts date objects to a windows timestamp.
+ *
+ * @param DateTime $date
+ *
+ * @return string
+ */
+ protected function convertDateTimeToWindows(DateTime $date)
+ {
+ return $date->format(
+ $date->getOffset() == 0 ? 'YmdHis.0\Z' : 'YmdHis.0O'
+ );
+ }
+
+ /**
+ * Converts standard windows integer dates to a date time object.
+ *
+ * @param int $value
+ *
+ * @throws \Exception
+ *
+ * @return DateTime|bool
+ */
+ protected function convertWindowsIntegerTimeToDateTime($value)
+ {
+ // ActiveDirectory dates that contain integers may return
+ // "0" when they are not set. We will validate that here.
+ if (! $value) {
+ return false;
+ }
+
+ return (new DateTime())->setTimestamp(
+ Utilities::convertWindowsTimeToUnixTime($value)
+ );
+ }
+
+ /**
+ * Converts date objects to a windows integer timestamp.
+ *
+ * @param DateTime $date
+ *
+ * @return float
+ */
+ protected function convertDateTimeToWindowsInteger(DateTime $date)
+ {
+ return Utilities::convertUnixTimeToWindowsTime($date->getTimestamp());
+ }
+}