diff --git a/setup/doc/chown.php b/setup/doc/chown.php new file mode 100755 index 0000000000..634388bedb --- /dev/null +++ b/setup/doc/chown.php @@ -0,0 +1,95 @@ +#!/usr/bin/php + + * @package setup + * @copyright (c) 2013 by Ralf Becker + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @version $Id$ + */ + +chdir(dirname(__FILE__)); // to enable our relative pathes to work + +if (isset($_SERVER['HTTP_HOST'])) // security precaution: forbit calling as web-page +{ + die('

setup/doc/chown.php must NOT be called as web-page --> exiting !!!

'); +} + +$recursive = false; + +$cmd = array_shift($_SERVER['argv']); + +if ($_SERVER['argv'] && in_array($_SERVER['argv'][0], array('-R', '--recursive'))) +{ + $recursive = true; + array_shift($_SERVER['argv']); +} + +if (count($_SERVER['argv']) != 2) +{ + usage(); +} + +function usage() +{ + die("\nUsage: $cmd [-R|--recursive] from-id,to-id[,from-id2,to-id2,...] path\n\nonly nummeric ids are allowed, group-ids have to be negative!\n\n"); +} +$ids = explode(',', $_SERVER['argv'][0]); +$change = array(); +while($ids) +{ + $from = (int)array_shift($ids); + $to = (int)array_shift($ids); + + if (!$from || !$to || ($from < 0) != ($to < 0)) + { + echo "from-id and to-id must be nummeric and have same sign (negative for groups)!\n\n"; + usage(); + } + $change[$from] = $to; +} + +$path = $_SERVER['argv'][1]; +if (!file_exists($path)) +{ + echo "File or directory '$path' not found!\n\n"; + usage(); +} + +if (posix_getuid()) +{ + die("\nNeed to run as root, to be able to change owner and group!\n\n"); +} + +chown_grp($path, null, $recursive); + +function chown_grp($path, array $stat=null, $recursive=false) +{ + global $change; + + if (is_null($stat) && !($stat = stat($path))) return false; + + if (isset($change[$stat['uid']]) && !chown($path, $uid=$change[$stat['uid']])) + { + echo "Faild to set new owner #$uid for $path\n"; + } + if (isset($change[-$stat['gid']]) && !chgrp($path, $gid=-$change[-$stat['gid']])) + { + echo "Faild to set new group #$gid for $path\n"; + } + + if ($recursive && is_dir($path)) + { + foreach(new DirectoryIterator($path) as $child) + { + if (!$child->isDot()) + chown_grp($child->getPathname(), array( + 'uid' => $child->getOwner(), + 'gid' => $child->getGroup(), + ), $recursive); + } + } +} diff --git a/setup/inc/class.setup_cmd_ldap.inc.php b/setup/inc/class.setup_cmd_ldap.inc.php index 03293b6c29..e9befd11f8 100644 --- a/setup/inc/class.setup_cmd_ldap.inc.php +++ b/setup/inc/class.setup_cmd_ldap.inc.php @@ -1,11 +1,11 @@ * @package setup - * @copyright (c) 2007-10 by Ralf Becker + * @copyright (c) 2007-13 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ @@ -15,8 +15,17 @@ * * All commands can be run via setup-cli eg: * - * setup/setup-cli.php --setup_cmd_ldap stylite.de,config-user,config-pw sub_command=set_mailbox \ - * ldap_base=dc=local ldap_admin=cn=admin,dc=local ldap_admin_pw=secret ldap_host=localhost test=1 + * setup/setup-cli.php [--dry-run] --setup_cmd_ldap ,, sub_command=set_mailbox \ + * ldap_base=dc=local ldap_root_dn=cn=admin,dc=local ldap_root_pw=secret ldap_host=localhost + * + * Changing uid/gidNumber to match SID in preparation to Samba4 migration: + * + * setup/setup-cli.php [--dry-run] --setup_cmd_ldap ,, sub_command=sid2uidnumber \ + * ldap_base=dc=local ldap_root_dn=cn=admin,dc=local ldap_root_pw=secret ldap_host=localhost + * + * - 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. */ class setup_cmd_ldap extends setup_cmd { @@ -91,8 +100,10 @@ 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) return true; // further checks can only done locally - + if ($this->remote_id && $check_only && !in_array($this->sub_command, array('set_mailbox', 'sid2uidnumber'))) + { + return true; // further checks can only done locally + } $this->_merge_defaults(); //_debug_array($this->as_array()); @@ -115,7 +126,10 @@ class setup_cmd_ldap extends setup_cmd $msg = $this->migrate($this->sub_command == 'migrate_to_ldap'); break; case 'set_mailbox': - $msg = $this->set_mailbox(); + $msg = $this->set_mailbox($check_only); + break; + case 'sid2uidnumber': + $msg = $this->sid2uidnumber($check_only); break; case 'create_ldap': default: @@ -125,6 +139,97 @@ class setup_cmd_ldap extends setup_cmd return $msg; } + const sambaSID = 'sambasid'; + + /** + * Change uidNumber and gidNumber to match rid (last part of sambaSID) + * + * 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 chown.php command to change filesystem uid/gid in samba share. + * + * @param boolean $check_only=false true: only connect and output necessary commands + */ + private function sid2uidnumber($check_only=false) + { + $msg = array(); + $this->connect(); + + // check if 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, + '(&(|(objectClass=posixAccount)(objectClass=posixGroup))('.$search=self::sambaSID.'=*)(!(gecos=*)))', + array('uidNumber','gidNumber','uid','cn', 'objectClass',self::sambaSID))) || + !($entries = ldap_get_entries($this->test_ldap->ds, $sr))) + { + throw new egw_exception(lang('Error searching "dn=%1" for "%2"!',$this->ldap_base, $search)); + } + $change = $accounts = array(); + $cmd_change_account_id = 'admin/admin-cli.php --change-account-id @,'; + $change_account_id = ''; + foreach($entries as $key => $entry) + { + if ($key === 'count') continue; + + $entry = self::ldap2array($entry); + $accounts[$entry['dn']] = $entry; + //print_r($entry); + + $parts = explode('-', $entry[self::sambaSID]); + $rid = array_pop($parts); + + if (in_array('posixAccount', $entry['objectclass'])) + { + $id = $entry['uidnumber']; + } + else + { + $id = -$entry['gidnumber']; + $rid *= -1; + } + if ($id != $rid) + { + $change[$id] = $rid; + $change_account_id .= ','.$id.','.$rid; + } + } + //print_r($change); die('Stop'); + + // change account-ids inside EGroupware + if ($check_only) $msg[] = "You need to run now:\n$cmd_change_account_id $change_account_id"; + //$cmd = new admin_cmd_change_account_id($change); + //$msg[] = $cmd->run($time=null, $set_modifier=false, $skip_checks=false, $check_only); + + // now change them in LDAP + $changed = 0; + foreach($accounts as $dn => $account) + { + $modify = array(); + if (!empty($account['uidnumber']) && isset($change[$account['uidnumber']])) + { + $modify['uidnumber'] = $change[$account['uidnumber']]; + } + if (isset($change[-$account['gidnumber']])) + { + $modify['gidnumber'] = -$change[-$account['gidnumber']]; + } + if (!$check_only && $modify && !ldap_modify($this->test_ldap->ds, $dn, $modify)) + { + throw new egw_exception("Failed to modify ldap: !ldap_modify({$this->test_ldap->ds}, '$dn', ".array2string($modify).") ".ldap_error($this->test_ldap->ds). + "\n- ".implode("\n- ", $msg)); // EGroupware change already run successful + } + if ($modify) ++$changed; + } + $msg[] = "You need to run now on your samba share(s):\nsetup/doc/chown.php -R $change_account_id "; + + return ($check_only ? 'Need to update' : 'Updated')." $changed entries with new uid/gidNumber in LDAP". + "\n- ".implode("\n- ", $msg); + } + /** * Migrate to other account storage * @@ -383,6 +488,35 @@ 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 * @@ -602,7 +736,7 @@ class setup_cmd_ldap extends setup_cmd * @return string with success message N entries modified * @throws egw_exception if dn not found, not listable or delete fails */ - private function set_mailbox() + private function set_mailbox($check_only=false) { $this->connect($this->ldap_admin,$this->ldap_admin_pw); @@ -639,16 +773,16 @@ class setup_cmd_ldap extends setup_cmd if ($mbox === $entry[$mbox_attr][0]) continue; // nothing to change - if (!$this->test && !ldap_modify($this->test_ldap->ds,$entry['dn'],array( + if (!$check_only && !ldap_modify($this->test_ldap->ds,$entry['dn'],array( $mbox_attr => $mbox, ))) { throw new egw_exception(lang("Error modifying dn=%1: %2='%3'!",$dn,$mbox_attr,$mbox)); } ++$modified; - if ($this->test) echo "$modified: $entry[dn]: $mbox_attr={$entry[$mbox_attr][0]} --> $mbox\n"; + if ($check_only) echo "$modified: $entry[dn]: $mbox_attr={$entry[$mbox_attr][0]} --> $mbox\n"; } - return $this->test ? lang('%1 entries would have been modified.',$modified) : + return $check_only ? lang('%1 entries would have been modified.',$modified) : lang('%1 entries modified.',$modified); } diff --git a/setup/setup-cli.php b/setup/setup-cli.php index 2bdc6cb7f6..bf241d1509 100755 --- a/setup/setup-cli.php +++ b/setup/setup-cli.php @@ -6,7 +6,7 @@ * @link http://www.egroupware.org * @package setup * @author Ralf Becker - * @copyright (c) 2006-9 by Ralf Becker + * @copyright (c) 2006-13 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ @@ -17,10 +17,17 @@ if (isset($_SERVER['HTTP_HOST'])) // security precaution: forbit calling setup-c { die('

