php_ldap - Try StartTLS but keep going when it fails - php

Because of various customers environments running the same code, I'm trying to build an LDAP authentification system with PHP that may work in 3 modes :
Do not use StartTLS at all (for when the LDAP server prefers LDAPS instead)
Try StartTLS but keep going unsecured if the LDAP server refuses TLS
Try StartTLS and abort authentication if the LDAP server refuses TLS
Here is a simplified version of the code :
// ldap_connect is successful and returns $ldap
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_NEVER); // I tried ALLOW and TRY as well and got the same results
// $configStartTls contains 1, 2 or 3 (see options above)
if ($configStartTls > 1) {
$tlsOk = ldap_start_tls($ldap) || ($configStartTls === 2);
} else {
$tlsOk = true;
}
if ($tlsOk) {
// ldap_bind here
}
Here are the results with a LDAP server that does not support TLS (ldap_start_tls always returns false), for each value of $configStartTls
ldap_bind is successful - OK
ldap_bind fails - KO
ldap_bind is not attempted - OK
In case 2 :
ldap_start_tls logs "Connect error" with error code 11
ldap_bind logs "Can't contact LDAP server" with error -1
I don't understand why ldap_bind fails in case 2. It's as if using ldap_start_tls made the use of TLS mandatory. I expected it would be possible to keep communicating with the LDAP server after a failure, in a non-secured way.
Do I have to cancel the use of StartTLS, after a failure, to implement option 2 ? How would I do this ?

I simply needed to do ldap_connect again to start over without using StartTLS. I had tried that and failed, but only because I forgot to re-apply the LDAP options between ldap_connect and ldap_bind, which means I was probably using LDAP v2 on the second attempt, and that's why the server refused it.
Here is full working code. It includes URI, BindDN and password of a free public LDAP server with no TLS support, so anyone who would face the same problem can try the code themselves and tinker with it. Just change the value of $startTlsMode to any of the constants to try other modes.
const TLS_NO = 1;
const TLS_OPTIONAL = 2;
const TLS_MANDATORY = 3;
$startTlsMode = TLS_OPTIONAL;
$ldap = connectAndSetOptions();
if ($startTlsMode === TLS_OPTIONAL || $startTlsMode === TLS_MANDATORY) {
$tlsOk = ldap_start_tls($ldap);
} else {
$tlsOk = true;
}
// TLS optional and StartTLS failed, start over without using StartTLS
if ($startTlsMode === TLS_OPTIONAL && !$tlsOk) {
$ldap = connectAndSetOptions();
$tlsOk = true;
}
// TLS successful or not necessary, proceed with binding
if ($tlsOk) {
$bindOK = ldap_bind($ldap, 'cn=read-only-admin,dc=example,dc=com', 'password');
echo $bindOK ? 'Bind successful' : 'Bind failed';
} else {
echo 'No bind attempt';
}
function connectAndSetOptions() {
$ldap = ldap_connect('ldap://ldap.forumsys.com:389');
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_X_TLS_REQUIRE_CERT, LDAP_OPT_X_TLS_TRY);
return $ldap;
}

Related

XAMPP Needs Strong(er) Authentication When Using LDAP

