From 91cd43dfd202f6d480d10ca622837afe21d5ac8b Mon Sep 17 00:00:00 2001 From: Daniel Rudolf Date: Thu, 23 Feb 2023 23:24:50 +0100 Subject: [PATCH] Mailbox Automatic Aliases 2.0 (#290) Refactors @mfechner's `ViMbAdminPlugin_MailboxAutomaticAliases`. The new plugin is fully backwards compatible, but adds a whole lot of flexibility. One can now use plain usernames as default mapping (`defaultMapping.abuse = "postmaster"` creates an alias abuse@example.com -> postmaster@example.com), as well as domain wildcards (`defaultMapping.abuse = "@example.net"` creates an alias abuse@example.com -> abuse@example.net). Additionally there's a wildcard mapping to simplify configuration (`defaultMapping.* = "root@example.com"` causes all automatic aliases without explicit default mapping to redirect to root@example.com). The plugin will furthermore respect full domain aliases (@example.com -> @example.net), bypassing the need to create distinct automatic aliases, since they are caught by the domain alias anyway. Protection rules are now fully enforced. Next to preventing domain admins to delete mailboxes that are used as goto address for an automatic alias, the plugin now enforces the same when disabling a mailbox, and when deleting or disabling another alias that is used as goto address. The plugin no longer relies on the default mappings for these checks, but actually checks the alias' goto address. As before, the actual automatic aliases are protected as well. The same now applies to domain aliases when there are no distinct automatic aliases. Please note that protection rules aren't enforced recursively and not accross domain boundaries. These checks didn't exist before and IMHO aren't really worth the effort. Also adds the `mailbox_toggleActive_preToggle` event and fixes the output of the `alias_toggleActive_preToggle` event. --- application/configs/application.ini.dist | 15 +- application/configs/application.ini.vagrant | 22 +- application/controllers/AliasController.php | 2 +- application/controllers/MailboxController.php | 24 +- .../plugins/MailboxAutomaticAliases.php | 600 ++++++++++++------ 5 files changed, 436 insertions(+), 227 deletions(-) diff --git a/application/configs/application.ini.dist b/application/configs/application.ini.dist index bb6f7f8..5851f3c 100644 --- a/application/configs/application.ini.dist +++ b/application/configs/application.ini.dist @@ -367,13 +367,6 @@ skipVersionCheck = 0 skipInstallPingback = 0 - - - - - - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Allow admins to dictate whether a user can use BOTH, IMAP ONLY, ; POP3 ONLY when creating mailboxes. @@ -395,7 +388,6 @@ vimbadmin_plugins.AccessPermissions.type.POP3 = "POP3" vimbadmin_plugins.AccessPermissions.type.SIEVE = "SIEVE" - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Allow admins to force that for a mailbox/domain basic aliases are existing ; If a new mailbox is created the system will check if the aliases are existing, if not they are created. @@ -411,10 +403,9 @@ vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "hostmaster" vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "webmaster" ; Define this if emails should be forwarded to a fixed address instead of the first mailbox address of the domain -;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.postmaster = "postmaster@example.net" -;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.abuse = "abuse@example.net" -;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.hostmaster = "hostmaster@example.net" -;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.webmaster = "webmaster@example.net" +;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.postmaster = "@other.tld" +;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.abuse = "postmaster" +;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.* = "root@domain.tld" diff --git a/application/configs/application.ini.vagrant b/application/configs/application.ini.vagrant index b36d77f..89de83a 100644 --- a/application/configs/application.ini.vagrant +++ b/application/configs/application.ini.vagrant @@ -347,13 +347,6 @@ skipVersionCheck = 0 skipInstallPingback = 0 - - - - - - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Allow admins to dictate whether a user can use BOTH, IMAP ONLY, ; POP3 ONLY when creating mailboxes. @@ -375,11 +368,24 @@ vimbadmin_plugins.AccessPermissions.type.POP3 = "POP3" vimbadmin_plugins.AccessPermissions.type.SIEVE = "SIEVE" +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Allow admins to force that for a mailbox/domain basic aliases are existing +; If a new mailbox is created the system will check if the aliases are existing, if not they are created. +vimbadmin_plugins.MailboxAutomaticAliases.disabled = true +; These aliases should always exist, it is not recommened to delete it +vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "postmaster" +vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "abuse" +; These aliases are optional, but it recommended to not remove them +vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "hostmaster" +vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "webmaster" - +; Define this if emails should be forwarded to a fixed address instead of the first mailbox address of the domain +;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.postmaster = "@other.tld" +;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.abuse = "postmaster" +;vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.* = "root@domain.tld" diff --git a/application/controllers/AliasController.php b/application/controllers/AliasController.php index 9fd5b5f..580325e 100644 --- a/application/controllers/AliasController.php +++ b/application/controllers/AliasController.php @@ -327,10 +327,10 @@ public function ajaxToggleActiveAction() $this->notify( 'alias', 'toggleActive', 'preflush', $this, [ 'active' => $this->getAlias()->getActive() ] ); $this->getD2EM()->flush(); $this->notify( 'alias', 'toggleActive', 'postflush', $this, [ 'active' => $this->getAlias()->getActive() ] ); + print 'ok'; } else { print 'ko'; } - print 'ok'; } diff --git a/application/controllers/MailboxController.php b/application/controllers/MailboxController.php index 7135e0e..cedebe8 100644 --- a/application/controllers/MailboxController.php +++ b/application/controllers/MailboxController.php @@ -408,18 +408,22 @@ public function ajaxToggleActiveAction() if( !$this->getMailbox() ) print 'ko'; - $this->getMailbox()->setActive( !$this->getMailbox()->getActive() ); - $this->getMailbox()->setModified( new \DateTime() ); + if($this->notify( 'mailbox', 'toggleActive', 'preToggle', $this, [ 'active' => $this->getMailbox()->getActive() ]) === true) { + $this->getMailbox()->setActive( !$this->getMailbox()->getActive() ); + $this->getMailbox()->setModified( new \DateTime() ); - $this->log( - $this->getMailbox()->getActive() ? \Entities\Log::ACTION_MAILBOX_ACTIVATE : \Entities\Log::ACTION_MAILBOX_DEACTIVATE, - "{$this->getAdmin()->getFormattedName()} " . ( $this->getMailbox()->getActive() ? 'activated' : 'deactivated' ) . " mailbox {$this->getMailbox()->getUsername()}" - ); + $this->log( + $this->getMailbox()->getActive() ? \Entities\Log::ACTION_MAILBOX_ACTIVATE : \Entities\Log::ACTION_MAILBOX_DEACTIVATE, + "{$this->getAdmin()->getFormattedName()} " . ( $this->getMailbox()->getActive() ? 'activated' : 'deactivated' ) . " mailbox {$this->getMailbox()->getUsername()}" + ); - $this->notify( 'mailbox', 'toggleActive', 'postflush', $this, [ 'active' => $this->getMailbox()->getActive() ] ); - $this->getD2EM()->flush(); - $this->notify( 'mailbox', 'toggleActive', 'postflush', $this, [ 'active' => $this->getMailbox()->getActive() ] ); - print 'ok'; + $this->notify( 'mailbox', 'toggleActive', 'preflush', $this, [ 'active' => $this->getMailbox()->getActive() ] ); + $this->getD2EM()->flush(); + $this->notify( 'mailbox', 'toggleActive', 'postflush', $this, [ 'active' => $this->getMailbox()->getActive() ] ); + print 'ok'; + } else { + print 'ko'; + } } diff --git a/application/plugins/MailboxAutomaticAliases.php b/application/plugins/MailboxAutomaticAliases.php index ab315f2..dc0156e 100644 --- a/application/plugins/MailboxAutomaticAliases.php +++ b/application/plugins/MailboxAutomaticAliases.php @@ -1,196 +1,404 @@ - - */ - -/** - * The Mailbox Automatic Aliases Plugin - * - * The plugin ensures that a required set of aliases for a domain are existent. - * Required aliases are: - * postmaster@domain.tld - * abuse@domain.tld - * Optional aliases are: - * webmaster@domain.tld - * hostmaster@domains.tld - * - * See https://github.com/idefix6/vimbadmin-mailbox-automatic-aliases - * - * Add the following lines to configs/application.ini: - * vimbadmin_plugins.MailboxAutomaticAliases.disabled = false - * vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "postmaster" - * vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "abuse" - * vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "hostmaster" - * vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "webmaster" - * - * @package ViMbAdmin - * @subpackage Plugins - */ - class ViMbAdminPlugin_MailboxAutomaticAliases extends ViMbAdmin_Plugin implements OSS_Plugin_Observer { - private $defaultAliases; - private $defaultMapping; - - public function __construct(OSS_Controller_Action $controller) { - parent::__construct($controller, get_class() ); - - // read config parameters - $this->defaultAliases = isset( $controller->getOptions()['vimbadmin_plugins']['MailboxAutomaticAliases']['defaultAliases'] ) - ? $controller->getOptions()['vimbadmin_plugins']['MailboxAutomaticAliases']['defaultAliases'] : []; - - $this->defaultMapping = isset( $controller->getOptions()['vimbadmin_plugins']['MailboxAutomaticAliases']['defaultMapping'] ) - ? $controller->getOptions()['vimbadmin_plugins']['MailboxAutomaticAliases']['defaultMapping'] : []; - } - - /** - * Is called after a mailbox is created. It ensures that required aliases are created. - * - * @param $controller - * @param $options - */ - public function mailbox_add_addPostflush($controller, $options) { - // get domain - $domainId = $controller->getDomain()->getId(); - $domain = $controller->getDomain()->getDomain(); - - // get mailbox - $mailbox = $controller->getMailbox()->getUsername(); - - // check if domain has enforced aliases or do we have to create them? - if($this->defaultAliases) { - foreach($this->defaultAliases as $key => $item) { - $aliasList = $controller->getD2EM()->getRepository( "\\Entities\\Alias" )->filterForAliasList( $item . '@' . $domain, $controller->getAdmin(), $domainId, true ); - if(count($aliasList) == 0) { - $alias = new \Entities\Alias(); - $alias->setAddress($item.'@'.$domain); - if($this->defaultMapping[$item]) { - $alias->setGoto($this->defaultMapping[$item]); - } else { - $alias->setGoto($mailbox); - } - $alias->setDomain($controller->getDomain()); - $alias->setActive(1); - $alias->setCreated(new \DateTime()); - $controller->getD2EM()->persist($alias); - // Increase alias count for domain - $controller->getDomain()->increaseAliasCount(); - $controller->getD2EM()->flush(); - $controller->addMessage( sprintf(_("Auto-Created alias %s -> %s."), $alias->getAddress(), $alias->getGoto())); - } - } - } - } - - /** - * Check if the aliases is allowed to be removed. If not return false, else return true. - * Check if the alias is used as administrative destination and if yes deny removal. - * - * @param $controller - * @param $options - * @return bool - */ - public function alias_delete_preRemove($controller, $options) { - // get alias that should be deleted - $alias = $controller->getAlias()->getAddress(); - $domain = $controller->getDomain()->getDomain(); - - // check if the alias to delete is not enforced by the plugin - if($this->defaultAliases) { - foreach($this->defaultAliases as $key => $item) { - if($alias == $item.'@'.$domain) { - // not allowed to delete, show error message and stop delete - $controller->addMessage( sprintf( _("Alias %s is required and cannot be deleted. See RFC2142"), $alias), OSS_Message::ERROR); - return false; - } - } - } - - # @TODO Add check for administrative alias removal - return true; - } - - public function alias_toggleActive_preToggle($controller, $options) { - // get alias that should be deleted - $alias = $controller->getAlias()->getAddress(); - $domain = $controller->getDomain()->getDomain(); - - if($options['active'] == 'true') { - // we have to check if it is allowed to disable this alias - if($this->defaultAliases) { - foreach($this->defaultAliases as $key => $item) { - if($alias == $item.'@'.$domain) { - // not allowed to delete, show error message and stop delete - print( sprintf( _("Alias %s is required and cannot be disabled. See RFC2142"), $alias)); - exit(0); - } - } - } - - } - # @TODO Check here also for administrative destination alias - return true; - } - - /** - * Is called after an alias is created. It ensures that required aliases are created. - * - * @param $controller - * @param $options - */ - public function alias_add_addPreflush($controller, $options) { - // get domain - $domainId = $controller->getDomain()->getId(); - $domain = $controller->getDomain()->getDomain(); - - // get alias - $aliasGoto = $controller->getalias()->getGoto(); - - // check if domain has enforced aliases or do we have to create them? - if($this->defaultAliases) { - foreach($this->defaultAliases as $key => $item) { - $aliasList = $controller->getD2EM()->getRepository( "\\Entities\\Alias" )->filterForAliasList( $item . '@' . $domain, $controller->getAdmin(), $domainId, true ); - if(count($aliasList) == 0) { - $alias = new \Entities\Alias(); - $alias->setAddress($item.'@'.$domain); - if($this->defaultMapping[$item]) { - $alias->setGoto($this->defaultMapping[$item]); - }else{ - $alias->setGoto($aliasGoto); - } - $alias->setDomain($controller->getDomain()); - $alias->setActive(1); - $alias->setCreated(new \DateTime()); - $controller->getD2EM()->persist($alias); - // Increase alias count for domain - $controller->getDomain()->increaseAliasCount(); - $controller->getD2EM()->flush(); - $controller->addMessage( sprintf(_("Auto-Created alias %s -> %s."), $alias->getAddress(), $alias->getGoto())); - } - } - } - } - - /** - * Check that a mailbox cannot be removed if used as administrative destination mailbox - * - */ - public function mailbox_purge_preRemove($controller, $options) { - /* check if mailbox is not an administrative mailbox */ - $mailbox = $controller->getMailbox()->getUserName(); - - if($this->defaultMapping) { - foreach($this->defaultMapping as $key => $item) { - if($mailbox == $item) { - // not allowed to delete, show error message and stop delete - $controller->addMessage( sprintf( _("Mailbox %s is defined as automatic alias to fullfill RFC2142. If you want to remove it, update you application.ini file first. Check key vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping."), $mailbox), OSS_Message::ERROR); - //print( sprintf( _("Mailbox %s is defined as automatic alias to fullfill RFC2142. If you want to remove it, update you application.ini file first. Check key vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping."), $mailbox)); - return false; - } - } - } - return true; - } - - } \ No newline at end of file +. + * + * Open Source Solutions Limited T/A Open Solutions + * 147 Stepaside Park, Stepaside, Dublin 18, Ireland. + * Barry O'Donovan + * + * @copyright Copyright (c) 2014 Matthias Fechner + * @copyright Copyright (c) 2022 Daniel Rudolf + * @license http://opensource.org/licenses/gpl-3.0.html GNU General Public License, version 3 (GPLv3) + * @author Matthias Fechner + * @author Daniel Rudolf + */ + +/** + * The Mailbox Automatic Aliases Plugin + * + * The plugin ensures that a required set of aliases for a domain are existent. + * + * Required aliases: + * postmaster@domain.tld + * abuse@domain.tld + * Optional aliases: + * webmaster@domain.tld + * hostmaster@domains.tld + * + * Add the following lines to configs/application.ini: + * vimbadmin_plugins.MailboxAutomaticAliases.disabled = false + * vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "postmaster" + * vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "abuse" + * vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "hostmaster" + * vimbadmin_plugins.MailboxAutomaticAliases.defaultAliases[] = "webmaster" + * + * Automatic aliases are created when a new mailbox or alias is created. They + * either use a configured defaultMapping, or the just created mailbox or alias + * as goto address. See configs/application.ini: + * vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.postmaster = "@other.tld" + * vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.abuse = "postmaster" + * vimbadmin_plugins.MailboxAutomaticAliases.defaultMapping.* = "root@domain.tld" + * + * @package ViMbAdmin + * @subpackage Plugins + */ + +class ViMbAdminPlugin_MailboxAutomaticAliases extends ViMbAdmin_Plugin implements OSS_Plugin_Observer +{ + + /** @var string */ + private $defaultAliases; + + /** @var array */ + private $defaultMapping; + + public function __construct( OSS_Controller_Action $controller ) + { + parent::__construct( $controller, get_class() ); + + // read config parameters + $this->defaultAliases = isset( $controller->getOptions()['vimbadmin_plugins']['MailboxAutomaticAliases']['defaultAliases'] ) + ? $controller->getOptions()['vimbadmin_plugins']['MailboxAutomaticAliases']['defaultAliases'] : []; + + $this->defaultMapping = isset( $controller->getOptions()['vimbadmin_plugins']['MailboxAutomaticAliases']['defaultMapping'] ) + ? $controller->getOptions()['vimbadmin_plugins']['MailboxAutomaticAliases']['defaultMapping'] : []; + } + + /** + * Create automatic aliases after a mailbox was created + * + * @param MailboxController $controller + * @param array|null $options + */ + public function mailbox_add_addPostflush( MailboxController $controller, $options ) + { + $domain = $controller->getDomain()->getDomain(); + $mailbox = $controller->getMailbox()->getUsername(); + + if( $this->defaultAliases ) { + // no default aliases are required to exist if the whole domain is aliased + if( $this->hasActiveDomainAlias( $controller ) ) { + return; + } + + foreach( $this->defaultAliases as $item ) { + // automatic alias exists + if( $this->getAlias( $controller, $item . '@' . $domain ) !== null ) { + continue; + } + + // create automatic alias + $alias = $this->createAutomaticAlias( $controller, $item, $mailbox ); + + $message = _( 'Auto-created alias %s -> %s.' ); + $controller->addMessage( sprintf( $message, $alias->getAddress(), $alias->getGoto() ) ); + } + } + } + + /** + * Checks whether a mailbox is an automatic alias' goto mailbox and + * prevents its deletion + * + * @param MailboxController $controller + * @param array|null $options + * @return bool + */ + public function mailbox_purge_preRemove( MailboxController $controller, $options ) + { + $domain = $controller->getDomain()->getDomain(); + $mailbox = $controller->getMailbox()->getUserName(); + + if( $this->defaultAliases ) { + // no default aliases are required to exist if the whole domain is aliased + if( $this->hasActiveDomainAlias( $controller ) ) { + return true; + } + + foreach( $this->defaultAliases as $item ) { + // prevent deletion of an automatic alias' goto mailbox + $alias = $this->getAlias( $controller, $item . '@' . $domain ); + if( $alias !== null && $alias['goto'] === $mailbox ) { + $message = _( 'Mailbox %s is used to fullfill automatic alias %s. ' + . 'See RFC2142. ' + . 'If you want to delete it, update the alias to use a different goto address first.' ); + $controller->addMessage( sprintf( $message, $mailbox, $alias['address'] ), OSS_Message::ERROR ); + return false; + } + } + } + + return true; + } + + /** + * Checks whether a mailbox is an automatic alias' goto mailbox and + * prevents disabling the mailbox + * + * @param MailboxController $controller + * @param array|null $options + * @return bool + */ + public function mailbox_toggleActive_preToggle( MailboxController $controller, $options ) + { + $domain = $controller->getDomain()->getDomain(); + $mailbox = $controller->getMailbox()->getUserName(); + + if( $options['active'] && $this->defaultAliases ) { + // no default aliases are required to exist if the whole domain is aliased + if( $this->hasActiveDomainAlias( $controller ) ) { + return true; + } + + foreach( $this->defaultAliases as $item ) { + // prevent toggling an automatic alias' goto mailbox off + $alias = $this->getAlias( $controller, $item . '@' . $domain ); + if( $alias !== null && $alias['goto'] === $mailbox ) { + $message = _( 'Mailbox %s is used to fullfill automatic alias %s. ' + . 'See RFC2142. ' + . 'If you want to disable it, update the alias to use a different goto address first.' ); + printf( $message, $mailbox, $alias['address'] ); + exit(0); + } + } + } + + return true; + } + + /** + * Create automatic aliases after an alias was created + * + * @param AliasController $controller + * @param array|null $options + */ + public function alias_add_addPostflush( AliasController $controller, $options ) + { + $domain = $controller->getDomain()->getDomain(); + $aliasAddress = $controller->getAlias()->getAddress(); + + if( $this->defaultAliases ) { + // no default aliases are required to exist if the whole domain is aliased + if( $this->hasActiveDomainAlias( $controller ) ) { + return; + } + + // create automatic aliases, if required + foreach( $this->defaultAliases as $item ) { + // automatic alias exists + if( $this->getAlias( $controller, $item . '@' . $domain ) !== null ) { + continue; + } + + // create automatic alias + $alias = $this->createAutomaticAlias( $controller, $item, $aliasAddress ); + + $message = _( 'Auto-created alias %s -> %s.' ); + $controller->addMessage( sprintf( $message, $alias->getAddress(), $alias->getGoto() ) ); + } + } + } + + /** + * Checks whether an alias is an automatic alias or an automatic alias' + * goto alias, and prevents its deletion + * + * @param AliasController $controller + * @param array|null $options + * @return bool + */ + public function alias_delete_preRemove( AliasController $controller, $options ) + { + $domain = $controller->getDomain()->getDomain(); + $aliasAddress = $controller->getAlias()->getAddress(); + + if( $this->defaultAliases ) { + // if we're about to delete a domain alias, ensure that distinct automatic aliases exist + if( '@' . $domain === $aliasAddress ) { + foreach( $this->defaultAliases as $item ) { + $alias = $this->getAlias( $controller, $item . '@' . $domain ); + if( $alias === null || !$alias['active'] ) { + $message = _( 'Alias %s is used to fullfill automatic alias %s. ' + . 'See RFC2142. ' + . 'If you want to delete it, create a distinct alias first.' ); + $controller->addMessage( sprintf( $message, $aliasAddress, $item . '@' . $domain ), OSS_Message::ERROR ); + return false; + } + } + + return true; + } + + // no default aliases are required to exist if the whole domain is aliased + if( $this->hasActiveDomainAlias( $controller ) ) { + return true; + } + + foreach( $this->defaultAliases as $item ) { + // prevent deletion of an automatic alias + if( $item . '@' . $domain === $aliasAddress ) { + $message = _( 'Alias %s is required and cannot be deleted. ' + . 'See RFC2142.'); + $controller->addMessage( sprintf( $message, $aliasAddress ), OSS_Message::ERROR ); + return false; + } + + // prevent deletion of an automatic alias' goto alias + $alias = $this->getAlias( $controller, $item . '@' . $domain ); + if( $alias !== null && $alias['goto'] === $aliasAddress ) { + $message = _( 'Alias %s is used to fullfill automatic alias %s. ' + . 'See RFC2142. ' + . 'If you want to delete it, update the alias to use a different goto address first.' ); + $controller->addMessage( sprintf( $message, $aliasAddress, $alias['address'] ), OSS_Message::ERROR ); + return false; + } + } + } + + return true; + } + + /** + * Checks whether an alias is an automatic alias or an automatic alias' + * goto alias, and prevents disabling the alias + * + * @param AliasController $controller + * @param array|null $options + * @return bool + */ + public function alias_toggleActive_preToggle( AliasController $controller, $options ) + { + $domain = $controller->getDomain()->getDomain(); + $aliasAddress = $controller->getAlias()->getAddress(); + + if( $options['active'] && $this->defaultAliases ) { + // if we're about to disable a domain alias, ensure that distinct automatic aliases exist + if( '@' . $domain === $aliasAddress ) { + foreach( $this->defaultAliases as $item ) { + $alias = $this->getAlias( $controller, $item . '@' . $domain ); + if( $alias === null || !$alias['active'] ) { + $message = _( 'Alias %s is used to fullfill automatic alias %s. ' + . 'See RFC2142. ' + . 'If you want to disable it, create a distinct alias first.' ); + printf( $message, $aliasAddress, $item . '@' . $domain ); + exit(0); + } + } + + return true; + } + + // no default aliases are required to exist if the whole domain is aliased + if( $this->hasActiveDomainAlias( $controller ) ) { + return true; + } + + foreach( $this->defaultAliases as $item ) { + // prevent toggling an automatic alias off + if( $item . '@' . $domain === $aliasAddress ) { + $message = _( 'Alias %s is required and cannot be disabled. ' + . 'See RFC2142'); + printf( $message, $aliasAddress ); + exit(0); + } + + // prevent toggling an automatic alias' goto alias off + $alias = $this->getAlias( $controller, $item . '@' . $domain ); + if( $alias !== null && $alias['goto'] === $aliasAddress ) { + $message = _( 'Alias %s is used to fullfill automatic alias %s. ' + . 'See RFC2142. ' + . 'If you want to disable it, update the alias to use a different goto address first.' ); + printf( $message, $aliasAddress, $alias['address'] ); + exit(0); + } + } + } + + return true; + } + + /** + * Returns an alias' entity + * + * @param OSS_Controller_Action $controller + * @param string $alias + * @return array|null + */ + private function getAlias( OSS_Controller_Action $controller, $alias ) + { + $aliasList = $controller->getD2EM()->getRepository( "\\Entities\\Alias" ) + ->filterForAliasList( $alias, $controller->getAdmin(), $controller->getDomain()->getId(), true ); + return $aliasList ? reset($aliasList) : null; + } + + /** + * Checks whether a domain alias exists and is active + * + * @param OSS_Controller_Action $controller + * @return bool + */ + private function hasActiveDomainAlias( OSS_Controller_Action $controller ) + { + $alias = $this->getAlias( $controller, '@' . $controller->getDomain()->getDomain() ); + return $alias !== null && $alias['active']; + } + + /** + * Creates a new alias + * + * @param OSS_Controller_Action $controller + * @param string $item + * @param string $goto + * @return \Entities\Alias|null + */ + private function createAutomaticAlias( OSS_Controller_Action $controller, $item, $goto ) + { + $domain = $controller->getDomain()->getDomain(); + + if( isset( $this->defaultMapping[$item] ) ) { + $goto = $this->defaultMapping[$item]; + } elseif( isset( $this->defaultMapping['*'] ) ) { + $goto = $this->defaultMapping['*']; + } + + if( strpos( $goto, '@' ) === false ) { + $goto = $goto . '@' . $domain; + } elseif( $goto[0] === '@' ) { + $goto = $item . $goto; + } + + $alias = new \Entities\Alias(); + $alias->setAddress( $item . '@' . $domain ); + $alias->setGoto( $goto ); + $alias->setDomain( $controller->getDomain() ); + $alias->setActive( 1 ); + $alias->setCreated( new \DateTime() ); + $controller->getD2EM()->persist( $alias ); + + $controller->getDomain()->increaseAliasCount(); + $controller->getD2EM()->flush(); + + return $alias; + } + +}