setup-cli.php must NOT be called as web-page --> exiting !!!

'); } -elseif ($_SERVER['argc'] > 1) +$dry_run = false; +array_shift($_SERVER['argv']); + +if ($_SERVER['argv']) { + if ($_SERVER['argv'][0] == '--dry-run') + { + $dry_run = true; + array_shift($_SERVER['argv']); + } $arguments = $_SERVER['argv']; - array_shift($arguments); $action = array_shift($arguments); if (isset($arguments[0])) list($_POST['FormDomain']) = explode(',',$arguments[0]); // header include needs that to detects the right domain } @@ -52,7 +59,7 @@ $GLOBALS['egw_setup']->system_charset = $charset; if ((float) PHP_VERSION < $GLOBALS['egw_setup']->required_php_version) { - throw new egw_exception_wrong_userinput(lang('You are using PHP version %1. eGroupWare now requires %2 or later, recommended is PHP %3.',PHP_VERSION,$GLOBALS['egw_setup']->required_php_version,$GLOBALS['egw_setup']->recommended_php_version),98); + throw new egw_exception_wrong_userinput(lang('You are using PHP version %1. EGroupware now requires %2 or later, recommended is PHP %3.',PHP_VERSION,$GLOBALS['egw_setup']->required_php_version,$GLOBALS['egw_setup']->recommended_php_version),98); } switch($action) @@ -136,7 +143,7 @@ switch($action) } } $cmd = new $class($args); - $msg = $cmd->run(); + $msg = $cmd->run($time=null, $set_modifier=true, $skip_checks=false, $check_only=$dry_run); if (is_array($msg)) $msg = print_r($msg,true); echo "$msg\n"; break; @@ -146,7 +153,7 @@ switch($action) exit(0); /** - * Configure eGroupWare + * Configure EGroupware * * @param array $args domain(default),[config user(admin)],password,[,name=value,...] --files-dir --backup-dir --mailserver */ @@ -351,7 +358,7 @@ function _check_auth_config($arg,$stop,$set_lang=true) } /** - * Install eGroupWare + * Install EGroupware * * @param array $args array(0 => "domain,[config user(admin)],password,[backup-file],[charset],[lang]", "name=value", ...) */ @@ -461,7 +468,7 @@ function do_usage($what='') if (!$what) { - echo '--check '.lang('checks eGroupWare\'s installed, it\'s versions and necessary upgrads (return values see --exit-codes)')."\n"; + echo '--check '.lang('checks EGroupware\'s installed, it\'s versions and necessary upgrads (return values see --exit-codes)')."\n"; echo '--install '.lang('domain(default),[config user(admin)],password,[backup to install],[charset(default depends on language)]')."\n"; } if (!$what || $what == 'config') @@ -491,7 +498,7 @@ function do_usage($what='') } if (!$what || $what == 'header') { - echo lang('Create or edit the eGroupWare configuration file: header.inc.php:')."\n"; + echo lang('Create or edit the EGroupware configuration file: header.inc.php:')."\n"; echo '--create-header '.lang('header-password[,header-user(admin)]')."\n"; echo '--edit-header '.lang('[header-password],[header-user],[new-password],[new-user]')."\n"; if (!$what) echo ' --help header '.lang('gives further options')."\n"; @@ -499,14 +506,14 @@ function do_usage($what='') if ($what == 'header') { echo "\n".lang('Additional options and there defaults (in brackets)')."\n"; - echo '--server-root '.lang('path of eGroupWare install directory (default auto-detected)')."\n"; + echo '--server-root '.lang('path of EGroupware install directory (default auto-detected)')."\n"; echo '--session-type '.lang('{db | php(default) | php-restore}')."\n"; echo '--limit-access '.lang('comma separated ip-addresses or host-names, default access to setup from everywhere')."\n"; echo '--mcrypt '.lang('use mcrypt to crypt session-data: {off(default) | on},[mcrypt-init-vector(default randomly generated)],[mcrypt-version]')."\n"; echo '--db-persistent '.lang('use persistent db connections: {on(default) | off}')."\n"; echo '--domain-selectbox '.lang('{off(default) | on}')."\n"; - echo "\n".lang('Adding, editing or deleting an eGroupWare domain / database instance:')."\n"; + echo "\n".lang('Adding, editing or deleting an EGroupware domain / database instance:')."\n"; echo '--domain '.lang('add or edit a domain: [domain-name(default)],[db-name(egroupware)],[db-user(egroupware)],db-password,[db-type(mysql)],[db-host(localhost)],[db-port(db specific)],[config-user(as header)],[config-passwd(as header)]')."\n"; echo '--delete-domain '.lang('domain-name')."\n"; }