I am doing some initial tests on my website to allow domain users to login to it using a ldap_bind. I am running this simple test with a form to send the username and password.
$ldapconn = ldap_connect("ldap://DC01.DOMAIN.NET") or die ("Could Not Connecet to LDAP Server");
if ($ldapconn) {
$ldapbind = ldap_bind($ldapconn, $user, $password);
if ($ldapbind) {
echo "Bind Success";
}
else {
echo "Bind Failure";
}
When I pass a valid username and password I get the following error: **Warning**: ldap_bind(); Unable to bind to server: Strong(er) authentication required... Bind Failure
I am currently running 7.4.12. Server the XAMPP is running on has a valid certificate for the domain. What am I missing?
Ok so it sounds like you need to connect using TLS.
There are two ways to use TLS for LDAP connections: LDAPS and StartTLS. That you're getting a response on regular port 389 means StartTLS is likely to be supported.
So the first thing to do is to set the protocol version. Then you need to tell it to initiate a TLS handshake.
So your example above would become something like this:
$ldapconn = ldap_connect("ldap://DC01.DOMAIN.NET") or die ("Could Not Connecet to LDAP Server");
if ($ldapconn) {
if (! ldap_set_option($ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3)) {
die("Cannot set protocol v3");
}
if (! ldap_start_tls($ldapconn)) {
die("Cannot initiate TLS");
}
$ldapbind = ldap_bind($ldapconn, $user, $password);
if ($ldapbind) {
echo "Bind Success";
}
else {
echo "Bind Failure";
}
}
This assumes the server running PHP already trusts the CA that signed the LDAP server's certificate. If not, you'll also need to get the CA Cert, and either put it in the system trust store, or tell php to use it explicitly, before the call to ldap_start_tls:
ldap_set_option(LDAP_OPT_X_TLS_CACERTFILE, '/path/to/cacert.file');

ldap_error() gives "Encoding error" but I can't tell why

I've got the following test script running well (returns a 200 HTTP response) on my local machine (Windows 7 with PHP 5.6). However, when I move this script to our Linux dev server (PHP 5.6, with LDAP enabled), ldap_error() returns with the error: "Encoding error" from the LDAP server (via PHP).
I cannot figure out why this would be. The LDAP server is running LDAP version 3 (UTF-8 charset by default). I tried adding ini_set('default_charset', 'UTF-8'); at the top of my script to ensure that I was sending UTF-8 passwords/usernames. I also tried wrapping my user/pass in utf8_encode() but this didn't seem to make any difference. The script still worked well on my local machine, but did not on the Linux dev server.
Does anyone has experience with this error? I'm starting to think that this may not be an encoding issue. Could it be an SSL issue? I don't have an SSL cert installed on my localhost and the LDAP server accepts connections on port 389 (the default LDAP port) and over ldaps.
Thanks for your help.
<?php
ini_set('default_charset', 'UTF-8');
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
if (empty($_POST['username']) || empty($_POST['password'])) {
header('HTTP/1.0 500 Internal Server Error');
die('No credentials sent. Send a POST request with credentials.');
}
$username = $_POST['username'];
$password = $_POST['password'];
// https://stackoverflow.com/a/7586808/1171790
putenv('LDAPTLS_REQCERT=never');
// https://stackoverflow.com/questions/24522383/php-ldap-opt-debug-level-7-not-tracing
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
//$ldap = ldap_connect('ldaps://idir.bcgov:636');
$ldap = ldap_connect('ldap://idir.bcgov:389');
$ldaprdn = 'idir'.'\\'.$username;
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
$bind = #ldap_bind($ldap, $ldaprdn, $password);
if (ldap_error($ldap) !== 'Success') {
header('HTTP/1.0 500 Internal Server Error');
echo 'ldap_error(): '.ldap_error($ldap).PHP_EOL;
ldap_get_option($ldap, LDAP_OPT_ERROR_STRING, $err);
echo "ldap_get_option (LDAP_ENCODING_ERROR): $err".PHP_EOL;
ldap_get_option($ldap, LDAP_OPT_DEBUG_LEVEL, $err2);
echo "ldap_get_option (LDAP_OPT_DEBUG_LEVEL): $err2".PHP_EOL;
}
// Check if we have a connection.
// Did our LDAP connection get bound with the credentials provided?
// If not, perhaps the credentials are incorrect???
if ($bind) {
echo 'Holy geez! LDAP is bound!'.PHP_EOL;
$user = array();
$filter = "(sAMAccountName=$username)";
$result = ldap_search($ldap, "DC=idir,DC=BCGOV", $filter);
// Sort the returned results from Active Directory.
ldap_sort($ldap, $result, "sn");
// The user's info/data.
$info = ldap_get_entries($ldap, $result);
for ($i = 0; $i < $info["count"]; $i++) {
if ($info['count'] > 1) {
break;
}
$user['distinguishedname'] = $info[$i]['distinguishedname'][0];
}
// Close the connection to LDAP.
#ldap_close($ldap);
print_r($user);
} else {
// Our credentials did not validate.
header('HTTP/1.0 500 Internal Server Error');
die('Could not bind to LDAP.');
}

Couldn't bind LDAP using PHP

I try to bind LDAP using PHP and I getting this error
Warning: ldap_bind() [function.ldap-bind]: Unable to bind to server: Can't contact LDAP server on line 21
and the script on line 21 is this..
$bind_status = ldap_bind($conn_status, $app_user, $app_pass);
Here's the script to connect in LDAP:
$conn_status = ldap_connect('ldaps://ldap.domain.com/', 389);
if ($conn_status === FALSE) {
die("Couldn't connect to LDAP service");
} else {
echo "Successful! <br/>";
}
Here's the script of Bind to LDAP:
$app_user = 'cn=user, dc=domain, dc=com';
$app_pass = 'password';
$username = 'user'; //same as cn
$password = 'password'; //same as $app_pass
$bind_status = ldap_bind($conn_status, $app_user, $app_pass);
if ($bind_status === FALSE) {
die("Couldn't bind to LDAP as application user");
} else {
echo "Bind to LDAP successfully <br/>";
}
My updated LDAP bind script
$bind_status = ldap_bind($conn_status, $username, $password);
if ($bind_status === FALSE) {
//die("Couldn't bind to LDAP <br/>");
echo "LDAP-Errno: " . ldap_errno($ds) . "<br />";
} else {
echo "Bind to LDAP successfully <br/>";
}
And now I got this error:
Warning: ldap_bind() [function.ldap-bind]: Unable to bind to server: Operations error on line 21
Line 21 is this:
$bind_status = ldap_bind($conn_status, $username, $password);
When I use
var_dump (#ldap_bind($conn_status, "cn=Username, ou=domain, ou=com"));
The result is
bool(false)
Pls help me to fix this. Thank you
Typically ldaps listens on port 636/tcp and ldap with starttls listens on port 389/tcp.
$ldap_URI = "ldap://ldap.example.com/" ;
$ldap_bind_dn = "cn=myapplication,ou=service accounts,dc=example,dc=com" ;
$ldap_bind_dn_password = "hopefully something long and complicated" ;
$ldap_connection = ldap_connect($ldap_URI) ;
if(ldap_start_tls($ldap_connection)){
if(!ldap_bind($ldap_connection,$ldap_bind_dn,$ldap_bind_dn_password)) ;
//TODO: return/throw some error/exception here to be handled by caller, regarding invalid credentials
}else{
ldap_close($ldap_connection);
//TODO: return/throw some error/exception here to be handled by caller, regarding starttls failure
}
Check the TLS settings of your global ldap config, usually
/etc/openldap/ldap.conf or /etc/ldap/ldap.conf.
If you use SELinux, check httpd_can_connect_ldap, i.e. $ getsebool httpd_can_connect_ldap
Also:
When OpenLDAP 2.x.x is used, ldap_connect() will always return a resource as it does not actually connect but just initializes the connecting parameters. The actual connect happens with the next calls to ldap_* funcs, usually with ldap_bind(). --php manual
In your ldap_connect method, you specified a secure ldap connection ldaps and yet used the standard port for 389. If you are trying to make a secure connection, then remove the port number and ldap_connect will figure out the right port or use port 636. Otherwise use ldap with port number 389 for the unsecure connection.
Either
$conn_status = ldap_connect('ldap://ldap.domain.com/');
$conn_status = ldap_connect('ldap://ldap.domain.com/', 389);
OR
$conn_status = ldap_connect('ldaps://ldap.domain.com/');
$conn_status = ldap_connect('ldaps://ldap.domain.com/', 636);

PHP ldap_connect + ldap_bind unable to bind

I have this PHP code:
$ldap = ldap_connect("aaa.bbbbb.cc")
or die("Could not connect to LDAP server.");
ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
$ldapbind = ldap_bind($ldap);
if ($ldapbind) {
echo "LDAP bind successful...";
} else {
echo "LDAP bind failed..."; //end up here
}
And I'm unable to make it bind successfully, I have tried all sorts of combinations.
Passing credentials, using an URI with "ldap://".
The following C# code works fine when binding to the same LDAP server:
var connection = new LdapConnection("aaa.bbbbb.cc");
connection.Bind();
The LDAP API for PHP seemssomewhat hard to debug, since if bind fails, you don't get any result, so I have no idea how to see what fails, if it cant access the server, bad credentials, or something else..
So, any ideas what could cause the PHP code to fail? is there something special I need to do?
(LDAP extension is enabled for PHP)
I've had problems with PHP LDAP before.
Luckily I found this in the doc comments:
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
Which will give you verbose output when trying to open a connection and bind.
I usually do this:
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
$ldap = ldap_connect("ldap://" . $domainController, $port);
$bind = #ldap_bind($ldap, $username . $accountSuffix, $password);
if (!$bind) {
// throw or something
echo ldap_error($ldap); // http://php.net/manual/en/function.ldap-error.php
}
Wikipedia has a nice write up of the various parts to the LDAP protocol.

Do I need ldap.conf when I connect to Active Directory using PHP?

I'm trying to authenticate to Active Directory using PHP (5.3.2)/Apache (2.0.59)/Windows (2003).
However, I'm getting the following error:
ldap_start_tls() [function.ldap-start-tls]: Unable to start TLS: Connect error in E:\my_page.php on line 15
Here is my current script:
putenv('LDAPTLS_REQCERT=never');
$resource = ldap_connect("xxx")
or die("Failed to connect to LDAP server.");
echo "Connected to LDAP server.<br />";
//these options may not be necessary in all environments
ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($resource, LDAP_OPT_REFERRALS, 0);
$result = ldap_start_tls($resource) or die("Failed to start TLS.<br />");
echo "Started TLS.<br />";
$result = ldap_sasl_bind($resource, NULL, '', 'GSSAPI', 'xxx', '', '')
or die("Failed to GSSAPI bind.<br />");
echo "GSSAPI bound.";
I've looked at this question for help, however, I keep seeing references to ldap.conf.
Is this for OpenLDAP as in, the LDAP server your connecting to? If it is, I could ignore it due to using an existing enteprise Active Directory ?
Or is this for the PHP libraries connecting to an LDAP server (ie. ldap_connect())?
Edit #1
Screenshot of Wireshark...
I see in there, unknown CA... how would I go solving this (looking online ATM).
Edit #2
Update, I'm now getting a different error. I created ldap.conf on c:\ and c:\openldap\sysconf
Content of ldap.conf:
#
# LDAP Defaults
#
TLS_REQCERT never
Now, it's stuck at the ldap_sasl_bind method which is normal - it's not installed.
Edit #3
Final product:
function isAuthenticated($user, $pass){
//init
$ldap_server = "";
$ldap_user = "";
$ldap_pass = "";
$ldap_dn = "";
$ldap_filter_fields = array("dn","cn","samaccountname");
//establish connection
$ldap_conn = ldap_connect($ldap_server)
or die("Failed to connect to LDAP server.");
//these options may not be necessary in all environments
ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($resource, LDAP_OPT_REFERRALS, 0);
//Magic happens here, encrypted tunnel starts!
$result = ldap_start_tls($ldap_conn) or die("Failed to start TLS.<br />");
$out = 0;
//connect using our known user
if($bind = ldap_bind($ldap_conn, $ldap_user, $ldap_pass)){
//search for the user
$ldap_search_results = ldap_search($ldap_conn, $ldap_dn, "samaccountname=".$user, $ldap_filter_fields) or die ("Failed to search LDAP");
//get entry
$ldap_record = ldap_get_entries($ldap_conn, $ldap_search_results);
debug($ldap_record);
if($ldap_record["count"] > 0){
//try to authenticate user here
if($bind2 = #ldap_bind($ldap_conn, $ldap_record[0]["dn"], $pass))
$out = 1;
else
//wrong password
$out = 0;
}
else
//user wasn't found
$out = 3;
}
else
//something happened when connecting with our ldap_user
$out = 2;
return $out;
}
You're on the right track with your unknown CA. I have had a similar issue with PHP on CentOS connecting to AD. I had to export the CA certificate from the AD server and configure it to be trusted on the CentOS system, which involved copying the certificate to /etc/openldap/cacerts and running OpenSSL's c_rehash. Unfortunately, I'm not sure how to tell you to get that same setup working under Windows.
Yes you are required to change the ldap.conf file and change the value of TLS_REQCERT to demand if you are trying to connect with the AD for which the trust has not been established on your host machine running Apache. If you have certificate from a trusted CA i.e. already installed on the machine OS keystore then it may run with the TLS_REQCERT set to never otherwise you will have to explicitly give the certificates and change the variable TLS_REQCERT.

Categories