2007-12-09 09:03:15 +01:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* eGgroupWare setup - test or create the ldap connection and hierarchy
|
|
|
|
*
|
|
|
|
* @link http://www.egroupware.org
|
|
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
|
|
* @package setup
|
2010-08-18 11:14:30 +02:00
|
|
|
* @copyright (c) 2007-10 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
2007-12-09 09:03:15 +01:00
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
2009-12-05 18:42:18 +01:00
|
|
|
* @version $Id$
|
2007-12-09 09:03:15 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* setup command: test or create the ldap connection and hierarchy
|
|
|
|
*/
|
2009-12-05 18:42:18 +01:00
|
|
|
class setup_cmd_ldap extends setup_cmd
|
2007-12-09 09:03:15 +01:00
|
|
|
{
|
2008-01-14 06:44:32 +01:00
|
|
|
/**
|
|
|
|
* Allow to run this command via setup-cli
|
|
|
|
*/
|
|
|
|
const SETUP_CLI_CALLABLE = true;
|
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
/**
|
|
|
|
* Instance of ldap object
|
|
|
|
*
|
|
|
|
* @var ldap
|
|
|
|
*/
|
|
|
|
private $test_ldap;
|
|
|
|
|
|
|
|
/**
|
2007-12-09 20:00:16 +01:00
|
|
|
* Constructor
|
2007-12-09 09:03:15 +01:00
|
|
|
*
|
|
|
|
* @param string/array $domain domain-name to customize the defaults or array with all parameters
|
|
|
|
* @param string $ldap_host=null
|
|
|
|
* @param string $ldap_suffix=null base of the whole ldap install, default "dc=local"
|
|
|
|
* @param string $ldap_admin=null root-dn needed to create new entries in the suffix
|
|
|
|
* @param string $ldap_admin_pw=null
|
|
|
|
* @param string $ldap_base=null base of the instance, default "o=$domain,$suffix"
|
2007-12-11 02:28:07 +01:00
|
|
|
* @param string $ldap_root_dn=null root-dn used for the instance, default "cn=admin,$base"
|
2007-12-09 09:03:15 +01:00
|
|
|
* @param string $ldap_root_pw=null
|
|
|
|
* @param string $ldap_context=null ou for accounts, default "ou=accounts,$base"
|
|
|
|
* @param string $ldap_search_filter=null search-filter for accounts, default "(uid=%user)"
|
2007-12-11 02:28:07 +01:00
|
|
|
* @param string $ldap_group_context=null ou for groups, default "ou=groups,$base"
|
2007-12-09 09:03:15 +01:00
|
|
|
* @param string $sub_command='create_ldap' 'create_ldap', 'test_ldap', 'test_ldap_root'
|
|
|
|
*/
|
|
|
|
function __construct($domain,$ldap_host=null,$ldap_suffix=null,$ldap_admin=null,$ldap_admin_pw=null,
|
2007-12-11 02:28:07 +01:00
|
|
|
$ldap_base=null,$ldap_root_dn=null,$ldap_root_pw=null,$ldap_context=null,$ldap_search_filter=null,
|
|
|
|
$ldap_group_context=null,$sub_command='create_ldap')
|
2007-12-09 09:03:15 +01:00
|
|
|
{
|
|
|
|
if (!is_array($domain))
|
|
|
|
{
|
2007-12-10 05:59:01 +01:00
|
|
|
$domain = array(
|
2007-12-09 09:03:15 +01:00
|
|
|
'domain' => $domain,
|
|
|
|
'ldap_host' => $ldap_host,
|
|
|
|
'ldap_suffix' => $ldap_suffix,
|
|
|
|
'ldap_admin' => $ldap_admin,
|
|
|
|
'ldap_admin_pw' => $ldap_admin_pw,
|
|
|
|
'ldap_base' => $ldap_base,
|
2007-12-11 02:28:07 +01:00
|
|
|
'ldap_root_dn' => $ldap_root_dn,
|
2007-12-09 09:03:15 +01:00
|
|
|
'ldap_root_pw' => $ldap_root_pw,
|
|
|
|
'ldap_context' => $ldap_context,
|
|
|
|
'ldap_search_filter' => $ldap_search_filter,
|
2007-12-11 02:28:07 +01:00
|
|
|
'ldap_group_context' => $ldap_group_context,
|
2007-12-09 09:03:15 +01:00
|
|
|
'sub_command' => $sub_command
|
|
|
|
);
|
|
|
|
}
|
|
|
|
//echo __CLASS__.'::__construct()'; _debug_array($domain);
|
|
|
|
admin_cmd::__construct($domain);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2007-12-13 03:41:55 +01:00
|
|
|
* run the command: test or create the ldap connection and hierarchy
|
2009-12-05 18:42:18 +01:00
|
|
|
*
|
2007-12-09 09:03:15 +01:00
|
|
|
* @param boolean $check_only=false only run the checks (and throw the exceptions), but not the command itself
|
2007-12-13 03:41:55 +01:00
|
|
|
* @return string success message
|
2007-12-09 09:03:15 +01:00
|
|
|
* @throws Exception(lang('Wrong credentials to access the header.inc.php file!'),2);
|
|
|
|
* @throws Exception('header.inc.php not found!');
|
|
|
|
*/
|
|
|
|
protected function exec($check_only=false)
|
|
|
|
{
|
|
|
|
if (!empty($this->domain) && !preg_match('/^([a-z0-9_-]+\.)*[a-z0-9]+/i',$this->domain))
|
|
|
|
{
|
|
|
|
throw new egw_exception_wrong_userinput(lang("'%1' is no valid domain name!",$this->domain));
|
|
|
|
}
|
|
|
|
if ($this->remote_id && $check_only) return true; // further checks can only done locally
|
|
|
|
|
|
|
|
$this->_merge_defaults();
|
|
|
|
//_debug_array($this->as_array());
|
|
|
|
|
2007-12-11 02:28:07 +01:00
|
|
|
switch($this->sub_command)
|
2007-12-09 09:03:15 +01:00
|
|
|
{
|
2007-12-11 02:28:07 +01:00
|
|
|
case 'test_ldap_root':
|
|
|
|
$msg = $this->connect($this->ldap_admin,$this->ldap_admin_pw);
|
|
|
|
break;
|
|
|
|
case 'test_ldap':
|
|
|
|
$msg = $this->connect();
|
|
|
|
break;
|
2010-08-20 15:27:37 +02:00
|
|
|
case 'delete_ldap':
|
2010-08-20 16:18:21 +02:00
|
|
|
$msg = $this->delete_base();
|
|
|
|
break;
|
|
|
|
case 'users_ldap':
|
|
|
|
$msg = $this->users();
|
2010-08-20 15:27:37 +02:00
|
|
|
break;
|
2007-12-11 02:28:07 +01:00
|
|
|
case 'create_ldap':
|
|
|
|
default:
|
|
|
|
$msg = $this->create();
|
|
|
|
break;
|
2007-12-09 09:03:15 +01:00
|
|
|
}
|
|
|
|
return $msg;
|
|
|
|
}
|
2009-12-05 18:42:18 +01:00
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
/**
|
|
|
|
* Connect to ldap server
|
|
|
|
*
|
2007-12-11 02:28:07 +01:00
|
|
|
* @param string $dn=null default $this->ldap_root_dn
|
2007-12-09 09:03:15 +01:00
|
|
|
* @param string $pw=null default $this->ldap_root_pw
|
|
|
|
* @throws egw_exception_wrong_userinput Can not connect to ldap ...
|
|
|
|
*/
|
|
|
|
private function connect($dn=null,$pw=null)
|
|
|
|
{
|
2007-12-11 02:28:07 +01:00
|
|
|
if (is_null($dn)) $dn = $this->ldap_root_dn;
|
2007-12-09 09:03:15 +01:00
|
|
|
if (is_null($pw)) $pw = $this->ldap_root_pw;
|
2009-12-05 18:42:18 +01:00
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
if (!$pw) // ldap::ldapConnect use the current eGW's pw otherwise
|
|
|
|
{
|
|
|
|
throw new egw_exception_wrong_userinput(lang('You need to specify a password!'));
|
|
|
|
}
|
|
|
|
$this->test_ldap = new ldap();
|
2009-12-05 18:42:18 +01:00
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
$error_rep = error_reporting();
|
|
|
|
//error_reporting($error_rep & ~E_WARNING); // switch warnings of, in case they are on
|
|
|
|
ob_start();
|
|
|
|
$ds = $this->test_ldap->ldapConnect($this->ldap_host,$dn,$pw);
|
|
|
|
ob_end_clean();
|
|
|
|
error_reporting($error_rep);
|
2009-12-05 18:42:18 +01:00
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
if (!$ds)
|
|
|
|
{
|
|
|
|
throw new egw_exception_wrong_userinput(lang('Can not connect to LDAP server on host %1 using DN %2!',
|
|
|
|
$this->ldap_host,$dn).($this->test_ldap->ds ? ' ('.ldap_error($this->test_ldap->ds).')' : ''));
|
|
|
|
}
|
|
|
|
return lang('Successful connected to LDAP server on %1 using DN %2.',$this->ldap_host,$dn);
|
|
|
|
}
|
2010-08-20 16:18:21 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Count active (not expired) users
|
|
|
|
*
|
|
|
|
* @return int number of active users
|
|
|
|
* @throws egw_exception_wrong_userinput
|
|
|
|
*/
|
|
|
|
private function users()
|
|
|
|
{
|
|
|
|
$this->connect();
|
|
|
|
|
|
|
|
$sr = ldap_list($this->test_ldap->ds,$this->ldap_context,'ObjectClass=posixAccount',array('dn','shadowExpire'));
|
|
|
|
if (!($entries = ldap_get_entries($this->test_ldap->ds, $sr)))
|
|
|
|
{
|
|
|
|
throw new egw_exception('Error listing "dn=%1"!',$this->ldap_context);
|
|
|
|
}
|
|
|
|
$num = 0;
|
|
|
|
foreach($entries as $n => $entry)
|
|
|
|
{
|
|
|
|
if ($n === 'count') continue;
|
|
|
|
if (isset($entry['shadowexpire']) && $entry['shadowexpire'][0]*24*3600 < time()) continue;
|
|
|
|
++$num;
|
|
|
|
}
|
|
|
|
return $num;
|
|
|
|
}
|
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
/**
|
|
|
|
* Check and if does not yet exist create the new database and user
|
|
|
|
*
|
|
|
|
* @return string with success message
|
|
|
|
* @throws egw_exception_wrong_userinput
|
|
|
|
*/
|
|
|
|
private function create()
|
|
|
|
{
|
|
|
|
$this->connect($this->ldap_admin,$this->ldap_admin_pw);
|
2009-12-05 18:42:18 +01:00
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
foreach(array(
|
|
|
|
$this->ldap_base => array(),
|
|
|
|
$this->ldap_context => array(),
|
2007-12-11 02:28:07 +01:00
|
|
|
$this->ldap_group_context => array(),
|
|
|
|
$this->ldap_root_dn => array('userPassword' => '{crypt}'.crypt($this->ldap_root_pw)),
|
2007-12-09 09:03:15 +01:00
|
|
|
) as $dn => $extra)
|
|
|
|
{
|
2007-12-11 02:28:07 +01:00
|
|
|
if (!$this->_create_node($dn,$extra,$check_only) && $dn == $this->ldap_root_dn)
|
2007-12-09 09:03:15 +01:00
|
|
|
{
|
|
|
|
// ldap_root already existed, lets check the pw is correct
|
|
|
|
$this->connect();
|
|
|
|
}
|
|
|
|
}
|
2007-12-09 20:00:16 +01:00
|
|
|
return lang('Successful connected to LDAP server on %1 and created/checked required structur %2.',
|
2007-12-09 09:03:15 +01:00
|
|
|
$this->ldap_host,$this->ldap_base);
|
|
|
|
}
|
2009-12-05 18:42:18 +01:00
|
|
|
|
2010-08-20 15:27:37 +02:00
|
|
|
/**
|
|
|
|
* Delete whole LDAP tree of an instance dn=$this->ldap_base using $this->ldap_admin/_pw
|
|
|
|
*
|
|
|
|
* @return string with success message
|
|
|
|
* @throws egw_exception if dn not found, not listable or delete fails
|
|
|
|
*/
|
2010-08-20 16:18:21 +02:00
|
|
|
private function delete_base()
|
2010-08-20 15:27:37 +02:00
|
|
|
{
|
|
|
|
$this->connect($this->ldap_admin,$this->ldap_admin_pw);
|
|
|
|
|
|
|
|
// if base not set, use context minus one hierarchy, eg. ou=accounts,(o=domain,dc=local)
|
|
|
|
if (empty($this->ldap_base) && $this->ldap_context)
|
|
|
|
{
|
|
|
|
list(,$this->ldap_base) = explode(',',$this->ldap_context,2);
|
|
|
|
}
|
|
|
|
// some precausion to not delete whole ldap tree!
|
|
|
|
if (count(explode(',',$this->ldap_base)) < 2)
|
|
|
|
{
|
2010-08-20 16:18:21 +02:00
|
|
|
throw new egw_exception_assertion_failed(lang('Refusing to delete dn "%1"!',$this->ldap_base));
|
2010-08-20 15:27:37 +02:00
|
|
|
}
|
|
|
|
// check if base does exist
|
|
|
|
if (!@ldap_read($this->test_ldap->ds,$this->ldap_base,'objectClass=*'))
|
|
|
|
{
|
2010-08-20 16:18:21 +02:00
|
|
|
throw new egw_exception_wrong_userinput(lang('Base dn "%1" NOT found!',$this->ldap_base));
|
2010-08-20 15:27:37 +02:00
|
|
|
}
|
|
|
|
return lang('LDAP dn="%1" with %2 entries deleted.',
|
|
|
|
$this->ldap_base,$this->rdelete($this->ldap_base));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Recursive delete a dn
|
|
|
|
*
|
|
|
|
* @param string $dn
|
|
|
|
* @return int integer number of deleted entries
|
|
|
|
* @throws egw_exception if dn not listable or delete fails
|
|
|
|
*/
|
|
|
|
private function rdelete($dn)
|
|
|
|
{
|
2010-08-20 16:18:21 +02:00
|
|
|
if (!($sr = ldap_list($this->test_ldap->ds,$dn,'ObjectClass=*',array(''))) ||
|
|
|
|
!($entries = ldap_get_entries($this->test_ldap->ds, $sr)))
|
2010-08-20 15:27:37 +02:00
|
|
|
{
|
2010-08-20 16:18:21 +02:00
|
|
|
throw new egw_exception(lang('Error listing "dn=%1"!',$dn));
|
2010-08-20 15:27:37 +02:00
|
|
|
}
|
|
|
|
foreach($entries as $n => $entry)
|
|
|
|
{
|
2010-08-20 16:18:21 +02:00
|
|
|
if ($n === 'count') continue;
|
|
|
|
$this->rdelete($entry['dn']);
|
2010-08-20 15:27:37 +02:00
|
|
|
}
|
|
|
|
if (!ldap_delete($this->test_ldap->ds,$dn))
|
|
|
|
{
|
2010-08-20 16:18:21 +02:00
|
|
|
throw new egw_exception(lang('Error deleting "dn=%1"!',$dn));
|
2010-08-20 15:27:37 +02:00
|
|
|
}
|
|
|
|
return 1 + $entries['count'];
|
|
|
|
}
|
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
/**
|
|
|
|
* array with objectclasses for the objects we can create
|
|
|
|
*
|
|
|
|
* @var array of name => objectClass pairs (or array with multiple)
|
|
|
|
*/
|
|
|
|
static $requiredObjectclasses = array(
|
|
|
|
'o' => 'organization',
|
|
|
|
'ou' => 'organizationalUnit',
|
2010-08-18 11:14:30 +02:00
|
|
|
'cn' => array('organizationalRole','simpleSecurityObject'),
|
2007-12-09 09:03:15 +01:00
|
|
|
'dc' => array('organization','dcObject'),
|
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new node in the ldap tree
|
|
|
|
*
|
|
|
|
* @param string $dn dn to create, eg. "cn=admin,dc=local"
|
|
|
|
* @param array $extra=array() extra attributes to set
|
|
|
|
* @return boolean true if the node was create, false if it was already there
|
|
|
|
* @throws egw_exception_wrong_userinput
|
|
|
|
*/
|
|
|
|
private function _create_node($dn,$extra=array())
|
|
|
|
{
|
|
|
|
//echo "<p>_create_node($dn,".print_r($extra,true).")</p>\n";
|
|
|
|
// check if the node already exists and return if it does
|
|
|
|
if (@ldap_read($this->test_ldap->ds,$dn,'objectClass=*'))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
list($node,$base) = explode(',',$dn,2);
|
2009-12-05 18:42:18 +01:00
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
if (!@ldap_read($this->test_ldap->ds,$base,'objectClass=*'))
|
|
|
|
{
|
|
|
|
$this->_create_node($base); // create the base if it's not already there
|
|
|
|
}
|
|
|
|
// now we need to create the node itself
|
|
|
|
list($name,$value) = explode('=',$node);
|
2009-12-05 18:42:18 +01:00
|
|
|
|
2007-12-09 09:03:15 +01:00
|
|
|
if (!isset(self::$requiredObjectclasses[$name]))
|
|
|
|
{
|
|
|
|
throw new egw_exception_wrong_userinput(lang('Can not create DN %1!',$dn).' '.
|
|
|
|
lang('Supported node types:').implode(', ',array_keys(self::$requiredObjectclasses)));
|
|
|
|
}
|
|
|
|
if ($name == 'dc') $extra['o'] = $value; // required by organisation
|
|
|
|
|
|
|
|
if (!@ldap_add($this->test_ldap->ds,$dn,$attr = array(
|
|
|
|
$name => $value,
|
|
|
|
'objectClass' => self::$requiredObjectclasses[$name],
|
|
|
|
)+$extra))
|
|
|
|
{
|
|
|
|
throw new egw_exception_wrong_userinput(lang('Can not create DN %1!',$dn).
|
|
|
|
' ('.ldap_error($this->test_ldap->ds).', attributes='.print_r($attr,true).')');
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return default database settings for a given domain
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
static function defaults()
|
|
|
|
{
|
|
|
|
return array(
|
|
|
|
'ldap_host' => 'localhost',
|
|
|
|
'ldap_suffix' => 'dc=local',
|
|
|
|
'ldap_admin' => 'cn=admin,$suffix',
|
|
|
|
'ldap_admin_pw' => '',
|
|
|
|
'ldap_base' => 'o=$domain,$suffix',
|
2007-12-11 02:28:07 +01:00
|
|
|
'ldap_root_dn' => 'cn=admin,$base',
|
|
|
|
'ldap_root_pw' => self::randomstring(),
|
2007-12-09 09:03:15 +01:00
|
|
|
'ldap_context' => 'ou=accounts,$base',
|
|
|
|
'ldap_search_filter' => '(uid=%user)',
|
2007-12-11 02:28:07 +01:00
|
|
|
'ldap_group_context' => 'ou=groups,$base',
|
2007-12-09 09:03:15 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Merges the default into the current properties, if they are empty or contain placeholders
|
|
|
|
*/
|
|
|
|
private function _merge_defaults()
|
|
|
|
{
|
2007-12-11 02:28:07 +01:00
|
|
|
foreach(self::defaults() as $name => $default)
|
2007-12-09 09:03:15 +01:00
|
|
|
{
|
2010-08-20 16:18:21 +02:00
|
|
|
if ($this->sub_command == 'delete_ldap' && in_array($name,array('ldap_base','ldap_context')))
|
|
|
|
{
|
|
|
|
continue; // no default on what to delete!
|
|
|
|
}
|
2007-12-09 09:03:15 +01:00
|
|
|
if (!$this->$name)
|
|
|
|
{
|
|
|
|
//echo "<p>setting $name='{$this->$name}' to it's default='$default'</p>\n";
|
|
|
|
$this->set_defaults[$name] = $this->$name = $default;
|
|
|
|
}
|
|
|
|
if (strpos($this->$name,'$') !== false)
|
|
|
|
{
|
2010-08-25 14:24:11 +02:00
|
|
|
$this->set_defaults[$name] = $this->$name = str_replace(array(
|
2007-12-09 09:03:15 +01:00
|
|
|
'$domain',
|
|
|
|
'$suffix',
|
|
|
|
'$base',
|
2009-12-05 18:42:18 +01:00
|
|
|
'$admin_pw',
|
2007-12-09 09:03:15 +01:00
|
|
|
),array(
|
|
|
|
$this->domain,
|
|
|
|
$this->ldap_suffix,
|
|
|
|
$this->ldap_base,
|
2009-12-05 18:42:18 +01:00
|
|
|
$this->ldap_admin_pw,
|
2007-12-09 09:03:15 +01:00
|
|
|
),$this->$name);
|
|
|
|
}
|
2009-12-05 18:42:18 +01:00
|
|
|
}
|
2007-12-09 09:03:15 +01:00
|
|
|
}
|
|
|
|
}
|