diff --git a/lam/HISTORY b/lam/HISTORY index d0f49d20..9d1bc4c0 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -2,6 +2,7 @@ September 2013 4.3 - Custom SSL CA certificates can be setup in LAM main configuration - Unix user and group support for Samba 4 - Samba 3 groups: support local members + - Kolab: support for Kolab group accounts - SSH public key: support file upload and self service enhancements (RFE 101) - LAM Pro: -> PPolicy: check password history for password reuse diff --git a/lam/docs/manual-sources/howto.xml b/lam/docs/manual-sources/howto.xml index 66a29ffc..66eea377 100644 --- a/lam/docs/manual-sources/howto.xml +++ b/lam/docs/manual-sources/howto.xml @@ -11,7 +11,7 @@ an LDAP directory. LAM runs on any webserver with PHP5 support and connects to your LDAP server unencrypted or via SSL/TLS. - LAM supports Samba 3, Unix, Zarafa, Kolab 2, address book entries, + LAM supports Samba 3, Unix, Zarafa, Kolab 2/3, address book entries, NIS mail aliases, MAC addresses and much more. There is a tree viewer included to allow access to the raw LDAP attributes. You can use templates for account creation and use multiple configuration profiles. @@ -967,10 +967,10 @@ Have fun! certificates here. Please note that this can affect other web applications on the - same server if they require different certificates. You may also need - to restart Apache. In case of any problems please delete the uploaded - certificates and use the system - setup. + same server if they require different certificates. There seem to be + problems on Debian systems and you may also need to restart Apache. In + case of any problems please delete the uploaded certificates and use + the system setup. You can either upload a DER/PEM formatted certificate file or import the certificates directly from an LDAP server that is available @@ -2235,7 +2235,7 @@ ldbmodify -H /var/lib/samba/private/sam.ldb passwordSelfReset-Samba4-objectClass Samba 4 you need to add the extension, save, and then select a question and set the answer. If you add the extension, set question/answer and then save all together this will cause an LDAP - error and no changes will be saved. + error and no changes will be saved. @@ -2409,6 +2409,17 @@ ldbmodify -H /var/lib/samba/private/sam.ldb passwordSelfReset-Samba4-objectClass This module supports to manage Kolab accounts with LAM. E.g. you can set the user's mail quota and define invitation policies. + Please add the Kolab user module in your LAM server profile to + activate Kolab support. + + + + + + + + + Please enter an email address at the Personal page and set a Unix password first. Both are required that Kolab accepts the accounts. The email address ("Personal" page) must match your Kolab @@ -3027,6 +3038,32 @@ ldbmodify -H /var/lib/samba/private/sam.ldb passwordSelfReset-Samba4-objectClass +
+ Kolab + + Please activate the Kolab group module in your LAM server + profile to activate Kolab support. + + + + + + + + + + You can specify the email address and also set allowed sender + and recipient addresses. + + + + + + + + +
+
Quota diff --git a/lam/docs/manual-sources/images/mod_kolab.png b/lam/docs/manual-sources/images/mod_kolab.png index 8cd243d9..7aa4f2b9 100644 Binary files a/lam/docs/manual-sources/images/mod_kolab.png and b/lam/docs/manual-sources/images/mod_kolab.png differ diff --git a/lam/docs/manual-sources/images/mod_kolab2.png b/lam/docs/manual-sources/images/mod_kolab2.png new file mode 100644 index 00000000..8b0a732a Binary files /dev/null and b/lam/docs/manual-sources/images/mod_kolab2.png differ diff --git a/lam/docs/manual-sources/images/mod_kolab3.png b/lam/docs/manual-sources/images/mod_kolab3.png new file mode 100644 index 00000000..867d1b4e Binary files /dev/null and b/lam/docs/manual-sources/images/mod_kolab3.png differ diff --git a/lam/docs/manual-sources/images/mod_kolab4.png b/lam/docs/manual-sources/images/mod_kolab4.png new file mode 100644 index 00000000..924abd05 Binary files /dev/null and b/lam/docs/manual-sources/images/mod_kolab4.png differ diff --git a/lam/graphics/kolab.png b/lam/graphics/kolab.png index 1b68a024..fdac0919 100644 Binary files a/lam/graphics/kolab.png and b/lam/graphics/kolab.png differ diff --git a/lam/lib/account.inc b/lam/lib/account.inc index 91dda9da..63e0486e 100644 --- a/lam/lib/account.inc +++ b/lam/lib/account.inc @@ -488,6 +488,9 @@ function get_preg($argument, $regexp) { case "mailLocalAddress": $pregexpr = '/^([0-9a-zA-Z+\\/\\._-])+([@]([0-9a-zA-Z-])+([.]([0-9a-zA-Z-])+)*)?$/'; break; + case 'kolabEmailPrefix': + $pregexpr = '/^([-])?([0-9a-zA-Z+\\/\\._-])*([@]([0-9a-zA-Z\\.-])*)?$/'; + break; case "postalAddress": // Allow all but \, <, >, =, ? $pregexpr = '/^[^\\\<>=\\?]*$/'; break; diff --git a/lam/lib/modules/kolabGroup.inc b/lam/lib/modules/kolabGroup.inc new file mode 100644 index 00000000..84698efc --- /dev/null +++ b/lam/lib/modules/kolabGroup.inc @@ -0,0 +1,459 @@ +autoAddObjectClasses = false; + } + + /** + * Returns meta data that is interpreted by parent class + * + * @return array array with meta data + * + * @see baseModule::get_metaData() + */ + function get_metaData() { + $return = array(); + // icon + $return['icon'] = 'kolab.png'; + // manages host accounts + $return["account_types"] = array('group'); + // alias name + $return["alias"] = _("Kolab"); + // module dependencies + $return['dependencies'] = array('depends' => array(), 'conflicts' => array()); + // managed object classes + $return['objectClasses'] = array('kolabGroupOfUniqueNames'); + // managed attributes + $return['attributes'] = array('kolabAllowSMTPRecipient', 'kolabAllowSMTPSender', 'kolabDeleteflag'); + if ($this->manageMail()) { + $return['attributes'][] = 'mail'; + } + // help Entries + $return['help'] = array( + 'mail' => array( + "Headline" => _("Email address"), 'attr' => 'mail', + "Text" => _("The list's email address.") + ), + 'mailList' => array( + "Headline" => _("Email address"), 'attr' => 'mail', + "Text" => _("The list's email address.") . ' ' . _("Multiple values are separated by semicolon.") + ), + 'kolabAllowSMTPRecipient' => array ( + "Headline" => _('Allowed recepients'), 'attr' => 'kolabAllowSMTPRecipient', + "Text" => _('Describes the allowed or disallowed SMTP recipient addresses for mail sent by this account (e.g. "domain.tld" or "-user@domain.tld").') + ), + 'kolabAllowSMTPRecipientList' => array ( + "Headline" => _('Allowed recepients'), 'attr' => 'kolabAllowSMTPRecipient', + "Text" => _('Describes the allowed or disallowed SMTP recipient addresses for mail sent by this account (e.g. "domain.tld" or "-user@domain.tld").') + . ' ' . _("Multiple values are separated by semicolon.") + ), + 'kolabAllowSMTPSender' => array ( + "Headline" => _('Allowed senders'), 'attr' => 'kolabAllowSMTPSender', + "Text" => _('Describes the allowed or disallowed SMTP addresses sending mail to this account (e.g. "domain.tld" or "-user@domain.tld").') + ), + 'kolabAllowSMTPSenderList' => array ( + "Headline" => _('Allowed senders'), 'attr' => 'kolabAllowSMTPSender', + "Text" => _('Describes the allowed or disallowed SMTP addresses sending mail to this account (e.g. "domain.tld" or "-user@domain.tld").') + . ' ' . _("Multiple values are separated by semicolon.") + ), + 'deleteFlag' => array( + "Headline" => _("Mark for deletion"), 'attr' => 'kolabDeleteflag', + "Text" => _("This will set a special flag on the account which tells Kolabd to remove it. Use this to cleanly delete Kolab accounts (e.g. this removes mail boxes).") + ), + 'autoAdd' => array( + "Headline" => _("Automatically add this extension"), + "Text" => _("This will enable the extension automatically if this profile is loaded.") + ), + ); + // profile options + $profileContainer = new htmlTable(); + $profileContainer->addElement(new htmlTableExtendedInputCheckbox('kolabGroup_addExt', false, _('Automatically add this extension'), 'autoAdd'), true); + $return['profile_options'] = $profileContainer; + // upload fields + $return['upload_columns'] = array( + array( + 'name' => 'kolabGroup_kolabAllowSMTPRecipient', + 'description' => _('Allowed recepients'), + 'help' => 'kolabAllowSMTPRecipientList', + 'example' => '.com; -.net', + ), + array( + 'name' => 'kolabGroup_kolabAllowSMTPSender', + 'description' => _('Allowed senders'), + 'help' => 'kolabAllowSMTPSenderList', + 'example' => '.com; -.net', + ), + ); + if ($this->manageMail()) { + $return['upload_columns'][] = array( + 'name' => 'kolabGroup_mail', + 'description' => _('Email address'), + 'help' => 'mailList', + 'example' => 'list@company.com', + 'required' => true, + 'unique' => true, + ); + } + // available PDF fields + $return['PDF_fields'] = array( + 'kolabAllowSMTPRecipient' => _('Allowed recepients'), + 'kolabAllowSMTPSender' => _('Allowed senders'), + ); + if ($this->manageMail()) { + $return['PDF_fields']['mail'] = _('Email address'); + } + return $return; + } + + /** + * This function fills the $messages variable with output messages from this module. + */ + function load_Messages() { + $this->messages['mail'][0] = array('ERROR', _('Email address'), _('Please enter a valid email address!')); + $this->messages['mail'][1] = array('ERROR', _('Account %s:') . ' kolabGroup_mail', _('Please enter a valid email address!')); + $this->messages['mail'][2] = array('ERROR', _('Email address'), _('Email address already exists.')); + $this->messages['mail'][3] = array('ERROR', _('Account %s:') . ' kolabGroup_mail', _('Email address already exists.')); + $this->messages['kolabAllowSMTPRecipient'][0] = array('ERROR', _('Allowed recepients'), _('Please enter a valid recepient expression.')); + $this->messages['kolabAllowSMTPRecipient'][1] = array('ERROR', _('Account %s:') . ' kolabGroup_kolabAllowSMTPRecipient', _('Please enter a valid recepient expression.')); + $this->messages['kolabAllowSMTPSender'][0] = array('ERROR', _('Allowed senders'), _('Please enter a valid sender expression.')); + $this->messages['kolabAllowSMTPSender'][1] = array('ERROR', _('Account %s:') . ' kolabGroup_kolabAllowSMTPSender', _('Please enter a valid sender expression.')); + } + + /** + * Returns the HTML meta data for the main account page. + * + * @return htmlElement HTML meta data + */ + function display_html_attributes() { + $container = new htmlTable(); + if (isset($this->attributes['objectClass']) && in_array('kolabGroupOfUniqueNames', $this->attributes['objectClass'])) { + // check if account is marked for deletion + if (isset($this->attributes['kolabDeleteflag'])) { + $container->addElement(new htmlOutputText(_('This account is marked for deletion.'))); + return $container; + } + // mail + if ($this->manageMail()) { + $this->addSimpleInputTextField($container, 'mail', _('Email address'), true); + } + // allowed recepients + $this->addMultiValueInputTextField($container, 'kolabAllowSMTPRecipient', _('Allowed recepients')); + // allowed senders + $this->addMultiValueInputTextField($container, 'kolabAllowSMTPSender', _('Allowed senders')); + // delete flag + $this->loadMailHostCache(); + if (!$this->getAccountContainer()->isNewAccount && (sizeof($this->mailHostCache) > 0)) { + $deleteContainer = new htmlTable(); + $deleteContainer->addElement(new htmlSpacer(null, '20px'), true); + $deleteContainer->addElement(new htmlAccountPageButton(get_class($this), 'delete', 'open', _('Mark account for deletion'))); + $deleteContainer->addElement(new htmlHelpLink('deleteFlag')); + $container->addElement($deleteContainer); + } + } + else { + // add button + $container->addElement(new htmlButton('addObjectClass', _('Add Kolab extension'))); + } + return $container; + } + + /** + * Processes user input of the primary module page. + * It checks if all input values are correct and updates the associated LDAP attributes. + * + * @return array list of info/error messages + */ + function process_attributes() { + $errors = array(); + if (isset($_POST['addObjectClass'])) { + $this->attributes['objectClass'][] = 'kolabGroupOfUniqueNames'; + return $errors; + } + if (isset($_POST['remObjectClass'])) { + $this->attributes['objectClass'] = array_delete(array('kolabGroupOfUniqueNames'), $this->attributes['objectClass']); + $attrs = $this->meta['attributes']; + foreach ($attrs as $name) { + if (isset($this->attributes[$name])) { + unset($this->attributes[$name]); + } + } + return $errors; + } + // skip processing if object class is not set + if (!isset($this->attributes['objectClass']) || !in_array('kolabGroupOfUniqueNames', $this->attributes['objectClass'])) { + return $errors; + } + // mail + if ($this->manageMail()) { + if (!empty($_POST['mail'])) { + $this->attributes['mail'][0] = $_POST['mail']; + // check format + if (!get_preg($_POST['mail'], 'email')) { + $errors[] = $this->messages['mail'][0]; + } + // check if unique + if ($this->getAccountContainer()->isNewAccount || (!empty($this->orig['mail'][0]) && ($this->orig['mail'][0] != $this->attributes['mail'][0]))) { + $this->loadMailCache(); + if (in_array_ignore_case($_POST['mail'], $this->mailCache)) { + $errors[] = $this->messages['mail'][2]; + } + } + } + elseif (isset($this->attributes['mail'])) { + unset($this->attributes['mail']); + } + } + // allowed recepients + $this->processMultiValueInputTextField('kolabAllowSMTPRecipient', $errors, 'kolabEmailPrefix'); + // allowed senders + $this->processMultiValueInputTextField('kolabAllowSMTPSender', $errors, 'kolabEmailPrefix'); + return $errors; + } + + /** + * This function will create the meta HTML code to show a page to mark an account for deletion. + * + * @return htmlElement HTML meta data + */ + function display_html_delete() { + $return = new htmlTable(); + $message = new htmlOutputText(_('Do you really want to mark this account for deletion?')); + $return->addElement($message, true); + $return->addElement(new htmlSpacer(null, '10px'), true); + $serverTable = new htmlTable(); + $serverTable->addElement(new htmlTableExtendedSelect('deletionServer', $this->mailHostCache, array(), _('Server'), 'deleteFlag')); + $return->addElement($serverTable, true); + $return->addElement(new htmlSpacer(null, '10px'), true); + $buttonGroup = new htmlGroup(); + $buttonGroup->addElement(new htmlAccountPageButton(get_class($this), 'attributes', 'confirm', _('Mark account for deletion'))); + $buttonGroup->addElement(new htmlAccountPageButton(get_class($this), 'attributes', 'cancel', _('Cancel'))); + $return->addElement($buttonGroup, true); + return $return; + } + + /** + * Write variables into object and do some regex checks + */ + function process_delete() { + if (isset($_POST['form_subpage_kolabGroup_attributes_confirm'])) { + // set delete flag + $this->attributes['kolabDeleteflag'][0] = $_POST['deletionServer']; + } + } + + /** + * Returns a list of modifications which have to be made to the LDAP account. + * + * @return array list of modifications + *
This function returns an array with 3 entries: + *
array( DN1 ('add' => array($attr), 'remove' => array($attr), 'modify' => array($attr)), DN2 .... ) + *
DN is the DN to change. It may be possible to change several DNs (e.g. create a new user and add him to some groups via attribute memberUid) + *
"add" are attributes which have to be added to LDAP entry + *
"remove" are attributes which have to be removed from LDAP entry + *
"modify" are attributes which have to been modified in LDAP entry + *
"info" are values with informational value (e.g. to be used later by pre/postModify actions) + */ + function save_attributes() { + if (!in_array('kolabGroupOfUniqueNames', $this->attributes['objectClass']) && !in_array('kolabGroupOfUniqueNames', $this->orig['objectClass'])) { + // skip saving if the extension was not added/modified + return array(); + } + return parent::save_attributes(); + } + + /** + * Loads the values of an account profile into internal variables. + * + * @param array $profile hash array with profile values (identifier => value) + */ + function load_profile($profile) { + // profile mappings in meta data + parent::load_profile($profile); + // add extension + if (isset($profile['kolabGroup_addExt'][0]) && ($profile['kolabGroup_addExt'][0] == "true")) { + if (!in_array('kolabGroupOfUniqueNames', $this->attributes['objectClass'])) { + $this->attributes['objectClass'][] = 'kolabGroupOfUniqueNames'; + } + } + } + + /** + * In this function the LDAP account is built up. + * + * @param array $rawAccounts list of hash arrays (name => value) from user input + * @param array $ids list of IDs for column position (e.g. "posixAccount_uid" => 5) + * @param array $partialAccounts list of hash arrays (name => value) which are later added to LDAP + * @param array $selectedModules list of selected account modules + * @return array list of error messages if any + */ + function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules) { + $messages = array(); + for ($i = 0; $i < sizeof($rawAccounts); $i++) { + // add object classes + if (!in_array('kolabGroupOfUniqueNames', $partialAccounts[$i]['objectClass'])) { + $partialAccounts[$i]['objectClass'][] = 'kolabGroupOfUniqueNames'; + } + // mail + if ($this->manageMail() && !empty($rawAccounts[$i][$ids['kolabGroup_mail']])) { + if (get_preg($rawAccounts[$i][$ids['kolabGroup_mail']], 'email')) { + $this->loadMailCache(); + if (!in_array_ignore_case(trim($rawAccounts[$i][$ids['kolabGroup_mail']]), $this->mailCache)) { + $partialAccounts[$i]['mail'] = trim($rawAccounts[$i][$ids['kolabGroup_mail']]); + } + else { + $errMsg = $this->messages['mail'][3]; + array_push($errMsg, array($i)); + $messages[] = $errMsg; + } + } + else { + $errMsg = $this->messages['mail'][1]; + array_push($errMsg, array($i)); + $messages[] = $errMsg; + } + } + // allowed recipients + if (!empty($rawAccounts[$i][$ids['kolabGroup_kolabAllowSMTPRecipient']])) { + $mails = preg_split('/;[ ]*/', $rawAccounts[$i][$ids['kolabGroup_kolabAllowSMTPRecipient']]); + for ($m = 0; $m < sizeof($mails); $m++) { + if (get_preg($mails[$m], 'kolabEmailPrefix')) { + $partialAccounts[$i]['kolabAllowSMTPRecipient'][] = $mails[$m]; + } + else { + $errMsg = $this->messages['kolabAllowSMTPRecipient'][1]; + array_push($errMsg, array($i)); + $messages[] = $errMsg; + break; + } + } + } + // allowed senders + if (!empty($rawAccounts[$i][$ids['kolabGroup_kolabAllowSMTPSender']])) { + $mails = preg_split('/;[ ]*/', $rawAccounts[$i][$ids['kolabGroup_kolabAllowSMTPSender']]); + for ($m = 0; $m < sizeof($mails); $m++) { + if (get_preg($mails[$m], 'kolabEmailPrefix')) { + $partialAccounts[$i]['kolabAllowSMTPSender'][] = $mails[$m]; + } + else { + $errMsg = $this->messages['kolabAllowSMTPSender'][1]; + array_push($errMsg, array($i)); + $messages[] = $errMsg; + break; + } + } + } + } + return $messages; + } + + /** + * Returns a list of PDF entries + */ + function get_pdfEntries() { + $return = array(); + $this->addSimplePDFField($return, 'mail', _('Email address')); + $this->addSimplePDFField($return, 'kolabAllowSMTPRecipient', _('Allowed recepients')); + $this->addSimplePDFField($return, 'kolabAllowSMTPSender', _('Allowed senders')); + return $return; + } + + /** + * Returns if the mail attribute should be managed. + * + * @return boolean manage mail attribute + */ + private function manageMail() { + if (isset($_SESSION['config'])) { + $conf = $_SESSION['config']; + if (in_array('qmailGroup', $conf->get_AccountModules($this->get_scope()))) { + return false; + } + else { + return true; + } + } + return false; + } + + /** + * Loads the list of email addresses into the cache. + */ + private function loadMailCache() { + if ($this->mailCache != null) { + return; + } + $results = searchLDAPByFilter('(mail=*)', array('mail'), array($this->get_scope())); + $this->mailCache = array(); + foreach ($results as $result) { + if (isset($result['mail'][0])) { + $this->mailCache[] = $result['mail'][0]; + } + } + } + + /** + * Loads the list of mail hosts into the cache. + */ + private function loadMailHostCache() { + if ($this->mailHostCache != null) { + return; + } + $results = searchLDAPByFilter('(mailHost=*)', array('mailHost'), array('user')); + $this->mailHostCache = array(); + foreach ($results as $result) { + if (isset($result['mailhost'][0]) && !in_array_ignore_case($result['mailhost'][0], $this->mailHostCache)) { + $this->mailHostCache[] = $result['mailhost'][0]; + } + } + } + +} + + +?>