View Issue Details

IDProjectCategoryView StatusLast Update
0009816phpList 3 applicationAuthentication Systempublic14-11-18 09:35
Reporterbpeabody 
PrioritynormalSeverityfeatureReproducibilityalways
Status newResolutionopen 
Product Version2.10.4 
Target Versionnext majorFixed in Version 
Summary0009816: LDAP Authentication Patch
Descriptionhere is a patch for using LDAP as the backend for authentication of phplist admin users. it has support for two different types of admin accounts: a) everyone who matches the criteria you specify in your config is automatically an admin, with the records in the database being created as the user logs in the first time, b) everyone who matches your criteria is a potential admin, but only those who already have an admin record in the phplist database are able to log in. you can use one or both of these methods at the same time.

the text file in the attached package contains more info.

feedback is welcome
TagsNo tags attached.

Relationships

related to 0002705 closed PHPList v2.11 release 

Activities

23-04-07 05:25

 

phplist-ldap-0.1.patch (11,626 bytes)
--- ./public_html/lists/config/config.php.orig	2006-12-15 18:25:08.000000000 -0800
+++ ./public_html/lists/config/config.php	2007-04-22 16:22:03.000000000 -0700
@@ -175,6 +175,73 @@
 # make the 0 a 1, if you want to use it
 $check_for_host = 0;
 
+
+
+/*
+
+=========================================================================
+
+LDAP admin authentication settings
+
+=========================================================================
+
+*/
+
+# set this to 1 to enable LDAP authentication for admin logins
+$ldap_enabled = 0;
+
+# the LDAP host to use
+$ldap_url = "ldap://YOUR_LDAP_HOST/";
+
+# the DN of the user to bind as when searching for the actual admin
+# entry that matches what the user typed in on the auth page
+# (this DN should have access only to search and read the accounts
+# which are eligible for use as admin logins; it does not need to
+# and should not be able to read the password fields nor modify
+# any other information in the directory)
+$ldap_auth_bind_dn = "uid=auth,ou=System,c=US";
+
+# the password for $ldap_auth_bind_dn
+$ldap_auth_bind_pw = "secret";
+
+# users under this point in the directory will have their accounts
+# created automatically when they first login - if you don't want
+# this feature, comment this out
+$ldap_all_user_base_dn = "ou=IT,c=US";
+
+# the filter pattern to locate the entry that matches the user's
+# login name ("__LOGIN__" is replaced with the user's login name)
+$ldap_all_user_pattern = "(uid=__LOGIN__)";
+
+# the attribute which contains the login name
+$ldap_all_user_uid_attribute = "uid";
+
+# set this to 1 if users who are "all_user" are super admins, or 0
+# means that they are not
+$ldap_all_user_is_super = 1;
+
+# the users under this point in the directory must have their admin
+# accounts explicitly created in PHPList and the directory is
+# justed used to check the authentication - if you don't want this
+# feature, comment this out
+$ldap_matching_user_base_dn = "ou=Marketing,c=US";
+
+# the filter pattern to locate the entry that matches the user's
+# login name ("__LOGIN__" is replaced with the user's login name)
+$ldap_matching_user_pattern = "(uid=__LOGIN__)";
+
+# the attribute which contains the login name
+$ldap_matching_user_uid_attribute = "uid";
+
+# users who are in this array are not authenticated via LDAP, the
+# password given in the "admin" table is used for them;
+# only set this if you want to give yourself a local admin account
+# in the event that PHPList can't connect to the LDAP server or
+# something else goes terribly wrong - it is generally more secure
+# to leave this commented out
+#$ldap_except_users = array('admin');
+
+
 /*
 
 =========================================================================
--- ./public_html/lists/admin/auth/phplist_auth.inc.orig	2007-04-21 21:31:42.000000000 -0700
+++ ./public_html/lists/admin/auth/phplist_auth.inc	2007-04-22 16:54:26.000000000 -0700
@@ -4,7 +4,7 @@
 
 class admin_auth {
 
-  function validateLogin($login,$password) {
+  function localValidateLogin($login,$password) {
     $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],$login));
     if ($admindata["disabled"]) {
       return array(0,"your account has been disabled");
@@ -61,6 +61,228 @@
     return $result;
   }
 
+  /**
+   * New validateLogin() function performs LDAP authentication, if enabled, and
+   * passes regular table-based validation to localValidateLogin().
+   */
+  function validateLogin($login,$password) {
+
+    // get all of the values from the config
+    global $ldap_enabled;
+    global $ldap_url;
+    global $ldap_auth_bind_dn;
+    global $ldap_auth_bind_pw;
+    global $ldap_all_user_base_dn;
+    global $ldap_all_user_pattern;
+    global $ldap_all_user_uid_attribute;
+    global $ldap_all_user_is_super;
+    global $ldap_matching_user_base_dn;
+    global $ldap_matching_user_pattern;
+    global $ldap_matching_user_uid_attribute;
+    global $ldap_except_users;
+
+    // tables is global
+    global $tables;
+
+    // make sure our comparisons against the password
+    // field don't do anything funky
+    $password = strval($password);
+
+    // only do LDAP if it's enabled
+    if ($ldap_enabled) {
+      // do not allow blank password
+      if (strlen($password) < 1) {
+        return array(0, 'Password required');
+      }
+
+      // check ldap_except_users to see if this should be forced to be
+      // local auth
+      if ($ldap_except_users && in_array($login, $ldap_except_users)) {
+        return $this->localValidateLogin($login, $password);
+      }
+
+      // check LDAP auth for "all_users"
+      $myPattern = str_replace("__LOGIN__", $login, $ldap_all_user_pattern);
+      $myResult = $this->checkLdapAuth(
+          $ldap_url, $ldap_auth_bind_dn, $ldap_auth_bind_pw,
+          $ldap_all_user_base_dn,
+          $myPattern, $password, $ldap_all_user_uid_attribute
+        );
+
+      // check to see if it worked
+      if (strval($myResult[0]) == $login) {
+
+        // see if there is an existing record
+        $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+
+        // if not found, then we create it
+        if (!$admindata) {
+          // create a new record
+          Sql_Query(sprintf('insert into %s (loginname,namelc,created) values("%s","%s",now())',
+            $tables["admin"],addslashes($login),addslashes($login)));
+          $id = Sql_Insert_Id();
+          $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+        }
+
+        // set disabled flag off (by definition "all_users" means enabled
+        // accounts) - this ensures that account control for "all_users" lies
+        // in the LDAP directory, not in PHPList
+        Sql_Query(sprintf('update %s set disabled = 0 where loginname = "%s"',
+          $tables["admin"],addslashes($login)));
+
+        // set the super-user flag appropriately
+        Sql_Query(sprintf('update %s set superuser = '.strval($ldap_all_user_is_super).' where loginname = "%s"',
+          $tables["admin"],addslashes($login)));
+
+        // update table to reflect the email address from the directory
+        if (strlen(strval($myResult[2]['mail'][0])) > 0) {
+          Sql_Query(sprintf('update %s set email = "%s" where loginname = "%s"',
+            $tables["admin"],addslashes(strval($myResult[2]['mail'][0])),addslashes($login)));
+        }
+
+        // return success
+        return array($admindata["id"],"OK");
+
+      }
+
+      // "all_users" auth failed, try again with "matching_users"
+      $myPattern = str_replace("__LOGIN__", $login, $ldap_matching_user_pattern);
+      $myResult = $this->checkLdapAuth(
+          $ldap_url, $ldap_auth_bind_dn, $ldap_auth_bind_pw,
+          $ldap_matching_user_base_dn,
+          $myPattern, $password, $ldap_matching_user_uid_attribute
+        );
+
+      // check to see if it worked this time
+      if (strval($myResult[0]) == $login) {
+
+        // it worked in LDAP, now check for the database record
+        $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+
+        if ($admindata) {
+
+          // check for disabled account
+          if ($admindata["disabled"]) {
+            return array(0,"your account has been disabled");
+          }
+
+          // update table to reflect the email address from the directory
+          if (strlen(strval($myResult[2]['mail'][0])) > 0) {
+            Sql_Query(sprintf('update %s set email = "%s" where loginname = "%s"',
+              $tables["admin"],addslashes(strval($myResult[2]['mail'][0])),addslashes($login)));
+          }
+
+          // all good, return success
+          return array($admindata["id"],"OK");
+        }
+      }
+      
+      //echo $myResult[0] . " - " . $myResult[1];
+      
+      // no luck - game over
+      return array(0, 'Authentication failed');
+      //return $myResult;
+
+    }
+    // LDAP not enabled, do local check
+    else {
+      return $this->localValidateLogin($login, $password);
+    }
+
+  }
+
+  /**
+   * Performs LDAP authentication.  Returns
+   * array(value_of_uidAttr, "OK", full_ldap_entry_for_target_user)
+   * on success, or array(0, "ERROR MESSAGE DESCRIBING WHAT HAPPENED") on
+   * failure. This function checks for LDAP authentication by first binding
+   * as a different user, searching to find a "target DN" (the DN
+   * that corresponds to the end user's login) and then rebinding
+   * with that.
+   */
+  function checkLdapAuth(
+    $aLdapUrl, // the url used to connect to the LDAP server
+    $aBindDn,  // the user to bind as
+    $aBindPw,  // the password
+    $aBaseDn,  // the base of where to search for the actual target user
+    $aFilter,  // the search filter to find the target user's DN
+    $aUserPw,  // the password of the target user (used to bind again
+               // after the DN of the target user is found
+    $aUidAttr  // the attribute which contains the login ID
+               // (the text name of the login)
+    ) {
+
+    if (strlen(strval($aBaseDn)) == 0) {
+      return array(0, 'Authentication method disabled');
+    }
+
+    // do not allow blank password
+    $aUserPw = strval($aUserPw);
+    if (strlen($aUserPw) < 1) {
+      return array(0, 'Password required');
+    }
+
+    // cover all bases
+    $myResult = array(0, "Unknown error");
+
+    // connect to the LDAP server
+    $myLdapConn = ldap_connect($aLdapUrl);
+
+    // if the connection succeeded
+    if ($myLdapConn) {
+      // do an LDAP bind
+      $myBindResult = ldap_bind($myLdapConn, $aBindDn, $aBindPw);
+      // check to see if bind failed
+      if (!$myBindResult) {
+        $myResult = array(0, 'Bind to LDAP server failed');
+      }
+      // bind was fine, keep going
+      else {
+        // search for the user in question
+        $myLdapSearchResult = ldap_search($myLdapConn, $aBaseDn, $aFilter);
+        if (!$myLdapSearchResult) {
+          $myResult = array(0, 'User not found');
+        }
+        // if user was found, try to bind again as that user
+        else {
+          // get the details about the result entries
+          $myLdapEntries = ldap_get_entries($myLdapConn, $myLdapSearchResult);
+          if ($myLdapEntries['count'] > 0) {
+            // now try another bind as the user that we found
+            $myBindResult = ldap_bind($myLdapConn, $myLdapEntries[0]['dn'], $aUserPw);
+            if (!$myBindResult) {
+              $myResult = array(0, 'Authentication failed');
+            }
+            else {
+              // all good
+              if (count($myLdapEntries[0]["$aUidAttr"]) > 0) {
+                $myResult = array($myLdapEntries[0]["$aUidAttr"][0], "OK", $myLdapEntries[0]);
+              }
+              else {
+                $myResult = array(0, 'Unable to find attribute');
+              }
+            }
+          }
+          else {
+            $myResult = array(0, 'No such user');
+          }
+        }
+      }
+      // cleanup the connection
+      ldap_close($myLdapConn);
+    }
+    // connection failure
+    else {
+      $myResult = array(0, 'Connect failed');
+    }
+
+    // echo result before returning
+    //echo "myResult = " . $myResult[0] . ", " . $myResult[1];
+
+    return $myResult;
+
+  }
+
 }
 
 ?>
