backport of Univention support to 14.1:

r49257: support for Univention LDAP schemas
r49263: mail plugin for UCS
r49274: UCS mail plugin: fix typo in name of alias attribute, quota in MB and reading of enabled attribute
r49275: setting sambaPrimaryGroupSID and univentionObject with univentionObjectType
r49276: automatic install in UCS enviroment
r49298: setting univentionMailHomeServer attribute to trigger directory listener to create INBOX and create configured folders in mail app
r49299: using univention-directory-manager cli to create not accounts to get Kerberos stuff addded
This commit is contained in:
Ralf Becker 2014-11-05 20:43:10 +00:00
commit 01e85bcee9
10 changed files with 547 additions and 82 deletions

View File

@ -11,7 +11,7 @@
if (php_sapi_name() !== 'cli') // security precaution: forbit calling post_install as web-page
{
die('<h1>rpm_post_install.php must NOT be called as web-page --> exiting !!!</h1>');
die('<h1>post_install.php must NOT be called as web-page --> exiting !!!</h1>');
}
$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));
}

View File

@ -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);

View File

@ -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 "<br>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)
{

View File

@ -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);

View File

@ -0,0 +1,102 @@
<?php
/**
* EGroupware: Postfix with Univention mailAccount schema
*
* @link http://www.egroupware.org
* @package emailadmin
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2014 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @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);
}
}
}
}

View File

@ -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)
{
if ($this->ldapServerInfo->supportsObjectClass($objectclass) &&
($old && in_array($objectclass,$old['objectclass']) || $data_utf8['account_email'] || $old['mail']))
{
$extra_attr = false;
if (is_array($forward)) list($forward,$extra_attr) = $forward;
$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_attr]))
{
if ($data_utf8['account_email']) // setting an email
{
if (!in_array($objectclass,$old ? $old['objectclass'] : $to_write['objectclass']))
@ -291,8 +309,10 @@ 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 ($forward)
{
if (!$members) $members = $this->members($data['account_id']);
$to_write[$forward] = array();
foreach (array_keys($members) as $member)
@ -303,11 +323,13 @@ class accounts_ldap
}
}
}
}
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)
unset($data['memberuid']['count']);
foreach($data['memberuid'] as $lid)
{
if ($this->ldapServerInfo->supportsObjectClass($objectclass))
if (($id = $this->name2id($lid, 'account_lid', 'u')))
{
$group['mailAllowed'] = $objectclass;
break;
$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 "<p>accounts_ldap::set_members(".print_r($members,true).",$gid)</p>\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 ($groupOfNames && !$to_write['member'])
if (in_array('groupofnames', $objectclass))
{
$to_write['member'][] = $member_dn;
}
if (array_intersect(array('groupofuniquenames','univentiongroup'), $objectclass))
{
$to_write['uniquemember'][] = $member_dn;
}
}
}
// hack as groupOfNames requires the member attribute
if (in_array('groupofnames', $objectclass) && !$to_write['member'])
{
$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,12 +1145,15 @@ class accounts_ldap
if (is_array($forward)) list($forward,$extra_attr) = $forward;
if ($extra_attr && ($uid = $this->id2name($gid))) $to_write[$extra_attr] = $uid;
if ($forward)
{
$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))
{
echo "ldap_modify(,'cn=$cn,$this->group_context',".print_r($to_write,true)."))\n";

View File

@ -0,0 +1,82 @@
<?php
/**
* API - accounts Univention LDAP backend
*
* @link http://www.egroupware.org
* @author Ralf Becker <rb@stylite.de>
*
* @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);
}
}

View File

@ -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'])
{
case 'auth_type':
$options[$data['name']] = self::auth_types();
continue;
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
*

View File

@ -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
*

View File

@ -173,9 +173,7 @@
<td>{lang_Select_where_you_want_to_store/retrieve_user_accounts}:</td>
<td>
<select name="newsettings[account_repository]">
<option value="sql"{selected_account_repository_sql}>SQL</option>
<option value="ldap"{selected_account_repository_ldap}>LDAP</option>
<option value="ads"{selected_account_repository_ads}>Active Directory</option>
{hook_account_repository}
</select>
</td>
</tr>