Source for file abook_ldap_server.php

Documentation is available at abook_ldap_server.php

  1. <?php
  2.  
  3. /**
  4.  * abook_ldap_server.php
  5.  *
  6.  * Address book backend for LDAP server
  7.  *
  8.  * LDAP filtering code by Tim Bell
  9.  *   <bhat at users.sourceforge.net> (#539534)
  10.  * ADS limit_scope code by Michael Brown
  11.  *   <mcb30 at users.sourceforge.net> (#1035454)
  12.  * StartTLS code by John Lane
  13.  *   <starfry at users.sourceforge.net> (#1197703)
  14.  * Code for remove, add, modify, lookup by David Härdeman
  15.  *   <david at hardeman.nu> (#1495763)
  16.  *
  17.  * This backend uses LDAP person (RFC2256), organizationalPerson (RFC2256)
  18.  * and inetOrgPerson (RFC2798) objects and dn, description, sn, givenname,
  19.  * cn, mail attributes. Other attributes are ignored.
  20.  * 
  21.  * @copyright 1999-2014 The SquirrelMail Project Team
  22.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  23.  * @version $Id: abook_ldap_server.php 14420 2014-01-01 20:33:20Z pdontthink $
  24.  * @package squirrelmail
  25.  * @subpackage addressbook
  26.  */
  27.  
  28. /**
  29.  * Address book backend for LDAP server
  30.  *
  31.  * An array with the following elements must be passed to
  32.  * the class constructor (elements marked ? are optional)
  33.  *
  34.  * Main settings:
  35.  * <pre>
  36.  *    host      => LDAP server hostname, IP-address or any other URI compatible
  37.  *                 with used LDAP library.
  38.  *    base      => LDAP server root (base dn). Empty string allowed.
  39.  *  ? port      => LDAP server TCP port number (default: 389)
  40.  *  ? charset   => LDAP server charset (default: utf-8)
  41.  *  ? name      => Name for LDAP server (default "LDAP: hostname")
  42.  *                 Used to tag the result data
  43.  *  ? maxrows   => Maximum # of rows in search result
  44.  *  ? timeout   => Timeout for LDAP operations (in seconds, default: 30)
  45.  *                 Might not work for all LDAP libraries or servers.
  46.  *  ? binddn    => LDAP Bind DN.
  47.  *  ? bindpw    => LDAP Bind Password.
  48.  *  ? protocol  => LDAP Bind protocol.
  49.  * </pre>
  50.  * Advanced settings:
  51.  * <pre>
  52.  *  ? filter    => Filter expression to limit ldap search results.
  53.  *    You can use this to *limit* the result set, based on specific
  54.  *    requirements. The filter must be enclosed in parentheses, e.g.:
  55.  *    '(objectclass=mailRecipient)'
  56.  *    or '(&(objectclass=mailRecipient)(obectclass=myCustomClass))'
  57.  *    The default value is empty.
  58.  *
  59.  *  ? search_expression => Custom expression to expand ldap searches.
  60.  *    This can help *expand* the result set, because of hits in more
  61.  *    LDAP attributes. It must be a printf()-style string with either
  62.  *    one placeholder '%s', or, if you want to repeat the expression
  63.  *    many times, '%1$s'. The default value is:
  64.  *    '(|(cn=*%1$s*)(mail=*%1$s*)(sn=*%1$s*))'
  65.  *    that is, the search expression is search in the fields cn (common
  66.  *    name), sn (surname) and mail.
  67.  *
  68.  *  ? limit_scope => Limits scope to base DN (Specific to Win2k3 ADS).
  69.  *  ? listing   => Controls listing of LDAP directory.
  70.  *  ? writeable => Controls write access to address book
  71.  *  ? search_tree => Controls subtree or one level search.
  72.  *  ? starttls  => Controls use of StartTLS on LDAP connections
  73.  * </pre>
  74.  * NOTE. This class should not be used directly. Use addressbook_init()
  75.  *       function instead.
  76.  * @package squirrelmail
  77.  * @subpackage addressbook
  78.  */
  79.     /**
  80.      * @var string backend type
  81.      */
  82.     var $btype = 'remote';
  83.     /**
  84.      * @var string backend name
  85.      */
  86.     var $bname = 'ldap_server';
  87.  
  88.     /* Parameters changed by class */
  89.     /**
  90.      * @var string displayed name
  91.      */
  92.     var $sname   = 'LDAP';       /* Service name */
  93.     /**
  94.      * @var string LDAP server name or address or url
  95.      */
  96.     var $server  = '';
  97.     /**
  98.      * @var integer LDAP server port
  99.      */
  100.     var $port    = 389;
  101.     /**
  102.      * @var string LDAP base DN
  103.      */
  104.     var $basedn  = '';
  105.     /**
  106.      * @var string charset used for entries in LDAP server
  107.      */
  108.     var $charset = 'utf-8';
  109.     /**
  110.      * @var object PHP LDAP link ID
  111.      */
  112.     var $linkid  = false;
  113.     /**
  114.      * @var bool True if LDAP server is bound
  115.      */
  116.     var $bound   = false;
  117.     /**
  118.      * @var integer max rows in result
  119.      */
  120.     var $maxrows = 250;
  121.     /**
  122.      * @var string ldap filter
  123.      * @since 1.5.1
  124.      */
  125.     var $filter = '';
  126.     /**
  127.      * @var string printf()-style ldap search expression.
  128.      *  The default is to search for same string in cn, mail and sn.
  129.      * @since 1.5.2
  130.      */
  131.     var $search_expression = '(|(cn=*%1$s*)(mail=*%1$s*)(sn=*%1$s*))';
  132.     /**
  133.      * @var integer timeout of LDAP operations (in seconds)
  134.      */
  135.     var $timeout = 30;
  136.     /**
  137.      * @var string DN to bind to (non-anonymous bind)
  138.      * @since 1.5.0 and 1.4.3
  139.      */
  140.     var $binddn = '';
  141.     /**
  142.      * @var string  password to bind with (non-anonymous bind)
  143.      * @since 1.5.0 and 1.4.3
  144.      */
  145.     var $bindpw = '';
  146.     /**
  147.      * @var integer protocol used to connect to ldap server
  148.      * @since 1.5.0 and 1.4.3
  149.      */
  150.     var $protocol = '';
  151.     /**
  152.      * @var boolean limits scope to base dn
  153.      * @since 1.5.1
  154.      */
  155.     var $limit_scope = false;
  156.     /**
  157.      * @var boolean controls listing of directory
  158.      * @since 1.5.1
  159.      */
  160.     var $listing = false;
  161.     /**
  162.      * @var boolean true if removing/adding/modifying entries is allowed
  163.      * @since 1.5.2
  164.      */
  165.     var $writeable = false;
  166.     /**
  167.      * @var boolean controls ldap search type.
  168.      *  only first level entries are displayed if set to false
  169.      * @since 1.5.1
  170.      */
  171.     var $search_tree = true;
  172.     /**
  173.      * @var boolean controls use of StartTLS on ldap
  174.      *  connections. Requires php 4.2+ and protocol >= 3
  175.      * @since 1.5.1
  176.      */
  177.     var $starttls = false;
  178.  
  179.     /**
  180.      * Constructor. Connects to database
  181.      * @param array connection options
  182.      */
  183.     function abook_ldap_server($param{
  184.         if(!function_exists('ldap_connect')) {
  185.             $this->set_error(_("PHP install does not have LDAP support."));
  186.             return;
  187.         }
  188.         if(is_array($param)) {
  189.             $this->server = $param['host'];
  190.             // remove whitespace from basedn
  191.             $this->basedn = preg_replace('/,\s*/',',',trim($param['base']));
  192.  
  193.             if(!empty($param['port']))
  194.                 $this->port = $param['port'];
  195.  
  196.             if(!empty($param['charset']))
  197.                 $this->charset = strtolower($param['charset']);
  198.  
  199.             if(isset($param['maxrows']))
  200.                 $this->maxrows = $param['maxrows'];
  201.  
  202.             if(isset($param['timeout']))
  203.                 $this->timeout = $param['timeout'];
  204.  
  205.             if(isset($param['binddn']))
  206.                 $this->binddn = $param['binddn'];
  207.  
  208.             if(isset($param['bindpw']))
  209.                 $this->bindpw = $param['bindpw'];
  210.  
  211.             if(isset($param['protocol']))
  212.                 $this->protocol = (int) $param['protocol'];
  213.  
  214.             if(isset($param['filter']))
  215.                 $this->filter = trim($param['filter']);
  216.             
  217.             if(isset($param['search_expression']&&
  218.                (strstr($param['search_expression']'%s'|| strstr($param['search_expression']'%1$s'))) {
  219.                 $this->search_expression = trim($param['search_expression']);
  220.             }
  221.  
  222.             if(isset($param['limit_scope']))
  223.                 $this->limit_scope = (bool) $param['limit_scope'];
  224.  
  225.             if(isset($param['listing']))
  226.                 $this->listing = (bool) $param['listing'];
  227.  
  228.             if(isset($param['writeable'])) {
  229.                 $this->writeable = (bool) $param['writeable'];
  230.                 // switch backend type to local, if it is writable
  231.                 if($this->writeable$this->btype = 'local';
  232.             }
  233.  
  234.             if(isset($param['search_tree']))
  235.                 $this->search_tree = (bool) $param['search_tree'];
  236.  
  237.             if(isset($param['starttls']))
  238.                 $this->starttls = (bool) $param['starttls'];
  239.  
  240.             if(empty($param['name'])) {
  241.                 $this->sname = 'LDAP: ' $param['host'];
  242.             else {
  243.                 $this->sname = $param['name'];
  244.             }
  245.  
  246.             /*
  247.              * don't open LDAP server on addressbook_init(),
  248.              * open ldap connection only on search. Speeds up
  249.              * addressbook_init() call.
  250.              */
  251.             // $this->open(true);
  252.         else {
  253.             $this->set_error('Invalid argument to constructor');
  254.         }
  255.     }
  256.  
  257.  
  258.     /**
  259.      * Open the LDAP server.
  260.      * @param bool $new is it a new connection
  261.      * @return bool 
  262.      */
  263.     function open($new false{
  264.         $this->error = '';
  265.  
  266.         /* Connection is already open */
  267.         if($this->linkid != false && !$new{
  268.             return true;
  269.         }
  270.  
  271.         $this->linkid = @ldap_connect($this->server$this->port);
  272.         /**
  273.          * check if connection was successful
  274.          * It does not work with OpenLDAP 2.x libraries. Connect error will be 
  275.          * displayed only on ldap command that tries to make connection 
  276.          * (ldap_start_tls or ldap_bind). 
  277.          */
  278.         if(!$this->linkid{
  279.             return $this->set_error($this->ldap_error('ldap_connect failed'));
  280.         }
  281.  
  282.         if(!empty($this->protocol)) {
  283.             // make sure that ldap_set_option() is available before using it
  284.             if(function_exists('ldap_set_option'||
  285.                !@ldap_set_option($this->linkidLDAP_OPT_PROTOCOL_VERSION$this->protocol)) {
  286.                 return $this->set_error('unable to set ldap protocol number');
  287.             }
  288.         }
  289.  
  290.         /**
  291.          * http://www.php.net/ldap-start-tls
  292.          * Check if v3 or newer protocol is used,
  293.          * check if ldap_start_tls function is available.
  294.          * Silently ignore setting, if these requirements are not satisfied.
  295.          * Break with error message if somebody tries to start TLS on
  296.          * ldaps or socket connection.
  297.          */
  298.         if($this->starttls && 
  299.            !empty($this->protocol&& $this->protocol >= &&
  300.            function_exists('ldap_start_tls') ) {
  301.             // make sure that $this->server is not ldaps:// or ldapi:// URL.
  302.             if (preg_match("/^ldap[si]:\/\/.+/i",$this->server)) {
  303.                 return $this->set_error("you can't enable starttls on ldaps and ldapi connections.");
  304.             }
  305.             
  306.             // try starting tls
  307.             if (@ldap_start_tls($this->linkid)) {
  308.                 // set error if call fails
  309.                 return $this->set_error($this->ldap_error('ldap_start_tls failed'));
  310.             }
  311.         }
  312.  
  313.         if(!empty($this->limit_scope&& $this->limit_scope{
  314.             if(empty($this->protocol|| intval($this->protocol3{
  315.                 return $this->set_error('limit_scope requires protocol >= 3');
  316.             }
  317.             // See http://msdn.microsoft.com/library/en-us/ldap/ldap/ldap_server_domain_scope_oid.asp
  318.             $ctrl array "oid" => "1.2.840.113556.1.4.1339""iscritical" => TRUE );
  319.             /*
  320.              * Option is set only during connection.
  321.              * It does not cause immediate errors with OpenLDAP 2.x libraries.
  322.              */
  323.             if(function_exists('ldap_set_option'||
  324.                !@ldap_set_option($this->linkidLDAP_OPT_SERVER_CONTROLSarray($ctrl))) {
  325.                 return $this->set_error($this->ldap_error('limit domain scope failed'));
  326.             }
  327.         }
  328.  
  329.         // authenticated bind
  330.         if(!empty($this->binddn)) {
  331.             if(!@ldap_bind($this->linkid$this->binddn$this->bindpw)) {
  332.                 return $this->set_error($this->ldap_error('authenticated ldap_bind failed'));
  333.             }
  334.         else {
  335.             // anonymous bind
  336.             if(!@ldap_bind($this->linkid)) {
  337.                 return $this->set_error($this->ldap_error('anonymous ldap_bind failed'));
  338.             }
  339.         }
  340.  
  341.         $this->bound = true;
  342.  
  343.         return true;
  344.     }
  345.  
  346.     /**
  347.      * Encode string to the charset used by this LDAP server
  348.      * @param string string that has to be encoded
  349.      * @return string encoded string
  350.      */
  351.     function charset_encode($str{
  352.         global $default_charset;
  353.         if($this->charset != $default_charset{
  354.             return charset_convert($default_charset,$str,$this->charset,false);
  355.         else {
  356.             return $str;
  357.         }
  358.     }
  359.  
  360.     /**
  361.      * Decode from charset used by this LDAP server to charset used by translation
  362.      *
  363.      * Uses SquirrelMail charset_decode functions
  364.      * @param string string that has to be decoded
  365.      * @return string decoded string
  366.      */
  367.     function charset_decode($str{
  368.         global $default_charset;
  369.         if ($this->charset != $default_charset{
  370.             return charset_convert($this->charset,$str,$default_charset,false);
  371.         else {
  372.             return $str;
  373.         }
  374.     }
  375.  
  376.     /**
  377.      * Sanitizes ldap search strings.
  378.      * See rfc2254
  379.      * @link http://www.faqs.org/rfcs/rfc2254.html
  380.      * @since 1.5.1 and 1.4.5
  381.      * @param string $string 
  382.      * @return string sanitized string
  383.      */
  384.     function ldapspecialchars($string{
  385.         $sanitized=array('\\' => '\5c',
  386.                          '*' => '\2a',
  387.                          '(' => '\28',
  388.                          ')' => '\29',
  389.                          "\x00" => '\00');
  390.  
  391.         return str_replace(array_keys($sanitized),array_values($sanitized),$string);
  392.     }
  393.  
  394.     /**
  395.      * Prepares user input for use in a ldap query.
  396.      *
  397.      * Function converts input string to character set used in LDAP server
  398.      * (charset_encode() method) and sanitizes it (ldapspecialchars()).
  399.      *
  400.      * @param string $string string to encode
  401.      * @return string ldap encoded string
  402.      * @since 1.5.2
  403.      */
  404.     function quotevalue($string{
  405.         $sanitized $this->charset_encode($string);
  406.         return $this->ldapspecialchars($sanitized);
  407.     }
  408.  
  409.     /**
  410.      * Search LDAP server.
  411.      *
  412.      * Warning: You must make sure that ldap query is correctly formated and
  413.      * sanitize use of special ldap keywords.
  414.      * @param string $expression ldap query
  415.      * @param boolean $singleentry (since 1.5.2) whether we are looking for a
  416.      *   single entry. Boolean true forces LDAP_SCOPE_BASE search.
  417.      * @return array search results (false on error)
  418.      * @since 1.5.1
  419.      */
  420.     function ldap_search($expression$singleentry false{
  421.         /* Make sure connection is there */
  422.         if(!$this->open()) {
  423.             return false;
  424.         }
  425.  
  426.         $attributes array('dn''description''sn''givenName''cn''mail');
  427.  
  428.         if ($singleentry{
  429.             // ldap_read - search for one single entry
  430.             $sret @ldap_read($this->linkid$expression"objectClass=*",
  431.                                $attributes0$this->maxrows$this->timeout);
  432.         elseif ($this->search_tree{
  433.             // ldap_search - search subtree
  434.             $sret @ldap_search($this->linkid$this->basedn$expression,
  435.                 $attributes0$this->maxrows$this->timeout);
  436.         else {
  437.             // ldap_list - search one level
  438.             $sret @ldap_list($this->linkid$this->basedn$expression,
  439.                 $attributes0$this->maxrows$this->timeout);
  440.         }
  441.  
  442.         /* Return error if search failed */
  443.         if(!$sret{
  444.             // Check for LDAP_NO_SUCH_OBJECT (0x20 or 32) error
  445.             if (ldap_errno($this->linkid)==32{
  446.                 return array();
  447.             else {
  448.                 return $this->set_error($this->ldap_error('ldap_search failed'));
  449.             }
  450.         }
  451.  
  452.         if(@ldap_count_entries($this->linkid$sret<= 0{
  453.             return array();
  454.         }
  455.  
  456.         /* Get results */
  457.         $ret array();
  458.         $returned_rows 0;
  459.         $res @ldap_get_entries($this->linkid$sret);
  460.         for($i $i $res['count'$i++{
  461.             $row $res[$i];
  462.  
  463.             /* Extract data common for all e-mail addresses
  464.              * of an object. Use only the first name */      
  465.             $nickname $this->charset_decode($row['dn']);
  466.  
  467.             /**
  468.              * remove trailing basedn
  469.              * remove whitespaces between RDNs
  470.              * remove leading "cn="
  471.              * which gives nicknames which are shorter while still unique
  472.              */
  473.             $nickname preg_replace('/,\s*/',','trim($nickname));
  474.             $offset strlen($nicknamestrlen($this->basedn);
  475.  
  476.             if($offset && substr($nickname$offset== $this->basedn{
  477.                 $nickname substr($nickname0$offset);
  478.                 if(substr($nickname-1== ",")
  479.                     $nickname substr($nickname0-1);
  480.             }
  481.             if(strncasecmp($nickname"cn="3== 0)
  482.                 $nickname=substr($nickname3);         
  483.  
  484.             if(empty($row['description'][0])) {
  485.                 $label '';
  486.             else {
  487.                 $label $this->charset_decode($row['description'][0]);
  488.             }
  489.  
  490.             if(empty($row['givenname'][0])) {
  491.                 $firstname '';
  492.             else {
  493.                 $firstname $this->charset_decode($row['givenname'][0]);
  494.             }
  495.  
  496.             if(empty($row['sn'][0])) {
  497.                 $surname '';
  498.             else {
  499.                 // remove whitespace in order to handle sn set to empty string
  500.                 $surname trim($this->charset_decode($row['sn'][0]));
  501.             }
  502.  
  503.             $fullname $this->fullname($firstname,$surname);
  504.  
  505.             /* Add one row to result for each e-mail address */
  506.             if(isset($row['mail']['count'])) {
  507.                 for($j $j $row['mail']['count'$j++{
  508.                     array_push($retarray('nickname'  => $nickname,
  509.                    'name'      => $fullname,
  510.                    'firstname' => $firstname,
  511.                    'lastname'  => $surname,
  512.                    'email'     => $row['mail'][$j],
  513.                    'label'     => $label,
  514.                    'backend'   => $this->bnum,
  515.                    'source'    => &$this->sname));
  516.  
  517.                     // Limit number of hits
  518.                     $returned_rows++;
  519.                     if(($returned_rows >= $this->maxrows&&
  520.                        ($this->maxrows > 0) ) {
  521.                         ldap_free_result($sret);
  522.                         return $ret;
  523.                     }
  524.  
  525.                 // for($j ...)
  526.  
  527.             // isset($row['mail']['count'])
  528.  
  529.         }
  530.  
  531.         ldap_free_result($sret);
  532.         return $ret;
  533.     }
  534.  
  535.     /**
  536.      * Add an entry to LDAP server.
  537.      *
  538.      * Warning: You must make sure that the arguments are correctly formated and
  539.      * sanitize use of special ldap keywords.
  540.      * @param string $dn the dn of the entry to be added
  541.      * @param array $data the values of the entry to be added
  542.      * @return boolean result (false on error)
  543.      * @since 1.5.2
  544.      */
  545.     function ldap_add($dn$data{
  546.         /* Make sure connection is there */
  547.         if(!$this->open()) {
  548.             return false;
  549.         }
  550.  
  551.         if(!@ldap_add($this->linkid$dn$data)) {
  552.             $this->set_error(_("Write to address book failed"));
  553.             return false;
  554.         }
  555.         
  556.         return true;
  557.     }
  558.  
  559.     /**
  560.      * Remove an entry from LDAP server.
  561.      *
  562.      * Warning: You must make sure that the argument is correctly formated and
  563.      * sanitize use of special ldap keywords.
  564.      * @param string $dn the dn of the entry to remove
  565.      * @return boolean result (false on error)
  566.      * @since 1.5.2
  567.      */
  568.     function ldap_remove($dn{
  569.         /* Make sure connection is there */
  570.         if(!$this->open()) {
  571.             return false;
  572.         }
  573.  
  574.         if(!@ldap_delete($this->linkid$dn)) {
  575.             $this->set_error(_("Removing entry from address book failed"));
  576.             return false;
  577.         }
  578.  
  579.         return true;
  580.     }
  581.  
  582.     /**
  583.      * Rename an entry on LDAP server.
  584.      *
  585.      * Warning: You must make sure that the arguments are correctly formated and
  586.      * sanitize use of special ldap keywords.
  587.      * @param string $sourcedn the dn of the entry to be renamed
  588.      * @param string $targetdn the dn which $sourcedn should be renamed to
  589.      * @param string $parent the dn of the parent entry
  590.      * @return boolean result (false on error)
  591.      * @since 1.5.2
  592.      */
  593.     function ldap_rename($sourcedn$targetdn$parent{
  594.         /* Make sure connection is there */
  595.         if(!$this->open()) {
  596.             return false;
  597.         }
  598.  
  599.         /* Make sure that the protocol version supports rename */
  600.         if($this->protocol < 3{
  601.             $this->set_error(_("LDAP rename is not supported by used protocol version"));
  602.             return false;
  603.         }
  604.         /**
  605.          * Function is available only in OpenLDAP 2.x.x or Netscape Directory 
  606.          * SDK x.x, and was added in PHP 4.0.5
  607.          * @todo maybe we can use copy + delete instead of ldap_rename()
  608.          */
  609.         if(!function_exists('ldap_rename')) {
  610.             $this->set_error(_("LDAP rename is not supported by used LDAP library. You can't change nickname"));
  611.             return false;
  612.         }
  613.  
  614.         /* OK, go for it */
  615.         if(!@ldap_rename($this->linkid$sourcedn$targetdn$parenttrue)) {
  616.             $this->set_error(_("LDAP rename failed"));
  617.             return false;
  618.         }
  619.  
  620.         return true;
  621.     }
  622.  
  623.     /**
  624.      * Modify the values of an entry on LDAP server.
  625.      *
  626.      * Warning: You must make sure that the arguments are correctly formated and
  627.      * sanitize use of special ldap keywords.
  628.      * @param string $dn the dn of the entry to be modified
  629.      * @param array $data the new values of the entry
  630.      * @param array $deleted_attribs attributes that should be deleted.
  631.      * @return bool result (false on error)
  632.      * @since 1.5.2
  633.      */
  634.     function ldap_modify($dn$data$deleted_attribs{
  635.         /* Make sure connection is there */
  636.         if(!$this->open()) {
  637.             return false;
  638.         }
  639.  
  640.         if(!@ldap_modify($this->linkid$dn$data)) {
  641.             $this->set_error(_("Write to address book failed"));
  642.             return false;
  643.         }
  644.  
  645.         if (!@ldap_mod_del($this->linkid$dn$deleted_attribs)) {
  646.             $this->set_error(_("Unable to remove some field values"));
  647.             return false;
  648.         }
  649.  
  650.         return true;
  651.     }
  652.  
  653.     /**
  654.      * Get error from LDAP resource if possible
  655.      *
  656.      * Should get error from server using the ldap_errno() and ldap_err2str() functions
  657.      * @param string $sError error message used when ldap error functions
  658.      *  and connection resource are unavailable
  659.      * @return string error message
  660.      * @since 1.5.1
  661.      */
  662.     function ldap_error($sError{
  663.         // it is possible that function_exists() tests are not needed
  664.         if(function_exists('ldap_err2str'&& 
  665.            function_exists('ldap_errno'&& 
  666.            is_resource($this->linkid)) {
  667.             return ldap_err2str(ldap_errno($this->linkid));
  668.             // return ldap_error($this->linkid);
  669.         else {
  670.             return $sError;
  671.         }
  672.     }
  673.  
  674.     /**
  675.      * Determine internal attribute name given one of
  676.      * the SquirrelMail SM_ABOOK_FIELD_* constants
  677.      *
  678.      * @param integer $attr The SM_ABOOK_FIELD_* contant to look up
  679.      *
  680.      * @return string The desired attribute name, or the string "ERROR"
  681.      *                 if the $field is not understood (the caller
  682.      *                 is responsible for handing errors)
  683.      *
  684.      */
  685.     function get_attr_name($attr{
  686.         switch ($attr{
  687.             case SM_ABOOK_FIELD_NICKNAME:
  688.                 return 'cn';
  689.             case SM_ABOOK_FIELD_FIRSTNAME:
  690.                 return 'givenName';
  691.             case SM_ABOOK_FIELD_LASTNAME:
  692.                 return 'sn';
  693.             case SM_ABOOK_FIELD_EMAIL:
  694.                 return 'mail';
  695.             case SM_ABOOK_FIELD_LABEL:
  696.                 return 'description';
  697.             default:
  698.                 return 'ERROR';
  699.         }
  700.     }
  701.  
  702.     /* ========================== Public ======================== */
  703.  
  704.     /**
  705.      * Search the LDAP server
  706.      * @param string $expr search expression
  707.      * @return array search results
  708.      */
  709.     function search($expr{
  710.         /* To be replaced by advanded search expression parsing */
  711.         if(is_array($expr)) return false;
  712.  
  713.         // don't allow wide search when listing is disabled.
  714.         if ($expr=='*' && $this->listing{
  715.             return array();
  716.         elseif ($expr=='*'{
  717.             // allow use of wildcard when listing is enabled.
  718.             $expression '(cn=*)';
  719.         else {
  720.             /* Convert search from user's charset to the one used in ldap and sanitize */
  721.             $expr $this->quotevalue($expr);
  722.  
  723.             /* If search expr contains %s or %1$s, replace them with escaped values,
  724.              * so that a wrong printf()-style string is not created by mistake.
  725.              * (Probably overkill but who knows...) */
  726.             $expr str_replace('%s''\\25s'$expr);
  727.             $expr str_replace('%1$s''\\251$s'$expr);
  728.  
  729.             /* Substitute %s or %1$s in printf()-formatted search_expresison with
  730.              * the value that the user searches for. */
  731.             $expression sprintf($this->search_expression$expr);
  732.  
  733.             /* Undo sanitizing of * symbol */
  734.             $expression str_replace('\2a','*',$expression);
  735.  
  736.             /* Replace '**', '***' etc. with '*' in case it occurs in final 
  737.              * search expression */
  738.             while(strstr($expression'**')) {
  739.                 $expression str_replace('**''*'$expression);
  740.             }
  741.         }
  742.  
  743.         /* Add search filtering */
  744.         if ($this->filter!='')
  745.             $expression '(&' $this->filter . $expression ')';
  746.  
  747.         /* Use internal search function and return search results */
  748.         return $this->ldap_search($expression);
  749.     }
  750.  
  751.     /**
  752.      * Lookup an address by the indicated field.
  753.      *
  754.      * @param string  $value The value to look up
  755.      * @param integer $field The field to look in, should be one
  756.      *                        of the SM_ABOOK_FIELD_* constants
  757.      *                        defined in include/constants.php
  758.      *                        (OPTIONAL; defaults to nickname field)
  759.      *                        NOTE: uniqueness is only guaranteed
  760.      *                        when the nickname field is used here;
  761.      *                        otherwise, the first matching address
  762.      *                        is returned.
  763.      *
  764.      * @return array Array with lookup results when the value
  765.      *                was found, an empty array if the value was
  766.      *                not found.
  767.      *
  768.      * @since 1.5.2
  769.      *
  770.      */
  771.     function lookup($value$field=SM_ABOOK_FIELD_NICKNAME{
  772.  
  773.  
  774.         $attr get_attr_name($field);
  775.         if ($attr == 'ERROR'{
  776.             return $this->set_error(sprintf(_("Unknown field name: %s")$field));
  777.         }
  778.  
  779.         // Generate the dn
  780.         $dn $attr '=' $this->quotevalue($value',' $this->basedn;
  781.  
  782.         // Do the search
  783.         $result $this->ldap_search($dntrue);
  784.         if (!is_array($result|| count($result1)
  785.             return array();
  786.  
  787.         return $result[0];
  788.     }
  789.  
  790.     /**
  791.      * List all entries present in LDAP server
  792.      *
  793.      * maxrows setting might limit list of returned entries.
  794.      * Careful with this -- it could get quite large for big sites.
  795.      * @return array all entries in ldap server
  796.      */
  797.      function list_addr({
  798.          if ($this->listing)
  799.              return array();
  800.  
  801.          /* set wide search expression */
  802.          $expression '(cn=*)';
  803.  
  804.          /* add filtering */
  805.          if ($this->filter!='')
  806.              $expression '(&' $this->filter . $expression .')';
  807.  
  808.          /* use internal search function and return search results */
  809.          return $this->ldap_search($expression);
  810.      }
  811.  
  812.     /**
  813.      * Add address
  814.      * @param array $userdata new data
  815.      * @return boolean 
  816.      * @since 1.5.2
  817.      */
  818.     function add($userdata{
  819.         if(!$this->writeable{
  820.             return $this->set_error(_("Address book is read-only"));
  821.         }
  822.  
  823.         /* Convert search from user's charset to the one used in ldap and sanitize */
  824.         $cn $this->quotevalue($userdata['nickname']);
  825.         $dn 'cn=' $cn ',' trim($this->basedn);
  826.  
  827.         /* See if user exists already */
  828.         $user $this->ldap_search($dntrue);
  829.         if (!is_array($user)) {
  830.             return false;
  831.         elseif (count($user0{
  832.             return $this->set_error(sprintf(_("User \"%s\" already exists")$userdata['nickname']));
  833.         }
  834.  
  835.         /* init variable */
  836.         $data array();
  837.  
  838.         /* Prepare data */
  839.         $data['cn'$cn;
  840.         $data['mail'$this->quotevalue($userdata['email']);
  841.         $data["objectclass"][0"top";
  842.         $data["objectclass"][1"person";
  843.         $data["objectclass"][2"organizationalPerson";
  844.         $data["objectclass"][3"inetOrgPerson";
  845.         /* sn is required in person object */
  846.         if(!empty($userdata['lastname'])) {
  847.             $data['sn'$this->quotevalue($userdata['lastname']);
  848.         else {
  849.             $data['sn'' ';
  850.         }
  851.         /* optional fields */
  852.         if(!empty($userdata['firstname']))
  853.             $data['givenName'$this->quotevalue($userdata['firstname']);
  854.         if(!empty($userdata['label'])) {
  855.             $data['description'$this->quotevalue($userdata['label']);
  856.         }
  857.         return $this->ldap_add($dn$data);
  858.     }
  859.  
  860.     /**
  861.      * Delete address
  862.      * @param array $aliases array of entries that have to be removed.
  863.      * @return boolean 
  864.      * @since 1.5.2
  865.      */
  866.     function remove($aliases{
  867.         if(!$this->writeable{
  868.             return $this->set_error(_("Address book is read-only"));
  869.         }
  870.  
  871.         foreach ($aliases as $alias{
  872.             /* Convert nickname from user's charset and derive cn/dn */
  873.             $cn $this->quotevalue($alias);
  874.             $dn 'cn=' $cn ',' $this->basedn;
  875.  
  876.             if (!$this->ldap_remove($dn))
  877.                 return false;
  878.         }
  879.  
  880.         return true;
  881.     }
  882.  
  883.     /**
  884.      * Modify address
  885.      * @param string $alias modified alias
  886.      * @param array $userdata new data
  887.      * @return boolean 
  888.      * @since 1.5.2
  889.      */
  890.     function modify($alias$userdata{
  891.         if(!$this->writeable{
  892.             return $this->set_error(_("Address book is read-only"));
  893.         }
  894.  
  895.         /* Convert search from user's charset to the one used in ldap and sanitize */
  896.         $sourcecn $this->quotevalue($alias);
  897.         $sourcedn 'cn=' $sourcecn ',' trim($this->basedn);
  898.         $targetcn $this->quotevalue($userdata['nickname']);
  899.         $targetdn 'cn=' $targetcn ',' trim($this->basedn);
  900.  
  901.         /* Check that the dn to modify exists */
  902.         $sourceuser $this->lookup($alias);
  903.         if (!is_array($sourceuser|| count($sourceuser1)
  904.             return false;
  905.  
  906.         /* Check if dn is going to change */
  907.         if ($alias != $userdata['nickname']{
  908.  
  909.             /* Check that the target dn doesn't exist */
  910.             $targetuser $this->lookup($userdata['nickname']);
  911.             if (is_array($targetuser&& count($targetuser0)
  912.                 return $this->set_error(sprintf(_("User \"%s\" already exists")$userdata['nickname']));
  913.  
  914.             /* Rename from the source dn to target dn */
  915.             if (!$this->ldap_rename($sourcedn'cn=' $targetcn$this->basedn))
  916.                     return $this->set_error(sprintf(_("Unable to rename user \"%s\" to \"%s\"")$alias$userdata['nickname']));
  917.         }
  918.  
  919.         // initial vars
  920.         $data array();
  921.         $deleted_attribs array();
  922.  
  923.         /* Prepare data */
  924.         $data['cn'$this->quotevalue($targetcn);
  925.         $data['mail'$this->quotevalue($userdata['email']);
  926.         $data["objectclass"][0"top";
  927.         $data["objectclass"][1"person";
  928.         $data["objectclass"][2"organizationalPerson";
  929.         $data["objectclass"][3"inetOrgPerson";
  930.  
  931.         if(!empty($userdata['firstname'])) {
  932.             $data['givenName'$this->quotevalue($userdata['firstname']);
  933.         elseif (!empty($sourceuser['firstname'])) {
  934.             $deleted_attribs['givenName'$this->quotevalue($sourceuser['firstname']);
  935.         }
  936.  
  937.         if(!empty($userdata['lastname'])) {
  938.             $data['sn'$this->quotevalue($userdata['lastname']);
  939.         else {
  940.             // sn is required attribute in LDAP person object.
  941.             // SquirrelMail requires givenName or Surname 
  942.             $data['sn'' ';
  943.         }
  944.  
  945.         if(!empty($userdata['label'])) {
  946.             $data['description'$this->quotevalue($userdata['label']);
  947.         elseif (!empty($sourceuser['label'])) {
  948.             $deleted_attribs['description'$this->quotevalue($sourceuser['label']);
  949.         }
  950.  
  951.         return $this->ldap_modify($targetdn$data$deleted_attribs);
  952.     }
  953. }

Documentation generated on Thu, 18 Dec 2014 04:17:04 +0100 by phpDocumentor 1.4.3