Skip to end of metadata
Go to start of metadata

<h1>Zend_Auth_Adapter_Ldap Operator's Guide</h1>

<p>This page describes the <code>Zend_Auth_Adapter_Ldap</code> class for performaing LDAP based authentication. Also described is the zf-ldap demo application used to exercise the adapter. The <a href="http://framework.zend.com/wiki/display/ZFPROP/Zend_Ldap+-+Michael+B.+Allen">Zend_Ldap</a> and <code>Zend_Ldap_Exception</code> classes will also be referenced as they perform the bulk of the work including binding and username canonicalization.</p>

<ac:macro ac:name="info"><ac:parameter ac:name="title">The Zend_Auth_Adapter_Ldap Proposal</ac:parameter><ac:rich-text-body>
<p>This work has been submitted for inclusion in Zend Framework. See the <a href="http://framework.zend.com/wiki/display/ZFPROP/Zend_Auth_Adapter_Ldap+-+Michael+B.+Allen">Zend_Auth_Adapter_Ldap proposal page</a> for details (not to be confused with the <a href="http://framework.zend.com/wiki/x/fmM">old proposal</a>).</p></ac:rich-text-body></ac:macro>

<ac:macro ac:name="toc" />

<h2>Installation</h2>

<p>This adapter is available in the incubator subdirectory in SVN. See the <em>Anonymous Checkout</em> and <em>Using the Zend Framework Incubator Components</em> sections at the below link for details:</p>

<p><a href="http://framework.zend.com/wiki/display/ZFDEV/Subversion+Standards">http://framework.zend.com/wiki/display/ZFDEV/Subversion+Standards</a></p>

<p>This guide also references a demo application in the <code>zf-ldap</code> package available at <a href="http://www.ioplex.com/code/">http://www.ioplex.com/code/</a>. The simple <code>Zend_Controller</code> based application provides a login form and index page that prints the authenticated user's identity and any result messages. The <code>zf-ldap</code> package also contains the aforementioned LDAP classes although they are not current compared to SVN and will not be updated regularly.</p>

<p>To install the <code>zf-ldap</code> package, unpack it, export the <code>html</code> directory through your web server and adjust <code>html/.htaccess</code> or do what is necessary for your web server to execute a <code>Zend_Controller</code> based application. See the <code>Zend_Controller</code> documentation for details regarding setting up and running a <code>Zend_Controller</code> application.</p>

<h2>Usage</h2>

<p>To incorporate <code>Zend_Auth_Adapter_Ldap</code> authentication into your application quickly, just copy the demo application's <code>application/controllers/UserController.php</code> code. Even if you're not using <code>Zend_Controller</code>, the meat of your code should look something like the following:</p>

<ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
$username = $this->_request->getParam('username');
$password = $this->_request->getParam('password');

$auth = Zend_Auth::getInstance();

require_once('Zend/Config/Ini.php');
$config = new Zend_Config_Ini('../application/config/config.ini', 'production');
$log_path = $config->ldap->log_path;
$options = $config->ldap->toArray();
unset($options['log_path']);

require_once 'Zend/Auth/Adapter/Ldap.php';
$adapter = new Zend_Auth_Adapter_Ldap($options, $username, $password);

$result = $auth->authenticate($adapter);

if ($log_path) {
$messages = $result->getMessages();

require_once 'Zend/Log.php';
require_once 'Zend/Log/Writer/Stream.php';
require_once 'Zend/Log/Filter/Priority.php';
$logger = new Zend_Log();
$logger->addWriter(new Zend_Log_Writer_Stream($log_path));
$filter = new Zend_Log_Filter_Priority(Zend_Log::DEBUG);
$logger->addFilter($filter);

foreach ($messages as $i => $message) {
if ($i-- > 1)

Unknown macro: { // $messages[2] and up are log messages $message = str_replace("n", "n ", $message); $logger->log("Ldap}

}
}

]]></ac:plain-text-body></ac:macro>

