1<?php
2/*
3
4  This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/)
5  Copyright (C) 2010 - 2011  Pavel Pozdniak
6                2010 - 2020  Roland Gruber
7
8  This program is free software; you can redistribute it and/or modify
9  it under the terms of the GNU General Public License as published by
10  the Free Software Foundation; either version 2 of the License, or
11  (at your option) any later version.
12
13  This program is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU General Public License for more details.
17
18  You should have received a copy of the GNU General Public License
19  along with this program; if not, write to the Free Software
20  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21*/
22
23/**
24 * Manages mailboxes on an IMAP server.
25 *
26 * @package modules
27 * @author Pavel Pozdniak
28 * @author Roland Gruber
29 */
30
31/**
32 * Manages mailboxes on an IMAP server.
33 *
34 * @package modules
35 * @author Pavel Pozdniak
36 * @author Roland Gruber
37 */
38class imapAccess extends baseModule {
39
40	/** quota limit from profile */
41	private $profileQuotaLimit = null;
42
43	/** user name */
44	private $user;
45	/** email address */
46	private $email;
47
48	/**
49	* Returns true if this module can manage accounts of the current type, otherwise false.
50	*
51	* @return boolean true if module fits
52	*/
53	public function can_manage() {
54		return in_array($this->get_scope(), array('user'));
55	}
56
57	/**
58	 * Returns meta data that is interpreted by parent class
59	 *
60	 * @return array array with meta data
61	 *
62	 * @see baseModule::get_metaData()
63	 */
64	function get_metaData() {
65		$return = array();
66		// alias name
67		$return["alias"] = _("Mailbox");
68		// module dependencies
69		$return['dependencies'] = array('depends' => array(array('inetOrgPerson', 'windowsUser')), 'conflicts' => array());
70		// managed object classes
71		$return['objectClasses'] = array();
72		// managed attributes
73		$return['attributes'] = array();
74		// PHP extensions
75		$return['extensions'] = array('imap');
76		// icon
77		$return['icon'] = 'mailBig.png';
78		// help
79		$return['help'] = array(
80			'ImapServerAddress' => array(
81				"Headline" => _("Server address"),
82				"Text" => _("Address of IMAP server (e.g. mail.example.org).")),
83			'ImapServerEncryptionProtocol' => array(
84				"Headline" => _("Encryption protocol"),
85				"Text" => _("Encryption protocol for connecting to IMAP server. LAM requires an encrypted connection.")),
86			'ImapValidateServerCert' => array(
87				"Headline" => _("Validate server certificate"),
88				"Text" => _("This option allows you to disable the certificate check of your IMAP server certificate. Disabling the certificate check is not recommended.")),
89			'ImapAdmin' => array(
90				"Headline" => _("IMAP admin user"),
91				"Text" => _("The login name of your IMAP user who has rights to create/delete mailboxes.") . ' ' . _('Use wildcards like $uid$ for LDAP attributes of the current LAM admin user.')),
92			'ImapAdminPasswordSelect' => array(
93				"Headline" => _("IMAP password input"),
94				"Text" => _("Choose the way how to provide the IMAP admin password. You can use the same password as for the LAM login or LAM will ask you for a password when it is required.")
95							. ' ' . _('Storing the password in your server profile is also possible but not recommended.')
96			),
97			'ImapAdminPassword_Sess' => array(
98				"Headline" => _("Password of IMAP admin user"),
99				"Text" => _("The password of your IMAP admin user. The login name for the IMAP admin user is stored in the LAM server profile.")),
100			'ImapUserPrefix' => array(
101				"Headline" => _("Prefix for mailboxes"),
102				"Text" => _("Some IMAP servers store mailboxes with a prefix (e.g. \"user\" for Cyrus which results in \"user.username\").")),
103			'ImapMailDomain' => array(
104				"Headline" => _("Mail domains"),
105				"Text" => _("Please enter a comma separated list of domain names (e.g. \"company.com,example.com\"). LAM will only manage mailboxes from these domains.")),
106			'ImapUserNameAttr' => array(
107				"Headline" => _("User name attribute"),
108				"Text" => _("Please choose the attribute to get the IMAP user name. The default is \"mail\" but you can also use \"uid\" or \"userPrincipalName\".")),
109			'MailAddress' => array(
110				"Headline" => _("Mailbox"),
111				"Text" => _("This mailbox will be created/deleted.")),
112			'ImapUserQuotaLimit' => array(
113				"Headline" => _("Quota"),
114				"Text" => _("Please enter the quota limit of this mailbox in kilobytes.")),
115			'pathSeparator' => array(
116				"Headline" => _("Path separator"),
117				"Text" => _("This is the separator for the mailbox path. Usually, this is \".\" but e.g. Cyrus with \"unixhierarchysep\" will require \"/\".")),
118			'initialFolders' => array(
119				"Headline" => _("Initial folders"),
120				"Text" => _("Use this to provide a list of folders (e.g. Trash) to add for new accounts.")),
121			'createMailbox' => array(
122				"Headline" => _("Create mailbox"),
123				"Text" => _('Set to "true" to create the mailbox.')),
124		);
125		// configuration checks
126		$return['config_checks']['all']['ImapAccess_ImapServerAddress'] = array (
127			'type' => 'ext_preg',
128			'regex' => 'DNSname',
129			'required' => true,
130			'required_message' => $this->messages['config'][0],
131			'error_message' => $this->messages['config'][0]);
132		$return['config_checks']['all']['ImapAccess_ImapDomain'] = array (
133			'type' => 'regex_i',
134			'regex' => '[\\*a-z0-9\\._-]+(,[a-z0-9\\._-]+)*',
135			'required' => true,
136			'required_message' => $this->messages['config'][1],
137			'error_message' => $this->messages['config'][1]);
138		// profile options
139		$profileContainer = new htmlResponsiveRow();
140		$profileContainer->add(new htmlResponsiveInputField(_('Quota'), 'ImapAccess_QuotaLimit', null, 'ImapUserQuotaLimit'), 12);
141		$return['profile_options'] = $profileContainer;
142		$return['profile_checks']['ImapAccess_QuotaLimit'] = array(
143			'type' => 'ext_preg',
144			'regex' => 'digit',
145			'error_message' => $this->messages['managemailbox'][8]);
146		return $return;
147	}
148
149	/**
150	 * This function fills the error message array with messages
151	 */
152	function load_Messages() {
153		$this->messages['config'][0] = array('ERROR', _('Please enter a valid server name where the mailboxes reside.'));
154		$this->messages['config'][1] = array('ERROR', _('Please enter a correct list of valid mail domains.'));
155		$this->messages['config'][2] = array('ERROR', _('The IMAP admin password is empty.'));
156		$this->messages['managemailbox'][0] = array('ERROR', _('Unable to change ACL on IMAP server for mailbox deletion.'));
157		$this->messages['managemailbox'][1] = array('ERROR', _('Unable to delete mailbox from IMAP server.'));
158		$this->messages['managemailbox'][2] = array('ERROR', _('Unable to create mailbox on IMAP server.'));
159		$this->messages['managemailbox'][3] = array('ERROR', _('Unable to locate mailbox on IMAP.'));
160		$this->messages['managemailbox'][4] = array('ERROR', _('Your IMAP domains and email address domain do not match.'));
161		$this->messages['managemailbox'][5] = array('ERROR', _('Invalid password for IMAP admin or other problem occurred.'));
162		$this->messages['managemailbox'][6] = array('WARN', _('Your LAM login password was not accepted by the IMAP server.'));
163		$this->messages['managemailbox'][7] = array('ERROR', _('Cannot update quota.'));
164		$this->messages['managemailbox'][8] = array('ERROR', _('Wrong quota format. Quota must be numeric.'));
165		$this->messages['managemailbox'][9] = array('ERROR', _('Account %s:') . ' imapAccess_quota', _('Wrong quota format. Quota must be numeric.'));
166		$this->messages['managemailbox'][10] = array('ERROR', _('Cannot read quota.'));
167		$this->messages['createMailbox'][0] = array('ERROR', _('Account %s:') . ' imapAccess_createMailbox', _('This value can only be "true" or "false".'));
168	}
169
170	/**
171	 * Extracts user name and email address from inetOrgPerson/posixAccount/windowsUser modules.
172	 *
173	 * @param array $attrs LDAP attributes (retrieved from other account modules if empty)
174	 * @return htmlStatusMessage message if any
175	 */
176	private function extractUserAndEmail($attrs = null) {
177		$this->email = '';
178		if ($attrs === null) {
179			if ($this->getAccountContainer()->getAccountModule('inetOrgPerson') != null) {
180				$attrs = $this->getAccountContainer()->getAccountModule('inetOrgPerson')->getAttributes();
181			}
182			else {
183				$attrs = $this->getAccountContainer()->getAccountModule('windowsUser')->getAttributes();
184			}
185			if ($this->getAccountContainer()->getAccountModule('posixAccount') != null) {
186				$attrs = array_merge($attrs, $this->getAccountContainer()->getAccountModule('posixAccount')->getAttributes());
187			}
188		}
189		$this->email = !empty($attrs['mail'][0]) ? $attrs['mail'][0] : '';
190		$this->user = '';
191		// extract user name from email address
192		if (empty($this->moduleSettings['ImapAccess_UserNameAttribute'][0]) || $this->moduleSettings['ImapAccess_UserNameAttribute'][0] == 'mail') {
193			$email_parts = explode('@', $this->email, 2);
194			$this->user = array_shift($email_parts);
195		}
196		elseif ($this->moduleSettings['ImapAccess_UserNameAttribute'][0] == 'userPrincipalName') {
197			if (!empty($attrs['userPrincipalName'][0])) {
198				$parts = explode('@', $attrs['userPrincipalName'][0], 2);
199				$this->user = array_shift($parts);
200			}
201		}
202		// extract user name from Unix user name (might be in inetOrgPerson/windowUser or posixAccount module)
203		else {
204			$this->user = !empty($attrs['uid'][0]) ? $attrs['uid'][0] : '';
205		}
206
207		if (empty($this->email)) {
208			$modName = ($this->getAccountContainer()->getAccountModule('inetOrgPerson') != null) ? 'inetOrgPerson' : 'windowsUser';
209			return new htmlStatusMessage('INFO', _("Please enter an email address on this page: %s"), '', array($this->getAccountContainer()->getAccountModule($modName)->get_alias()));
210		}
211	}
212
213	/**
214	 * @inheritDoc
215	 */
216	public function display_html_attributes() {
217		$return = new htmlResponsiveRow();
218		if (!checkIfWriteAccessIsAllowed($this->get_scope())) {
219			return $return;
220		}
221		$msg = $this->extractUserAndEmail();
222		if ($msg != null) {
223			$return->add($msg, 12);
224			return $return;
225		}
226		$prefix = $this->getMailboxPrefix();
227
228		$email_domain = substr(strstr($this->email, '@'), 1);
229		$adminPassword = $this->getAdminPassword(); // check for password for fall back mechanism
230		if (!isset($_SESSION['imapAdmPass']) && !isset($adminPassword)) {
231			return $this->display_html_password();
232		}
233
234		$return->addLabel(new htmlOutputText(_('Email address')));
235		$return->addField(new htmlOutputText($this->email));
236
237		$adminUser = $this->getAdminUser();
238		$adminPassword = $this->getAdminPassword();
239		try {
240			$client = $this->connect($adminUser, $adminPassword);
241		}
242		catch (LAMException $e) {
243			return $this->display_html_password(new htmlStatusMessage('ERROR', $e->getTitle(), $e->getMessage()));
244		}
245
246		$return->addLabel(new htmlOutputText(_('Mailbox')));
247		$mailboxGroup = new htmlGroup();
248		$mailboxGroup->addElement(new htmlOutputText($prefix . $this->getSep() . $this->user));
249		$mailboxGroup->addElement(new htmlHelpLink('MailAddress'));
250		$return->addField($mailboxGroup);
251		$return->addVerticalSpacer('2rem');
252
253		$list = $client->listMailboxes($prefix . $this->getSep() . $this->user, Horde_Imap_Client::MBOX_ALL);
254		if (is_array($list) && sizeof($list) == 1) {
255			$this->renderQuotasForMailbox($return, $client, $prefix . $this->getSep() . $this->user);
256			$return->addVerticalSpacer('2rem');
257			$return->add(new htmlButton('deleteMailbox', _('Delete mailbox')), 12, 12, 12, 'text-center');
258		}
259		else {
260			$mailboxMessage = new htmlOutputText(_("Mailbox does not exist on IMAP server."));
261			$return->add($mailboxMessage, 12, 12, 12, 'text-center');
262			$return->addVerticalSpacer('2rem');
263			if ($this->isWrongDomain($email_domain)) {
264				$return->add(new htmlStatusMessage('INFO', $this->messages['managemailbox'][4][1]), 12);
265				$return->addVerticalSpacer('1rem');
266			}
267			else {
268				$createButton = new htmlButton('createMailbox', _('Create mailbox'));
269				$return->add($createButton, 12, 12, 12, 'text-center');
270			}
271		}
272		$client->logout();
273		return $return;
274	}
275
276	/**
277	 * Returns the HTML meta data for the password page.
278	 *
279	 * @param htmlStatusMessage|null $message status message
280	 * @return htmlResponsiveRow HTML meta data
281	 */
282	function display_html_password($message = null) {
283		$return = new htmlResponsiveRow();
284		if ($message !== null) {
285			$return->add($message, 12);
286			$return->addVerticalSpacer('1rem');
287		}
288		$passwordInput = new htmlResponsiveInputField(_("Password of IMAP admin user"), 'ImapAdminPassword', '', 'ImapAdminPassword_Sess');
289		$passwordInput->setIsPassword(true);
290		$passwordInput->setRequired(true);
291		$passwordInput->setOnKeyPress('SubmitForm(\'enterPasswordButton\', event);');
292		$return->add($passwordInput, 12);
293		$return->addVerticalSpacer('2rem');
294		$return->add(new htmlButton('enterPasswordButton', _('Ok')), 12, 12, 12, 'text-center');
295		return $return;
296	}
297
298	/**
299	 * Display the mailbox quota.
300	 *
301	 * @param htmlResponsiveRow $container structure that contained information to be displayed
302	 * @param Horde_Imap_Client_Socket $client IMAP client
303	 * @param String $username user name to connect to IMAP server
304	 * @return htmlResponsiveRow table with added information about user quotas or controls to add quota
305	 */
306	function renderQuotasForMailbox($container, $client, $username) {
307		if (($this->profileQuotaLimit != null) && ($this->profileQuotaLimit != '')) {
308			$client->setQuota($username, array('storage' => $this->profileQuotaLimit));
309			$this->profileQuotaLimit = null;
310		}
311		try {
312			$quotaRoot = $client->getQuotaRoot($username);
313			$quota_values = array();
314			if (!empty($quotaRoot)) {
315				$quota_values = $client->getQuota($username);
316			}
317			if (!empty($quota_values)) {
318				if (isset($quota_values['storage']) && is_array($quota_values['storage'])) {
319					$quotaLimit = $quota_values['storage']['limit'];
320					$container->addLabel(new htmlOutputText(_("Current usage (kB)")));
321					$container->addField(new htmlOutputText($quota_values['storage']['usage']), true);
322					$quotaLimitInput = new htmlResponsiveInputField(_("Quota limit (kB)"), 'ImapUserQuotaLimit', $quotaLimit, 'ImapUserQuotaLimit');
323					$container->add($quotaLimitInput, 12);
324					$container->addVerticalSpacer('2rem');
325					$container->add(new htmlButton('updateQuota', _('Update quota')), 12, 12, 12, 'text-center');
326					$container->addVerticalSpacer('1rem');
327				}
328			}
329			else {
330				$quotaLimit = "";
331				$quotaLimitInput = new htmlResponsiveInputField(_("Quota limit (kB)"), 'ImapUserQuotaLimit', $quotaLimit, 'ImapUserQuotaLimit');
332				$container->add($quotaLimitInput, 12);
333				$container->addVerticalSpacer('2rem');
334				$container->add(new htmlButton('updateQuota', _('Update quota')), 12, 12, 12, 'text-center');
335				$container->addVerticalSpacer('1rem');
336			}
337		}
338		catch (Exception $e) {
339			$container->add(new htmlStatusMessage('ERROR', $this->messages['managemailbox'][10][1], $e->getMessage()), 12);
340		}
341		return $container;
342	}
343
344	/**
345	 * Processes user input of the primary module page.
346	 * It checks if all input values are correct and updates the associated LDAP attributes.
347	 *
348	 * @return array list of info/error messages
349	 */
350	function process_attributes() {
351		$errors = array();
352		if (!checkIfWriteAccessIsAllowed($this->get_scope())) {
353			return $errors;
354		}
355		$prefix = $this->getMailboxPrefix();
356
357		$adminUser = $this->getAdminUser();
358		if (isset($_POST['ImapAdminPassword']) && isset($_POST['enterPasswordButton'])) {
359			$errors = $this->doLogin();
360		}
361		$adminPassword = $this->getAdminPassword();
362
363		try {
364			$client = $this->connect($adminUser, $adminPassword);
365			$this->extractUserAndEmail();
366			$email_domain = substr(strstr($this->email, '@'), 1);
367
368			if (isset($_POST['deleteMailbox'])) {
369				if ($this->isWrongDomain($email_domain)) {
370					$errors[] = $this->messages['managemailbox'][4];
371				}
372				else {
373					$root = $prefix . $this->getSep() . $this->user;
374					try {
375						$client->setACL($root, $adminUser, array('rights' => 'c', 'action' => 'add'));
376						$client->deleteMailbox($root);
377					}
378					catch (Exception $e) {
379						$message = $this->messages['managemailbox'][1];
380						$message[] = $e->getMessage();
381						$errors[] = $message;
382					}
383				}
384			}
385
386			if (isset($_POST['createMailbox'])) {
387				$createMessages = $this->createMailbox($client, $this->user, $email_domain);
388				$errors = array_merge($errors, $createMessages);
389			}
390			if (isset($_POST['updateQuota'])) {
391				$quota = $_POST['ImapUserQuotaLimit'];
392				$quotaMessages = $this->setQuota($client, $this->user, $email_domain, $quota);
393				$errors = array_merge($errors, $quotaMessages);
394			}
395			$client->logout();
396		}
397		catch (LAMException $e) {
398			return array(array('ERROR', $e->getTitle(), $e->getMessage()));
399		}
400		// Return error-messages
401		return $errors;
402	}
403
404	/**
405	 * Creates the mailbox for a user.
406	 *
407	 * @param Horde_Imap_Client_Socket $client IMAP client
408	 * @param string $userName user name
409	 * @param string $email_domain email domain
410	 * @return array error messages
411	 */
412	private function createMailbox($client, $userName, $email_domain) {
413		$errors = array();
414		$prefix = $this->getMailboxPrefix();
415		if ($this->isWrongDomain($email_domain)) {
416			$errors[] = $this->messages['managemailbox'][4];
417		}
418		else {
419			$root = $prefix . $this->getSep() . $userName;
420			logNewMessage(LOG_DEBUG, 'Creating mailbox: ' . $root);
421			try {
422				$client->createMailbox($root);
423				logNewMessage(LOG_DEBUG, 'Mailbox created');
424				$list = $client->listMailboxes($root, Horde_Imap_Client::MBOX_ALL);
425				if (empty($list)) {
426					$errors[] = $this->messages['managemailbox'][3];
427					return $errors;
428				}
429				// create initial folders
430				foreach ($this->getInitialFolders() as $folder) {
431					$fullFolderName = $root . $this->getSep() . $folder;
432					logNewMessage(LOG_DEBUG, 'Creating folder: ' . $fullFolderName);
433					$client->createMailbox($fullFolderName);
434					logNewMessage(LOG_DEBUG, 'Folder created: ' . $fullFolderName);
435				}
436			}
437			catch (Exception $e) {
438				$message = $this->messages['managemailbox'][2];
439				$message[] = $e->getMessage();
440				$errors[] = $message;
441			}
442		}
443		return $errors;
444	}
445
446	/**
447	 * Sets the mailbox quota for a user.
448	 *
449	 * @param Horde_Imap_Client_Socket $client IMAP client
450	 * @param string $userName user name
451	 * @param string $email_domain email domain
452	 * @param string $quota mailbox quota
453	 * @return array error messages
454	 */
455	private function setQuota($client, $userName, $email_domain, $quota) {
456		$prefix = $this->getMailboxPrefix();
457		$errors = array();
458		$root = $prefix . $this->getSep() . $userName;
459		if ($this->isWrongDomain($email_domain)) {
460			$errors[] = $this->messages['managemailbox'][4];
461		}
462		else {
463			if ($quota == '') {
464				try {
465					$client->setQuota($root, array('storage' => '-1'));
466				}
467				catch (Exception $e) {
468					$message = $this->messages['managemailbox'][7];
469					$message[] = $e->getMessage();
470					$errors[] = $message;
471				}
472			}
473			elseif (get_preg($quota, 'digit')) {
474				logNewMessage(LOG_DEBUG, 'Setting quota ' . $quota . ' for ' . $root);
475				try {
476					$client->setQuota($root, array('storage' => $quota));
477				}
478				catch (Exception $e) {
479					$message = $this->messages['managemailbox'][7];
480					$message[] = $e->getMessage();
481					$errors[] = $message;
482				}
483			}
484			else {
485				$errors[] = $this->messages['managemailbox'][8];
486			}
487		}
488		return $errors;
489	}
490
491	/**
492	 * Loads the values of an account profile into internal variables.
493	 *
494	 * @param array $profile hash array with profile values (identifier => value)
495	 */
496	function load_profile($profile) {
497		// profile mappings in meta data
498		parent::load_profile($profile);
499		if (isset($profile['ImapAccess_QuotaLimit'][0]) && $profile['ImapAccess_QuotaLimit'][0] != '') {
500			$this->profileQuotaLimit =  $profile['ImapAccess_QuotaLimit'][0];
501		}
502	}
503
504	/**
505	 * Returns a list of configuration options.
506	 *
507	 * @param array $scopes account types (user, group, host)
508	 * @param array $allScopes list of all active account modules and their scopes (module => array(scopes))
509	 * @return mixed htmlElement or array of htmlElement
510	 *
511	 * @see htmlElement
512	 */
513	public function get_configOptions($scopes, $allScopes) {
514		// configuration settings
515		$configContainer = new htmlResponsiveRow();
516		$configServer = new htmlResponsiveInputField(_('Server address'), 'ImapAccess_ImapServerAddress', '', 'ImapServerAddress');
517		$configServer->setRequired(true);
518		$configContainer->add($configServer, 12);
519		$configContainer->add(new htmlResponsiveSelect('ImapAccess_ImapServerEncriptionProtocol', array('TLS', 'SSL'), array('TLS'), _("Encryption protocol"), 'ImapServerEncryptionProtocol'), 12);
520		$configCertValidate = new htmlResponsiveSelect('ImapAccess_ImapValidateServerCert', array(_('Yes') => 'validate-cert', _('No') => 'novalidate-cert'), array('validate-cert'), _("Validate server certificate"), 'ImapValidateServerCert');
521		$configCertValidate->setHasDescriptiveElements(true);
522		$configContainer->add($configCertValidate, 12);
523		$configUser = new htmlResponsiveInputField(_('IMAP admin user'), 'ImapAccess_ImapAdmin', '', 'ImapAdmin');
524		$configUser->setRequired(true);
525		$configContainer->add($configUser, 12);
526		$pwdSelectOptions = array(
527			_('LAM user password') => 'lam_user_pass',
528			_('Ask') => 'ask_pass',
529			_('Server profile') => 'config');
530		$configPasswordType = new htmlResponsiveSelect('ImapAccess_ImapAdminPasswordSelect', $pwdSelectOptions, array('ask_pass'), _("IMAP password input"), 'ImapAdminPasswordSelect');
531		$configPasswordType->setHasDescriptiveElements(true);
532		$configPasswordType->setTableRowsToShow(array('config' => array('ImapAccess_ImapAdminPassword')));
533		$configPasswordType->setTableRowsToHide(array('lam_user_pass' => array('ImapAccess_ImapAdminPassword'), 'ask_pass' => array('ImapAccess_ImapAdminPassword')));
534		$configContainer->add($configPasswordType, 12);
535		$adminPwdInput = new htmlResponsiveInputField(_('Admin password'), 'ImapAccess_ImapAdminPassword', null, 'ImapAdminPasswordSelect');
536		$adminPwdInput->setIsPassword(true);
537		$adminPwdInput->setObfuscate(true);
538		$configContainer->add($adminPwdInput, 12);
539		$mailDomainsInput = new htmlResponsiveInputField(_('Mail domains'), 'ImapAccess_ImapDomain', '', 'ImapMailDomain');
540		$mailDomainsInput->setRequired(true);
541		$configContainer->add($mailDomainsInput, 12);
542		$configContainer->add(new htmlResponsiveInputField(_('Prefix for mailboxes'), 'ImapAccess_ImapUserPrefix', '', 'ImapUserPrefix'), 12);
543		$configContainer->add(new htmlResponsiveInputTextarea('ImapAccess_initialFolders', '', 10, 3, _('Initial folders'), 'initialFolders'), 12);
544		$configUserName = new htmlResponsiveSelect('ImapAccess_UserNameAttribute', array('mail', 'uid', 'userPrincipalName'), array('mail'), _("User name attribute"), 'ImapUserNameAttr');
545		$configContainer->add($configUserName, 12);
546		$configPathSeparator = new htmlResponsiveSelect('ImapAccess_pathSeparator', array('.', '/'), array('.'), _("Path separator"), 'pathSeparator');
547		$configContainer->add($configPathSeparator, 12);
548		return $configContainer;
549	}
550
551	/**
552	 * {@inheritDoc}
553	 * @see baseModule::check_configOptions()
554	 */
555	public function check_configOptions($typeIds, &$options) {
556		$errors = parent::check_configOptions($typeIds, $options);
557		if (($options['ImapAccess_ImapAdminPasswordSelect'][0] == 'config')
558				&& empty($options['ImapAccess_ImapAdminPassword'][0])) {
559			$errors[] = $this->messages['config'][2];
560		}
561		return $errors;
562	}
563
564	/**
565	 * Returns the user name of the IMAP admin.
566	 *
567	 * @return String admin user name
568	 */
569	private function getAdminUser() {
570		if (isset($_SESSION['imapAdmUser'])) {
571			return $_SESSION['imapAdmUser'];
572		}
573		$user = $this->moduleSettings['ImapAccess_ImapAdmin'][0];
574		// check if user name contains any wildcards that need to be replaced with LDAP attribute values
575		$matches = array();
576		preg_match_all('/\\$[a-z0-9_-]+\\$/i', $this->moduleSettings['ImapAccess_ImapAdmin'][0], $matches);
577		if (sizeof($matches) > 0) {
578			// find wildcards
579			$attrNames = array();
580			foreach ($matches as $match) {
581				foreach ($match as $attr) {
582					$attrNames[] = substr($attr, 1, -1);
583				}
584			}
585			$attrNames = array_values(array_unique($attrNames));
586			$attrNames = array_change_key_case($attrNames, CASE_LOWER);
587			// read LAM login user data
588			$dn = $_SESSION['ldap']->getUserName();
589			$sr = @ldap_read($_SESSION['ldap']->server(), $dn, '(objectclass=*)', $attrNames, 0, 0, 0, LDAP_DEREF_NEVER);
590			if ($sr) {
591				$info = @ldap_get_entries($_SESSION['ldap']->server(), $sr);
592				if ($info) {
593					cleanLDAPResult($info);
594					$info = $info[0];
595				}
596			}
597			// replace wildcards
598			foreach ($attrNames as $attr) {
599				if (empty($info[$attr])) {
600					continue;
601				}
602				$user = preg_replace('/\\$' . $attr . '\\$/i', $info[$attr][0], $user);
603			}
604		}
605		logNewMessage(LOG_DEBUG, 'IMAP admin user: ' . $user);
606		$_SESSION['imapAdmUser'] = $user;
607		return $user;
608	}
609
610	/**
611	 * Returns the admin password.
612	 *
613	 * @return String password
614	 */
615	private function getAdminPassword() {
616		//perform admin password
617		$password = null; //default value is null, it can be changed during the work
618		if (isset($_SESSION['imapAdmPass'])) {
619			$password = lamDecrypt($_SESSION['imapAdmPass']);
620		}
621		elseif (isset($this->moduleSettings['ImapAccess_ImapAdminPasswordSelect'][0]) && ($this->moduleSettings['ImapAccess_ImapAdminPasswordSelect'][0] == "lam_user_pass")) {
622			$password = $_SESSION['ldap']->getPassword();
623		}
624		elseif (!empty($this->moduleSettings['ImapAccess_ImapAdminPasswordSelect'][0]) && ($this->moduleSettings['ImapAccess_ImapAdminPasswordSelect'][0] == "config")
625			&& !empty($this->moduleSettings['ImapAccess_ImapAdminPassword'][0])) {
626			$password = deobfuscateText($this->moduleSettings['ImapAccess_ImapAdminPassword'][0]);
627		}
628		return $password;
629	}
630
631	/**
632     * Checks the password given by user and save it as session parameter.
633     *
634     * @return array list of error messages
635     */
636	function doLogin() {
637		$errors = array();
638		$adminUser = $this->getAdminUser();
639		if (isset($_POST['ImapAdminPassword']) && $_POST['ImapAdminPassword'] != "") {
640			$adminPassword = $_POST['ImapAdminPassword'];
641			try {
642				$client = $this->connect($adminUser, $adminPassword);
643				$_SESSION['imapAdmPass'] = lamEncrypt($_POST['ImapAdminPassword']);
644				$client->logout();
645			}
646			catch (LAMException $e) {
647				$error = $this->messages['managemailbox'][5];
648				$error[] = $e->getMessage();
649				$errors[] = $error;
650			}
651		}
652		return $errors;
653	}
654
655	/**
656	 * Connects to the IMAP server.
657	 *
658	 * @param string $user user name
659	 * @param string $password password
660	 * @return Horde_Imap_Client_Socket IMAP client
661	 */
662	private function connect($user, $password) {
663		include_once __DIR__ . '/../3rdParty/composer/autoload.php';
664		$encryptionType = $this->moduleSettings['ImapAccess_ImapServerEncriptionProtocol'][0];
665		if (strrpos($this->moduleSettings['ImapAccess_ImapServerAddress'][0], ":")) {
666			$port = substr(strstr($this->moduleSettings['ImapAccess_ImapServerAddress'][0], ':'), 1);
667			$serverName = array_shift(explode(':', $this->moduleSettings['ImapAccess_ImapServerAddress'][0], 2));
668		}
669		else {
670			$serverName = $this->moduleSettings['ImapAccess_ImapServerAddress'][0];
671			if ($encryptionType === "TLS") {
672				$port = 143;
673			}
674			else {
675				$port = 993;
676			}
677		}
678		$context = array(
679			'ssl' => array(
680				'cafile' => __DIR__ . '/../../serverCerts.pem'
681			)
682		);
683		if (isset($this->moduleSettings['ImapAccess_ImapValidateServerCert'][0]) && ($this->moduleSettings['ImapAccess_ImapValidateServerCert'][0] == 'novalidate-cert')) {
684			$context['ssl']['verify_peer'] = false;
685			$context['ssl']['verify_peer_name'] = false;
686		}
687		try {
688			$client = new Horde_Imap_Client_Socket(array(
689				'username' => $user,
690				'password' => $password,
691				'hostspec' => $serverName,
692				'port' => $port,
693				'secure' => strtolower($encryptionType),
694				'context' => $context
695			));
696			$client->login();
697			return $client;
698		}
699		catch (Exception $e) {
700			throw new LAMException(_('Unable to connect to IMAP server.'), $e->getMessage(), $e);
701		}
702	}
703
704	/**
705	 * This function returns the prefix for mailboxes.
706	 * If no prefix was given during configuration then "user" will be used (default for Cyrus).
707	 *
708	 * @return String prefix
709	 */
710	function getMailboxPrefix() {
711		if (!isset($this->moduleSettings['ImapAccess_ImapUserPrefix'][0]) || ($this->moduleSettings['ImapAccess_ImapUserPrefix'][0] == '')) {
712			return "user";
713		}
714		else {
715			return $this->moduleSettings['ImapAccess_ImapUserPrefix'][0];
716		}
717	}
718
719	/**
720	 * This function checks if the domain of the mailbox is not in the list of domains listed in the configuration.
721	 * If it is in the list then it returns false, otherwise returns true. If the list of domains is not set then it returns true.
722	 *
723	 * @param String $email_domain email domain
724	 * @return boolean true if domains match
725	 */
726	function isWrongDomain($email_domain) {
727		if (isset($this->moduleSettings['ImapAccess_ImapDomain'][0])) {
728			$domain_list_string = $this->moduleSettings['ImapAccess_ImapDomain'][0];
729			if ($domain_list_string == '*') {
730				return false;
731			}
732			$domains_array = explode(",", $domain_list_string);
733			if ((sizeof($domains_array) == 0) || in_array($email_domain, $domains_array)) {
734				return false;
735			}
736		}
737		else {
738			return false;
739		}
740		return true;
741	}
742
743	/**
744	 * Returns the path separator.
745	 *
746	 * @return String separator char
747	 */
748	private function getSep() {
749		if (isset($this->moduleSettings['ImapAccess_pathSeparator'][0])) {
750			return $this->moduleSettings['ImapAccess_pathSeparator'][0];
751		}
752		return '.'; // default
753	}
754
755	/**
756	 * Returns the list of initial folders to create for a new mailbox.
757	 *
758	 * @return array list of folders
759	 */
760	private function getInitialFolders() {
761		$list = array();
762		if (!empty($this->moduleSettings['ImapAccess_initialFolders'])) {
763			foreach ($this->moduleSettings['ImapAccess_initialFolders'] as $folder) {
764				$folder = trim($folder);
765				if (!empty($folder)) {
766					$list[] = $folder;
767				}
768			}
769		}
770		return $list;
771	}
772
773	/**
774	 * {@inheritDoc}
775	 * @see baseModule::get_uploadColumns()
776	 */
777	public function get_uploadColumns($selectedModules, &$type) {
778		$pwd = $this->getAdminPassword();
779		if (empty($pwd)) {
780			return array();
781		}
782		return array(
783			array(
784				'name' => 'imapAccess_createMailbox',
785				'description' => _('Create mailbox'),
786				'example' => 'false',
787				'default' => 'false',
788				'values' => 'true, false',
789				'help' => 'createMailbox'
790			),
791			array(
792				'name' => 'imapAccess_quota',
793				'description' => _('Quota limit (kB)'),
794				'example' => '1000000',
795				'help' => 'ImapUserQuotaLimit'
796			),
797		);
798	}
799
800	/**
801	 * {@inheritDoc}
802	 * @see baseModule::build_uploadAccounts()
803	 */
804	public function build_uploadAccounts($rawAccounts, $ids, &$partialAccounts, $selectedModules, &$type) {
805		$errors = array();
806		if (!isset($ids['imapAccess_createMailbox'])) {
807			return $errors;
808		}
809		for ($i = 0; $i < sizeof($rawAccounts); $i++) {
810			if (isset($rawAccounts[$i][$ids['imapAccess_createMailbox']])
811					&& !in_array($rawAccounts[$i][$ids['imapAccess_createMailbox']], array('true', 'false'))) {
812				$errMsg = $this->messages['createMailbox'][0];
813				array_push($errMsg, array($i));
814				$errors[] = $errMsg;
815			}
816			if (isset($rawAccounts[$i][$ids['imapAccess_createMailbox']])
817					&& ($rawAccounts[$i][$ids['imapAccess_createMailbox']] === 'true')
818					&& !empty($ids['imapAccess_quota'])
819					&& isset($rawAccounts[$i][$ids['imapAccess_quota']])
820					&& !get_preg($rawAccounts[$i][$ids['imapAccess_quota']], 'digit')) {
821				$errMsg = $this->messages['managemailbox'][9];
822				array_push($errMsg, array($i));
823				$errors[] = $errMsg;
824			}
825		}
826		return $errors;
827	}
828
829	/**
830	 * {@inheritDoc}
831	 * @see baseModule::doUploadPostActions()
832	 */
833	function doUploadPostActions(&$data, $ids, $failed, &$temp, &$accounts, $selectedModules, $type) {
834		if (!checkIfWriteAccessIsAllowed($this->get_scope())) {
835			die();
836		}
837		// on first call generate list of IMAP operations
838		if (!isset($temp['counter'])) {
839			$temp['users'] = array();
840			$temp['counter'] = 0;
841			$errors = array();
842			if (isset($ids['imapAccess_createMailbox'])) {
843				foreach ($data as $i => $dataRow) {
844					if (in_array($i, $failed)) {
845						continue; // ignore failed accounts
846					}
847					if ($dataRow[$ids['imapAccess_createMailbox']] === 'true') {
848						$limit = '';
849						if (isset($ids['imapAccess_quota'])
850								&& isset($dataRow[$ids['imapAccess_quota']])
851								&& ($dataRow[$ids['imapAccess_quota']] !== '')) {
852							$limit = $dataRow[$ids['imapAccess_quota']];
853						}
854						$attributes = $accounts[$i];
855						foreach ($attributes as $name => $value) {
856							if (!is_array($value)) {
857								$attributes[$name] = array($value);
858							}
859						}
860						$extractErrors = $this->extractUserAndEmail($attributes);
861						if (!empty($extractErrors)) {
862							$errors = array_merge($errors, $extractErrors);
863						}
864						$temp['users'][] = array(
865							'uid' => $this->user,
866							'limit' => $limit,
867							'email' => substr(strstr($this->email, '@'), 1)
868						);
869					}
870				}
871			}
872			return array(
873				'status' => 'inProgress',
874				'progress' => 0,
875				'errors' => $errors
876			);
877		}
878		// add mailbox
879		elseif ($temp['counter'] < sizeof($temp['users'])) {
880			$errors = array();
881			$data = $temp['users'][$temp['counter']];
882			$uid = $data['uid'];
883			$limit = $data['limit'];
884			$email_domain = $data['email'];
885			ob_start();
886			$imapUser = $this->getAdminUser();
887			$imapPassword = $this->getAdminPassword();
888			try {
889				$client = $this->connect($imapUser, $imapPassword);
890				$prefix = $this->getMailboxPrefix();
891				$list = $client->listMailboxes($prefix . $this->getSep() . $uid);
892				if (empty($list)) {
893					$createErrors = $this->createMailbox($client, $uid, $email_domain);
894					$errors = array_merge($errors, $createErrors);
895					if (empty($createErrors)) {
896						$quotaErrors = $this->setQuota($client, $uid, $email_domain, $limit);
897						$errors = array_merge($errors, $quotaErrors);
898					}
899				}
900				$client->logout();
901			}
902			catch (Exception $e) {
903				$message = $this->messages['managemailbox'][5];
904				$message[] = $e->getMessage();
905				$errors[] = $message;
906			}
907			ob_end_clean();
908			$temp['counter']++;
909			return array (
910				'status' => 'inProgress',
911				'progress' => ($temp['counter'] * 100) / sizeof($temp['users']),
912				'errors' => $errors
913			);
914		}
915		// all modifications are done
916		else {
917			return array (
918				'status' => 'finished',
919				'progress' => 100,
920				'errors' => array()
921			);
922		}
923	}
924
925}
926