phplist-ldap-0.1.patch (11,626 bytes)

23-04-07 05:26

 

phplist-ldap-0.1.txt (4,753 bytes)
******************************************************************
** phplist-ldap
**
** Author: Brad Peabody (bradpeabody at scientology.net)
** License: Same as PHPList (GPL)
**

A patch to provide LDAP support for PHPList administrator accounts.

******************************************************************
** Overview:

Organizations which have an LDAP directory which they use to store
accounts may want to leverage their directory to provide logins for
administrators of PHPList instances.

Note that this code does not explore the idea of having actual
email list entries in LDAP - just administrator accounts.

There are two basic types of admin users that you can have:

 * "all_users" - This means that all users that match the criteria
   of these settings are allowed into PHPList as admins.  If a
   database record doesn't exist for one of these users, it is
   created when the user logs in.  This gives you the ability to
   control via the LDAP directory exactly which users can log
   into PHPList - without having to do any per-admin-user
   PHPList setup.

 * "matching_users" - This means all users that match this criteria
   and also have a corresponding admin account in PHPList.  With
   this scenario you can have a large number of accounts in the
   directory that are in this category, but only those whose user
   ID matches an admin account PHPList are allowed access.

The "all_users" model suits ISPs or other organizations which need
to administer multiple instances and have administrative people
that can log into each instance without having to explicitly
be set up with an account on each.

The "matching_users" model is more appropriate where you have a
directory which contains a large number of clients, employees,
etc. and you just want to give access individually for specific
people to have administrative access to specific instances
(usually this would be the marketing-type person who will be
doing the mailings, etc.)

You may use one or both of these types of admin users at the same
time.  For example, you may want just your system administrators
setup as "all_users" so they can log into any instance with thier
account and debug issues easily, but then be able to also just
add specific users from the LDAP directory, with the sole benefit
being that the user doesn't have to remember/maintain another
set of login credentials - you can just enter his user name
as an admin in PHPLIst and he's in.

