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

Documentation generated on Mon, 13 Jan 2020 04:21:55 +0100 by phpDocumentor 1.4.3