<p>Of course the logging code is optional but it is highly recommended that you use a logger. <code>Zend_Auth_Adapter_Ldap</code> will record just about every bit of information anyone could want in <code>$messages</code> (more below) which is a nice feature in itself for something that has a history of being notoriously difficult to debug.</p>

<p>The <code>Zend_Config_Ini</code> code is used above to load the adapter options. It is also optional. A regular array would work equally well. The following is an example <code>application/config/config.ini</code> file that has options for two separate servers. With multiple sets of server options the adapter will try each in-order until the credentials are successfully authenticated. The names of the servers (e.g. <code>server1</code> and <code>server2</code>) are largely arbitrary. For details regarding the options array, see the <em>Server Options</em> section below. Note that <code>Zend_Config_Ini</code> requires that any values with equals characters (=) will need to be quoted (like the DNs shown below).</p>

<ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
[production]

ldap.log_path = /tmp/ldap.log

; Typical options for OpenLDAP
ldap.server1.host = s0.foo.net
ldap.server1.accountDomainName = foo.net
ldap.server1.accountDomainNameShort = FOO
ldap.server1.accountCanonicalForm = 3
ldap.server1.username = "CN=user1,DC=foo,DC=net"
ldap.server1.password = pass1
ldap.server1.baseDn = "OU=Sales,DC=foo,DC=net"
ldap.server1.bindRequiresDn = true

; Typical options for Active Directory
ldap.server2.host = dc1.w.net
ldap.server2.useSsl = true
ldap.server2.accountDomainName = w.net
ldap.server2.accountDomainNameShort = W
ldap.server2.accountCanonicalForm = 3
ldap.server2.baseDn = "CN=Users,DC=w,DC=net"
]]></ac:plain-text-body></ac:macro>

<p>The above configuration will instruct <code>Zend_Auth_Adapter_Ldap</code> to attempt to authenticate users with the OpenLDAP server <code>s0.foo.net</code> first. If the authentication fails for any reason, the AD server <code>dc1.w.net</code> will be tried.</p>

<p>With servers in different domains, this configuration illustrates multi-domain authentication. You can also have multiple servers in the same domain to provide redundancy.</p>

<p>Note that in this case, even though OpenLDAP has no need for the short NetBIOS style domain name used by Windows we provide it here for name canonicalization purposes (described in the <em>Username Canonicalization</em> section below).</p>

<h2>The API</h2>

<p>The <code>Zend_Auth_Adapter_Ldap</code> constructor accepts three parameters.</p>

<p>The <code>$options</code> parameter is required and must be an array containing one or more sets of options. Note that it is <em>an array of arrays</em> of <a href="http://framework.zend.com/wiki/display/ZFPROP/Zend_Ldap+-+Michael+B.+Allen">Zend_Ldap</a> options. Even if you will be using only one LDAP server, the options must still be within another array.</p>

<p>Below is <code>print_r</code> output of an example options parameter containing two sets of server options for LDAP servers <code>s0.foo.net</code> and <code>dc1.w.net</code> (same options as the above INI representation):</p>

<ac:macro ac:name="noformat"><ac:plain-text-body><![CDATA[
Array
(
[server2] => Array
(
[host] => dc1.w.net
[useSsl] => 1
[accountDomainName] => w.net
[accountDomainNameShort] => W
[accountCanonicalForm] => 3
[baseDn] => CN=Users,DC=w,DC=net
)

[server1] => Array
(
[host] => s0.foo.net
[accountDomainName] => foo.net
[accountDomainNameShort] => FOO
[accountCanonicalForm] => 3
[username] => CN=user1,DC=foo,DC=net
[password] => pass1
[baseDn] => OU=Sales,DC=foo,DC=net
[bindRequiresDn] => 1
)

)
]]></ac:plain-text-body></ac:macro>

<p>The information provided in each set of options above is different mainly because AD does not require a username be in DN form when binding (see the <code>bindRequiresDn</code> option in the <em>Server Options</em> section below) which means we can omit the a number of options associated with retrieving the DN for a username being authenticated.</p>

