01288: Add SASL for LDAP authentication to AuthUser

Summary: Add SASL for LDAP authentication to AuthUser
Created: 2012-06-20 22:41
Status: Open
Category: Feature
From: abone
Version: 2.2.11
OS: Solaris 10 / Apache 2.2 / PHP 5.3.8


Current PmWiki.AuthUser implementation (version from 2.2.11 to 2.2.38) only support simple LDAP bind. This mechanism uses plain text passwords in communication between pmwiki and LDAP server, which may lead to password leak, especially in distributed enterprise environment.

Modern LDAP v3 servers can use SASL for authentication (see RFC 4513), which may deliver much better security. I have access to Active Directory and OpenLDAP 2.4 catalogs, both suport SASL. I propose to add SASL to authuser.php.

Here is the patch I'm using on top of authuser.php r2606 (tested with OpenLDAP 2.4):

--- authuser.php.r2606  2010-12-15 02:00:41.460022000 +0500
+++ authuser.php        2012-06-21 09:39:17.619948816 +0600
@@ -130,20 +130,24 @@

 function AuthUserLDAP($pagename, $id, $pw, $pwlist) {
-  global $AuthLDAPBindDN, $AuthLDAPBindPassword;
+  global $AuthLDAPBindDN, $AuthLDAPBindPassword, $AuthLDAPSASLMech;
   if (!$pw) return false;
   if (!function_exists('ldap_connect')) 
     Abort('authuser: LDAP authentication requires PHP ldap functions','ldapfn');
   foreach ((array)$pwlist as $ldap) {
-    if (!preg_match('!(ldaps?://[^/]+)/(.*)$!', $ldap, $match))
+    if (!preg_match('!(ldaps?://[^/]+)(/(.*))?$!', $ldap, $match))
     ##  connect to the LDAP server
-    list($z, $url, $path) = $match;
+    list($z, $url, $zz, $path) = $match;
     $ds = ldap_connect($url);
     ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, 3);
-    ##  For Active Directory, don't specify a path and we simply
-    ##  attempt to bind with the username and password directly
-    if (!$path && @ldap_bind($ds, $id, $pw)) { ldap_close($ds); return true; }
+    ##  LDAP v3 servers may support SASL authentication (RFC 4513)
+    ##  Don't specify a path and we simply attempt to SASL bind with
+    ##  the username and password directly.
+    $sasl_mech = isset($AuthLDAPSASLMech) ? $AuthLDAPSASLMech : 'DIGEST-MD5';
+    if (!$path && @ldap_sasl_bind($ds, NULL, $pw, $sasl_mech, NULL, $id)) {
+      ldap_close($ds); return true;
+    }
     ##  Otherwise, we use Apache-style urls for LDAP authentication
     ##  Split the path into its search components
     list($basedn, $attr, $sub, $filter) = explode('?', $path);

Patch notes:

  • Change LDAP URL regexp to make trailing slash optional (ldap://host instead of ldap://host/). AFAIK in RFC 4516 trailing slash should present only with base DN.
  • Replace old Active Directory hack with more generic SASL method. But we may keep old hack because SASL bind may require explicit SASL mechanism specification to work, while AD seems to allow simple binds.
  • In many cases SASL require explicit authentication mech selection. I see that defaults especially if LDAP client and LDAP server has different vendors. I've used global AuthLDAPSASLMech with DIGEST-MD5 default, but you you may have better solution.