Note that there is no way (unless there's some bug in my code :0)
for any administrator to remove or disable an "all_users" admin
account by manipulating the admin accounts in PHPLIst - since
the account will be recreated and reenabled the next time that
person logs in.  ("all_users" accounts are added and removed in
the LDAP directory.)  This is intentional and prevents a system
administrator from being locked out.

******************************************************************
** Applying the Patch:

This is the easy part:

cd phplist-2.10.4
patch -p0 < phplist-ldap-0.1.patch

******************************************************************
** Configuring:

Just edit your config.php, which will now contain (after the patch)
example configuration parameters for LDAP.

Set $ldap_enabled = 1 to enable LDAP accounts.  Be sure to read
through and properly set the rest of the configuration parameters
before you do this, otherwise the effect will be to just prevent
you from logging in.

You may want to use the $ldap_except_users feature to retain some
accounts which are not checked against the LDAP directory. This
feature is disabled by default however, with the hope that most
people won't use it, thus creating a bit more security by
disabling the default admin/phplist credentials.

******************************************************************
** Security Tips:

* Be aware of the following scenario:

  1) Username "joe" is an "all_users" admin (Let's say this is
  "Joe A".  When he first logs in a record is created for him
  in the PHPList admins table.  "Joe A" can now access the
  system using 

  2) Username "joe" is in the list "matching_users" (this,
  however, is a DIFFERENT user - "Joe B").  "Joe B" was never
  meant to be an administrator, but since his account is available
  through the "matching_users" criteria that is searched when
  a login is attempted, and a record for "joe" (his UID) is
  in the list of PHPList admins - he can now log in an admin.

  In the above case, either one of "Joe A" or "Joe B" would be
  able to log in with their own username and password.

  The only way to prevent this (with the current way the code is
  written) is to remove or rename one of the accounts so you don't
  have two people with the same name.


phplist-ldap-0.1.txt (4,753 bytes)

michiel

23-04-07 16:50

manager   ~0026148

Interesting, thanks

Instead of patching phplist_auth.inc wouldn't it be easier to write a new ldap_auth.inc and then set the config file to use that one? That way the developments can go their own way.

That was initially the idea when creating the phplist_auth.inc file.

bpeabody

23-04-07 23:37

reporter   ~0026168

Yes, you're right. It definitely seems better to put this in a separate file (I missed that line in the config, sorry).

The only thing is that the LDAP code can fall back on the regular auth for specific users that you define (for example, you may want to keep the "admin" account local - not authenticating via LDAP, so in the event that your LDAP server is down, or there is some other connection issue, you can still get in;). This feature is currently implemented by renaming your existing validateLogin function to localValidateLogin and just calling in when needed.

Perhaps this could be resolved by implementing a new class called "ldap_admin_auth" which extends from "admin_auth" and lives in a separate "ldap_admin_auth.inc" file? But then I'm not sure how the rest of PHPList would know to use an instance of "ldap_admin_auth" instead of "admin_auth" - just including the file wouldn't quite do it.

Or I could just copy the existing admin_auth.inc to ldap_admin_auth.inc and make the mods to it. But that's a little less resilient to change, since ldap_admin_auth.inc would have to track with and carry over any relavent changes from admin_auth.inc as it evolves, instead of letting the language do that for us via inheritance.

Your call - whichever you think is best.

michiel

24-04-07 13:34

manager   ~0026188

yes, good point, I'll have a think. I'm not sure how soon, I'll be able to merge it into the main code, but in the meantime we can leave it as a patch

amichel

16-08-07 22:36

reporter   ~0030653

I made some small changes to this patch to account for anonymous binding. Our ldap server allows anonymous binds for searching. I recreated the entire patch with my changes, so this isn't an incremental patch, it's the full contents of the original patch plus my minor contribution, all rolled into one.

I'm new to generating patch files and whatnot, so if this is wrong, do what you must.

16-08-07 22:37

 

phplist-ldap-0.1a.patch (11,910 bytes)
--- ./public_html/lists/config/config.php.orig	2006-12-15 18:25:08.000000000 -0800
+++ ./public_html/lists/config/config.php	2007-08-16 15:29:00.000000000 -0700
@@ -175,6 +175,75 @@
 # make the 0 a 1, if you want to use it
 $check_for_host = 0;
 
