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());
+    }
+}