<ac:macro ac:name="info"><ac:parameter ac:name="title">What is a DN?</ac:parameter><ac:rich-text-body>
<p>A DN or "distinguished name" is a string that represents the path to an object within the LDAP directory. Each comma separated component is an attribute and value representing a node. The components are evaluated in reverse. For example, the user account <em>CN=Bob Carter,CN=Users,DC=w,DC=net</em> is located directly within the <em>CN=Users,DC=w,DC=net</em> container. This structure is best explored with an LDAP browser like the ADSI Edit MMC snap-in for Active Directory or phpLDAPadmin.</p></ac:rich-text-body></ac:macro>

<p>The names of servers (e.g. 'server1' and 'server2' shown above) are largely arbitrary but for the sake of using <code>Zend_Config</code> the identifiers should be present (as opposed to being numeric indexes) and should not contain any special characters used by the associated file formats (e.g. the '.' INI property separator, '&' for XML entity references, etc).</p>

<p>With multiple sets of server options, the adapter can authenticate users in multiple domains and provide failover so that if one server is not available, another will be queried.</p>

<ac:macro ac:name="info"><ac:parameter ac:name="title">The Gory Details - What exactly happens in the authenticate method?</ac:parameter><ac:rich-text-body>
<p>When the <code>authenticate()</code> method is called, the adapter iterates over each set of server options, sets them on the internal <code>Zend_Ldap</code> instance and calls the <code>Zend_Ldap::bind()</code> method with the username and password being authenticated. The <code>Zend_Ldap</code> class checks to see if the username is qualified with a domain (e.g. has a domain component like <em>alice@foo.net</em> or <em>FOO\alice</em>). If a domain is present but it does not match either of the server's domain names (<em>foo.net</em> or <em>FOO</em>), a special exception is thrown and caught by <code>Zend_Auth_Adapter_Ldap</code> that causes that server to be ignored and the next set of server options is selected. If a domain <em>does</em> match, or if the user did not supply a qualified username, <code>Zend_Ldap</code> proceeds to try to bind with the supplied credentials. If the bind is not successful, <code>Zend_Ldap</code> throws a <code>Zend_Ldap_Exception</code> which is caught by <code>Zend_Auth_Adapter_Ldap</code> and the next set of server options is tried. If the bind is successful, the iteration stops and the adapter's <code>authenticate()</code> method returns success. If all server options have been tried without success, the authentication fails and <code>authenticate()</code> returns a failure result with error messages from the last iteration.</p></ac:rich-text-body></ac:macro>

<p>The username and password parameters of the <code>Zend_Auth_Adapter_Ldap</code> constructor represent the credentials being authenticated (i.e. the credentials supplied by the user through your HTML login form). Alternatively they may also be set with the <code>setUsername()</code> and <code>setPassword()</code> methods.</p>

<h2>Server Options</h2>

<p>Each set of server options <em>in the context of Zend_Auth_Adapter_Ldap</em> must consist of the following. Options are passed largely unmodifed to <code>Zend_Ldap::setOptions()</code>.</p>

