lib/Auth lib/ext
Aleksander Machniak
machniak at kolabsys.com
Fri Feb 27 12:55:04 CET 2015
lib/Auth/LDAP.php | 335 +++++++-------------------------------------------
lib/ext/Net/LDAP3.php | 263 +++++++++++++++++++++++++++++++++++++--
2 files changed, 302 insertions(+), 296 deletions(-)
New commits:
commit 74319e95a36347f474e49bbbff04e95a561c106e
Author: Aleksander Machniak <machniak at kolabsys.com>
Date: Fri Feb 27 06:52:39 2015 -0500
Move domain_root_dn() and cache related methods into Net_LDAP3 (#4744)
diff --git a/lib/Auth/LDAP.php b/lib/Auth/LDAP.php
index a2d0ba3..1588500 100644
--- a/lib/Auth/LDAP.php
+++ b/lib/Auth/LDAP.php
@@ -40,9 +40,6 @@ class LDAP extends Net_LDAP3 {
$this->conf = Conf::get_instance();
- // Causes nesting levels to be too deep...?
- //$this->config_set('config_get_hook', array($this, "_config_get"));
-
$this->config_set("debug", Log::mode() == Log::TRACE);
$this->config_set("log_hook", array($this, "_log"));
@@ -53,6 +50,16 @@ class LDAP extends Net_LDAP3 {
$this->config_set("login_filter", $this->conf->get("kolab_wap", "login_filter"));
$this->config_set("vlv", $this->conf->get("ldap", "vlv", Conf::AUTO));
+ // configure the cache
+ $memcache_hosts = $this->conf->get('kolab_wap', 'memcache_hosts');
+ $this->config_set("memcache_pconnect", $this->conf->get('kolab_wap', 'memcache_pconnect', Conf::BOOL));
+ $this->config_set("memcache_hosts", $memcache_hosts);
+ $this->config_set("cache", !empty($memcache_hosts));
+
+ $this->config_set("domain_base_dn", $this->conf->get('ldap', 'domain_base_dn'));
+ $this->config_set("domain_filter", $this->conf->get('ldap', 'domain_filter'));
+ $this->config_set("domain_name_attribute", $this->conf->get('ldap', 'domain_name_attribute'));
+
// See if we are to connect to any domain explicitly defined.
if (empty($domain)) {
// If not, attempt to get the domain from the session.
@@ -196,8 +203,7 @@ class LDAP extends Net_LDAP3 {
}
// Query the ACI for the primary domain
- if ($domain_entry = $this->_find_domain($primary_domain)) {
- $domain_entry = array_shift($domain_entry);
+ if ($domain_entry = $this->find_domain($primary_domain)) {
if (in_array('inetdomainbasedn', $domain_entry)) {
$_base_dn = $domain_entry['inetdomainbasedn'];
}
@@ -424,7 +430,7 @@ class LDAP extends Net_LDAP3 {
$domain_dn = $this->entry_dn($domain, array(), $domain_base_dn);
if (!$domain_dn) {
- $result = $this->_find_domain($domain, $attributes);
+ $result = $this->find_domain($domain, $attributes);
}
else {
$result = $this->_read($domain_dn, $attributes);
@@ -1031,6 +1037,43 @@ class LDAP extends Net_LDAP3 {
}
/**
+ * Find domain by name
+ *
+ * @param string $domain Domain name
+ * @param array $attributes Result attributes
+ *
+ * @return array|bool Domain attributes or False on error
+ */
+ public function find_domain($domain, $attributes = array('*'))
+ {
+ if (empty($domain)) {
+ return false;
+ }
+
+ $ckey = 'domain::' . $domain . '::' . md5(implode(',', $attributes));
+
+ if (isset($this->icache[$ckey])) {
+ return $this->icache[$ckey];
+ }
+
+ // connect and bind...
+ if ($_SESSION['user'] && $_SESSION['user']->user_bind_dn) {
+ $bind_dn = $_SESSION['user']->user_bind_dn;
+ $bind_pw = $_SESSION['user']->user_bind_pw;
+ }
+ else {
+ $bind_dn = $this->conf->get('service_bind_dn');
+ $bind_pw = $this->conf->get('service_bind_pw');
+ }
+
+ if (!$this->bind($bind_dn, $bind_pw)) {
+ return false;
+ }
+
+ return parent::find_domain($domain, $attributes);
+ }
+
+ /**
* Wrapper for search_entries()
*/
protected function _list($base_dn, $filter, $scope, $attributes, $search, $params)
@@ -1164,27 +1207,6 @@ class LDAP extends Net_LDAP3 {
return $base_dn;
}
- public function _config_get($key, $default = NULL)
- {
- $key_parts = explode("_", $key);
- $this->_log(LOG_DEBUG, var_export($key_parts));
-
- while (!empty($key_parts)) {
- $value = $this->conf->get(implode("_", $key_parts));
- if (empty($value)) {
- $_discard = array_shift($key_parts);
- } else {
- break;
- }
- }
-
- if (empty($value)) {
- return $default;
- } else {
- return $value;
- }
- }
-
public function _log($level, $msg)
{
if (strstr($_SERVER["REQUEST_URI"], "/api/")) {
@@ -1271,7 +1293,6 @@ class LDAP extends Net_LDAP3 {
// List group memberships
$user_groups = $this->find_user_groups($_SESSION['user']->user_bind_dn);
- console("User's groups", $user_groups);
foreach ($user_groups as $user_group_dn) {
if ($user_is_admin)
@@ -1305,7 +1326,7 @@ class LDAP extends Net_LDAP3 {
'attributeLevelRights' => array(),
);
- $subject = $this->_search($subject_dn);
+ $subject = $this->search($subject_dn);
if (!$subject) {
return $rights;
@@ -1408,55 +1429,7 @@ class LDAP extends Net_LDAP3 {
************ Shortcut functions ****************
***********************************************************/
- /**
- * Translate a domain name into it's corresponding root dn.
- */
- private function domain_root_dn($domain)
- {
- if (empty($domain)) {
- return false;
- }
-
- $ckey = 'domain.root::' . $domain;
- if ($result = $this->icache[$ckey]) {
- return $result;
- }
-
- $this->_log(LOG_DEBUG, "Auth::LDAP::domain_root_dn(\$domain = $domain)");
-
- if ($entry_attrs = $this->_find_domain($domain)) {
- $entry_attrs = array_shift($entry_attrs);
-
- $domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
- if (empty($domain_name_attribute)) {
- $domain_name_attribute = 'associateddomain';
- }
-
- if (is_array($entry_attrs)) {
- if (array_key_exists('inetdomainbasedn', $entry_attrs) && !empty($entry_attrs['inetdomainbasedn'])) {
- $domain_root_dn = $entry_attrs['inetdomainbasedn'];
- }
- else {
- if (is_array($entry_attrs[$domain_name_attribute])) {
- $domain_root_dn = $this->_standard_root_dn($entry_attrs[$domain_name_attribute][0]);
- }
- else {
- $domain_root_dn = $this->_standard_root_dn($entry_attrs[$domain_name_attribute]);
- }
- }
- }
- }
-
- if (empty($domain_root_dn)) {
- $domain_root_dn = $this->_standard_root_dn($domain);
- }
-
- $this->icache[$ckey] = $domain_root_dn;
-
- return $domain_root_dn;
- }
-
- private function _read($entry_dn, $attributes = array('*'))
+ protected function _read($entry_dn, $attributes = array('*'))
{
$result = $this->search($entry_dn, '(objectclass=*)', 'base', $attributes);
@@ -1469,112 +1442,6 @@ class LDAP extends Net_LDAP3 {
}
}
- private function _search($base_dn, $filter = '(objectclass=*)', $attributes = array('*'))
- {
- $result = $this->search($base_dn, $filter, 'sub', $attributes);
- $this->_log(LOG_DEBUG, "Auth::LDAP::_search on $base_dn with $filter for attributes: " . var_export($attributes, true) . " with result: " . var_export($result, true));
- return $result;
- }
-
- /**
- * Find domain by name
- *
- * @param string $domain Domain name
- * @param array $attributes Result attributes
- *
- * @return array Domain records indexed by base DN
- */
- private function _find_domain($domain, $attributes = array('*'))
- {
- $this->_log(LOG_DEBUG, "Auth::LDAP::_find_domain($domain)");
-
- $ckey = 'domain::' . $domain;
-
- if (isset($this->icache[$ckey])) {
- return $this->icache[$ckey];
- }
-
- // use memcache
- $domain_dn = $this->get_cache_data($ckey);
-
- // connect and bind only if needed...
- if (empty($this->_current_bind_dn)) {
- $bind_dn = $_SESSION['user'] ? $_SESSION['user']->user_bind_dn : null;
-
- // ...and it is needed if we ned to call _read() or _search() below
- if (!$domain_dn || !$bind_dn) {
- if ($bind_dn) {
- $bind_pw = $_SESSION['user']->user_bind_pw;
- }
- else {
- $bind_dn = $this->conf->get('service_bind_dn');
- $bind_pw = $this->conf->get('service_bind_pw');
- }
-
- if (!$this->bind($bind_dn, $bind_pw)) {
- return false;
- }
- }
- }
-
- // Got cached domain DN, no need for searching, just read
- if ($domain_dn) {
- return $this->icache[$ckey] = $this->_read($domain_dn, $attributes);
- }
-
- $domain_base_dn = $this->conf->get('ldap', 'domain_base_dn');
- $domain_filter = $this->conf->get('ldap', 'domain_filter');
- $domain_name_attribute = $this->conf->get('ldap', 'domain_name_attribute');
-
- if (empty($domain_name_attribute)) {
- $domain_name_attribute = 'associateddomain';
- }
-
- $domain_filter = "(&" . $domain_filter . "(" . $domain_name_attribute . "=" . $domain . "))";
-
- if ($result = $this->_search($domain_base_dn, $domain_filter, $attributes)) {
- $result = $result->entries(true);
-
- // cache domain DN
- if (count($result) == 1) {
- $this->set_cache_data($ckey, key($result));
- }
- }
-
- return $this->icache[$ckey] = $result;
- }
-
- /**
- * From a domain name, such as 'kanarip.com', create a standard root
- * dn, such as 'dc=kanarip,dc=com'.
- *
- * As the parameter $associatedDomains, either pass it an array (such
- * as may have been returned by ldap_get_entries() or perhaps
- * ldap_list()), where the function will assume the first value
- * ($array[0]) to be the uber-level domain name, or pass it a string
- * such as 'kanarip.nl'.
- *
- * @return string
- */
- private function _standard_root_dn($associatedDomains)
- {
- if (is_array($associatedDomains)) {
- // Usually, the associatedDomain in position 0 is the naming attribute associatedDomain
- if ($associatedDomains['count'] > 1) {
- // Issue a debug message here
- $relevant_associatedDomain = $associatedDomains[0];
- }
- else {
- $relevant_associatedDomain = $associatedDomains[0];
- }
- }
- else {
- $relevant_associatedDomain = $associatedDomains;
- }
-
- return "dc=" . implode(',dc=', explode('.', $relevant_associatedDomain));
- }
-
/**
* Finds nsslapd-directory for specified domain
*/
@@ -1608,104 +1475,4 @@ class LDAP extends Net_LDAP3 {
return $directory;
}
-
- /**
- * Get global handle for memcache access
- *
- * @return object Memcache
- */
- public function get_cache()
- {
- if (!isset($this->memcache)) {
- // no memcache support in PHP
- if (!class_exists('Memcache')) {
- $this->memcache = false;
- return false;
- }
-
- // add all configured hosts to pool
- $pconnect = $this->conf->get('kolab_wap', 'memcache_pconnect', Conf::BOOL);
- $hosts = $this->conf->get('kolab_wap', 'memcache_hosts');
-
- if ($hosts) {
- $this->memcache = new Memcache;
- $this->mc_available = 0;
-
- $hosts = explode(',', $hosts);
- foreach ($hosts as $host) {
- $host = trim($host);
- if (substr($host, 0, 7) != 'unix://') {
- list($host, $port) = explode(':', $host);
- if (!$port) $port = 11211;
- }
- else {
- $port = 0;
- }
-
- $this->mc_available += intval($this->memcache->addServer(
- $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
- }
-
- // test connection and failover (will result in $this->mc_available == 0 on complete failure)
- $this->memcache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
- }
-
- if (!$this->mc_available) {
- $this->memcache = false;
- }
- }
-
- return $this->memcache;
- }
-
- /**
- * Callback for memcache failure
- */
- public function memcache_failure($host, $port)
- {
- static $seen = array();
-
- // only report once
- if (!$seen["$host:$port"]++) {
- $this->mc_available--;
- $this->_log(LOG_ERR, "Memcache failure on host $host:$port");
- }
- }
-
- /**
- * Get cached data
- *
- * @param string $key Cache key
- *
- * @return mixed Cached value
- */
- public function get_cache_data($key)
- {
- if ($cache = $this->get_cache()) {
- return $cache->get($key);
- }
- }
-
- /**
- * Store cached data
- *
- * @param string $key Cache key
- * @param mixed $data Data
- * @param int $ttl Cache TTL in seconds
- *
- * @return bool False on failure or when cache is disabled, True if data was saved succesfully
- */
- public function set_cache_data($key, $data, $ttl = 3600)
- {
- if ($cache = $this->get_cache()) {
- if (!$cache->replace($key, $data, MEMCACHE_COMPRESSED, $ttl)) {
- return $cache->set($key, $data, MEMCACHE_COMPRESSED, $ttl);
- }
- else {
- return true;
- }
- }
-
- return false;
- }
}
diff --git a/lib/ext/Net/LDAP3.php b/lib/ext/Net/LDAP3.php
index 37a0ea3..00a4483 100644
--- a/lib/ext/Net/LDAP3.php
+++ b/lib/ext/Net/LDAP3.php
@@ -88,6 +88,7 @@ class Net_LDAP3
protected $debug_level = false;
protected $list_page = 1;
protected $page_size = 10;
+ protected $icache = array();
protected $cache;
// Use public method config_set('log_hook', $callback) to have $callback be
@@ -564,7 +565,7 @@ class Net_LDAP3
else if (method_exists($this, "config_set_{$key}")) {
call_user_func_array(array($this, "config_set_$key"), array($value));
}
- else if (isset($this->$key)) {
+ else if (property_exists($this, $key)) {
$this->$key = $value;
}
else {
@@ -872,7 +873,7 @@ class Net_LDAP3
*/
public function entry_dn($subject, $attributes = array(), $base_dn = null)
{
- $this->_debug("entry_dn on subject $subject");
+ $this->_debug("Net_LDAP3::entry_dn($subject)");
$is_dn = ldap_explode_dn($subject, 1);
if (is_array($is_dn) && array_key_exists("count", $is_dn) && $is_dn["count"] > 0) {
@@ -1079,7 +1080,7 @@ class Net_LDAP3
public function login($username, $password, $domain = null, &$attributes = null)
{
- $this->_debug("Net_LDAP3::login(\$username = '" . $username . "', \$password = '****', \$domain = '" . $domain . "')");
+ $this->_debug("Net_LDAP3::login($username,***,$domain)");
$_bind_dn = $this->config_get('service_bind_dn');
$_bind_pw = $this->config_get('service_bind_pw');
@@ -1200,7 +1201,7 @@ class Net_LDAP3
public function list_group_members($dn, $entry = null, $recurse = true)
{
- $this->_debug("Called list_group_members(" . $dn . ")");
+ $this->_debug("Net_LDAP3::list_group_members($dn)");
if (is_array($entry) && in_array('objectclass', $entry)) {
if (!in_array(array('groupofnames', 'groupofuniquenames', 'groupofurls'), $entry['objectclass'])) {
@@ -2051,7 +2052,7 @@ class Net_LDAP3
return array();
}
- if ($this->cache && ($cached_config = $this->cache->get('vlvconfig'))) {
+ if ($cached_config = $this->get_cache_data('vlvconfig')) {
$this->_vlv_indexes_and_searches = $cached_config;
return $this->_vlv_indexes_and_searches;
}
@@ -2109,9 +2110,7 @@ class Net_LDAP3
}
// cache this
- if ($this->cache) {
- $this->cache->set('vlvconfig', $this->_vlv_indexes_and_searches);
- }
+ $this->set_cache_data('vlvconfig', $this->_vlv_indexes_and_searches);
return $this->_vlv_indexes_and_searches;
}
@@ -2175,7 +2174,7 @@ class Net_LDAP3
private function list_group_member($dn, $members, $recurse = true)
{
- $this->_debug("Called list_group_member(" . $dn . ")");
+ $this->_debug("Net_LDAP3::list_group_member($dn)");
$members = (array) $members;
$group_members = array();
@@ -2208,7 +2207,7 @@ class Net_LDAP3
private function list_group_uniquemember($dn, $uniquemembers, $recurse = true)
{
- $this->_debug("Called list_group_uniquemember(" . $dn . ")", $entry);
+ $this->_debug("Net_LDAP3::list_group_uniquemember($dn)", $entry);
$uniquemembers = (array)($uniquemembers);
$group_members = array();
@@ -2239,7 +2238,7 @@ class Net_LDAP3
private function list_group_memberurl($dn, $memberurls, $recurse = true)
{
- $this->_debug("Called list_group_memberurl(" . $dn . ")");
+ $this->_debug("Net_LDAP3::list_group_memberurl($dn)");
$group_members = array();
$memberurls = (array) $memberurls;
@@ -2381,7 +2380,8 @@ class Net_LDAP3
return true;
}
- private function parse_aclrights(&$attributes, $attribute_value) {
+ private function parse_aclrights(&$attributes, $attribute_value)
+ {
$components = explode(':', $attribute_value);
$_acl_target = array_shift($components);
$_acl_value = trim(implode(':', $components));
@@ -2872,4 +2872,243 @@ class Net_LDAP3
return true;
}
+ /**
+ * Get global handle for cache access
+ *
+ * @return object Cache object
+ */
+ public function get_cache()
+ {
+ if ($this->cache === true) {
+ // no memcache support in PHP
+ if (!class_exists('Memcache')) {
+ $this->cache = false;
+ return false;
+ }
+
+ // add all configured hosts to pool
+ $pconnect = $this->config_get('memcache_pconnect');
+ $hosts = $this->config_get('memcache_hosts');
+
+ if ($hosts) {
+ $this->cache = new Memcache;
+ $this->mc_available = 0;
+
+ $hosts = explode(',', $hosts);
+ foreach ($hosts as $host) {
+ $host = trim($host);
+ if (substr($host, 0, 7) != 'unix://') {
+ list($host, $port) = explode(':', $host);
+ if (!$port) $port = 11211;
+ }
+ else {
+ $port = 0;
+ }
+
+ $this->mc_available += intval($this->cache->addServer(
+ $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
+ }
+
+ // test connection and failover (will result in $this->mc_available == 0 on complete failure)
+ $this->cache->increment('__CONNECTIONTEST__', 1); // NOP if key doesn't exist
+ }
+
+ if (!$this->mc_available) {
+ $this->cache = false;
+ }
+ }
+
+ return $this->cache;
+ }
+
+ /**
+ * Callback for memcache failure
+ */
+ public function memcache_failure($host, $port)
+ {
+ static $seen = array();
+
+ // only report once
+ if (!$seen["$host:$port"]++) {
+ $this->mc_available--;
+ $this->_error("Memcache failure on host $host:$port");
+ }
+ }
+
+ /**
+ * Get cached data
+ *
+ * @param string $key Cache key
+ *
+ * @return mixed Cached value
+ */
+ public function get_cache_data($key)
+ {
+ if ($cache = $this->get_cache()) {
+ return $cache->get($key);
+ }
+ }
+
+ /**
+ * Store cached data
+ *
+ * @param string $key Cache key
+ * @param mixed $data Data
+ * @param int $ttl Cache TTL in seconds
+ *
+ * @return bool False on failure or when cache is disabled, True if data was saved succesfully
+ */
+ public function set_cache_data($key, $data, $ttl = 3600)
+ {
+ if ($cache = $this->get_cache()) {
+ if (!method_exists($cache, 'replace') || !$cache->replace($key, $data, MEMCACHE_COMPRESSED, $ttl)) {
+ return $cache->set($key, $data, MEMCACHE_COMPRESSED, $ttl);
+ }
+ else {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Translate a domain name into it's corresponding root dn.
+ *
+ * @param string $domain Domain name
+ *
+ * @return string|bool Domain root DN or False on error
+ */
+ public function domain_root_dn($domain)
+ {
+ if (empty($domain)) {
+ return false;
+ }
+
+ $ckey = 'domain.root::' . $domain;
+ if ($result = $this->icache[$ckey]) {
+ return $result;
+ }
+
+ $this->_debug("Net_LDAP3::domain_root_dn($domain)");
+
+ if ($entry_attrs = $this->find_domain($domain)) {
+ $name_attribute = $this->config_get('domain_name_attribute');
+
+ if (empty($name_attribute)) {
+ $name_attribute = 'associateddomain';
+ }
+
+ if (is_array($entry_attrs)) {
+ if (!empty($entry_attrs['inetdomainbasedn'])) {
+ $domain_root_dn = $entry_attrs['inetdomainbasedn'];
+ }
+ else {
+ if (is_array($entry_attrs[$name_attribute])) {
+ $domain_root_dn = $this->_standard_root_dn($entry_attrs[$name_attribute][0]);
+ }
+ else {
+ $domain_root_dn = $this->_standard_root_dn($entry_attrs[$name_attribute]);
+ }
+ }
+ }
+ }
+
+ if (empty($domain_root_dn)) {
+ $domain_root_dn = $this->_standard_root_dn($domain);
+ }
+
+ $this->_debug("Net_LDAP3::domain_root_dn() result: $domain_root_dn");
+
+ return $this->icache[$ckey] = $domain_root_dn;
+ }
+
+ /**
+ * Find domain by name
+ *
+ * @param string $domain Domain name
+ * @param array $attributes Result attributes
+ *
+ * @return array|bool Domain attributes or False if not found
+ */
+ public function find_domain($domain, $attributes = array('*'))
+ {
+ if (empty($domain)) {
+ return false;
+ }
+
+ $ckey = 'domain::' . $domain;
+ $ickey = $ckey . '::' . md5(implode(',', $attributes));
+
+ if (isset($this->icache[$ickey])) {
+ return $this->icache[$ickey];
+ }
+
+ $this->_debug("Net_LDAP3::find_domain($domain)");
+
+ // use cache
+ $domain_dn = $this->get_cache_data($ckey);
+
+ if ($domain_dn) {
+ $result = $this->get_entry_attributes($domain_dn, $attributes);
+ if (empty($result)) {
+ $result = false;
+ }
+ }
+ else {
+ $domain_base_dn = $this->config_get('domain_base_dn');
+ $domain_filter = $this->config_get('domain_filter');
+ $name_attribute = $this->config_get('domain_name_attribute');
+
+ if (empty($name_attribute)) {
+ $name_attribute = 'associateddomain';
+ }
+
+ $domain_filter = "(&" . $domain_filter . "(" . $name_attribute . "=" . self::quote_string($domain) . "))";
+
+ if ($result = $this->search($domain_base_dn, $domain_filter, 'sub', $attributes)) {
+ $result = $result->entries(true);
+ $domain_dn = key($result);
+ $result = $result[$domain_dn];
+
+ // cache domain DN
+ $this->set_cache_data($ckey, $domain_dn);
+ }
+ }
+
+ $this->_debug("Net_LDAP3::find_domain() result: " . var_export($result, true));
+
+ return $this->icache[$ickey] = $result;
+ }
+
+ /**
+ * From a domain name, such as 'kanarip.com', create a standard root
+ * dn, such as 'dc=kanarip,dc=com'.
+ *
+ * As the parameter $associatedDomains, either pass it an array (such
+ * as may have been returned by ldap_get_entries() or perhaps
+ * ldap_list()), where the function will assume the first value
+ * ($array[0]) to be the uber-level domain name, or pass it a string
+ * such as 'kanarip.nl'.
+ *
+ * @return string
+ */
+ protected function _standard_root_dn($associatedDomains)
+ {
+ if (is_array($associatedDomains)) {
+ // Usually, the associatedDomain in position 0 is the naming attribute associatedDomain
+ if ($associatedDomains['count'] > 1) {
+ // Issue a debug message here
+ $relevant_associatedDomain = $associatedDomains[0];
+ }
+ else {
+ $relevant_associatedDomain = $associatedDomains[0];
+ }
+ }
+ else {
+ $relevant_associatedDomain = $associatedDomains;
+ }
+
+ return 'dc=' . implode(',dc=', explode('.', $relevant_associatedDomain));
+ }
}
More information about the commits
mailing list