Happy New Year
[squirrelmail.git] / functions / abook_ldap_server.php
index 212b105ac9513b9c6176c15240292be2e5482969..cff3d0993cff5abb7ab2b0cb2c98b74089b5461c 100644 (file)
  * StartTLS code by John Lane
  *   <starfry at users.sourceforge.net> (#1197703)
  * Code for remove, add, modify, lookup by David Härdeman
- *   <david at 2gen.com> (#1495763)
+ *   <david at hardeman.nu> (#1495763)
  *
  * This backend uses LDAP person (RFC2256), organizationalPerson (RFC2256)
  * and inetOrgPerson (RFC2798) objects and dn, description, sn, givenname,
  * cn, mail attributes. Other attributes are ignored.
  * 
- * @copyright &copy; 1999-2006 The SquirrelMail Project Team
+ * @copyright 1999-2020 The SquirrelMail Project Team
  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
  * </pre>
  * Advanced settings:
  * <pre>
- *  ? filter    => Filter expression to limit ldap searches
+ *  ? filter    => Filter expression to limit ldap search results.
+ *    You can use this to *limit* the result set, based on specific
+ *    requirements. The filter must be enclosed in parentheses, e.g.:
+ *    '(objectclass=mailRecipient)'
+ *    or '(&(objectclass=mailRecipient)(obectclass=myCustomClass))'
+ *    The default value is empty.
+ *
+ *  ? search_expression => Custom expression to expand ldap searches.
+ *    This can help *expand* the result set, because of hits in more
+ *    LDAP attributes. It must be a printf()-style string with either
+ *    one placeholder '%s', or, if you want to repeat the expression
+ *    many times, '%1$s'. The default value is:
+ *    '(|(cn=*%1$s*)(mail=*%1$s*)(sn=*%1$s*))'
+ *    that is, the search expression is search in the fields cn (common
+ *    name), sn (surname) and mail.
+ *
  *  ? limit_scope => Limits scope to base DN (Specific to Win2k3 ADS).
  *  ? listing   => Controls listing of LDAP directory.
  *  ? writeable => Controls write access to address book
@@ -109,6 +124,12 @@ class abook_ldap_server extends addressbook_backend {
      * @since 1.5.1
      */
     var $filter = '';
+    /**
+     * @var string printf()-style ldap search expression.
+     * The default is to search for same string in cn, mail and sn.
+     * @since 1.5.2
+     */
+    var $search_expression = '(|(cn=*%1$s*)(mail=*%1$s*)(sn=*%1$s*))';
     /**
      * @var integer timeout of LDAP operations (in seconds)
      */
@@ -142,7 +163,7 @@ class abook_ldap_server extends addressbook_backend {
      * @var boolean true if removing/adding/modifying entries is allowed
      * @since 1.5.2
      */
-    var $writeable = true;
+    var $writeable = false;
     /**
      * @var boolean controls ldap search type.
      * only first level entries are displayed if set to false
@@ -157,10 +178,11 @@ class abook_ldap_server extends addressbook_backend {
     var $starttls = false;
 
     /**
-     * Constructor. Connects to database
+     * Constructor (PHP5 style, required in some future version of PHP)
+     * Connects to the database
      * @param array connection options
      */
-    function abook_ldap_server($param) {
+    function __construct($param) {
         if(!function_exists('ldap_connect')) {
             $this->set_error(_("PHP install does not have LDAP support."));
             return;
@@ -193,6 +215,11 @@ class abook_ldap_server extends addressbook_backend {
 
             if(isset($param['filter']))
                 $this->filter = trim($param['filter']);
+            
+            if(isset($param['search_expression']) &&
+               (strstr($param['search_expression'], '%s') || strstr($param['search_expression'], '%1$s'))) {
+                $this->search_expression = trim($param['search_expression']);
+            }
 
             if(isset($param['limit_scope']))
                 $this->limit_scope = (bool) $param['limit_scope'];
@@ -229,6 +256,14 @@ class abook_ldap_server extends addressbook_backend {
         }
     }
 
+    /**
+     * Constructor (PHP4 style, kept for compatibility reasons)
+     * Connects to the database
+     * @param array connection options
+     */
+    function abook_ldap_server($param) {
+        return self::__construct($param);
+    }
 
     /**
      * Open the LDAP server.
@@ -398,7 +433,7 @@ class abook_ldap_server extends addressbook_backend {
             return false;
         }
 
-        $attributes = array('dn', 'description', 'sn', 'givenname', 'cn', 'mail');
+        $attributes = array('dn', 'description', 'sn', 'givenName', 'cn', 'mail');
 
         if ($singleentry) {
             // ldap_read - search for one single entry
@@ -475,8 +510,7 @@ class abook_ldap_server extends addressbook_backend {
                 $surname = trim($this->charset_decode($row['sn'][0]));
             }
 
-            // FIXME: Write generic function to handle name order 
-            $fullname = trim($firstname . " " . $surname);
+            $fullname = $this->fullname($firstname,$surname);
 
             /* Add one row to result for each e-mail address */
             if(isset($row['mail']['count'])) {
@@ -647,6 +681,34 @@ class abook_ldap_server extends addressbook_backend {
         }
     }
 
+    /**
+     * Determine internal attribute name given one of
+     * the SquirrelMail SM_ABOOK_FIELD_* constants
+     *
+     * @param integer $attr The SM_ABOOK_FIELD_* contant to look up
+     *
+     * @return string The desired attribute name, or the string "ERROR"
+     *                if the $field is not understood (the caller
+     *                is responsible for handing errors)
+     *
+     */
+    function get_attr_name($attr) {
+        switch ($attr) {
+            case SM_ABOOK_FIELD_NICKNAME:
+                return 'cn';
+            case SM_ABOOK_FIELD_FIRSTNAME:
+                return 'givenName';
+            case SM_ABOOK_FIELD_LASTNAME:
+                return 'sn';
+            case SM_ABOOK_FIELD_EMAIL:
+                return 'mail';
+            case SM_ABOOK_FIELD_LABEL:
+                return 'description';
+            default:
+                return 'ERROR';
+        }
+    }
+
     /* ========================== Public ======================== */
 
     /**
@@ -668,11 +730,24 @@ class abook_ldap_server extends addressbook_backend {
             /* Convert search from user's charset to the one used in ldap and sanitize */
             $expr = $this->quotevalue($expr);
 
-            /* Search for same string in cn, main and sn */
-            $expression = '(|(cn=*'.$expr.'*)(mail=*'.$expr.'*)(sn=*'.$expr.'*))';
+            /* If search expr contains %s or %1$s, replace them with escaped values,
+             * so that a wrong printf()-style string is not created by mistake.
+             * (Probably overkill but who knows...) */
+            $expr = str_replace('%s', '\\25s', $expr);
+            $expr = str_replace('%1$s', '\\251$s', $expr);
+
+            /* Substitute %s or %1$s in printf()-formatted search_expresison with
+             * the value that the user searches for. */
+            $expression = sprintf($this->search_expression, $expr);
 
             /* Undo sanitizing of * symbol */
             $expression = str_replace('\2a','*',$expression);
+
+            /* Replace '**', '***' etc. with '*' in case it occurs in final 
+             * search expression */
+            while(strstr($expression, '**')) {
+                $expression = str_replace('**', '*', $expression);
+            }
         }
 
         /* Add search filtering */
@@ -684,17 +759,37 @@ class abook_ldap_server extends addressbook_backend {
     }
 
     /**
-     * Lookup an alias
-     * @param string $alias alias
-     * @return array search results
+     * Lookup an address by the indicated field.
+     *
+     * @param string  $value The value to look up
+     * @param integer $field The field to look in, should be one
+     *                       of the SM_ABOOK_FIELD_* constants
+     *                       defined in include/constants.php
+     *                       (OPTIONAL; defaults to nickname field)
+     *                       NOTE: uniqueness is only guaranteed
+     *                       when the nickname field is used here;
+     *                       otherwise, the first matching address
+     *                       is returned.
+     *
+     * @return array Array with lookup results when the value
+     *               was found, an empty array if the value was
+     *               not found.
+     *
      * @since 1.5.2
+     *
      */
-    function lookup($alias) {
-        /* Generate the dn and try to retrieve that single entry */
-        $cn = $this->quotevalue($alias);
-        $dn = 'cn=' . $cn . ',' . $this->basedn;
+    function lookup($value, $field=SM_ABOOK_FIELD_NICKNAME) {
+
+
+        $attr = get_attr_name($field);
+        if ($attr == 'ERROR') {
+            return $this->set_error(sprintf(_("Unknown field name: %s"), $field));
+        }
+
+        // Generate the dn
+        $dn = $attr . '=' . $this->quotevalue($value) . ',' . $this->basedn;
 
-        /* Do the search */
+        // Do the search
         $result = $this->ldap_search($dn, true);
         if (!is_array($result) || count($result) < 1)
             return array();