<table><tbody>
<tr>
<th><p>Name</p></th>
<th><p>Description</p></th>
</tr>
<tr>
<td><p><strong>host</strong> </p></td>
<td><p> The hostname of LDAP server that these options represent. This option is required.</p></td>
</tr>
<tr>
<td><p><strong>port</strong> </p></td>
<td><p> The port on which the LDAP server is listening. If <strong>useSsl</strong> is <code>true</code> the default <strong>port</strong> value is 636. If <strong>useSsl</strong> is <code>false</code> the default <strong>port</strong> value is 389.</p></td>
</tr>
<tr>
<td><p><strong>useSsl</strong> </p></td>
<td><p> If <code>true</code>, this value indicates that the LDAP client should use SSL / TLS encrypted transport. A value of <code>true</code> is strongly favored in production environments to prevent passwords from be transmitted in clear text. The default value is <code>false</code> as servers frequently require that a certificate be installed separately after installation. This value also changes the default <strong>port</strong> value (see <strong>port</strong> description above).</p></td>
</tr>
<tr>
<td><p><strong>username</strong> </p></td>
<td><p> The DN of the account used to perform account DN lookups. LDAP servers that require the username to be in DN form when performing the "bind" require this option. Meaning, if <strong>bindRequiresDn</strong> is <code>true</code>, this option is required. This account does not need to be a privileged account - a account with read-only access to objects under the <strong>baseDn</strong> is all that is necessary (and preferred based on the <em>Principle of Least Privilege</em>).</p></td>
</tr>
<tr>
<td><p><strong>password</strong> </p></td>
<td><p> The password of the account used to perform account DN lookups. If this option is not supplied, the LDAP client will attempt an "anonymous bind" when performing account DN lookups.</p></td>
</tr>
<tr>
<td><p><strong>bindRequiresDn</strong> </p></td>
<td><p> Some LDAP servers require that the username used to bind be in DN form like <em>CN=Alice Baker,OU=Sales,DC=foo,DC=net</em> (basically all servers <em>except</em> AD). If this option is <code>true</code>, this instructs <code>Zend_Ldap</code> to automatically retrieve the DN corresponding to the username being authenticated if it is not already in DN form and then re-bind with the proper DN. The default value is <code>false</code>. Currently only Microsoft Active Directory Server (ADS) is known <em>not</em> to require usernames to be in DN form when binding and therefore this option may be <code>false</code> with AD (and it should be as retrieving the DN requires an extra round trip to the server). Otherwise this option must be set to <code>true</code> (e.g. for OpenLDAP). This option also controls the default <strong>acountFilterFormat</strong> used when searching for accounts. See the <strong>accountFilterFormat</strong> option.</p></td>
</tr>
<tr>
<td><p><strong>baseDn</strong> </p></td>
<td><p> The DN under which all accounts being authenticated are located. This option is required. If you are uncertain about the correct <strong>baseDn</strong> value, it should be sufficient to derive it from the user's DNS domain using <em>DC=</em> components. For example, if the user's principal name is <em>alice@foo.net</em>, a <strong>baseDn</strong> of <em>DC=foo,DC=net</em> should work. A more precise location (e.g. <em>OU=Sales,DC=foo,DC=net</em>) will be more efficient however.</p></td>
</tr>
<tr>
<td><p><strong>accountCanonicalForm</strong> </p></td>
<td><p> A value of 2, 3 or 4 indicating the form account names should be canonicalized to after successful authentication. Values are as follows: 2 for traditional username style names (e.g. <em>alice</em>), 3 for backslash style names (e.g. <em>FOO\alice</em>) or 4 for principal style usernames (e.g. <em>alice@foo.net</em>). The default value is 4 (e.g. <em>alice@foo.net</em>). For example, with a value of 3, the identity returned by <code>Zend_Result::getIdentity()</code> (and <code>Zend_Auth::getIdentity()</code> if <code>Zend_Auth</code> was used) will always be <em>FOO\alice</em> regardless of what form Alice supplied whether it be <em>alice</em>, <em>alice@foo.net</em>, <em>FOO\alice</em>, <em>FoO\aLicE</em>, <em>foo.net\alice</em>, etc. See the <em>Account Name Canonicalization</em> section in the <a href="http://framework.zend.com/wiki/display/ZFPROP/Zend_Ldap+-+Michael+B.+Allen">Zend_Ldap</a> documentation for details. Note that when using multiple sets of server options it is recommended, but not required, that the same <strong>accountCanonicalForm</strong> be used with all server options so that the resulting usernames are always canonicalized to the same form (e.g. if you canonicalize to <em>EXAMPLE\username</em> with an AD server but <em>username@example.com</em> with an OpenLDAP that may be awkward for the application's higher level logic).</p></td>
</tr>
<tr>
<td><p><strong>accountDomainName</strong> </p></td>
<td><p> The FQDN domain name for which the target LDAP server is an authority (e.g. <em>example.com</em>). This option is used to canonicalize names so that the username supplied by the user can be converted as necessary for binding. It is also used to determine if the server is an authority for the supplied username (e.g. if <strong>accountDomainName</strong> is <em>foo.net</em> and the user supplies <em>bob@bar.net</em>, the server will not be queried and a failure will result). This option is not required but if it is not supplied, usernames in principal name form (e.g. <em>alice@foo.net</em>) are not supported. It is strongly recommended that you supply this option as there are many use-cases that require generating the principal name form.</p></td>
</tr>
<tr>
<td><p><strong>accountDomainNameShort</strong> </p></td>
<td><p> The 'short' domain for which the target LDAP server is an authority (e.g. <em>FOO</em>). Note that there is a 1:1 mapping between the <strong>accountDomainName</strong> and <strong>accountDomainNameShort</strong>. This option should be used to specify the NetBIOS domain name for Windows networks but may also be used by non-AD servers (e.g. for consistency when multiple sets of server options with the backslash style <strong>accountCanonicalForm</strong>). This option is not required but if it is not supplied, usernames in backslash form (e.g. <em>FOO\alice</em>) are not supported.</p></td>
</tr>
<tr>
<td><p><strong>accountFilterFormat</strong> </p></td>
<td><p> The LDAP search filter used to search for accounts. This string is a <code>printf</code> style expression that must contain one <code>'%s'</code> to accomodate the username. The default value is <code>'(&(objectClass=user)(sAMAccountName=%s))'</code> unless <strong>bindRequiresDn</strong> is set to <code>true</code> in which case the default is <code>'(&(objectClass=posixAccount)(uid=%s))'</code>. For example, if for some reason you wanted to use <code>bindRequiresDn = true</code> with AD you would need to set <code>accountFilterform = '(&(objectClass=user)(sAMAccountName=%s))'</code>.</p></td>
</tr>
</tbody></table>

<ac:macro ac:name="tip"><ac:rich-text-body>
<p>If you enable <code>useSsl = true</code> you may find that the LDAP client may generate an error claiming that it cannot validate the server's certificate. Assuming the PHP LDAP extension is ultimately linked to the OpenLDAP client libraries, to resolve this issue you can set <code>TLS_REQCERT never</code> in the OpenLDAP client <code>ldap.conf</code> (and restart the web server) to indicate to the OpenLDAP client library that you trust the server. Alternatively if you are concerned that the server could be spoofed (not usually the weakest link in an IntrAnet environment) you can export the LDAP server's root certificate and put it on the web server so that the OpenLDAP client can validate the server's identity.</p></ac:rich-text-body></ac:macro>

<h2>Collecting Debugging Messages</h2>

<p><code>Zend_Auth_Adapter_Ldap</code> collects debugging information within it's <code>authenticate()</code> method. This information is stored in the <code>Zend_Auth_Result</code> object as messages. The array returned by <code>Zend_Auth_Result::getMessages()</code> is described as follows.</p>

<table><tbody>
<tr>
<th><p>Messages Array Index</p></th>
<th><p>String Description</p></th>
</tr>
<tr>
<td><p>Index 0</p></td>
<td><p>An overall message that is suitable for displaying to users (e.g. Invalid credentials). If the authentication is successful, this string is empty.</p></td>
</tr>
<tr>
<td><p>Index 1</p></td>
<td><p>A more detailed error message that is not suitable to be displayed to users but should be logged for the benifit of server opterators. If the authentication is successful, this string is empty.</p></td>
</tr>
<tr>
<td><p>Indexes 2 and higher</p></td>
<td><p>All log messages in order starting at index 2.</p></td>
</tr>
</tbody></table>

<p>In practice index 0 should be displayed to the user (e.g. using the FlashMessenger helper), index 1 should be logged and, if debugging information is being collected, indexes 2 and higher could be logged as well (although the final message always includes the string from index 1).</p>

<h2>Common Options for Specific Servers</h2>

<h3><em>Options for Active Directory</em></h3>

<p>For ADS, the following options are noteworthy:</p>

<table><tbody>
<tr>
<th><p>Name</p></th>
<th><p>Additional Notes</p></th>
</tr>
<tr>
<td><p><strong>host</strong> </p></td>
<td><p> As with all servers, this option is required.</p></td>
</tr>
<tr>
<td><p><strong>useSsl</strong> </p></td>
<td><p> For the sake of security, this should be <code>true</code> if the server has the necessary certificate installed.</p></td>
</tr>
<tr>
<td><p><strong>baseDn</strong> </p></td>
<td><p> As with all servers, this option is required. By default AD places all user accounts are under the <em>Users</em> container (e.g. <em>CN=Users,DC=foo,DC=net</em>) but the default is not common in larger organizations. Ask your AD administrator what the best DN for accounts for your application would be.</p></td>
</tr>
<tr>
<td><p><strong>accountCanonicalForm</strong> </p></td>
<td><p> You almost certainly want this to be 3 for backslash style names (e.g. <em>FOO\alice</em>) which are most familar to Windows users. You should <em>not</em> use the unqualified form 2 (e.g. <em>alice</em>) as this may grant access to your application to users with the same username in other trusted domains (e.g. <em>BAR\alice</em> and <em>FOO\alice</em> will be treated as the same user) <ac:link><ri:page ri:content-title="1" /></ac:link>.</p></td>
</tr>
<tr>
<td><p><strong>accountDomainName</strong> </p></td>
<td><p> This is required with AD unless <strong>accountCanonicalForm</strong> 2 is used which, again, is discouraged.</p></td>
</tr>
<tr>
<td><p><strong>accountDomainNameShort</strong> </p></td>
<td><p> The NetBIOS name of the domain users are in and for which the AD server is in authority. This is required if the backslash style <strong>accountCanonicalForm</strong> is used.</p></td>
</tr>
</tbody></table>

<p><ac:link><ri:page ri:content-title="1" /></ac:link> Technically there should be no danger of accidental cross-domain authentication with the current <code>Zend_Auth_Adapter_Ldap</code> implementation since server domains are explicitly checked but this may not be true of a future implementation that discovers the domain at runtime or if an alternative adapter is used (e.g. Kerberos). In general, account name ambiguity is known to be the source of security issues so always try to use qualified account names.</p>

<h3><em>Options for OpenLDAP</em></h3>

<p>For OpenLDAP or a generic LDAP server using a typical posixAccount style schema, the following options are noteworthy:</p>

<table><tbody>
<tr>
<th><p>Name</p></th>
<th><p>Additional Notes</p></th>
</tr>
<tr>
<td><p><strong>host</strong> </p></td>
<td><p> As with all servers, this option is required.</p></td>
</tr>
<tr>
<td><p><strong>useSsl</strong> </p></td>
<td><p> For the sake of security, this should be <code>true</code> if the server has the necessary certificate installed.</p></td>
</tr>
<tr>
<td><p><strong>username</strong> </p></td>
<td><p> Required and must be a DN as OpenLDAP requires that usernames be in DN form when performing a bind. Try to use an unprivileged account.</p></td>
</tr>
<tr>
<td><p><strong>password</strong> </p></td>
<td><p> The password corresponding to the username above but this may be omitted if the LDAP server supports anonymous binds.</p></td>
</tr>
<tr>
<td><p><strong>bindRequiresDn</strong> </p></td>
<td><p> Required and must be <code>true</code> as OpenLDAP requires that usernames be in DN form when performing a bind.</p></td>
</tr>
<tr>
<td><p><strong>baseDn</strong> </p></td>
<td><p> As with all servers this option is required and indicates the DN under which all accounts being authenticated are located.</p></td>
</tr>
<tr>
<td><p><strong>accountCanonicalForm</strong> </p></td>
<td><p> Optional but the default value is 4 (principal style names like <em>alice@foo.net</em>) which may not be ideal if your users are used to backslash style names (e.g. <em>FOO\alice</em>). For backslash style names use value 3.</p></td>
</tr>
<tr>
<td><p><strong>accountDomainName</strong> </p></td>
<td><p> Required unless you're using <strong>accountCanonicalForm</strong> 2 which is not recommended.</p></td>
</tr>
<tr>
<td><p><strong>accountDomainNameShort</strong> </p></td>
<td><p> If AD is not also being used, this value is not required. Otherwise, if <strong>accountCanonicalForm</strong> 3 is used, this option is required and should be a short name that corresponds adequately to the <strong>accountDomainName</strong> (e.g. if your <strong>accountDomainName</strong> is <em>foo.net</em> a good <strong>accountDomainNameShort</strong> value might be <em>FOO</em>).</p></td>
</tr>
<tr>
<td><p><strong>accountFilterFormat</strong> </p></td>
<td><p> If you are not using the posixAccount object class for your accounts you will need to set this option. Otherwise the default is <code>'(&(objectClass=posixAccount)(uid=%s))'</code>.</p></td>
</tr>
</tbody></table>

<h2>Obtaining a Network Packet Capture on the Web Server</h2>

<p>This section describes how to obtain a network packet capture used by Zend_Ldap developers to diagnose failures. Note that all captures must be taken from a machine with access to LDAP traffic between the web server running the Zend_Ldap code and the directory server (e.g. the web server).</p>

<ac:macro ac:name="warning"><ac:parameter ac:name="title">Sensitive Information</ac:parameter><ac:rich-text-body>
<p>Packet captures must be obtained with <code>useSsl=false</code> which means passwords supplied to the authentication adapter will be captured in the capture file. You may prefer to use a temporary account or change your password before sharing the capture file with anyone. In general, network capture files can contain sensitive information about your network and users and should not be shared with people that you do not trust.</p></ac:rich-text-body></ac:macro>

<h3>Obtaining a Packet Capture on Linux</h3>

<p>Install the <em>tcpdump</em> program on the Linux web server. Make sure <code>useSsl=false</code> and run <em>tcpdump</em> as follows:</p>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
tcpdump -s 0 -w out.pcap port ldap
]]></ac:plain-text-body></ac:macro>
<p>Try the operation of interest to provoke the behavior being diagnosed. Press Ctrl-C to stop the capture. Try not to run the capture for too long as it may collect unwanted traffic. This should produce an <code>out.pcap</code> file in the current directory.</p>

