diff --git a/doc/rpm-build/post_install.php b/doc/rpm-build/post_install.php index 6100f62c8f..739e52083b 100755 --- a/doc/rpm-build/post_install.php +++ b/doc/rpm-build/post_install.php @@ -11,7 +11,7 @@ if (php_sapi_name() !== 'cli') // security precaution: forbit calling post_install as web-page { - die('

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

'); + die('

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

'); } $verbose = false; $config = array( @@ -55,6 +55,8 @@ $config = array( 'ldap_context' => 'ou=accounts,$base', 'ldap_search_filter' => '(uid=%user)', 'ldap_group_context' => 'ou=groups,$base', + 'ldap_encryption_type' => '', + 'sambaadmin/sambaSID'=> '', // SID for sambaadmin 'mailserver' => '', 'smtpserver' => 'localhost,25', 'smtp' => '', // see setup-cli.php --help config @@ -88,8 +90,11 @@ function set_distro_defaults($distro=null) global $config; if (is_null($distro)) { - $distro = file_exists('/etc/SuSE-release') ? 'suse' : (file_exists('/etc/debian_version') ? 'debian' : - (file_exists('/etc/mandriva-release') ? 'mandriva' : 'rh')); + $distro = file_exists('/etc/SuSE-release') ? 'suse' : + (file_exists('/etc/mandriva-release') ? 'mandriva' : + (file_exists('/etc/lsb-release') && preg_match('/^DISTRIB_ID="Univention"$/mi', + file_get_contents('/etc/lsb-release')) ? 'univention' : + (file_exists('/etc/debian_version') ? 'debian' : 'rh'))); } switch (($config['distro'] = $distro)) { @@ -141,6 +146,9 @@ function set_distro_defaults($distro=null) $config['ldap_context'] = 'ou=People,$base'; $config['ldap_group_context'] = 'ou=Group,$base'; break; + case 'univention': + set_univention_defaults(); + break; default: $config['distro'] = 'rh'; // fall through @@ -293,6 +301,7 @@ if (!file_exists($config['header']) || filesize($config['header']) < 200) // def foreach(array( 'domain','ldap_suffix','ldap_host','ldap_admin','ldap_admin_pw', // non-egw params: only used for create 'ldap_base','ldap_root_dn','ldap_root_pw','ldap_context','ldap_search_filter','ldap_group_context', // egw params + 'ldap_encryption_type', 'sambaadmin/sambaSID', ) as $name) { if (strpos($value=$config[$name],'$') !== false) @@ -551,7 +560,7 @@ function usage($error=null) foreach($config as $name => $default) { if (in_array($name, array('postfix','cyrus'))) continue; // do NOT report deprecated options - if (in_array($name,array('config_passwd','db_pass','admin_passwd','ldap_root_pw'))) + if (in_array($name,array('config_passwd','db_pass','admin_passwd','ldap_root_pw')) && strlen($config[$name]) == 16) { $default = '<16 char random string>'; } @@ -724,3 +733,98 @@ function fix_perms() system('/bin/chmod 700 /tmp/egw_cache'); } } + +/** + * Set Univention UCS specific defaults + * + * Defaults are read from ucr registry and /etc/*.secret files + */ +function set_univention_defaults() +{ + global $config; + + set_distro_defaults('debian'); + $config['distro'] = 'univention'; + + // mysql settings + $config['db_root_pw'] = _ucr_secret('mysql'); + + // check if ucs ldap server is configured + if (_ucr_get('ldap/server/ip')) + { + // ldap settings, see http://docs.univention.de/developer-reference-3.2.html#join:secret + $config['ldap_suffix'] = $config['ldap_base'] = _ucr_get('ldap/base'); + $config['ldap_host'] = 'tls://'._ucr_get('ldap/server/ip').':'._ucr_get('ldap/server/port'); + $config['ldap_admin'] = $config['ldap_root'] = 'cn=admin,$suffix'; + $config['ldap_admin_pw'] = $config['ldap_root_pw'] = _ucr_secret('ldap'); + $config['ldap_context'] = 'cn=users,$base'; + $config['ldap_group_context'] = 'cn=groups,$base'; + $config['ldap_search_filter'] = '(uid=%user)'; + + // ldap password hash (our default blowfish_crypt seems not to work) + $config['ldap_encryption_type'] = 'sha512_crypt'; + + $config['account_min_id'] = 1200; // UCS use 11xx for internal users/groups + + $config['account-auth'] = 'univention,ldap'; + + // set sambaadmin sambaSID + $config['sambaadmin/sambaSID'] = exec('/usr/bin/univention-ldapsearch -x "(objectclass=sambadomain)" sambaSID|sed -n "s/sambaSID: \(.*\)/\1/p"'); + + // mailserver, see setup-cli.php --help config + if (($mailserver = exec('/usr/bin/univention-ldapsearch -x "(univentionAppID=mailserver_*)" univentionAppInstalledOnServer|sed -n "s/univentionAppInstalledOnServer: \(.*\)/\1/p"')) && + _ucr_get('mail/cyrus/imap') == 'yes' && ($domains=_ucr_get('mail/hosteddomains'))) + { + if (!is_array($domains)) $domains = explode("\n", $domains); + $domain = array_shift($domains); + $config['smtpserver'] = "$mailserver,465,,,yes,tls"; + $config['smtp'] = 'no,emailadmin_smtp_ldap_univention'; + $config['mailserver'] = "$mailserver,993,$domain,email,tls"; + $config['imap'] = /*'cyrus,'._ucr_secret('cyrus')*/','.',emailadmin_imap_cyrus'; + $config['folder'] = 'INBOX/Sent,INBOX/Trash,INBOX/Drafts,INBOX/Templates,INBOX/Spam'; + if (($sieve_port = _ucr_get('mail/cyrus/sieve/port'))) + { + $config['sieve'] = "$mailserver,$sieve_port,starttls"; + } + } + } +} + +/** + * Get a value from Univention registry + * + * @param string $name + * @return string + */ +function _ucr_get($name) +{ + static $values=null; + if (!isset($values)) + { + $output = $matches = null; + exec('/usr/sbin/ucr dump', $output); + foreach($output as $line) + { + if (preg_match("/^([^:]+): (.*)\n?$/", $line, $matches)) + { + $values[$matches[1]] = $matches[2]; + } + } + } + return $values[$name]; +} + +/** + * Read one Univention secret/password eg. _ucr_secret('mysql') + * + * @param string $name + * @return string|boolean + */ +function _ucr_secret($name) +{ + if (!file_exists($filename = '/etc/'.basename($name).'.secret')) + { + return false; + } + return trim(file_get_contents($filename)); +} diff --git a/emailadmin/inc/class.emailadmin_hooks.inc.php b/emailadmin/inc/class.emailadmin_hooks.inc.php index 8626445a7f..6655d646a9 100644 --- a/emailadmin/inc/class.emailadmin_hooks.inc.php +++ b/emailadmin/inc/class.emailadmin_hooks.inc.php @@ -110,16 +110,17 @@ class emailadmin_hooks try { $account = new emailadmin_account($params); - if ($account->acc_imap_type != 'emailadmin_imap' && ($imap = $account->imapServer(true)) && - is_a($imap, 'emailadmin_imap') && get_class($imap) != 'emailadmin_imap') - { - $imap->$method($data); - } if ($account->acc_smtp_type != 'emailadmin_smtp' && ($smtp = $account->smtpServer(true)) && is_a($smtp, 'emailadmin_smtp') && get_class($smtp) != 'emailadmin_smtp') { $smtp->$method($data); } + if ($account->acc_imap_type != 'emailadmin_imap' && $account->acc_admin_username && + $account->acc_admin_password && ($imap = $account->imapServer(true)) && + is_a($imap, 'emailadmin_imap') && get_class($imap) != 'emailadmin_imap') + { + $imap->$method($data); + } } catch(Exception $e) { _egw_log_exception($e); diff --git a/emailadmin/inc/class.emailadmin_imapbase.inc.php b/emailadmin/inc/class.emailadmin_imapbase.inc.php index 76affe112a..8cfbbda0c5 100644 --- a/emailadmin/inc/class.emailadmin_imapbase.inc.php +++ b/emailadmin/inc/class.emailadmin_imapbase.inc.php @@ -1018,6 +1018,7 @@ class emailadmin_imapbase $_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_sent]='Sent'; //if (!empty($this->icServer->acc_folder_template) && !isset($_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template])) $_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_template]='Templates'; + $_specialUseFolders[$this->icServer->ImapServerId][$this->icServer->acc_folder_junk]='Junk'; //error_log(__METHOD__.' ('.__LINE__.') '.array2string($_specialUseFolders));//.'<->'.array2string($this->icServer)); self::$specialUseFolders = $_specialUseFolders[$this->icServer->ImapServerId]; egw_cache::setCache(egw_cache::INSTANCE,'email','specialUseFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$_specialUseFolders, $expiration=60*60*24*5); @@ -2158,11 +2159,17 @@ class emailadmin_imapbase } try { - $rv = $this->icServer->createMailbox($newFolderName); + $opts = array(); + // if new created folder is a specal-use-folder, mark it as such, so other clients know to use it too + if (isset(self::$specialUseFolders[$newFolderName])) + { + $opts['special_use'] = self::$specialUseFolders[$newFolderName]; + } + $rv = $this->icServer->createMailbox($newFolderName, $opts); } catch (Exception $e) { - error_log(__METHOD__.' ('.__LINE__.') '.' create Folder '.$newFolderName.'->'.$e->getMessage().' Namespace:'.array2string($this->icServer->getNameSpaces()).function_backtrace()); + error_log(__METHOD__.' ('.__LINE__.') '.' create Folder '.$newFolderName.'->'.$e->getMessage().' ('.$e->details.') Namespace:'.array2string($this->icServer->getNameSpaces()).function_backtrace()); return false; } try @@ -2459,7 +2466,8 @@ class emailadmin_imapbase //subscribed folders may be used in getFolderStatus egw_cache::setCache(egw_cache::INSTANCE,'email','subscribedFolders'.trim($GLOBALS['egw_info']['user']['account_id']),$subscribedFoldersForCache,$expiration=60*60*1); //echo "
FolderNameSpace To Process:";_debug_array($foldersNameSpace); - $autoFolderObjects = array(); + $autoFolderObjects = $folders = array(); + $autofolder_exists = array(); foreach( array('personal', 'others', 'shared') as $type) { if(isset($foldersNameSpace[$type])) { if($_subscribedOnly) { @@ -2520,12 +2528,23 @@ class emailadmin_imapbase $folders[$folderName] = $folderObject; } //error_log(__METHOD__.' ('.__LINE__.') '.':'.$folderObject->folderName); + if (!isset(self::$specialUseFolders)) $this->getSpecialUseFolders (); + if (isset(self::$specialUseFolders[$folderName])) + { + $autofolder_exists[$folderName] = self::$specialUseFolders[$folderName]; + } } } } if (is_array($autoFolderObjects) && !empty($autoFolderObjects)) { uasort($autoFolderObjects,array($this,"sortByAutoFolderPos")); } + // check if some standard folders are missing and need to be created + if (count($autofolder_exists) < count(self::$autoFolders) && $this->check_create_autofolders($autofolder_exists)) + { + // if new folders have been created, re-read folders ignoring the cache + return $this->getFolderObjects($_subscribedOnly, $_getCounters, $_alwaysGetDefaultFolders, false); // false = do NOT use cache + } if (is_array($folders)) uasort($folders,array($this,"sortByDisplayName")); //$folders2return = array_merge($autoFolderObjects,$folders); //_debug_array($folders2return); #exit; @@ -2542,12 +2561,33 @@ class emailadmin_imapbase return $folders2return[$this->icServer->ImapServerId]; } + /** + * Check if all automatic folders exist and create them if not + * + * @param array $autofolders_exists existing folders, no need to check their existance again + * @return int number of new folders created + */ + function check_create_autofolders(array $autofolders_exists=array()) + { + $num_created = 0; + foreach(self::$autoFolders as $folder) + { + $created = false; + if (!in_array($folder, $autofolders_exists) && $this->_getSpecialUseFolder($folder, true, $created) && + $created && $folder != 'Outbox') + { + $num_created++; + } + } + return $num_created; + } + /** * search Value In FolderObjects * * Helper function to search for a specific value within the foldertree objects * @param string $needle - * @param array $haystack, array of folderobjects + * @param array $haystack array of folderobjects * @return MIXED false or key */ static function searchValueInFolderObjects($needle, $haystack) @@ -2682,18 +2722,21 @@ class emailadmin_imapbase * abstraction layer for getDraftFolder, getTemplateFolder, getTrashFolder and getSentFolder * @param string $type the type to fetch (Drafts|Template|Trash|Sent) * @param boolean $_checkexistance, trigger check for existance + * @param boolean& $created =null on return true: if folder was just created, false if not * @return mixed string or false */ - function _getSpecialUseFolder($_type, $_checkexistance=TRUE) + function _getSpecialUseFolder($_type, $_checkexistance=TRUE, &$created=null) { static $types = array( - 'Drafts'=>array('prefName'=>'draftFolder','profileKey'=>'acc_folder_draft','autoFolderName'=>'Drafts'), - 'Template'=>array('prefName'=>'templateFolder','profileKey'=>'acc_folder_template','autoFolderName'=>'Templates'), - 'Trash'=>array('prefName'=>'trashFolder','profileKey'=>'acc_folder_trash','autoFolderName'=>'Trash'), - 'Sent'=>array('prefName'=>'sentFolder','profileKey'=>'acc_folder_sent','autoFolderName'=>'Sent'), - 'Junk'=>array('prefName'=>'junkFolder','profileKey'=>'acc_folder_junk','autoFolderName'=>'Junk'), - 'Outbox'=>array('prefName'=>'outboxFolder','profileKey'=>'acc_folder_outbox','autoFolderName'=>'Outbox'), + 'Drafts' => array('profileKey'=>'acc_folder_draft','autoFolderName'=>'Drafts'), + 'Template' => array('profileKey'=>'acc_folder_template','autoFolderName'=>'Templates'), + 'Trash' => array('profileKey'=>'acc_folder_trash','autoFolderName'=>'Trash'), + 'Sent' => array('profileKey'=>'acc_folder_sent','autoFolderName'=>'Sent'), + 'Junk' => array('profileKey'=>'acc_folder_junk','autoFolderName'=>'Junk'), + 'Outbox' => array('profileKey'=>'acc_folder_outbox','autoFolderName'=>'Outbox'), ); + if ($_type == 'Templates') $_type = 'Template'; // for some reason self::$autofolders uses 'Templates'! + $created = false; if (!isset($types[$_type])) { error_log(__METHOD__.' ('.__LINE__.') '.' '.$_type.' not supported for '.__METHOD__); @@ -2716,7 +2759,7 @@ class emailadmin_imapbase if ($_folderName && $_checkexistance && $_folderName !='none' && !$this->folderExists($_folderName,true)) { try { - $this->createFolder('', $_folderName, true); + if (($_folderName = $this->createFolder('', $_folderName, true))) $created = true; } catch(Exception $e) { diff --git a/emailadmin/inc/class.emailadmin_smtp_ldap.inc.php b/emailadmin/inc/class.emailadmin_smtp_ldap.inc.php index ee3b78dc02..8535305055 100644 --- a/emailadmin/inc/class.emailadmin_smtp_ldap.inc.php +++ b/emailadmin/inc/class.emailadmin_smtp_ldap.inc.php @@ -48,6 +48,11 @@ class emailadmin_smtp_ldap extends emailadmin_smtp */ const MAIL_ENABLE_ATTR = false; + /** + * Value for MAIL_ENABLED to use local mail address + */ + const MAIL_ENABLED_USE_MAIL = '@mail'; + /** * Attribute for aliases OR false to use mail */ @@ -224,7 +229,8 @@ class emailadmin_smtp_ldap extends emailadmin_smtp // does schema support enabling/disabling mail via attribute if (static::MAIL_ENABLE_ATTR) { - $newData[static::MAIL_ENABLE_ATTR] = static::MAIL_ENABLED; + $newData[static::MAIL_ENABLE_ATTR] = static::MAIL_ENABLED == self::MAIL_ENABLED_USE_MAIL ? + $mailLocalAddress : static::MAIL_ENABLE_ATTR; } // does schema support an explicit mailbox name --> set it if (static::MAILBOX_ATTR) @@ -232,6 +238,9 @@ class emailadmin_smtp_ldap extends emailadmin_smtp $newData[static::MAILBOX_ATTR] = self::mailbox_addr($_hookValues); } + // allow extending classes to add extra data + $this->addAccountExtra($_hookValues, $allValues[0], $newData); + if (!($ret = ldap_mod_replace($ds, $accountDN, $newData)) || $this->debug) { error_log(__METHOD__.'('.array2string(func_get_args()).") --> ldap_mod_replace(,'$accountDN',". @@ -241,6 +250,18 @@ class emailadmin_smtp_ldap extends emailadmin_smtp return $ret; } + /** + * Add additional values to addAccount and setUserData ($_hookValues['location']) + * + * @param array $_hookValues + * @param array $allValues existing data of account as returned by ldap query + * @param array $newData data to update + */ + function addAccountExtra(array $_hookValues, array $allValues, array &$newData) + { + unset($_hookValues, $allValues, $newData); // not used, but required by function signature + } + /** * Get all email addresses of an account * @@ -377,7 +398,8 @@ class emailadmin_smtp_ldap extends emailadmin_smtp if (static::MAIL_ENABLE_ATTR) { $accountStatus = isset($values[static::MAIL_ENABLE_ATTR]) && - (static::MAIL_ENABLED && !strcasecmp($values[static::MAIL_ENABLE_ATTR][0], static::MAIL_ENABLED) || + (static::MAIL_ENABLED === self::MAIL_ENABLED_USE_MAIL && !empty($values[static::MAIL_ENABLE_ATTR][0]) || + static::MAIL_ENABLED && !strcasecmp($values[static::MAIL_ENABLE_ATTR][0], static::MAIL_ENABLED) || !static::MAIL_ENABLED && $values[static::ALIAS_ATTR ? static::ALIAS_ATTR : 'mail']['count'] > 0) ? emailadmin_smtp::MAIL_ENABLED : ''; } @@ -567,7 +589,8 @@ class emailadmin_smtp_ldap extends emailadmin_smtp // does schema support enabling/disabling mail via attribute if (static::MAIL_ENABLE_ATTR) { - $newData[static::MAIL_ENABLE_ATTR] = $_accountStatus ? static::MAIL_ENABLED : array(); + $newData[static::MAIL_ENABLE_ATTR] = $_accountStatus ? + (static::MAIL_ENABLED == self::MAIL_ENABLED_USE_MAIL ? $_mailLocalAddress : static::MAIL_ENABLED) : array(); } // if we have no mail-enabled attribute, but require primary mail in aliases-attr // we do NOT write aliases, if mail is not enabled @@ -580,6 +603,9 @@ class emailadmin_smtp_ldap extends emailadmin_smtp { $newData[static::MAILBOX_ATTR] = $_setMailbox; } + + $this->addAccountExtra(array('location' => 'setUserData'), $allValues[0], $newData); + if ($this->debug) error_log(__METHOD__.'('.array2string(func_get_args()).") --> ldap_mod_replace(,'$accountDN',".array2string($newData).')'); return ldap_mod_replace($ldap, $accountDN, $newData); diff --git a/emailadmin/inc/class.emailadmin_smtp_univention.inc.php b/emailadmin/inc/class.emailadmin_smtp_univention.inc.php new file mode 100644 index 0000000000..d3a4934dbb --- /dev/null +++ b/emailadmin/inc/class.emailadmin_smtp_univention.inc.php @@ -0,0 +1,102 @@ + + * @copyright (c) 2014 by Ralf Becker + * @version $Id4$ + */ + +/** + * Postfix with Univention mailAccount schema + */ +class emailadmin_smtp_univention extends emailadmin_smtp_ldap +{ + /** + * Capabilities of this class (pipe-separated): default, forward + */ + const CAPABILITIES = 'default|forward'; + + /** + * Name of schema, has to be in the right case! + */ + const SCHEMA = 'univentionMail'; + + /** + * Attribute to enable mail for an account, OR false if existence of ALIAS_ATTR is enough for mail delivery + */ + const MAIL_ENABLE_ATTR = 'mailprimaryaddress'; + + /** + * Attribute value to enable mail for an account, OR false if existense of attribute is enough to enable account + */ + const MAIL_ENABLED = self::MAIL_ENABLED_USE_MAIL; + + /** + * Attribute for aliases OR false to use mail + */ + const ALIAS_ATTR = 'mailalternativeaddress'; + + /** + * Primary mail address required as an alias too: true or false + */ + const REQUIRE_MAIL_AS_ALIAS=false; + + /** + * Attribute for forwards OR false if not possible + */ + const FORWARD_ATTR = false; + + /** + * Attribute to only forward mail, OR false if not available + */ + const FORWARD_ONLY_ATTR = false; + /** + * Attribute value to only forward mail + */ + const FORWARD_ONLY = false; + + /** + * Attribute for mailbox, to which mail gets delivered OR false if not supported + */ + const MAILBOX_ATTR = false; + + /** + * Attribute for quota limit of user in MB + */ + const QUOTA_ATTR = 'univentionmailuserquota'; + + /** + * Internal quota in MB is multiplicated with this factor before stored in LDAP + */ + const QUOTA_FACTOR = 1; + + /** + * Log all LDAP writes / actions to error_log + */ + var $debug = false; + + /** + * Add additional values to addAccount + * + * @param array $_hookValues + * @param array $allValues + * @param array $newData + */ + function addAccountExtra(array $_hookValues, array $allValues, array &$newData) + { + unset($_hookValues); // not used, but required by function signature + + if (empty($allValues['univentionmailhomeserver'][0]) && $newData[self::MAIL_ENABLE_ATTR]) + { + $newData['univentionMailHomeServer'] = $this->host; + + if (strpos($newData['univentionMailHomeServer'], '://')) + { + $newData['univentionMailHomeServer'] = parse_url($newData['univentionMailHomeServer'], PHP_URL_HOST); + } + } + } +} diff --git a/phpgwapi/inc/class.accounts_ldap.inc.php b/phpgwapi/inc/class.accounts_ldap.inc.php index 9163532bfa..d64246a540 100644 --- a/phpgwapi/inc/class.accounts_ldap.inc.php +++ b/phpgwapi/inc/class.accounts_ldap.inc.php @@ -53,7 +53,7 @@ class accounts_ldap */ var $user_context; /** - * LDAP search filter for user accounts, eg. (uid=%name) + * LDAP search filter for user accounts, eg. (uid=%user) * * @var string */ @@ -82,16 +82,27 @@ class accounts_ldap 'user' => array( 'top','person','organizationalperson','inetorgperson','posixaccount','shadowaccount' ), - 'user-if-supported' => array( // these classes get added, only if the server supports them - 'mozillaabpersonalpha','mozillaorgperson','evolutionperson' + 'user-if-supported' => array( // these classes get added, if server supports them + 'mozillaabpersonalpha', 'mozillaorgperson', 'evolutionperson', + 'univentionperson', array('univentionobject', 'univentionObjectType' => 'users/user'), ), 'group' => array( 'top','posixgroup','groupofnames' + ), + 'group-if-supported' => array( // these classes get added, if servers supports them + 'univentiongroup', array('univentionobject', 'univentionObjectType' => 'groups/group'), ) ); /** * Classes allowing to set a mail-address for a group and specify the memberaddresses as forwarding addresses * + * $objectclass => $forward + * $objectclass => [$forward, $extra_attr, $mail_attr, $keep_objectclass] + * $forward : name of attribute to set forwards for members mail addresses, false if not used/required + * $extra_attr : required attribute (eg. 'uid'), which need to be set, default none + * $mail_attr : name of attribute for mail-address, if not 'mail' + * $keep_objectclass : true to not remove objectclass, if not mail set + * * @var array */ var $group_mail_classes = array( @@ -99,6 +110,7 @@ class accounts_ldap 'dbmailuser' => array('mailforwardingaddress','uid'), 'qmailuser' => array('mailforwardingaddress','uid'), 'mailaccount' => 'mailalias', + 'univentiongroup' => array(false, false, 'mailprimaryaddress', true), ); /** @@ -200,10 +212,7 @@ class accounts_ldap else { $old = ldap::result2array($old[0]); - foreach($old['objectclass'] as $n => $class) - { - $old['objectclass'][$n] = strtolower($class); - } + $old['objectclass'] = array_map('strtolower', $old['objectclass']); $key = false; if ($is_group && ($key = array_search('namedobject',$old['objectclass'])) !== false || $is_group && ($old['cn'] != $data_utf8['account_lid'] || substr($old['dn'],0,3) != 'cn=') || @@ -243,13 +252,20 @@ class accounts_ldap { $to_write['objectclass'] = $old ? $old['objectclass'] : array(); } - if (!$old && !$is_group) // for new accounts add additional addressbook object classes, if supported by server + if (!$old) // for new accounts add additional addressbook object classes, if supported by server { // as setting them later might loose eg. password, if we are not allowed to read them - foreach($this->requiredObjectClasses['user-if-supported'] as $additional) + foreach($this->requiredObjectClasses[$is_group?'group-if-supported':'user-if-supported'] as $additional) { + $add = array(); + if (is_array($additional)) + { + $add = $additional; + $additional = array_shift($add); + } if ($this->ldapServerInfo->supportsObjectClass($additional)) { $to_write['objectclass'][] = $additional; + if ($add) $to_write += $add; } } } @@ -269,20 +285,22 @@ class accounts_ldap $to_write = $this->_merge_group($to_write,$data_utf8); $data['account_type'] = 'g'; - $groupOfNames = in_array('groupofnames',$old ? $old['objectclass'] : $to_write['objectclass']); - if (!$old && $groupOfNames || $members) + $objectclass = $old ? $old['objectclass'] : $to_write['objectclass']; + if ($members || !$old && array_intersect(array('groupofnames','groupofuniquenames','univentiongroup'), $objectclass)) { - $to_write = array_merge($to_write,$this->set_members($members, - $data['account_id'],$groupOfNames,$dn)); + $to_write = array_merge($to_write, $this->set_members($members, $data['account_id'], $objectclass, $dn)); } // check if we should set a mail address and forwards for each member foreach($this->group_mail_classes as $objectclass => $forward) { + $extra_attr = false; + $mail_attr = 'mail'; + $keep_objectclass = false; + if (is_array($forward)) list($forward,$extra_attr,$mail_attr,$keep_objectclass) = $forward; + if ($this->ldapServerInfo->supportsObjectClass($objectclass) && - ($old && in_array($objectclass,$old['objectclass']) || $data_utf8['account_email'] || $old['mail'])) + ($old && in_array($objectclass,$old['objectclass']) || $data_utf8['account_email'] || $old[$mail_attr])) { - $extra_attr = false; - if (is_array($forward)) list($forward,$extra_attr) = $forward; if ($data_utf8['account_email']) // setting an email { if (!in_array($objectclass,$old ? $old['objectclass'] : $to_write['objectclass'])) @@ -291,23 +309,27 @@ class accounts_ldap $to_write['objectclass'][] = $objectclass; } if ($extra_attr) $to_write[$extra_attr] = $data_utf8['account_lid']; - $to_write['mail'] = $data_utf8['account_email']; + $to_write[$mail_attr] = $data_utf8['account_email']; - if (!$members) $members = $this->members($data['account_id']); - $to_write[$forward] = array(); - foreach (array_keys($members) as $member) + if ($forward) { - if (($email = $this->id2name($member,'account_email'))) + if (!$members) $members = $this->members($data['account_id']); + $to_write[$forward] = array(); + foreach (array_keys($members) as $member) { - $to_write[$forward][] = $email; + if (($email = $this->id2name($member,'account_email'))) + { + $to_write[$forward][] = $email; + } } } } elseif($old) // remove the mail and forwards only for existing entries { - $to_write['mail'] = $to_write[$forward] = array(); + $to_write[$mail_attr] = array(); + if ($forward) $to_write[$forward] = array(); if ($extra_attr) $to_write[$extra_attr] = array(); - if (($key = array_search($objectclass,$old['objectclass']))) + if (!$keep_objectclass && ($key = array_search($objectclass,$old['objectclass']))) { $to_write['objectclass'] = $old['objectclass']; unset($to_write['objectclass'][$key]); @@ -360,8 +382,6 @@ class accounts_ldap if ($err) { error_log(__METHOD__."() ldap_".($old ? 'modify' : 'add')."(,'$dn',".array2string($to_write).") --> ldap_error()=".ldap_error($this->ds)); - echo "ldap_".($old ? 'modify' : 'add')."(,$dn,".print_r($to_write,true).")\n"; - echo ldap_error($this->ds); return false; } } @@ -410,8 +430,23 @@ class accounts_ldap */ protected function _read_group($account_id) { + $mail_attr = 'mail'; + $group = array(); + if (!is_object($this->ldapServerInfo)) + { + $this->ldapServerInfo = $this->ldap->getLDAPServerInfo($this->frontend->config['ldap_host']); + } + foreach($this->group_mail_classes as $objectclass => $attrs) + { + if ($this->ldapServerInfo->supportsObjectClass($objectclass)) + { + $group['mailAllowed'] = $objectclass; + if (is_array($attrs) && $attrs[2]) $mail_attr = $attrs[2]; + break; + } + } $sri = ldap_search($this->ds, $this->group_context,'(&(objectClass=posixGroup)(gidnumber=' . abs($account_id).'))', - array('dn','gidnumber','cn','objectclass','mail')); + array('dn', 'gidnumber', 'cn', 'objectclass', $mail_attr, 'memberuid')); $ldap_data = ldap_get_entries($this->ds, $sri); if (!$ldap_data['count']) @@ -419,8 +454,9 @@ class accounts_ldap return false; // group not found } $data = translation::convert($ldap_data[0],'utf-8'); + unset($data['objectclass']['count']); - $group = array( + $group += array( 'account_dn' => $data['dn'], 'account_id' => -$data['gidnumber'][0], 'account_lid' => $data['cn'][0], @@ -428,21 +464,24 @@ class accounts_ldap 'account_firstname' => $data['cn'][0], 'account_lastname' => lang('Group'), 'account_fullname' => lang('Group').' '.$data['cn'][0], - 'groupOfNames' => in_array('groupOfNames',$data['objectclass']), - 'account_email' => $data['mail'][0], + 'objectclass' => array_map('strtolower', $data['objectclass']), + 'account_email' => $data[$mail_attr][0], + 'members' => array(), ); - if (!is_object($this->ldapServerInfo)) + + if (isset($data['memberuid'])) { - $this->ldapServerInfo = $this->ldap->getLDAPServerInfo($this->frontend->config['ldap_host']); - } - foreach(array_keys($this->group_mail_classes) as $objectclass) - { - if ($this->ldapServerInfo->supportsObjectClass($objectclass)) + unset($data['memberuid']['count']); + + foreach($data['memberuid'] as $lid) { - $group['mailAllowed'] = $objectclass; - break; + if (($id = $this->name2id($lid, 'account_lid', 'u'))) + { + $group['members'][$id] = $lid; + } } } + return $group; } @@ -745,7 +784,7 @@ class accounts_ldap 'account_modified' => isset($allVals['modifytimestamp'][0]) ? self::accounts_ldap2ts($allVals['modifytimestamp'][0]) : null, 'account_primary_group' => (string)-$allVals['gidnumber'][0], ); - error_log(__METHOD__."() ldap=".array2string($allVals)." --> account=".array2string($account)); + //error_log(__METHOD__."() ldap=".array2string($allVals)." --> account=".array2string($account)); if ($param['active'] && !$this->frontend->is_active($account)) { if (isset($totalcount)) --$totalcount; @@ -755,7 +794,7 @@ class accounts_ldap // return objectclass(es) if ($param['objectclass']) { - $account['objectclass'] = $allVals['objectclass']; + $account['objectclass'] = array_map('strtolower', $allVals['objectclass']); unset($account['objectclass']['count']); } $accounts[$account['account_id']] = $account; @@ -1005,9 +1044,11 @@ class accounts_ldap $members = array(); if (isset($group[0]['memberuid'])) { + unset($group[0]['memberuid']['count']); + foreach($group[0]['memberuid'] as $lid) { - if (($id = $this->name2id($lid))) + if (($id = $this->name2id($lid, 'account_lid', 'u'))) { $members[$id] = $lid; } @@ -1054,34 +1095,47 @@ class accounts_ldap * * @param array $members array with uidnumber or uid's * @param int $gid gidnumber of group to set - * @param boolean $groupOfNames =null should we set the member attribute of groupOfNames (default detect it) + * @param array $objectclass =null should we set the member and uniqueMember attributes (groupOf(Unique)Names|univentionGroup) (default detect it) * @param string $use_cn =null if set $cn is used instead $gid and the attributes are returned, not written to ldap + * @param boolean $uniqueMember =null should we set the uniqueMember attribute (default detect it) * @return boolean/array false on failure, array or true otherwise */ - function set_members($members,$gid,$groupOfNames=null,$use_cn=null) + function set_members($members, $gid, array $objectclass=null, $use_cn=null) { //echo "

accounts_ldap::set_members(".print_r($members,true).",$gid)

\n"; if (!($cn = $use_cn) && !($cn = $this->id2name($gid))) return false; - // do that group is a groupOfNames? - if (is_null($groupOfNames)) $groupOfNames = $this->id2name($gid,'groupOfNames'); + // do that group is a groupOf(Unique)Names or univentionGroup? + if (is_null($objectclass)) $objectclass = $this->id2name($gid,'objectclass'); $to_write = array('memberuid' => array()); foreach((array)$members as $key => $member) { + $member_dn = $this->id2name($member, 'account_dn'); if (is_numeric($member)) $member = $this->id2name($member); if ($member) { $to_write['memberuid'][] = $member; - if ($groupOfNames) $to_write['member'][] = 'uid='.$member.','.$this->user_context; + if (in_array('groupofnames', $objectclass)) + { + $to_write['member'][] = $member_dn; + } + if (array_intersect(array('groupofuniquenames','univentiongroup'), $objectclass)) + { + $to_write['uniquemember'][] = $member_dn; + } } } - if ($groupOfNames && !$to_write['member']) + // hack as groupOfNames requires the member attribute + if (in_array('groupofnames', $objectclass) && !$to_write['member']) { - // hack as groupOfNames requires the member attribute $to_write['member'][] = 'uid=dummy'.','.$this->user_context; } + if (array_intersect(array('groupofuniquenames','univentiongroup'), $objectclass) && !$to_write['uniquemember']) + { + $to_write['uniquemember'][] = 'uid=dummy'.','.$this->user_context; + } if ($use_cn) return $to_write; // set the member email addresses as forwards @@ -1091,10 +1145,13 @@ class accounts_ldap if (is_array($forward)) list($forward,$extra_attr) = $forward; if ($extra_attr && ($uid = $this->id2name($gid))) $to_write[$extra_attr] = $uid; - $to_write[$forward] = array(); - foreach($members as $key => $member) + if ($forward) { - if (($email = $this->id2name($member,'account_email'))) $to_write[$forward][] = $email; + $to_write[$forward] = array(); + foreach($members as $key => $member) + { + if (($email = $this->id2name($member,'account_email'))) $to_write[$forward][] = $email; + } } } if (!ldap_modify($this->ds,'cn='.ldap::quote($cn).','.$this->group_context,$to_write)) diff --git a/phpgwapi/inc/class.accounts_univention.inc.php b/phpgwapi/inc/class.accounts_univention.inc.php new file mode 100644 index 0000000000..bfafc8a426 --- /dev/null +++ b/phpgwapi/inc/class.accounts_univention.inc.php @@ -0,0 +1,82 @@ + + * + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @package api + * @subpackage accounts + * @version $Id$ + */ + +/** + * Univention LDAP Backend for accounts + * + * This backend is mostly identical to LDAP backend and need to be configured in the same way. + * Only difference is that new users get created via univention-directory-manager CLI program, + * to generate necesary Kerberos stuff. + */ +class accounts_univention extends accounts_ldap +{ + /** + * Name of binary to call + */ + const DIRECTORY_MANAGER_BIN = '/usr/sbin/univention-directory-manager'; + + /** + * Saves / adds the data of one account + * + * If no account_id is set in data the account is added and the new id is set in $data. + * + * @param array $data array with account-data + * @return int|boolean the account_id or false on error + */ + function save(&$data) + { + if (!$data['account_id'] && $data['account_type'] !== 'g' && self::available()) + { + $params = array( + 'users/user','create', + '--binddn', $this->frontend->config['ldap_root_dn'], + '--bindpwd', 5=>$this->frontend->config['ldap_root_pw'], + '--position', $this->frontend->config['ldap_context'], + '--set', 'username='.$data['account_lid'], + '--set', 'firstname='.$data['account_firstname'], + '--set', 'lastname='.$data['account_lastname'], + ); + if ($data['account_email']) + { + $params[] = '--set'; $params[] = 'mailPrimaryAddress='.$data['account_email']; + } + if (!empty($data['account_passwd'])) + { + $params[] = '--set'; $params[] = 'password='.$data['account_passwd']; + } + $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); + $output_arr = $ret = $matches = null; + exec($cmd, $output_arr, $ret); + $output = explode("\n", $output_arr); + if ($ret || !preg_match('/^Object created: (uid=.*)$/mui', $output, $matches)) + { + $params[5] = '********'; // mask out password! + $cmd = self::DIRECTORY_MANAGER_BIN.' '.implode(' ', array_map('escapeshellarg', $params)); + throw new egw_exception_wrong_userinput($cmd."\nreturned\n".$output); + } + $data['account_dn'] = $matches[1]; + $data['account_id'] = $this->name2id($matches[1], 'account_dn', 'u'); + } + return parent::save($data); + } + + /** + * Check if our function depending on an external binary is available + * + * @return boolean + */ + public static function available() + { + return file_exists(self::DIRECTORY_MANAGER_BIN) && is_executable(self::DIRECTORY_MANAGER_BIN); + } +} diff --git a/setup/inc/class.setup_cmd_config.inc.php b/setup/inc/class.setup_cmd_config.inc.php index 26383250d2..6cfc89d613 100644 --- a/setup/inc/class.setup_cmd_config.inc.php +++ b/setup/inc/class.setup_cmd_config.inc.php @@ -82,10 +82,15 @@ class setup_cmd_config extends setup_cmd { if (substr($name, 0, 4) == 'acc_') continue; + $app = 'phpgwapi'; + if (strpos($name, '/') !== false) + { + list($app, $name) = explode('/', $name); + } self::$egw_setup->db->insert(self::$egw_setup->config_table,array( 'config_value' => $value, ),array( - 'config_app' => 'phpgwapi', + 'config_app' => $app, 'config_name' => $name, ),__LINE__,__FILE__); } @@ -194,6 +199,7 @@ class setup_cmd_config extends setup_cmd '--ldap-context' => 'ldap_context', '--ldap-search-filter' => 'ldap_search_filter', '--ldap-group-context' => 'ldap_group_context', + '--sambaadmin-sid' => 'sambaadmin/sambaSID', '--allow-remote-admin' => 'allow_remote_admin', '--install-id' => 'install_id', '--ads-host' => 'ads_host', @@ -397,10 +403,14 @@ class setup_cmd_config extends setup_cmd { if (is_array($data) && isset($data['allowed'])) { - if ($data['name'] == 'auth_type') + switch ($data['name']) { - $options[$data['name']] = self::auth_types(); - continue; + case 'auth_type': + $options[$data['name']] = self::auth_types(); + continue 2; + case 'account_repository': + $options[$data['name']] = self::account_repositries(); + continue 2; } foreach($data['allowed'] as $label => $value) { @@ -460,6 +470,37 @@ class setup_cmd_config extends setup_cmd return $auth_types; } + /** + * Read auth-types (existing auth backends) from filesystem and fix our $options array + * + * @return array + */ + static function account_repositories() + { + static $account_repositories = array( + 'sql' => 'SQL', + 'ldap' => 'LDAP', + 'ads' => 'Active Directory', + ); + static $scan_done = null; + if (!$scan_done++) + { + // now add auth backends found in filesystem + foreach(scandir(EGW_INCLUDE_ROOT.'/phpgwapi/inc') as $file) + { + $matches = null; + if (preg_match('/^class\.accounts_([a-z]+)\.inc\.php$/', $file, $matches) && + !isset($account_repositories[$matches[1]]) && + class_exists($class='accounts_'.$matches[1]) && + (!is_callable($callable=$class.'::available') || call_user_func($callable))) + { + $account_repositories[$matches[1]] = ucfirst($matches[1]); + } + } + } + return $account_repositories; + } + /** * Return the defaults from the $options array * diff --git a/setup/inc/hook_config.inc.php b/setup/inc/hook_config.inc.php index 6bc2912a04..0779a36d69 100644 --- a/setup/inc/hook_config.inc.php +++ b/setup/inc/hook_config.inc.php @@ -185,6 +185,17 @@ function auth_type_activesync($config) return _options_from(setup_cmd_config::auth_types(),$config['auth_type_activesync']); } +/** + * Make account-repository-types from setup_cmd_config available + * + * @param array $config + * @return string + */ +function account_repository($config) +{ + return _options_from(setup_cmd_config::account_repositories(), $config['account_repository']); +} + /** * Returns options string * diff --git a/setup/templates/default/config.tpl b/setup/templates/default/config.tpl index 389349ce96..65723c6abd 100644 --- a/setup/templates/default/config.tpl +++ b/setup/templates/default/config.tpl @@ -173,9 +173,7 @@ {lang_Select_where_you_want_to_store/retrieve_user_accounts}: