X-Git-Url: https://vcs.fsf.org/?p=squirrelmail.git;a=blobdiff_plain;f=functions%2Fabook_ldap_server.php;h=87bacd9a6f530ac5ac1f87b94d58d95edabf89f9;hp=df618b8b0eacbb69633ba17b1e269ea4cc16bc60;hb=2999753577cd5fb23d7a88e2978c60d8bade44a6;hpb=327e2d96cfd955808879b8680508f47f1bd98469 diff --git a/functions/abook_ldap_server.php b/functions/abook_ldap_server.php index df618b8b..87bacd9a 100644 --- a/functions/abook_ldap_server.php +++ b/functions/abook_ldap_server.php @@ -1,17 +1,19 @@ (#539534) * ADS limit_scope code by Michael Brown * (#1035454) + * StartTLS code by John Lane + * (#1197703) * + * @copyright © 1999-2006 The SquirrelMail Project Team + * @license http://opensource.org/licenses/gpl-license.php GNU Public License * @version $Id$ * @package squirrelmail * @subpackage addressbook @@ -43,6 +45,8 @@ * ? filter => Filter expression to limit ldap searches * ? limit_scope => Limits scope to base DN (Specific to Win2k3 ADS). * ? listing => Controls listing of LDAP directory. + * ? search_tree => Controls subtree or one level search. + * ? starttls => Controls use of StartTLS on LDAP connections * * NOTE. This class should not be used directly. Use the * "AddressBook" class instead. @@ -126,6 +130,18 @@ class abook_ldap_server extends addressbook_backend { * @since 1.5.1 */ var $listing = false; + /** + * @var boolean controls ldap search type. + * only first level entries are displayed if set to false + * @since 1.5.1 + */ + var $search_tree = true; + /** + * @var boolean controls use of StartTLS on ldap + * connections. Requires php 4.2+ and protocol >= 3 + * @since 1.5.1 + */ + var $starttls = false; /** * Constructor. Connects to database @@ -159,16 +175,22 @@ class abook_ldap_server extends addressbook_backend { $this->bindpw = $param['bindpw']; if(isset($param['protocol'])) - $this->protocol = $param['protocol']; + $this->protocol = (int) $param['protocol']; if(isset($param['filter'])) $this->filter = trim($param['filter']); if(isset($param['limit_scope'])) - $this->limit_scope = $param['limit_scope']; + $this->limit_scope = (bool) $param['limit_scope']; if(isset($param['listing'])) - $this->listing = $param['listing']; + $this->listing = (bool) $param['listing']; + + if(isset($param['search_tree'])) + $this->search_tree = (bool) $param['search_tree']; + + if(isset($param['starttls'])) + $this->starttls = (bool) $param['starttls']; if(empty($param['name'])) { $this->sname = 'LDAP: ' . $param['host']; @@ -202,21 +224,44 @@ class abook_ldap_server extends addressbook_backend { } $this->linkid = @ldap_connect($this->server, $this->port); + /** + * check if connection was successful + * It does not work with OpenLDAP 2.x libraries. Connect error will be + * displayed only on ldap command that tries to make connection + * (ldap_start_tls or ldap_bind). + */ if(!$this->linkid) { - if(function_exists('ldap_error') && is_resource($this->linkid)) { - return $this->set_error(ldap_error($this->linkid)); - } else { - return $this->set_error('ldap_connect failed'); - } + return $this->set_error($this->ldap_error('ldap_connect failed')); } if(!empty($this->protocol)) { - if(!@ldap_set_option($this->linkid, LDAP_OPT_PROTOCOL_VERSION, $this->protocol)) { - if(function_exists('ldap_error')) { - return $this->set_error(ldap_error($this->linkid)); - } else { - return $this->set_error('ldap_set_option failed'); - } + // make sure that ldap_set_option() is available before using it + if(! function_exists('ldap_set_option') || + !@ldap_set_option($this->linkid, LDAP_OPT_PROTOCOL_VERSION, $this->protocol)) { + return $this->set_error('unable to set ldap protocol number'); + } + } + + /** + * http://www.php.net/ldap-start-tls + * Check if v3 or newer protocol is used, + * check if ldap_start_tls function is available. + * Silently ignore setting, if these requirements are not satisfied. + * Break with error message if somebody tries to start TLS on + * ldaps or socket connection. + */ + if($this->starttls && + !empty($this->protocol) && $this->protocol >= 3 && + function_exists('ldap_start_tls') ) { + // make sure that $this->server is not ldaps:// or ldapi:// URL. + if (preg_match("/^ldap[si]:\/\/.+/i",$this->server)) { + return $this->set_error("you can't enable starttls on ldaps and ldapi connections."); + } + + // try starting tls + if (! @ldap_start_tls($this->linkid)) { + // set error if call fails + return $this->set_error($this->ldap_error('ldap_start_tls failed')); } } @@ -226,30 +271,25 @@ class abook_ldap_server extends addressbook_backend { } // See http://msdn.microsoft.com/library/en-us/ldap/ldap/ldap_server_domain_scope_oid.asp $ctrl = array ( "oid" => "1.2.840.113556.1.4.1339", "iscritical" => TRUE ); - if(!@ldap_set_option($this->linkid, LDAP_OPT_SERVER_CONTROLS, array($ctrl))) { - if(function_exists('ldap_error')) { - return $this->set_error(ldap_error($this->linkid)); - } else { - return $this->set_error('limit domain scope failed'); - } + /* + * Option is set only during connection. + * It does not cause immediate errors with OpenLDAP 2.x libraries. + */ + if(! function_exists('ldap_set_option') || + !@ldap_set_option($this->linkid, LDAP_OPT_SERVER_CONTROLS, array($ctrl))) { + return $this->set_error($this->ldap_error('limit domain scope failed')); } } + // authenticated bind if(!empty($this->binddn)) { if(!@ldap_bind($this->linkid, $this->binddn, $this->bindpw)) { - if(function_exists('ldap_error')) { - return $this->set_error(ldap_error($this->linkid)); - } else { - return $this->set_error('authenticated ldap_bind failed'); - } - } + return $this->set_error($this->ldap_error('authenticated ldap_bind failed')); + } } else { + // anonymous bind if(!@ldap_bind($this->linkid)) { - if(function_exists('ldap_error')) { - return $this->set_error(ldap_error($this->linkid)); - } else { - return $this->set_error('anonymous ldap_bind failed'); - } + return $this->set_error($this->ldap_error('anonymous ldap_bind failed')); } } @@ -321,19 +361,21 @@ class abook_ldap_server extends addressbook_backend { return false; } - // TODO: ldap_search() | ldap_list() | ldap_read() option - $sret = @ldap_search($this->linkid, $this->basedn, $expression, - array('dn', 'o', 'ou', 'sn', 'givenname', 'cn', 'mail'), - 0, $this->maxrows, $this->timeout); + if ($this->search_tree) { + // ldap_search - search subtree + $sret = @ldap_search($this->linkid, $this->basedn, $expression, + array('dn', 'o', 'ou', 'sn', 'givenname', 'cn', 'mail'), + 0, $this->maxrows, $this->timeout); + } else { + // ldap_list - search one level + $sret = @ldap_list($this->linkid, $this->basedn, $expression, + array('dn', 'o', 'ou', 'sn', 'givenname', 'cn', 'mail'), + 0, $this->maxrows, $this->timeout); + } - /* Should get error from server using the ldap_error() function, - * but it only exist in the PHP LDAP documentation. */ + /* Return error if search failed */ if(!$sret) { - if(function_exists('ldap_error')) { - return $this->set_error(ldap_error($this->linkid)); - } else { - return $this->set_error('ldap_search failed'); - } + return $this->set_error($this->ldap_error('ldap_search failed')); } if(@ldap_count_entries($this->linkid, $sret) <= 0) { @@ -348,9 +390,16 @@ class abook_ldap_server extends addressbook_backend { $row = $res[$i]; /* Extract data common for all e-mail addresses - * of an object. Use only the first name */ + * of an object. Use only the first name */ $nickname = $this->charset_decode($row['dn']); - // TODO: remove basedn from $nickname + + /** + * calculate length of basedn and remove it from nickname + * ignore whitespaces between RDNs + * Nicknames are shorter and still unique + */ + $basedn_len=strlen(preg_replace('/,\s*/',',',trim($this->basedn))); + $nickname=substr(preg_replace('/,\s*/',',',$nickname),0,(-1 - $basedn_len)); $fullname = $this->charset_decode($row['cn'][0]); @@ -405,6 +454,27 @@ class abook_ldap_server extends addressbook_backend { return $ret; } + /** + * Get error from LDAP resource if possible + * + * Should get error from server using the ldap_errno() and ldap_err2str() functions + * @param string $sError error message used when ldap error functions + * and connection resource are unavailable + * @return string error message + * @since 1.5.1 + */ + function ldap_error($sError) { + // it is possible that function_exists() tests are not needed + if(function_exists('ldap_err2str') && + function_exists('ldap_errno') && + is_resource($this->linkid)) { + return ldap_err2str(ldap_errno($this->linkid)); + // return ldap_error($this->linkid); + } else { + return $sError; + } + } + /* ========================== Public ======================== */ /** @@ -426,12 +496,14 @@ class abook_ldap_server extends addressbook_backend { /* Convert search from user's charset to the one used in ldap */ $expr = $this->charset_encode($expr); - /* Make sure that search does not contain ldap special chars */ - $expression = '(cn=*' . $this->ldapspecialchars($expr) . '*)'; + /* sanitize search string */ + $expr = $this->ldapspecialchars($expr); + + /* Search for same string in cn, main and sn */ + $expression = '(|(cn=*'.$expr.'*)(mail=*'.$expr.'*)(sn=*'.$expr.'*))'; /* Undo sanitizing of * symbol */ $expression = str_replace('\2a','*',$expression); - /* TODO: implement any single character (?) matching */ } /* Add search filtering */