<h3>Obtaining a Packet Capture on Windows</h3>

<p>For a Windows web server, install the <em>XP Support Tools</em> from CD-ROM as described in the following KB article:</p>

<p><a href="http://support.microsoft.com/kb/306794/EN-US/">http://support.microsoft.com/kb/306794/EN-US/</a></p>

<p>Run <em>cmd.exe</em> and then <em>netcap.exe /?</em> to see if it was installed properly.</p>

<ac:macro ac:name="info"><ac:rich-text-body>
<p>If you have multiple network interfaces look at the end of the output of netcap.exe for the list of adapters. If the one your LDAP traffic is on is not primary, you will need to specify <em>/N:<number></em> where number is the numeric value for that adapter so make a note of that value.</p></ac:rich-text-body></ac:macro>
<p>Now run <em>netcap.exe</em> again as illustrated here (adding the <em>/N:<number></em> if you need to specify a particular adapter):</p>
<ac:macro ac:name="code"><ac:plain-text-body><![CDATA[
C:\tmp>netcap /c:out.cap
]]></ac:plain-text-body></ac:macro>
<p>This will start to write network packets to the named file out.cap in the current directory. Now try the Zend_Ldap authentication adapter and press the space bar in the <em>cmd.exe</em> window to stop the capture. Try not to allow the capture to run for too long as it may collect unwanted traffic.</p>

