From 4d982d38a85567491a163d2f1561ad938640dea2 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Thu, 17 Feb 2011 09:35:06 -0500
Subject: [PATCH] - Add LDAP SASL bind and proxy authentication (#1486692)

---
 CHANGELOG                      |    1 
 program/include/rcube_ldap.php |   84 ++++++++++++++++++++++++++++++++++++++---
 config/main.inc.php.dist       |    5 ++
 3 files changed, 83 insertions(+), 7 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 85f2bc3..db6c4af 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Add LDAP SASL bind and proxy authentication (#1486692)
 - Add variable for 'Today' label in date_today option (#1486120)
 - Fix dont_override setting does not override existing user preferences (#1487664)
 - Use only one from IMAP authentication methods to prevent login delays (1487784)
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 3d9cd06..db029f1 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -470,6 +470,11 @@
   // The login name is used to search for the DN to bind with
   'search_base_dn' => '',
   'search_filter'  => '',   // e.g. '(&(objectClass=posixAccount)(uid=%u))'
+  // Optional authentication identifier to be used as SASL authorization proxy
+  // bind_dn need to be empty
+  'auth_cid'       => '',
+  // SASL authentication method (for proxy auth), e.g. DIGEST-MD5
+  'auth_method'    => '',
   // Indicates if we can write to the LDAP directory or not.
   // If writable is true then these fields need to be populated:
   // LDAP_Object_Classes, required_fields, LDAP_rdn
diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php
index 308c4f2..c825371 100644
--- a/program/include/rcube_ldap.php
+++ b/program/include/rcube_ldap.php
@@ -162,11 +162,16 @@
         {
             $this->ready = true;
 
+            $bind_pass = $this->prop['bind_pass'];
+            $bind_user = $this->prop['bind_user'];
+            $bind_dn   = $this->prop['bind_dn'];
+            $base_dn   = $this->prop['base_dn'];
+
             // User specific access, generate the proper values to use.
             if ($this->prop['user_specific']) {
                 // No password set, use the session password
-                if (empty($this->prop['bind_pass'])) {
-                    $this->prop['bind_pass'] = $RCMAIL->decrypt($_SESSION['password']);
+                if (empty($bind_pass)) {
+                    $bind_pass = $RCMAIL->decrypt($_SESSION['password']);
                 }
 
                 // Get the pieces needed for variable replacement.
@@ -190,19 +195,31 @@
                         $this->_debug("S: search returned dn: $bind_dn");
 
                         if ($bind_dn) {
-                            $this->prop['bind_dn'] = $bind_dn;
                             $dn = ldap_explode_dn($bind_dn, 1);
                             $replaces['%dn'] = $dn[0];
                         }
                     }
                 }
                 // Replace the bind_dn and base_dn variables.
-                $this->prop['bind_dn'] = strtr($this->prop['bind_dn'], $replaces);
-                $this->prop['base_dn'] = strtr($this->prop['base_dn'], $replaces);
+                $bind_dn   = strtr($bind_dn, $replaces);
+                $base_dn   = strtr($base_dn, $replaces);
+
+                if (empty($bind_user)) {
+                    $bind_user = $u;
+                }
             }
 
-            if (!empty($this->prop['bind_dn']) && !empty($this->prop['bind_pass']))
-                $this->ready = $this->_bind($this->prop['bind_dn'], $this->prop['bind_pass']);
+            if (!empty($bind_pass)) {
+                if (!empty($bind_dn)) {
+                    $this->ready = $this->_bind($bind_dn, $bind_pass);
+                }
+                else if (!empty($this->prop['auth_cid'])) {
+                    $this->ready = $this->_sasl_bind($this->prop['auth_cid'], $bind_pass, $bind_user);
+                }
+                else {
+                    $this->ready = $this->_sasl_bind($bind_user, $bind_pass);
+                }
+            }
         }
         else
             raise_error(array('code' => 100, 'type' => 'ldap',
@@ -217,6 +234,59 @@
 
 
     /**
+     * Bind connection with (SASL-) user and password
+     *
+     * @param string $authc Authentication user
+     * @param string $pass  Bind password
+     * @param string $authz Autorization user
+     *
+     * @return boolean True on success, False on error
+     */
+    private function _sasl_bind($authc, $pass, $authz=null)
+    {
+        if (!$this->conn) {
+            return false;
+        }
+
+        if (!function_exists('ldap_sasl_bind')) {
+            raise_error(array(
+                'code' => 100, 'type' => 'ldap',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Unable to bind: ldap_sasl_bind() not exists"),
+            true, true);
+        }
+
+        if (!empty($authz)) {
+            $authz = 'u:' . $authz;
+        }
+
+        if (!empty($this->prop['auth_method'])) {
+            $method = $this->prop['auth_method'];
+        }
+        else {
+            $method = 'DIGEST-MD5';
+        }
+
+        $this->_debug("C: Bind [mech: $method, authc: $authc, authz: $authz] [pass: $pass]");
+
+        if (ldap_sasl_bind($this->conn, NULL, $pass, $method, NULL, $authc, $authz)) {
+            $this->_debug("S: OK");
+            return true;
+        }
+
+        $this->_debug("S: ".ldap_error($this->conn));
+
+        raise_error(array(
+            'code' => ldap_errno($this->conn), 'type' => 'ldap',
+            'file' => __FILE__, 'line' => __LINE__,
+            'message' => "Bind failed for authcid=$authc ".ldap_error($this->conn)),
+            true);
+
+        return false;
+    }
+
+
+    /**
     * Bind connection with DN and password
     *
     * @param string Bind DN

--
Gitblit v1.9.1