+
+
+/*
+
+=========================================================================
+
+LDAP admin authentication settings
+
+=========================================================================
+
+*/
+
+# set this to 1 to enable LDAP authentication for admin logins
+$ldap_enabled = 0;
+
+# the LDAP host to use
+$ldap_url = "ldap://YOUR_LDAP_HOST/";
+
+# the DN of the user to bind as when searching for the actual admin
+# entry that matches what the user typed in on the auth page
+# (this DN should have access only to search and read the accounts
+# which are eligible for use as admin logins; it does not need to
+# and should not be able to read the password fields nor modify
+# any other information in the directory)
+# Set this value empty if you want to do an anonymous bind.
+$ldap_auth_bind_dn = "uid=auth,ou=System,c=US";
+
+# the password for $ldap_auth_bind_dn
+# (Will be ignored if ldap_auth_bind_dn is empty)
+$ldap_auth_bind_pw = "secret";
+
+# users under this point in the directory will have their accounts
+# created automatically when they first login - if you don't want
+# this feature, comment this out
+$ldap_all_user_base_dn = "ou=IT,c=US";
+
+# the filter pattern to locate the entry that matches the user's
+# login name ("__LOGIN__" is replaced with the user's login name)
+$ldap_all_user_pattern = "(uid=__LOGIN__)";
+
+# the attribute which contains the login name
+$ldap_all_user_uid_attribute = "uid";
+
+# set this to 1 if users who are "all_user" are super admins, or 0
+# means that they are not
+$ldap_all_user_is_super = 1;
+
+# the users under this point in the directory must have their admin
+# accounts explicitly created in PHPList and the directory is
+# justed used to check the authentication - if you don't want this
+# feature, comment this out
+$ldap_matching_user_base_dn = "ou=Marketing,c=US";
+
+# the filter pattern to locate the entry that matches the user's
+# login name ("__LOGIN__" is replaced with the user's login name)
+$ldap_matching_user_pattern = "(uid=__LOGIN__)";
+
+# the attribute which contains the login name
+$ldap_matching_user_uid_attribute = "uid";
+
+# users who are in this array are not authenticated via LDAP, the
+# password given in the "admin" table is used for them;
+# only set this if you want to give yourself a local admin account
+# in the event that PHPList can't connect to the LDAP server or
+# something else goes terribly wrong - it is generally more secure
+# to leave this commented out
+#$ldap_except_users = array('admin');
+
+
 /*
 
 =========================================================================
--- ./public_html/lists/admin/auth/phplist_auth.inc.orig	2007-04-21 21:31:42.000000000 -0700
+++ ./public_html/lists/admin/auth/phplist_auth.inc	2007-08-16 15:29:26.000000000 -0700
@@ -4,7 +4,7 @@
 
 class admin_auth {
 
-  function validateLogin($login,$password) {
+  function localValidateLogin($login,$password) {
     $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],$login));
     if ($admindata["disabled"]) {
       return array(0,"your account has been disabled");
@@ -61,6 +61,233 @@
     return $result;
   }
 
+  /**
+   * New validateLogin() function performs LDAP authentication, if enabled, and
+   * passes regular table-based validation to localValidateLogin().
+   */
+  function validateLogin($login,$password) {
+
+    // get all of the values from the config
+    global $ldap_enabled;
+    global $ldap_url;
+    global $ldap_auth_bind_dn;
+    global $ldap_auth_bind_pw;
+    global $ldap_all_user_base_dn;
+    global $ldap_all_user_pattern;
+    global $ldap_all_user_uid_attribute;
+    global $ldap_all_user_is_super;
+    global $ldap_matching_user_base_dn;
+    global $ldap_matching_user_pattern;
+    global $ldap_matching_user_uid_attribute;
+    global $ldap_except_users;
+
+    // tables is global
+    global $tables;
+
+    // make sure our comparisons against the password
+    // field don't do anything funky
+    $password = strval($password);
+
+    // only do LDAP if it's enabled
+    if ($ldap_enabled) {
+      // do not allow blank password
+      if (strlen($password) < 1) {
+        return array(0, 'Password required');
+      }
+
+      // check ldap_except_users to see if this should be forced to be
+      // local auth
+      if ($ldap_except_users && in_array($login, $ldap_except_users)) {
+        return $this->localValidateLogin($login, $password);
+      }
+
+      // check LDAP auth for "all_users"
+      $myPattern = str_replace("__LOGIN__", $login, $ldap_all_user_pattern);
+      $myResult = $this->checkLdapAuth(
+          $ldap_url, $ldap_auth_bind_dn, $ldap_auth_bind_pw,
+          $ldap_all_user_base_dn,
+          $myPattern, $password, $ldap_all_user_uid_attribute
+        );
+
+      // check to see if it worked
+      if (strval($myResult[0]) == $login) {
+
+        // see if there is an existing record
+        $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+
+        // if not found, then we create it
+        if (!$admindata) {
+          // create a new record
+          Sql_Query(sprintf('insert into %s (loginname,namelc,created) values("%s","%s",now())',
+            $tables["admin"],addslashes($login),addslashes($login)));
+          $id = Sql_Insert_Id();
+          $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+        }
+
+        // set disabled flag off (by definition "all_users" means enabled
+        // accounts) - this ensures that account control for "all_users" lies
+        // in the LDAP directory, not in PHPList
+        Sql_Query(sprintf('update %s set disabled = 0 where loginname = "%s"',
+          $tables["admin"],addslashes($login)));
+
+        // set the super-user flag appropriately
+        Sql_Query(sprintf('update %s set superuser = '.strval($ldap_all_user_is_super).' where loginname = "%s"',
+          $tables["admin"],addslashes($login)));
+
+        // update table to reflect the email address from the directory
+        if (strlen(strval($myResult[2]['mail'][0])) > 0) {
+          Sql_Query(sprintf('update %s set email = "%s" where loginname = "%s"',
+            $tables["admin"],addslashes(strval($myResult[2]['mail'][0])),addslashes($login)));
+        }
+
+        // return success
+        return array($admindata["id"],"OK");
+
+      }
+
+      // "all_users" auth failed, try again with "matching_users"
+      $myPattern = str_replace("__LOGIN__", $login, $ldap_matching_user_pattern);
+      $myResult = $this->checkLdapAuth(
+          $ldap_url, $ldap_auth_bind_dn, $ldap_auth_bind_pw,
+          $ldap_matching_user_base_dn,
+          $myPattern, $password, $ldap_matching_user_uid_attribute
+        );
+
+      // check to see if it worked this time
+      if (strval($myResult[0]) == $login) {
+
+        // it worked in LDAP, now check for the database record
+        $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+
+        if ($admindata) {
+
+          // check for disabled account
+          if ($admindata["disabled"]) {
+            return array(0,"your account has been disabled");
+          }
+
+          // update table to reflect the email address from the directory
+          if (strlen(strval($myResult[2]['mail'][0])) > 0) {
+            Sql_Query(sprintf('update %s set email = "%s" where loginname = "%s"',
+              $tables["admin"],addslashes(strval($myResult[2]['mail'][0])),addslashes($login)));
+          }
+
+          // all good, return success
+          return array($admindata["id"],"OK");
+        }
+      }
+      
+      //echo $myResult[0] . " - " . $myResult[1];
+      
+      // no luck - game over
+      return array(0, 'Authentication failed');
+      //return $myResult;
+
+    }
+    // LDAP not enabled, do local check
+    else {
+      return $this->localValidateLogin($login, $password);
+    }
+
+  }
+
+  /**
+   * Performs LDAP authentication.  Returns
+   * array(value_of_uidAttr, "OK", full_ldap_entry_for_target_user)
+   * on success, or array(0, "ERROR MESSAGE DESCRIBING WHAT HAPPENED") on
+   * failure. This function checks for LDAP authentication by first binding
+   * as a different user, searching to find a "target DN" (the DN
+   * that corresponds to the end user's login) and then rebinding
+   * with that.
+   */
+  function checkLdapAuth(
+    $aLdapUrl, // the url used to connect to the LDAP server
+    $aBindDn,  // the user to bind as
+    $aBindPw,  // the password
+    $aBaseDn,  // the base of where to search for the actual target user
+    $aFilter,  // the search filter to find the target user's DN
+    $aUserPw,  // the password of the target user (used to bind again
+               // after the DN of the target user is found
+    $aUidAttr  // the attribute which contains the login ID
+               // (the text name of the login)
+    ) {
+
+    if (strlen(strval($aBaseDn)) == 0) {
+      return array(0, 'Authentication method disabled');
+    }
+
+    // do not allow blank password
+    $aUserPw = strval($aUserPw);
+    if (strlen($aUserPw) < 1) {
+      return array(0, 'Password required');
+    }
+
+    // cover all bases
+    $myResult = array(0, "Unknown error");
+
+    // connect to the LDAP server
+    $myLdapConn = ldap_connect($aLdapUrl);
+
+    // if the connection succeeded
+    if ($myLdapConn) {
+      // do an LDAP bind
+      // if we have a bind dn, use it
+      // otherwise bind anonymously
+      if (strlen($aBindDn) > 0)
+        $myBindResult = ldap_bind($myLdapConn, $aBindDn, $aBindPw);
+      else
+        $myBindResult = ldap_bind($myLdapConn);
+      // check to see if bind failed
+      if (!$myBindResult) {
+        $myResult = array(0, 'Bind to LDAP server failed');
+      }
+      // bind was fine, keep going
+      else {
+        // search for the user in question
+        $myLdapSearchResult = ldap_search($myLdapConn, $aBaseDn, $aFilter);
+        if (!$myLdapSearchResult) {
+          $myResult = array(0, 'User not found');
+        }
+        // if user was found, try to bind again as that user
+        else {
+          // get the details about the result entries
+          $myLdapEntries = ldap_get_entries($myLdapConn, $myLdapSearchResult);
+          if ($myLdapEntries['count'] > 0) {
+            // now try another bind as the user that we found
+            $myBindResult = ldap_bind($myLdapConn, $myLdapEntries[0]['dn'], $aUserPw);
+            if (!$myBindResult) {
+              $myResult = array(0, 'Authentication failed');
+            }
+            else {
+              // all good
+              if (count($myLdapEntries[0]["$aUidAttr"]) > 0) {
+                $myResult = array($myLdapEntries[0]["$aUidAttr"][0], "OK", $myLdapEntries[0]);
+              }
+              else {
+                $myResult = array(0, 'Unable to find attribute');
+              }
+            }
+          }
+          else {
+            $myResult = array(0, 'No such user');
+          }
+        }
+      }
+      // cleanup the connection
+      ldap_close($myLdapConn);
+    }
+    // connection failure
+    else {
+      $myResult = array(0, 'Connect failed');
+    }
+
+    // echo result before returning
+    //echo "myResult = " . $myResult[0] . ", " . $myResult[1];
+
+    return $myResult;
+
+  }
+
 }
 
 ?>
