From e5b3a836930806fa7ab2ecb066ce62afbce9a5b9 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 17 Nov 2014 12:19:56 +0000 Subject: [PATCH] allow to update passwords from LDAP, if accounts stored in SQL and authentication is against LDAP --- setup/account_migration.php | 23 +++- setup/inc/class.setup_cmd_ldap.inc.php | 125 ++++++++++++------ setup/templates/default/account_migration.tpl | 1 + 3 files changed, 101 insertions(+), 48 deletions(-) diff --git a/setup/account_migration.php b/setup/account_migration.php index 4b6edb8012..186a3ef6d3 100644 --- a/setup/account_migration.php +++ b/setup/account_migration.php @@ -66,7 +66,7 @@ array_shift($base_parts); $cmd = new setup_cmd_ldap(array( 'domain' => $GLOBALS['egw_setup']->ConfigDomain, - 'sub_command' => 'migrate_to_'.$to, + 'sub_command' => $_POST['passwords2sql'] ? 'passwords_to_sql' : 'migrate_to_'.$to, // allow to set ldap root DN (ldap_admin) to create instance specific admin DN and structure 'ldap_admin' => !empty($_POST['ldap_admin']) ? $_POST['ldap_admin'] : $GLOBALS['egw_info']['server']['ldap_root_dn'], 'ldap_admin_pw' => !empty($_POST['ldap_admin']) ? $_POST['ldap_admin_pw'] : $GLOBALS['egw_info']['server']['ldap_root_pw'], @@ -74,7 +74,7 @@ $cmd = new setup_cmd_ldap(array( 'truncate_egw_accounts' => !empty($_POST['truncate_egw_accounts']), )+$GLOBALS['egw_info']['server']); -if (!$_POST['migrate']) +if (!$_POST['migrate'] && !$_POST['passwords2sql']) { $accounts = $cmd->accounts($from == 'ldap'); @@ -100,7 +100,7 @@ if (!$_POST['migrate']) else { $user_list .= '\n"; } } @@ -119,6 +119,10 @@ if (!$_POST['migrate']) $setup_tpl->set_var('ldap_admin_pw_label', lang('Root DN password')); $setup_tpl->set_var('migrate',$direction); $setup_tpl->set_var('cancel',lang('Cancel')); + if ($from == 'sql' && $GLOBALS['egw_info']['server']['auth_type'] == 'ldap') + { + $setup_tpl->set_var('extra_button', html::submit_button('passwords2sql', lang('Passwords --> SQL'))); + } $setup_tpl->pfp('out','header'); if($user_list) @@ -143,14 +147,19 @@ if (!$_POST['migrate']) } else // do the migration { - $cmd->only = array_merge((array)$_POST['users'],(array)$_POST['groups']); + $cmd->only = (array)$_POST['users']; + if (empty($_POST['passwords2sql'])) $cmd->only = array_merge($cmd->only, (array)$_POST['groups']); $cmd->verbose = true; echo '

'.str_replace("\n","

\n

",$cmd->run())."

\n"; + // store new repostory (and auth_type), as we are migrated now - config::save_value('account_repository', $GLOBALS['egw_info']['server']['account_repository']=$to, 'phpgwapi'); - if (empty($GLOBALS['egw_info']['server']['auth_type']) || $GLOBALS['egw_info']['server']['auth_type'] == $from) + if ($_POST['migrate']) { - config::save_value('auth_type', $GLOBALS['egw_info']['server']['auth_type']=$to, 'phpgwapi'); + config::save_value('account_repository', $GLOBALS['egw_info']['server']['account_repository']=$to, 'phpgwapi'); + if (empty($GLOBALS['egw_info']['server']['auth_type']) || $GLOBALS['egw_info']['server']['auth_type'] == $from) + { + config::save_value('auth_type', $GLOBALS['egw_info']['server']['auth_type']=$to, 'phpgwapi'); + } } echo '

'.lang('Click here to return to setup.')."

\n"; } diff --git a/setup/inc/class.setup_cmd_ldap.inc.php b/setup/inc/class.setup_cmd_ldap.inc.php index 39347f5f02..e2373e3bba 100644 --- a/setup/inc/class.setup_cmd_ldap.inc.php +++ b/setup/inc/class.setup_cmd_ldap.inc.php @@ -44,6 +44,11 @@ * * - copies mail-attributes from ldap to AD (example is from Mandriva mailAccount schema, need to adapt to other schema!) * (no_sid_check=1 uses all objectClass=posixAccount, not checking for having a SID and uid not ending in $ for computer accounts) + * + * setup/setup-cli.php [--dry-run] --setup_cmd_ldap ,, sub_command=passwords_to_sql \ + * ldap_base=dc=local ldap_root_dn=cn=admin,dc=local ldap_root_pw=secret ldap_host=localhost + * + * Updating passwords for existing users in SQL from LDAP, eg. to switch off authentication to LDAP on a SQL install. */ class setup_cmd_ldap extends setup_cmd { @@ -63,19 +68,19 @@ class setup_cmd_ldap extends setup_cmd * Constructor * * @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" - * @param string $ldap_root_dn=null root-dn used for the instance, default "cn=admin,$base" - * @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)" - * @param string $ldap_group_context=null ou for groups, default "ou=groups,$base" - * @param string $sub_command='create_ldap' 'create_ldap', 'test_ldap', 'test_ldap_root', see exec method - * @param string $ldap_encryption_type='des' - * @param boolean $truncate_egw_accounts=false truncate accounts table before migration to SQL + * @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" + * @param string $ldap_root_dn =null root-dn used for the instance, default "cn=admin,$base" + * @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)" + * @param string $ldap_group_context =null ou for groups, default "ou=groups,$base" + * @param string $sub_command ='create_ldap' 'create_ldap', 'test_ldap', 'test_ldap_root', see exec method + * @param string $ldap_encryption_type ='des' + * @param boolean $truncate_egw_accounts =false truncate accounts table before migration to SQL */ function __construct($domain,$ldap_host=null,$ldap_suffix=null,$ldap_admin=null,$ldap_admin_pw=null, $ldap_base=null,$ldap_root_dn=null,$ldap_root_pw=null,$ldap_context=null,$ldap_search_filter=null, @@ -107,7 +112,7 @@ class setup_cmd_ldap extends setup_cmd /** * run the command: test or create the ldap connection and hierarchy * - * @param boolean $check_only=false only run the checks (and throw the exceptions), but not the command itself + * @param boolean $check_only =false only run the checks (and throw the exceptions), but not the command itself * @return string success message * @throws Exception(lang('Wrong credentials to access the header.inc.php file!'),2); * @throws Exception('header.inc.php not found!'); @@ -141,7 +146,8 @@ class setup_cmd_ldap extends setup_cmd break; case 'migrate_to_ldap': case 'migrate_to_sql': - $msg = $this->migrate($this->sub_command == 'migrate_to_ldap'); + case 'passwords_to_sql': + $msg = $this->migrate($this->sub_command); break; case 'set_mailbox': $msg = $this->set_mailbox($check_only); @@ -169,7 +175,7 @@ class setup_cmd_ldap extends setup_cmd * Then run admin/admin-cli.php --change-account-id and after this command again without --dry-run. * After that you need to run the given chown.php command to change filesystem uid/gid in samba share. * - * @param boolean $check_only=false true: only connect and output necessary commands + * @param boolean $check_only =false true: only connect and output necessary commands */ private function sid2uidnumber($check_only=false) { @@ -254,7 +260,7 @@ class setup_cmd_ldap extends setup_cmd /** * Copy given attributes of accounts of one ldap to active directory * - * @param boolean $check_only=false true: only connect and output necessary commands + * @param boolean $check_only =false true: only connect and output necessary commands */ private function copy2ad($check_only=false) { @@ -326,13 +332,14 @@ class setup_cmd_ldap extends setup_cmd throw new egw_exception(lang('Error searching "dn=%1" for "%2"!',$this->ldap_base, $search)); } $changed = 0; + $utc_diff = null; foreach($entries as $key => $entry) { if ($key === 'count') continue; - $entry = ldap::result2array($entry); - $uid = $entry['uid']; - $entry = array_diff_key($entry, $ignore_attr); + $entry_arr = ldap::result2array($entry); + $uid = $entry_arr['uid']; + $entry = array_diff_key($entry_arr, $ignore_attr); if (!($sr = ldap_search($ads->ds, $this->ads_context, $search='(&(objectClass=user)(sAMAccountName='.ldap::quote($uid).'))', array('dn'))) || @@ -354,8 +361,8 @@ class setup_cmd_ldap extends setup_cmd else { if (is_null($utc_diff)) $utc_diff = date('Z'); - $entry['shadowexpire'] = $value*24*3600+$utc_diff; // ldap time to unixTime - $entry['shadowexpire'] = accounts_ads::convertUnixTimeToWindowsTime($entry['shadowexpire']); + $entry['shadowexpire'] = accounts_ads::convertUnixTimeToWindowsTime( + $entry['shadowexpire']*24*3600+$utc_diff); // ldap time to unixTime } } $update = array(); @@ -364,7 +371,7 @@ class setup_cmd_ldap extends setup_cmd if ($value || $attr === '') { $to = isset($rename[$attr]) ? $rename[$attr] : $attr; - unset($prefix); + $prefix = null; if ($to[0] == '{') // eg. {smtp:}proxyAddresses=forwardTo { list($prefix, $to) = explode('}', substr($to, 1)); @@ -414,12 +421,15 @@ class setup_cmd_ldap extends setup_cmd /** * Migrate to other account storage * - * @param boolean $to_ldap true: sql --> ldap, false: ldap --> sql + * @param boolean|string $to_ldap true: sql --> ldap, false: ldap --> sql, "passwords_to_sql", "migrate_to_(sql|ldap)" * @return string with success message * @throws Exception on error */ private function migrate($to_ldap) { + $passwords2sql = $to_ldap === "passwords_to_sql"; + if (is_string($to_ldap)) $to_ldap = substr($to_ldap, -8) == '_to_ldap'; + $msg = array(); // if migrating to ldap, check ldap and create context if not yet exiting if ($to_ldap && !empty($this->ldap_admin_pw)) @@ -431,7 +441,7 @@ class setup_cmd_ldap extends setup_cmd $msg[] = $this->connect(); } // read accounts from old store - $accounts = $this->accounts(!$to_ldap); + $accounts = $this->accounts(!$to_ldap, $passwords2sql ? 'accounts' : 'both'); // clean up SQL before migration if (!$to_ldap && $this->truncate_egw_accounts) @@ -442,7 +452,8 @@ class setup_cmd_ldap extends setup_cmd // instanciate accounts obj for new store $accounts_obj = $this->accounts_obj($to_ldap); - $accounts_created = $groups_created = $errors = 0; + $accounts_created = $groups_created = $errors = $egw_info_set = 0; + $emailadmin_src = $ldap_class = null; $target = $to_ldap ? 'LDAP' : 'SQL'; foreach($accounts as $account_id => $account) { @@ -456,6 +467,31 @@ class setup_cmd_ldap extends setup_cmd // invalidate cache: otherwise no migration takes place, if cached results says account already exists accounts::cache_invalidate($account_id); + if ($passwords2sql) + { + if (!($sql_account = $accounts_obj->read($account_id))) + { + $msg[] = lang('%1 does NOT exist in %2.',$what,$target); + $errors++; + } + else + { + $sql_account['account_passwd'] = self::hash_ldap2sql($account['account_pwd']); + + if (!$accounts_obj->save($sql_account)) + { + $msg[] = lang('Update of %1 in %2 failed !!!',$what,$target); + $errors++; + } + else + { + $msg[] = lang('%1 password set in %2.',$what,$target); + $accounts_created++; + } + } + continue; + } + if ($account['account_type'] == 'u') { if ($accounts_obj->exists($account_id)) @@ -601,6 +637,11 @@ class setup_cmd_ldap extends setup_cmd $accounts_obj->set_members($account['members'],$account_id); } } + if ($passwords2sql) + { + return lang('%1 passwords updated, %3 errors',$accounts_created,$groups_created,$errors). + ($errors || $this->verbose ? "\n- ".implode("\n- ",$msg) : ''); + } // migrate addressbook data $GLOBALS['egw_info']['user']['apps']['admin'] = true; // otherwise migration will not run in setup! $addressbook = new addressbook_so(); @@ -613,12 +654,12 @@ class setup_cmd_ldap extends setup_cmd } ob_start(); $addressbook->migrate2ldap($to_ldap ? 'accounts' : 'accounts-back'); - $msg = array_merge($msg, explode("\n", strip_tags(ob_get_clean()))); + $msgs = array_merge($msg, explode("\n", strip_tags(ob_get_clean()))); $this->restore_db(); return lang('%1 users and %2 groups created, %3 errors',$accounts_created,$groups_created,$errors). - ($errors || $this->verbose ? "\n- ".implode("\n- ",$msg) : ''); + ($errors || $this->verbose ? "\n- ".implode("\n- ",$msgs) : ''); } /** @@ -631,6 +672,7 @@ class setup_cmd_ldap extends setup_cmd { if (!($type = $GLOBALS['egw_info']['server']['sql_encryption_type'])) $type = 'md5'; + $matches = null; if (preg_match('/^\\{(.*)\\}(.*)$/',$hash,$matches)) { list(,$type,$hash) = $matches; @@ -672,15 +714,16 @@ class setup_cmd_ldap extends setup_cmd /** * Read all accounts from sql or ldap * - * @param boolean $from_ldap=true true: ldap, false: sql + * @param boolean $from_ldap =true true: ldap, false: sql + * @param string $type ='both' * @return array */ - public function accounts($from_ldap=true) + public function accounts($from_ldap=true, $type='both') { $accounts_obj = $this->accounts_obj($from_ldap); //error_log(__METHOD__."(from_ldap=".array2string($from_ldap).') get_class(accounts_obj->backend)='.get_class($accounts_obj->backend)); - $accounts = $accounts_obj->search(array('type' => 'both', 'objectclass' => true)); + $accounts = $accounts_obj->search(array('type' => $type, 'objectclass' => true)); foreach($accounts as $account_id => &$account) { @@ -713,7 +756,7 @@ class setup_cmd_ldap extends setup_cmd */ private function accounts_obj($ldap) { - static $enviroment_setup; + static $enviroment_setup=null; if (!$enviroment_setup) { parent::_setup_enviroment($this->domain); @@ -739,9 +782,9 @@ class setup_cmd_ldap extends setup_cmd /** * Connect to ldap server * - * @param string $dn=null default $this->ldap_root_dn - * @param string $pw=null default $this->ldap_root_pw - * @param string $host=null default $this->ldap_host, hostname, ip or ldap-url + * @param string $dn =null default $this->ldap_root_dn + * @param string $pw =null default $this->ldap_root_pw + * @param string $host =null default $this->ldap_host, hostname, ip or ldap-url * @throws egw_exception_wrong_userinput Can not connect to ldap ... */ private function connect($dn=null,$pw=null,$host=null) @@ -813,7 +856,7 @@ class setup_cmd_ldap extends setup_cmd $this->ldap_root_dn => array('userPassword' => auth::encrypt_ldap($this->ldap_root_pw,'ssha')), ) as $dn => $extra) { - if (!$this->_create_node($dn,$extra,$check_only) && $dn == $this->ldap_root_dn) + if (!$this->_create_node($dn,$extra,$this->check_only) && $dn == $this->ldap_root_dn) { // ldap_root already existed, lets check the pw is correct $this->connect(); @@ -884,9 +927,9 @@ class setup_cmd_ldap extends setup_cmd * * Uses $this->ldap_host, $this->ldap_admin and $this->ldap_admin_pw to connect. * - * @param string $this->object_class='qmailUser' - * @param string $this->mbox_attr='mailmessagestore' lowercase!!! - * @param string $this->mail_login_type='email' 'email', 'vmailmgr', 'standard' or 'uidNumber' + * @param string $this->object_class ='qmailUser' + * @param string $this->mbox_attr ='mailmessagestore' lowercase!!! + * @param string $this->mail_login_type ='email' 'email', 'vmailmgr', 'standard' or 'uidNumber' * @return string with success message N entries modified * @throws egw_exception if dn not found, not listable or delete fails */ @@ -931,7 +974,7 @@ class setup_cmd_ldap extends setup_cmd $mbox_attr => $mbox, ))) { - throw new egw_exception(lang("Error modifying dn=%1: %2='%3'!",$dn,$mbox_attr,$mbox)); + throw new egw_exception(lang("Error modifying dn=%1: %2='%3'!",$entry['dn'],$mbox_attr,$mbox)); } ++$modified; if ($check_only) echo "$modified: $entry[dn]: $mbox_attr={$entry[$mbox_attr][0]} --> $mbox\n"; @@ -957,7 +1000,7 @@ class setup_cmd_ldap extends setup_cmd * 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 + * @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 */ diff --git a/setup/templates/default/account_migration.tpl b/setup/templates/default/account_migration.tpl index 65e5bc6baf..e3b9a346af 100644 --- a/setup/templates/default/account_migration.tpl +++ b/setup/templates/default/account_migration.tpl @@ -54,6 +54,7 @@ + {extra_button}