protected function _connect()
{
/* Connecting is briefly described in RFC1777. Basicly it works like
* this:
* 1. set up TCP connection
* 2. secure that connection if neccessary
* 3a. setVersion to tell server which version we want to speak
* 3b. perform bind
* 3c. setVersion to tell server which version we want to speak
* together with a test for supported versions
* 4. set additional protocol options */
/* Return if we are already connected. */
if ($this->_link) {
return;
}
/* Connnect to the LDAP server if we are not connected. Note that
* ldap_connect() may return a link value even if no connection is
* made. We need to do at least one anonymous bind to ensure that a
* connection is actually valid.
*
* See: http://www.php.net/manual/en/function.ldap-connect.php */
/* Default error message in case all connection attempts fail but no
* message is set. */
$current_error = new Horde_Ldap_Exception('Unknown connection error');
/* Catch empty $_hostList arrays. */
if (!is_array($this->_hostList) || !count($this->_hostList)) {
throw new Horde_Ldap_Exception('No servers configured');
}
/* Cycle through the host list. */
foreach ($this->_hostList as $host) {
/* Ensure we have a valid string for host name. */
if (is_array($host)) {
$current_error = new Horde_Ldap_Exception('No Servers configured');
continue;
}
/* Skip this host if it is known to be down. */
if (in_array($host, $this->_downHostList)) {
continue;
}
/* Record the host that we are actually connecting to in case we
* need it later. */
$this->_config['hostspec'] = $host;
/* The ldap extension doesn't allow to provide connection timeouts
* and seems to default to 2 minutes. Open a socket manually
* instead to ping the server. */
$failed = true;
$url = @parse_url($host);
$sockhost = !empty($url['host']) ? $url['host'] : $host;
if ($fp = @fsockopen($sockhost, $this->_config['port'], $errno, $errstr, $this->_config['timeout'])) {
$failed = false;
fclose($fp);
}
/* Attempt a connection. */
if (!$failed) {
$this->_link = @ldap_connect($host, $this->_config['port']);
}
if (!$this->_link) {
$current_error = new Horde_Ldap_Exception('Could not connect to ' . $host . ':' . $this->_config['port']);
$this->_downHostList[] = $host;
continue;
}
/* If we're supposed to use TLS, do so before we try to bind, as
* some strict servers only allow binding via secure
* connections. */
if ($this->_config['tls']) {
try {
$this->startTLS();
} catch (Horde_Ldap_Exception $e) {
$current_error = $e;
$this->_link = false;
$this->_downHostList[] = $host;
continue;
}
}
/* Try to set the configured LDAP version on the connection if LDAP
* server needs that before binding (eg OpenLDAP).
* This could be necessary since RFC 1777 states that the protocol
* version has to be set at the bind request.
* We use force here which means that the test in the rootDSE is
* skipped; this is neccessary, because some strict LDAP servers
* only allow to read the LDAP rootDSE (which tells us the
* supported protocol versions) with authenticated clients.
* This may fail in which case we try again after binding.
* In this case, most probably the bind() or setVersion() call
* below will also fail, providing error messages. */
$version_set = false;
$this->setVersion(0, true);
/* Attempt to bind to the server. If we have credentials
* configured, we try to use them, otherwise it's an anonymous
* bind.
* As stated by RFC 1777, the bind request should be the first
* operation to be performed after the connection is established.
* This may give an protocol error if the server does not support
* v2 binds and the above call to setVersion() failed.
* If the above call failed, we try an v2 bind here and set the
* version afterwards (with checking to the rootDSE). */
try {
$this->bind();
} catch (Exception $e) {
/* The bind failed, discard link and save error msg.
* Then record the host as down and try next one. */
if ($this->errorName($e->getCode()) == 'LDAP_PROTOCOL_ERROR' && !$version_set) {
/* Provide a finer grained error message if protocol error
* arises because of invalid version. */
$e = new Horde_Ldap_Exception($e->getMessage() . ' (could not set LDAP protocol version to ' . $this->_config['version'] . ')', $e->getCode());
}
$this->_link = false;
$current_error = $e;
$this->_downHostList[] = $host;
continue;
}
/* Set desired LDAP version if not successfully set before.
* Here, a check against the rootDSE is performed, so we get a
* error message if the server does not support the version.
* The rootDSE entry should tell us which LDAP versions are
* supported. However, some strict LDAP servers only allow
* bound users to read the rootDSE. */
if (!$version_set) {
try {
$this->setVersion();
} catch (Exception $e) {
$current_error = $e;
$this->_link = false;
$this->_downHostList[] = $host;
continue;
}
}
/* Set LDAP parameters, now that we know we have a valid
* connection. */
if (isset($this->_config['options']) && is_array($this->_config['options']) && count($this->_config['options'])) {
foreach ($this->_config['options'] as $opt => $val) {
try {
$this->setOption($opt, $val);
} catch (Exception $e) {
$current_error = $e;
$this->_link = false;
$this->_downHostList[] = $host;
continue 2;
}
}
}
/* At this stage we have connected, bound, and set up options, so
* we have a known good LDAP server. Time to go home. */
return;
}
/* All connection attempts have failed, return the last error. */
throw $current_error;
}