From 62913186350cb3aad0f38a587a9687a9ee3d6794 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 19 Jun 2013 20:29:41 +0000 Subject: [PATCH] * Setup/LDAP/ADS: script to copy from samba-tool classicupgrade not copied inetOrgPerson and mail-server attributes --- setup/inc/class.setup_cmd_ldap.inc.php | 189 ++++++++++++++++++++----- 1 file changed, 153 insertions(+), 36 deletions(-) diff --git a/setup/inc/class.setup_cmd_ldap.inc.php b/setup/inc/class.setup_cmd_ldap.inc.php index e9befd11f8..f2a33361e6 100644 --- a/setup/inc/class.setup_cmd_ldap.inc.php +++ b/setup/inc/class.setup_cmd_ldap.inc.php @@ -25,7 +25,14 @@ * * - First run it with --dry-run to get ids to change / admin-cli command to change ids in EGroupware. * - 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 setup/doc/chown.php command to change filesystem uid/gid in samba share. + * - After that you can run the given setup/doc/chown.php command to change filesystem uid/gid in samba share. + * + * setup/setup-cli.php [--dry-run] --setup-cmd-ldap ,, sub_command=copy2ad \ + * ldap_base=dc=local ldap_root_dn=cn=admin,dc=local ldap_root_pw=secret ldap_host=localhost \ + * ads_domain=samba4.intern [ads_admin_user=Administrator] ads_admin_pw=secret ads_host=ad.samba4.intern [ads_connection=(ssl|tls)] \ + * attributes=@inetOrgPerson,{smtp:}proxyAddresses=mail,{smtp:}proxyAddresses=mailalias,{quota:}proxyAddresses=mailuserquota,{forward:}proxyaddresses=maildrop + * + * - copies from samba-tool clasicupgrade not copied inetOrgPerson attributes and mail attributes to AD */ class setup_cmd_ldap extends setup_cmd { @@ -100,7 +107,7 @@ class setup_cmd_ldap extends setup_cmd { throw new egw_exception_wrong_userinput(lang("'%1' is no valid domain name!",$this->domain)); } - if ($this->remote_id && $check_only && !in_array($this->sub_command, array('set_mailbox', 'sid2uidnumber'))) + if ($this->remote_id && $check_only && !in_array($this->sub_command, array('set_mailbox', 'sid2uidnumber', 'copy2ad'))) { return true; // further checks can only done locally } @@ -131,6 +138,9 @@ class setup_cmd_ldap extends setup_cmd case 'sid2uidnumber': $msg = $this->sid2uidnumber($check_only); break; + case 'copy2ad': + $msg = $this->copy2ad($check_only); + break; case 'create_ldap': default: $msg = $this->create(); @@ -162,7 +172,7 @@ class setup_cmd_ldap extends setup_cmd } if (!($sr = ldap_search($this->test_ldap->ds,$this->ldap_base, - '(&(|(objectClass=posixAccount)(objectClass=posixGroup))('.$search=self::sambaSID.'=*)(!(gecos=*)))', + $search='(&(|(objectClass=posixAccount)(objectClass=posixGroup))('.self::sambaSID.'=*)(!(uid=*$)))', array('uidNumber','gidNumber','uid','cn', 'objectClass',self::sambaSID))) || !($entries = ldap_get_entries($this->test_ldap->ds, $sr))) { @@ -175,7 +185,7 @@ class setup_cmd_ldap extends setup_cmd { if ($key === 'count') continue; - $entry = self::ldap2array($entry); + $entry = ldap::result2array($entry); $accounts[$entry['dn']] = $entry; //print_r($entry); @@ -230,6 +240,140 @@ class setup_cmd_ldap extends setup_cmd "\n- ".implode("\n- ", $msg); } + /** + * Copy given attributes of accounts of one ldap to active directory + * + * @param boolean $check_only=false true: only connect and output necessary commands + */ + private function copy2ad($check_only=false) + { + $msg = array(); + $attrs = $rename = array(); + foreach(explode(',', $this->attributes) as $attr) + { + if ($attr[0] == '@' || // copy whole objectclass without renaming, eg. @inetOrgPerson + strpos($attr, '=') === false) + { + $attrs[] = $attr; + } + else + { + list($to, $from) = explode('=', $attr); + $attrs[] = $from; + $rename[strtolower($from)] = $to; + } + } + $attrs[] = 'uid'; // need to match account + + // connect to destination ads + if (empty($this->ads_context)) + { + $this->ads_context = 'CN=Users,DC='.implode(',DC=', explode('.', $this->ads_domain)); + } + if (empty($this->ads_admin_user)) $this->ads_admin_user = 'Administrator'; + $admin_dn = strpos($this->ads_admin_user, '=') !== false ? $this->ads_admin_user : + 'CN='.$this->ads_admin_user.','.$this->ads_context; + switch($this->ads_connection) + { + case 'ssl': + $url = 'ldaps://'.$this->ads_host.'/'; + break; + case 'tls': + $url = 'tls://'.$this->ads_host.'/'; + break; + default: + $url = 'ldap://'.$this->ads_host.'/'; + break; + } + $this->connect($admin_dn, $this->ads_admin_pw, $url); + $ads = $this->test_ldap; unset($this->test_ldap); + + // check if ads base does exist + if (!@ldap_read($ads->ds, $this->ads_context, 'objectClass=*')) + { + throw new egw_exception_wrong_userinput(lang('Ads dn "%1" NOT found!',$this->ads_context)); + } + + // connect to source ldap + $this->connect(); + + // check if ldap base does exist + if (!@ldap_read($this->test_ldap->ds,$this->ldap_base,'objectClass=*')) + { + throw new egw_exception_wrong_userinput(lang('Base dn "%1" NOT found!',$this->ldap_base)); + } + + if (!($sr = ldap_search($this->test_ldap->ds,$this->ldap_base, + $search='(&(objectClass=posixAccount)('.self::sambaSID.'=*)(!(uid=*$)))', $attrs)) || + !($entries = ldap_get_entries($this->test_ldap->ds, $sr))) + { + throw new egw_exception(lang('Error searching "dn=%1" for "%2"!',$this->ldap_base, $search)); + } + + $changed = 0; + foreach($entries as $key => $entry) + { + if ($key === 'count') continue; + + $entry = ldap::result2array($entry); + $uid = $entry['uid']; + $entry = array_diff_key($entry, array_flip(array('dn', 'uid', 'objectclass', 'cn', 'userpassword'))); + + if (!($sr = ldap_search($ads->ds, $this->ads_context, + $search='(&(objectClass=user)(sAMAccountName='.ldap::quote($uid).'))', array('dn'))) || + !($dest = ldap_get_entries($ads->ds, $sr))) + { + $msg[] = lang('User "%1" not found!', $uid); + continue; + } + $dn = $dest[0]['dn']; + $update = array(); + foreach($entry as $attr => $value) + { + if ($value) + { + $to = isset($rename[$attr]) ? $rename[$attr] : $attr; + unset($prefix); + if ($to[0] == '{') // eg. {smtp:}proxyAddresses=forwardTo + { + list($prefix, $to) = explode('}', substr($to, 1)); + } + foreach((array)$value as $val) + { + if (isset($update[$to])) + { + if (!is_array($update[$to])) $update[$to] = array($update[$to]); + $update[$to][] = $prefix.$val; + } + else + { + $update[$to] = $prefix.$val; + } + } + } + } + if ($check_only) + { + print_r($dn); + print_r($update); + continue; + } + if ($update && !ldap_modify($ads->ds, $dn, $update)) + { + error_log(lang('Failed updating user "%1" dn="%2"!', $uid, $dn).' '.ldap_error($ads->ds)); + } + else + { + print_r(lang('User "%1" dn="%2" successful updated.', $uid, $dn)."\n"); + $changed++; + } + } + if ($check_only) return lang("%1 accounts to copy found.", count($entries)); + + return "Copied data of $changed accounts from LDAP to AD ". + (count($msg) > $changed ? ' ('.(count($msg)-$changed).' errors!)' : ''); + } + /** * Migrate to other account storage * @@ -488,35 +632,6 @@ class setup_cmd_ldap extends setup_cmd return $hash; } - /** - * Convert a single ldap value into a associative array - * - * @param array $ldap array with numerical and associative indexes and count's - * @return array with only associative index and no count's - */ - public static function ldap2array($ldap) - { - if (!is_array($ldap)) return false; - - $arr = array(); - foreach($ldap as $var => $val) - { - if (is_int($var) || $var === 'count') continue; - - if (is_array($val) && $val['count'] == 1) - { - $arr[$var] = $val[0]; - } - else - { - if (is_array($val)) unset($val['count']); - - $arr[$var] = $val; - } - } - return $arr; - } - /** * Read all accounts from sql or ldap * @@ -589,12 +704,14 @@ class setup_cmd_ldap extends setup_cmd * * @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) + private function connect($dn=null,$pw=null,$host=null) { if (is_null($dn)) $dn = $this->ldap_root_dn; if (is_null($pw)) $pw = $this->ldap_root_pw; + if (is_null($host)) $host = $this->ldap_host; if (!$pw) // ldap::ldapConnect use the current eGW's pw otherwise { @@ -605,14 +722,14 @@ class setup_cmd_ldap extends setup_cmd $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); + $ds = $this->test_ldap->ldapConnect($host,$dn,$pw); ob_end_clean(); error_reporting($error_rep); 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).')' : '')); + $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); }