phplist-ldap-0.1a.patch (11,910 bytes)

goverd

20-10-09 16:51

reporter   ~0050757

I recommend using strtolower when checking the login as the uid attribute is sometimes derived from the entry's name when created in the tree and would honor the case. (e.g jdoe in phplist should match with JDoe from ldap tree)

--- phplist_auth.inc 2009-10-20 12:50:13.000000000 -0400
+++ phplist_auth.inc.orig 2009-10-20 12:49:45.000000000 -0400
@@ -110,7 +110,7 @@
         );

       // check to see if it worked
- if (strval(strtolower($myResult[0])) == $login) {
+ if (strval($myResult[0]) == $login) {

@@ -154,7 +154,7 @@
         );

       // check to see if it worked this time
- if (strval(strtolower($myResult[0])) == $login) {
+ if (strval($myResult[0]) == $login) {

tazou

30-01-14 13:06

reporter   ~0052715

Hello,
I modified a little the patch to make it working with the 3.0.5 version of phplist.

Name : phplist-ldap-0.1b.patch

tazou

30-01-14 13:06

reporter  

phplist-ldap-0.1b.patch (11,743 bytes)
--- ./public_html/lists/config/config.php.orig	2006-12-15 18:25:08.000000000 -0800
+++ ./public_html/lists/config/config.php	2007-08-16 15:29:00.000000000 -0700
@@ -175,6 +175,75 @@
 # make the 0 a 1, if you want to use it
 $check_for_host = 0;
 
+
+
+/*
+
+=========================================================================
+
+LDAP admin authentication settings
+
+=========================================================================
+
+*/
+
+# set this to 1 to enable LDAP authentication for admin logins
+$ldap_enabled = 0;
+
+# the LDAP host to use
+$ldap_url = "ldap://YOUR_LDAP_HOST/";
+
+# the DN of the user to bind as when searching for the actual admin
+# entry that matches what the user typed in on the auth page
+# (this DN should have access only to search and read the accounts
+# which are eligible for use as admin logins; it does not need to
+# and should not be able to read the password fields nor modify
+# any other information in the directory)
+# Set this value empty if you want to do an anonymous bind.
+$ldap_auth_bind_dn = "uid=auth,ou=System,c=US";
+
+# the password for $ldap_auth_bind_dn
+# (Will be ignored if ldap_auth_bind_dn is empty)
+$ldap_auth_bind_pw = "secret";
+
+# users under this point in the directory will have their accounts
+# created automatically when they first login - if you don't want
+# this feature, comment this out
+$ldap_all_user_base_dn = "ou=IT,c=US";
+
+# the filter pattern to locate the entry that matches the user's
+# login name ("__LOGIN__" is replaced with the user's login name)
+$ldap_all_user_pattern = "(uid=__LOGIN__)";
+
+# the attribute which contains the login name
+$ldap_all_user_uid_attribute = "uid";
+
+# set this to 1 if users who are "all_user" are super admins, or 0
+# means that they are not
+$ldap_all_user_is_super = 1;
+
+# the users under this point in the directory must have their admin
+# accounts explicitly created in PHPList and the directory is
+# justed used to check the authentication - if you don't want this
+# feature, comment this out
+$ldap_matching_user_base_dn = "ou=Marketing,c=US";
+
+# the filter pattern to locate the entry that matches the user's
+# login name ("__LOGIN__" is replaced with the user's login name)
+$ldap_matching_user_pattern = "(uid=__LOGIN__)";
+
+# the attribute which contains the login name
+$ldap_matching_user_uid_attribute = "uid";
+
+# users who are in this array are not authenticated via LDAP, the
+# password given in the "admin" table is used for them;
+# only set this if you want to give yourself a local admin account
+# in the event that PHPList can't connect to the LDAP server or
+# something else goes terribly wrong - it is generally more secure
+# to leave this commented out
+#$ldap_except_users = array('admin');
+
+
 /*
 
 =========================================================================
--- ./public_html/lists/admin/auth/phplist_auth.inc.orig	2014-01-30 10:46:33.533151802 +0100
+++ ./public_html/lists/admin/auth/phplist_auth.inc	2014-01-30 10:52:57.973151657 +0100
@@ -4,7 +4,7 @@
 
 class admin_auth {
 
-  function validateLogin($login,$password) {
+  function localValidateLogin($login,$password) {
     $query
     = ' select password, disabled, id'
     . ' from %s'
@@ -119,6 +119,233 @@
     return $result;
   }
 
+  /**
+   * New validateLogin() function performs LDAP authentication, if enabled, and
+   * passes regular table-based validation to localValidateLogin().
+   */
+  function validateLogin($login,$password) {
+
+    // get all of the values from the config
+    global $ldap_enabled;
+    global $ldap_url;
+    global $ldap_auth_bind_dn;
+    global $ldap_auth_bind_pw;
+    global $ldap_all_user_base_dn;
+    global $ldap_all_user_pattern;
+    global $ldap_all_user_uid_attribute;
+    global $ldap_all_user_is_super;
+    global $ldap_matching_user_base_dn;
+    global $ldap_matching_user_pattern;
+    global $ldap_matching_user_uid_attribute;
+    global $ldap_except_users;
+
+    // tables is global
+    global $tables;
+
+    // make sure our comparisons against the password
+    // field don't do anything funky
+    $password = strval($password);
+
+    // only do LDAP if it's enabled
+    if ($ldap_enabled) {
+      // do not allow blank password
+      if (strlen($password) < 1) {
+        return array(0, 'Password required');
+      }
+
+      // check ldap_except_users to see if this should be forced to be
+      // local auth
+      if ($ldap_except_users && in_array($login, $ldap_except_users)) {
+        return $this->localValidateLogin($login, $password);
+      }
+
+      // check LDAP auth for "all_users"
+      $myPattern = str_replace("__LOGIN__", $login, $ldap_all_user_pattern);
+      $myResult = $this->checkLdapAuth(
+          $ldap_url, $ldap_auth_bind_dn, $ldap_auth_bind_pw,
+          $ldap_all_user_base_dn,
+          $myPattern, $password, $ldap_all_user_uid_attribute
+        );
+
+      // check to see if it worked
+      if (strval($myResult[0]) == $login) {
+
+        // see if there is an existing record
+        $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+
+        // if not found, then we create it
+        if (!$admindata) {
+          // create a new record
+          Sql_Query(sprintf('insert into %s (loginname,namelc,created) values("%s","%s",now())',
+            $tables["admin"],addslashes($login),addslashes($login)));
+          $id = Sql_Insert_Id();
+          $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+        }
+
+        // set disabled flag off (by definition "all_users" means enabled
+        // accounts) - this ensures that account control for "all_users" lies
+        // in the LDAP directory, not in PHPList
+        Sql_Query(sprintf('update %s set disabled = 0 where loginname = "%s"',
+          $tables["admin"],addslashes($login)));
+
+        // set the super-user flag appropriately
+        Sql_Query(sprintf('update %s set superuser = '.strval($ldap_all_user_is_super).' where loginname = "%s"',
+          $tables["admin"],addslashes($login)));
+
+        // update table to reflect the email address from the directory
+        if (strlen(strval($myResult[2]['mail'][0])) > 0) {
+          Sql_Query(sprintf('update %s set email = "%s" where loginname = "%s"',
+            $tables["admin"],addslashes(strval($myResult[2]['mail'][0])),addslashes($login)));
+        }
+
+        // return success
+        return array($admindata["id"],"OK");
+
+      }
+
+      // "all_users" auth failed, try again with "matching_users"
+      $myPattern = str_replace("__LOGIN__", $login, $ldap_matching_user_pattern);
+      $myResult = $this->checkLdapAuth(
+          $ldap_url, $ldap_auth_bind_dn, $ldap_auth_bind_pw,
+          $ldap_matching_user_base_dn,
+          $myPattern, $password, $ldap_matching_user_uid_attribute
+        );
+
+      // check to see if it worked this time
+      if (strval($myResult[0]) == $login) {
+
+        // it worked in LDAP, now check for the database record
+        $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+
+        if ($admindata) {
+
+          // check for disabled account
+          if ($admindata["disabled"]) {
+            return array(0,"your account has been disabled");
+          }
+
+          // update table to reflect the email address from the directory
+          if (strlen(strval($myResult[2]['mail'][0])) > 0) {
+            Sql_Query(sprintf('update %s set email = "%s" where loginname = "%s"',
+              $tables["admin"],addslashes(strval($myResult[2]['mail'][0])),addslashes($login)));
+          }
+
+          // all good, return success
+          return array($admindata["id"],"OK");
+        }
+      }
+      
+      //echo $myResult[0] . " - " . $myResult[1];
+      
+      // no luck - game over
+      return array(0, 'Authentication failed');
+      //return $myResult;
+
+    }
+    // LDAP not enabled, do local check
+    else {
+      return $this->localValidateLogin($login, $password);
+    }
+
+  }
+
+  /**
+   * Performs LDAP authentication.  Returns
+   * array(value_of_uidAttr, "OK", full_ldap_entry_for_target_user)
+   * on success, or array(0, "ERROR MESSAGE DESCRIBING WHAT HAPPENED") on
+   * failure. This function checks for LDAP authentication by first binding
+   * as a different user, searching to find a "target DN" (the DN
+   * that corresponds to the end user's login) and then rebinding
+   * with that.
+   */
+  function checkLdapAuth(
+    $aLdapUrl, // the url used to connect to the LDAP server
+    $aBindDn,  // the user to bind as
+    $aBindPw,  // the password
+    $aBaseDn,  // the base of where to search for the actual target user
+    $aFilter,  // the search filter to find the target user's DN
+    $aUserPw,  // the password of the target user (used to bind again
+               // after the DN of the target user is found
+    $aUidAttr  // the attribute which contains the login ID
+               // (the text name of the login)
+    ) {
+
+    if (strlen(strval($aBaseDn)) == 0) {
+      return array(0, 'Authentication method disabled');
+    }
+
+    // do not allow blank password
+    $aUserPw = strval($aUserPw);
+    if (strlen($aUserPw) < 1) {
+      return array(0, 'Password required');
+    }
+
+    // cover all bases
+    $myResult = array(0, "Unknown error");
+
+    // connect to the LDAP server
+    $myLdapConn = ldap_connect($aLdapUrl);
+
+    // if the connection succeeded
+    if ($myLdapConn) {
+      // do an LDAP bind
+      // if we have a bind dn, use it
+      // otherwise bind anonymously
+      if (strlen($aBindDn) > 0)
+        $myBindResult = ldap_bind($myLdapConn, $aBindDn, $aBindPw);
+      else
+        $myBindResult = ldap_bind($myLdapConn);
+      // check to see if bind failed
+      if (!$myBindResult) {
+        $myResult = array(0, 'Bind to LDAP server failed');
+      }
+      // bind was fine, keep going
+      else {
+        // search for the user in question
+        $myLdapSearchResult = ldap_search($myLdapConn, $aBaseDn, $aFilter);
+        if (!$myLdapSearchResult) {
+          $myResult = array(0, 'User not found');
+        }
+        // if user was found, try to bind again as that user
+        else {
+          // get the details about the result entries
+          $myLdapEntries = ldap_get_entries($myLdapConn, $myLdapSearchResult);
+          if ($myLdapEntries['count'] > 0) {
+            // now try another bind as the user that we found
+            $myBindResult = ldap_bind($myLdapConn, $myLdapEntries[0]['dn'], $aUserPw);
+            if (!$myBindResult) {
+              $myResult = array(0, 'Authentication failed');
+            }
+            else {
+              // all good
+              if (count($myLdapEntries[0]["$aUidAttr"]) > 0) {
+                $myResult = array($myLdapEntries[0]["$aUidAttr"][0], "OK", $myLdapEntries[0]);
+              }
+              else {
+                $myResult = array(0, 'Unable to find attribute');
+              }
+            }
+          }
+          else {
+            $myResult = array(0, 'No such user');
+          }
+        }
+      }
+      // cleanup the connection
+      ldap_close($myLdapConn);
+    }
+    // connection failure
+    else {
+      $myResult = array(0, 'Connect failed');
+    }
+
+    // echo result before returning
+    //echo "myResult = " . $myResult[0] . ", " . $myResult[1];
+
+    return $myResult;
+
+  }
+
 }
 
 ?>
phplist-ldap-0.1b.patch (11,743 bytes)

michiel

30-01-14 21:11

manager   ~0052722

great, will try to include it in next version

michiel

15-02-14 12:46

manager   ~0053039

not that straightforward to add to the core code, but those who need it will find this issue (second on "phplist ldap" google search).

acs

10-04-14 12:28

reporter   ~0053476

I recommend using v3 of LDAP.

Required patch of patch:
*** phplist_auth.inc.orig 2014-04-10 14:25:40.479664698 +0200
--- phplist_auth.inc 2014-04-10 14:25:58.776021119 +0200
***************
*** 286,291 ****
--- 286,294 ----
      // connect to the LDAP server
      $myLdapConn = ldap_connect($aLdapUrl);

+ // use LDAP v3 protocol
+ ldap_set_option($myLdapConn,LDAP_OPT_PROTOCOL_VERSION,3);
+
      // if the connection succeeded
      if ($myLdapConn) {

bendon

24-09-14 11:00

reporter   ~0055130

I've used the submitted patch against version 3.0.8 and it is still working.
I'm just missing an ldap group membership filter option...
Not being a (good (php)) developer, I'm not planning to implement this.

However, I had an issue with the default privileges defined when creating a non-existing admin user: value NULL is apparently allowing user to choose their own privileges, which looks a bit confusing to me!
Therefore, I've added an option to try to define privileges correctly.

I'll try to upload a patch later...

bendon

24-09-14 16:40

reporter  

phplist-ldap-0.1c.patch (13,054 bytes)
diff -rupN phplist-3.0.8/public_html/lists/admin/auth/phplist_auth.inc phplist-3.0.8-ldap.1/public_html/lists/admin/auth/phplist_auth.inc
--- phplist-3.0.8/public_html/lists/admin/auth/phplist_auth.inc	2014-09-08 13:19:39.000000000 +0200
+++ phplist-3.0.8-ldap.1/public_html/lists/admin/auth/phplist_auth.inc	2014-09-24 18:18:33.917795885 +0200
@@ -4,7 +4,7 @@ require_once dirname(__FILE__).'/../acce
 
 class admin_auth {
 
-  function validateLogin($login,$password) {
+  function localValidateLogin($login,$password) {
     $query
     = ' select password, disabled, id'
     . ' from %s'
@@ -119,6 +119,246 @@ class admin_auth {
     return $result;
   }
 
+  /**
+   * New validateLogin() function performs LDAP authentication, if enabled, and
+   * passes regular table-based validation to localValidateLogin().
+   */
+  function validateLogin($login,$password) {
+
+    // get all of the values from the config
+    global $ldap_enabled;
+    global $ldap_url;
+    global $ldap_auth_bind_dn;
+    global $ldap_auth_bind_pw;
+    global $ldap_all_user_base_dn;
+    global $ldap_all_user_pattern;
+    global $ldap_all_user_uid_attribute;
+    global $ldap_all_user_is_super;
+    global $ldap_matching_user_base_dn;
+    global $ldap_matching_user_pattern;
+    global $ldap_matching_user_uid_attribute;
+    global $ldap_except_users;
+    global $ldap_default_privs;
+
+    // tables is global
+    global $tables;
+
+    // make sure our comparisons against the password
+    // field don't do anything funky
+    $password = strval($password);
+
+    // only do LDAP if it's enabled
+    if ($ldap_enabled) {
+      // do not allow blank password
+      if (strlen($password) < 1) {
+        return array(0, 'Password required');
+      }
+
+      // check ldap_except_users to see if this should be forced to be
+      // local auth
+      if ($ldap_except_users && in_array($login, $ldap_except_users)) {
+        return $this->localValidateLogin($login, $password);
+      }
+
+      // check LDAP auth for "all_users"
+      $myPattern = str_replace("__LOGIN__", $login, $ldap_all_user_pattern);
+      $myResult = $this->checkLdapAuth(
+          $ldap_url, $ldap_auth_bind_dn, $ldap_auth_bind_pw,
+          $ldap_all_user_base_dn,
+          $myPattern, $password, $ldap_all_user_uid_attribute
+        );
+
+      // check to see if it worked
+      if (strval(strtolower($myResult[0])) == $login) {
+
+        // see if there is an existing record
+        $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+
+        // if not found, then we create it
+        if (!$admindata) {
+          // create a new record
+          if (! $ldap_default_privs) {
+            $ldap_default_privs = array(
+              'subscribers' => $ldap_all_user_is_super,
+              'campaigns' => $ldap_all_user_is_super,
+              'statistics' => $ldap_all_user_is_super,
+              'settings' => $ldap_all_user_is_super
+            );
+          }
+          Sql_Query(sprintf('insert into %s (loginname,namelc,created,privileges) values("%s","%s",now(),"%s")',
+            $tables["admin"],addslashes($login),addslashes($login),sql_escape(serialize($ldap_default_privs))));
+          $id = Sql_Insert_Id();
+          $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+        }
+
+        // set disabled flag off (by definition "all_users" means enabled
+        // accounts) - this ensures that account control for "all_users" lies
+        // in the LDAP directory, not in PHPList
+        Sql_Query(sprintf('update %s set disabled = 0 where loginname = "%s"',
+          $tables["admin"],addslashes($login)));
+
+        // set the super-user flag appropriately
+        Sql_Query(sprintf('update %s set superuser = '.strval($ldap_all_user_is_super).' where loginname = "%s"',
+          $tables["admin"],addslashes($login)));
+
+        // update table to reflect the email address from the directory
+        if (strlen(strval($myResult[2]['mail'][0])) > 0) {
+          Sql_Query(sprintf('update %s set email = "%s" where loginname = "%s"',
+            $tables["admin"],addslashes(strval($myResult[2]['mail'][0])),addslashes($login)));
+        }
+
+        // return success
+        return array($admindata["id"],"OK");
+
+      }
+
+      // "all_users" auth failed, try again with "matching_users"
+      $myPattern = str_replace("__LOGIN__", $login, $ldap_matching_user_pattern);
+      $myResult = $this->checkLdapAuth(
+          $ldap_url, $ldap_auth_bind_dn, $ldap_auth_bind_pw,
+          $ldap_matching_user_base_dn,
+          $myPattern, $password, $ldap_matching_user_uid_attribute
+        );
+
+      // check to see if it worked this time
+      if (strval(strtolower($myResult[0])) == $login) {
+
+        // it worked in LDAP, now check for the database record
+        $admindata = Sql_Fetch_Array_Query(sprintf('select password,disabled,id from %s where loginname = "%s"',$GLOBALS["tables"]["admin"],addslashes($login)));
+
+        if ($admindata) {
+
+          // check for disabled account
+          if ($admindata["disabled"]) {
+            return array(0,"your account has been disabled");
+          }
+
+          // update table to reflect the email address from the directory
+          if (strlen(strval($myResult[2]['mail'][0])) > 0) {
+            Sql_Query(sprintf('update %s set email = "%s" where loginname = "%s"',
+              $tables["admin"],addslashes(strval($myResult[2]['mail'][0])),addslashes($login)));
+          }
+
+          // all good, return success
+          return array($admindata["id"],"OK");
+        }
+      }
+      
+      //echo $myResult[0] . " - " . $myResult[1];
+      
+      // no luck - game over
+      return array(0, 'Authentication failed');
+      //return $myResult;
+
+    }
+    // LDAP not enabled, do local check
+    else {
+      return $this->localValidateLogin($login, $password);
+    }
+
+  }
+
+  /**
+   * Performs LDAP authentication.  Returns
+   * array(value_of_uidAttr, "OK", full_ldap_entry_for_target_user)
+   * on success, or array(0, "ERROR MESSAGE DESCRIBING WHAT HAPPENED") on
+   * failure. This function checks for LDAP authentication by first binding
+   * as a different user, searching to find a "target DN" (the DN
+   * that corresponds to the end user's login) and then rebinding
+   * with that.
+   */
+  function checkLdapAuth(
+    $aLdapUrl, // the url used to connect to the LDAP server
+    $aBindDn,  // the user to bind as
+    $aBindPw,  // the password
+    $aBaseDn,  // the base of where to search for the actual target user
+    $aFilter,  // the search filter to find the target user's DN
+    $aUserPw,  // the password of the target user (used to bind again
+               // after the DN of the target user is found
+    $aUidAttr,  // the attribute which contains the login ID
+               // (the text name of the login)
+    $aLdapVer = 3 // the ldap version protocol to use
+    ) {
+
+    if (strlen(strval($aBaseDn)) == 0) {
+      return array(0, 'Authentication method disabled');
+    }
+
+    // do not allow blank password
+    $aUserPw = strval($aUserPw);
+    if (strlen($aUserPw) < 1) {
+      return array(0, 'Password required');
+    }
+
+    // cover all bases
+    $myResult = array(0, "Unknown error");
+
+    // connect to the LDAP server
+    $myLdapConn = ldap_connect($aLdapUrl);
+
+    // specify LDAP version protocol
+    ldap_set_option($myLdapConn,LDAP_OPT_PROTOCOL_VERSION,$aLdapVer);
+
+    // if the connection succeeded
+    if ($myLdapConn) {
+      // do an LDAP bind
+      // if we have a bind dn, use it
+      // otherwise bind anonymously
+      if (strlen($aBindDn) > 0)
+        $myBindResult = ldap_bind($myLdapConn, $aBindDn, $aBindPw);
+      else
+        $myBindResult = ldap_bind($myLdapConn);
+      // check to see if bind failed
+      if (!$myBindResult) {
+        $myResult = array(0, 'Bind to LDAP server failed');
+      }
+      // bind was fine, keep going
+      else {
+        // search for the user in question
+        $myLdapSearchResult = ldap_search($myLdapConn, $aBaseDn, $aFilter);
+        if (!$myLdapSearchResult) {
+          $myResult = array(0, 'User not found');
+        }
+        // if user was found, try to bind again as that user
+        else {
+          // get the details about the result entries
+          $myLdapEntries = ldap_get_entries($myLdapConn, $myLdapSearchResult);
+          if ($myLdapEntries['count'] > 0) {
+            // now try another bind as the user that we found
+            $myBindResult = ldap_bind($myLdapConn, $myLdapEntries[0]['dn'], $aUserPw);
+            if (!$myBindResult) {
+              $myResult = array(0, 'Authentication failed');
+            }
+            else {
+              // all good
+              if (count($myLdapEntries[0]["$aUidAttr"]) > 0) {
+                $myResult = array($myLdapEntries[0]["$aUidAttr"][0], "OK", $myLdapEntries[0]);
+              }
+              else {
+                $myResult = array(0, 'Unable to find attribute');
+              }
+            }
+          }
+          else {
+            $myResult = array(0, 'No such user');
+          }
+        }
+      }
+      // cleanup the connection
+      ldap_close($myLdapConn);
+    }
+    // connection failure
+    else {
+      $myResult = array(0, 'Connect failed');
+    }
+
+    // echo result before returning
+    //echo "myResult = " . $myResult[0] . ", " . $myResult[1];
+
+    return $myResult;
+
+  }
+
 }
 
 ?>
diff -rupN phplist-3.0.8/public_html/lists/config/config.php phplist-3.0.8-ldap.1/public_html/lists/config/config.php
--- phplist-3.0.8/public_html/lists/config/config.php	2014-09-08 13:19:39.000000000 +0200
+++ phplist-3.0.8-ldap.1/public_html/lists/config/config.php	2014-09-24 18:32:49.790133159 +0200
@@ -107,3 +107,82 @@ $bounce_mailbox_purge_unprocessed = 1;
 # how many bounces in a row need to have occurred for a user to be marked unconfirmed
 $bounce_unsubscribe_threshold = 5;
 
+
+
+
+/*
+
+=========================================================================
+
+LDAP admin authentication settings
+
+=========================================================================
+
+*/
+
+# set this to 1 to enable LDAP authentication for admin logins
+$ldap_enabled = 0;
+
+# the LDAP host to use
+$ldap_url = "ldap[s:]//YOUR_LDAP_HOST[:YOUR_LDAP_PORT]/";
+
+# the DN of the user to bind as when searching for the actual admin
+# entry that matches what the user typed in on the auth page
+# (this DN should have access only to search and read the accounts
+# which are eligible for use as admin logins; it does not need to
+# and should not be able to read the password fields nor modify
+# any other information in the directory)
+# Set this value empty if you want to do an anonymous bind.
+$ldap_auth_bind_dn = "uid=auth,ou=System,c=US";
+
+# the password for $ldap_auth_bind_dn
+# (Will be ignored if ldap_auth_bind_dn is empty)
+$ldap_auth_bind_pw = "secret";
+
+# users under this point in the directory will have their accounts
+# created automatically when they first login - if you don't want
+# this feature, comment this out
+$ldap_all_user_base_dn = "ou=IT,c=US";
+
+# the filter pattern to locate the entry that matches the user's
+# login name ("__LOGIN__" is replaced with the user's login name)
+$ldap_all_user_pattern = "(uid=__LOGIN__)";
+
+# the attribute which contains the login name
+$ldap_all_user_uid_attribute = "uid";
+
+# set this to 1 if users who are "all_user" are super admins, or 0
+# means that they are not
+$ldap_all_user_is_super = 1;
+
+# set this array to give some specific privileges to newly created
+# admin users. By default, no privileges will be given, besides to
+# super admins.
+$ldap_default_privs = array(
+      'subscribers' => 1,
+      'campaigns' => 1,
+      'statistics' => 1,
+      'settings' => 0
+);
+
+# the users under this point in the directory must have their admin
+# accounts explicitly created in PHPList and the directory is
+# justed used to check the authentication - if you don't want this
+# feature, comment this out
+$ldap_matching_user_base_dn = "ou=Marketing,c=US";
+
+# the filter pattern to locate the entry that matches the user's
+# login name ("__LOGIN__" is replaced with the user's login name)
+$ldap_matching_user_pattern = "(uid=__LOGIN__)";
+
+# the attribute which contains the login name
+$ldap_matching_user_uid_attribute = "uid";
+
+# users who are in this array are not authenticated via LDAP, the
+# password given in the "admin" table is used for them;
+# only set this if you want to give yourself a local admin account
+# in the event that PHPList can't connect to the LDAP server or
+# something else goes terribly wrong - it is generally more secure
+# to leave this commented out
+#$ldap_except_users = array('admin');
+
phplist-ldap-0.1c.patch (13,054 bytes)

bendon

24-09-14 17:01

reporter   ~0055137

Done: phplist-ldap-0.1c.patch should collect all proposed changes so far...

Sorry if there is some issues... I'm not sure the patch has been correctly rendered.

Other remark: not sure if it make sense (for a LDAP search), but will it not be more safe to sanitize the login value ?

chewie71

08-02-16 23:23

reporter   ~0057495

Would like to work this in for 3.2.4, but the original validateLogin function has changed a bit since 3.0.8. Anyone updated this for 3.2+ yet?

michiel

10-02-16 21:50

manager   ~0057504

this thread is probably useful in this context: https://github.com/phpList/phplist3/pull/25

a re-organisation of the authentication system to use a plugin instead.

bendon

24-03-17 10:36

reporter   ~0058923

I've decided to port this patched into a authentication plugin as described above by michiel.

https://github.com/digital-me/phplist-plugin-ldap

Feel free to comment, test and contribute.
I have it working on 3.6.7 and 3.3.1.

samtuke

24-03-17 10:56

administrator   ~0058924

Thank you bendon!