<p>The above procedure may also work for other versions of Windows. If you have NetMon installed you may use that as well.</p>

Labels:
ldap ldap Delete
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Nov 05, 2007

    <p>I think this will be a valuable addition to the framework. I will have a need for an LDAP adapter like this in my own project, in about 6 to 9 months. Thank you for contributing this!</p>

  2. Nov 27, 2007

    <p>As per our offline conversation some time back..</p>

    <p>The signature of the adapter should prob be in line with this structure:</p>

    <p>class Zendx_Auth_Adapter_Ldap {<br />
    __construct($options, $username = null, $password = null) {}<br />
    }</p>

    <p>This will allow a "configured adapter" that is user independent at creation time, but allows for the adapter to accept usernames and passwords via accessors.</p>

    <p>I think the general rule I would like to see adhered to is the "Any adapter object should be capable of authenticating multiple users from a singular configured source."</p>

    <p>-ralph</p>

  3. Jan 23, 2008

    <p>I recently attempted to upgrade from Zend_Ldap 0.3 to version 0.7. Authentication is successful in version 0.3 and fails in version 0.7 with the following error message:</p>

    <p>2008-01-23T10:45:39-06:00 DEBUG (7): Invalid credentials<br />
    2008-01-23T10:45:39-06:00 DEBUG (7): 0x31: Invalid credentials:<br />
    80090308: LdapErr: DSID-0C090334, comment: AcceptSecurityContext error,<br />
    data 525, vece:</p>

    <p>The following is inside my config.ini:<br />
    ldap.server1.host = corp.example.net<br />
    ldap.server1.useSsl = false<br />
    ldap.server1.accountDomainName = example.net<br />
    ldap.server1.accountDomainNameShort = example<br />
    ldap.server1.username = "cn=proxyuser,cn=users;dc=corp,dc=example,dc=net"<br />
    ldap.server1.password = "password"<br />
    ldap.server1.baseDn = "ou=Corporate Users;dc=corp,dc=example,dc=net"<br />
    ldap.server1.accountFilterFormat = "(&(objectClass=user)(sAMAccountName=%s))"</p>

    <p>I also tried using proxyuser@example.net for the username with the same results. These are the same settings I use in version 0.3, updated to their version 0.7 counterparts.</p>

    1. Jan 23, 2008

      <p>You seem to have semicolons in your DNs where there should be commas. Is that just a typo in your post? If not, that would definitely cause an "Invalid credentials" error.</p>

      <p>If it is just a typo, try making accountDomainNameShort UPPERCASE. I would be surprised if that had any impact on anything but I don't recall testing the latest adapter with a lowercase value.</p>

      <p>Incedentally, you could remove useSsl and accountFilterFormat and it would have no impact on your config.</p>

      1. Jan 23, 2008

        <ac:macro ac:name="unmigrated-wiki-markup"><ac:plain-text-body><![CDATA[Actually it seems this is a bug. RFC 2253 says:

        Implementations MUST allow a semicolon character to be used instead
        of a comma to separate RDNs in a distinguished name, and MUST also
        allow whitespace characters to be present on either side of the comma
        or semicolon.

        Currently the _isDnString method does:

        545 protected function _isDnString($str)
        546

        Unknown macro: { 547 return $str && stristr($str, ',DC='); 548 }

        This will need to be changed to a stateful parser (which is probably more efficient anyway).

        I will create a fix and put the patch in the issue tracker.

        Mike
        ]]></ac:plain-text-body></ac:macro>

        1. Jan 24, 2008

          <p>Thanks for the tips. The semi-colons are new to me as well. I had the same response you did when I first received the path.</p>

          <p>Can you please post the issue tracker key number for the bug when you have it?</p>

          <p>Thanks,<br />
          Jacob</p>

          1. Jan 24, 2008

            <p>Created issue ZF-2474: Zend_Ldap::_isDnString() must allow semicolon per RFC</p>

            <p>Note that semicolons vs comma should have no impact on behavior. Commas should work equally well.</p>

            1. Jan 24, 2008

              <p>I tried using commas with the same result.</p>

              1. Jan 24, 2008

                <p>If you are still getting precisely the same error as before, that means that either A) the username and password supplied are in fact incorrect or B) the adapter is emitting a username and password that is incorrect. For example, regarding case A, the username must correspond to sAMAccountName@accountDomainName as per the documentation. Regarding case B, the adapter could be incorrectly canonicalizing the username or more generally submitting invalid username and password values.</p>

                <p>If you cannot determine which is the case, I will need a network packet capture. I have just added a section to the end of this guide describing that procedure. Please send the capture file to only me directly.</p>

          2. Jan 24, 2008

            <p>Created issue ZF-2474: Zend_Ldap::_isDnString() must allow semicolon per RFC</p>

            <p>Note that semi-colons vs. commas should have no impact on Zend_Ldap behavior. Please just use commas for now.</p>