From 593370a4c0a02343d1f6901d9abc98c6aea8f44a Mon Sep 17 00:00:00 2001 From: tokul Date: Sun, 3 Jul 2005 09:58:45 +0000 Subject: [PATCH] ldap_list/ldap_search and starttls support in address book ldap backend git-svn-id: https://svn.code.sf.net/p/squirrelmail/code/trunk/squirrelmail@9703 7612ce4b-ef26-0410-bec9-ea0150e637f0 --- ChangeLog | 4 + config/conf.pl | 63 ++++++++++++++ functions/abook_ldap_server.php | 146 +++++++++++++++++++++++--------- 3 files changed, 171 insertions(+), 42 deletions(-) diff --git a/ChangeLog b/ChangeLog index 595abb19..1b9063b2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -383,6 +383,10 @@ Version 1.5.1 -- CVS - Added Bluesome theme by Saku Lehtiö (#1188209). - Rewrite of advanced identity handlying to remove stupid extraction of all post variables. [CAN-2005-2095] + - Added StartTLS support to address book LDAP backend (#1197703). Patch + by John Lane. + - Added subtree/one level search options to address book LDAP backend + (#1212618). Version 1.5.0 - 2 February 2004 ------------------------------- diff --git a/config/conf.pl b/config/conf.pl index f5d7542c..53de21fe 100755 --- a/config/conf.pl +++ b/config/conf.pl @@ -267,6 +267,16 @@ while ( $line = ) { $tmp =~ s/[\'\"]?,?\s*$//; $tmp =~ s/[\'\"]?\);\s*$//; $listing = $tmp; + } elsif ( $tmp =~ /^\s*[\'\"]search_tree[\'\"]/i ) { + $tmp =~ s/^\s*[\'\"]search_tree[\'\"]\s*=>\s*[\'\"]?//i; + $tmp =~ s/[\'\"]?,?\s*$//; + $tmp =~ s/[\'\"]?\);\s*$//; + $search_tree = $tmp; + } elsif ( $tmp =~ /^\s*[\'\"]starttls[\'\"]/i ) { + $tmp =~ s/^\s*[\'\"]starttls[\'\"]\s*=>\s*[\'\"]?//i; + $tmp =~ s/[\'\"]?,?\s*$//; + $tmp =~ s/[\'\"]?\);\s*$//; + $starttls = $tmp; } } $ldap_host[$sub] = $host; @@ -281,6 +291,8 @@ while ( $line = ) { $ldap_protocol[$sub] = $protocol; $ldap_limit_scope[$sub] = $limit_scope; $ldap_listing[$sub] = $listing; + $ldap_search_tree[$sub] = $search_tree; + $ldap_starttls[$sub] = $starttls; } elsif ( $options[0] =~ /^(data_dir|attachment_dir|theme_css|org_logo|signout_page)$/ ) { ${ $options[0] } = &change_to_rel_path($options[1]); } else { @@ -2481,6 +2493,12 @@ sub command61 { if ( $ldap_listing[$count] ) { print " listing: $ldap_listing[$count]\n"; } + if ( $ldap_search_tree[$count] ) { + print " search_tree: $ldap_search_tree[$count]\n"; + } + if ( $ldap_starttls[$count] ) { + print " starttls: $ldap_starttls[$count]\n"; + } print "\n"; $count++; @@ -2630,6 +2648,35 @@ sub command61 { $name = 'false'; } $ldap_limit_scope[$sub] = $name; + + print "\n"; + + print "You can control ldap search type here.\n"; + print "Addresses can be searched in entire LDAP subtree (default)\n"; + print "or only first level entries are returned.\n"; + print "\n"; + print "Search entire LDAP subtree? (Y/n):"; + $name = ; + if ( $name =~ /^n\n/i ) { + $name = 'false'; + } else { + $name = 'true'; + } + $ldap_search_tree[$sub] = $name; + + print "\n"; + + print "You can control use of StartTLS on LDAP connection here.\n"; + print "This option requires use of v3 or newer LDAP protocol and php 4.2+.\n"; + print "\n"; + print "Use StartTLS? (y/N):"; + $name = ; + if ( $name =~ /^y\n/i ) { + $name = 'true'; + } else { + $name = 'false'; + } + $ldap_starttls[$sub] = $name; } print "\n"; @@ -2654,6 +2701,8 @@ sub command61 { @new_ldap_protocol = (); @new_ldap_limit_scope = (); @new_ldap_listing = (); + @new_ldap_search_tree = (); + @new_ldap_starttls = (); while ( $count <= $#ldap_host ) { if ( $count != $rem_num ) { @@ -2669,6 +2718,8 @@ sub command61 { @new_ldap_protocol = ( @new_ldap_protocol, $ldap_protocol[$count] ); @new_ldap_limit_scope = ( @new_ldap_limit_scope, $ldap_limit_scope[$count] ); @new_ldap_listing = ( @new_ldap_listing, $ldap_listing[$count] ); + @new_ldap_search_tree = ( @new_ldap_search_tree, $ldap_search_tree[$count] ); + @new_ldap_starttls = ( @new_ldap_starttls, $ldap_starttls[$count] ); } $count++; } @@ -2684,6 +2735,8 @@ sub command61 { @ldap_protocol = @new_ldap_protocol; @ldap_limit_scope = @new_ldap_limit_scope; @ldap_listing = @new_ldap_listing; + @ldap_search_tree = @new_ldap_search_tree; + @ldap_starttls = @new_ldap_starttls; } elsif ( $input =~ /^\s*\?\s*/ ) { print ".-------------------------.\n"; @@ -3499,6 +3552,16 @@ sub save_data { # boolean print CF " 'listing' => $ldap_listing[$count]"; } + if ( $ldap_search_tree[$count] ) { + print CF ",\n"; + # integer + print CF " 'search_tree' => $ldap_search_tree[$count]"; + } + if ( $ldap_listing[$count] ) { + print CF ",\n"; + # boolean + print CF " 'starttls' => $ldap_starttls[$count]"; + } print CF "\n"; print CF ");\n"; print CF "\n"; diff --git a/functions/abook_ldap_server.php b/functions/abook_ldap_server.php index df618b8b..72b94101 100644 --- a/functions/abook_ldap_server.php +++ b/functions/abook_ldap_server.php @@ -11,6 +11,8 @@ * (#539534) * ADS limit_scope code by Michael Brown * (#1035454) + * StartTLS code by John Lane + * (#1197703) * * @version $Id$ * @package squirrelmail @@ -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,43 @@ 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 requirements are not satisfied + */ + if($this->starttls && + !empty($this->protocol) && $this->protocol >= 3 && + function_exists('ldap_start_tls') ) { + // make sure that $this->host is not ldaps:// URL. + if (preg_match("/^ldaps:\/\/.+/i",$this->server)) { + return $this->set_error("you can't enable starttls on ldaps connection."); + } + // TODO: starttls and ldapi:// tests are needed + + // 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 +270,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 +360,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) { @@ -405,6 +446,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 ======================== */ /** -- 2.25.1