diff --git a/plugins/proxy-auth/README.md b/plugins/proxy-auth/README.md index 1f25b7fcc..c9db401e6 100644 --- a/plugins/proxy-auth/README.md +++ b/plugins/proxy-auth/README.md @@ -19,10 +19,12 @@ The following steps are require in SnappyMail: - Master User is dependent on Dovecot config (see below) - Master User Password is dependent on Dovecot config (see below) - Header Name is dependent on authentication solution. This is the header containing the name of currently logged in user. In case of Authelia, this is "Remote-User". - - Check Proxy: Since this plugin partially bypasses authentication, it is important to only allow this access from well-defined hosts. It is highly recommended to activate this option! - - When checking for reverse proxy, it is required to set the IP filter to either an IP address or a subnet. - Automatic Login: Automatically logs in the user of user header is present (see below) +> **Security note** +> +> This plugin trusts the configured request header as proof of identity. Anyone who can reach the SnappyMail container directly and set that header can log in as any user. You **must** ensure that SnappyMail is only reachable through your reverse proxy / SSO chain (e.g. via the docker network, a firewall, or by binding SnappyMail to a non-public interface) and that the upstream proxy strips any client-supplied value of the header before forwarding. The plugin itself does no source-IP validation — earlier versions had a `check_proxy` option, but it inspected the forwarded client IP (the end user's IP) and therefore did not actually verify that the request came from the proxy. It has been removed; gate access at the network layer instead. + This concludes the setup of SnappyMail. ### Dovecot diff --git a/plugins/proxy-auth/index.php b/plugins/proxy-auth/index.php index d13a37c40..36db5d9c2 100644 --- a/plugins/proxy-auth/index.php +++ b/plugins/proxy-auth/index.php @@ -6,7 +6,7 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin NAME = 'Proxy Auth', AUTHOR = 'Philipp', URL = 'https://www.mundhenk.org/', - VERSION = '0.6', + VERSION = '0.7', RELEASE = '2026-05-17', REQUIRED = '2.36.1', CATEGORY = 'Login', @@ -21,26 +21,6 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin $this->addHook('login.credentials', 'MapEmailAddress'); } - /* by https://gist.github.com/tott/7684443 */ - /** - * Check if a given ip is in a network - * @param string $ip IP to check in IPV4 format eg. 127.0.0.1 - * @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed - * @return boolean true if the ip is in this range / false if not. - */ - private function ip_in_range( $ip, $range ) { - if ( strpos( $range, '/' ) == false ) { - $range .= '/32'; - } - // $range is in IP/CIDR format eg 127.0.0.1/24 - list( $range, $netmask ) = explode( '/', $range, 2 ); - $range_decimal = ip2long( $range ); - $ip_decimal = ip2long( $ip ); - $wildcard_decimal = pow( 2, ( 32 - $netmask ) ) - 1; - $netmask_decimal = ~ $wildcard_decimal; - return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) ); - } - public function MapEmailAddress(string &$sEmail, string &$sImapUser, string &$sPassword, string &$sSmtpUser) { $oActions = \RainLoop\Api::Actions(); @@ -80,59 +60,19 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin $sMsg = "Remote User: " . $sRemoteUser; $oLogger->Write($sMsg, $sLevel, $sPrefix); - $sProxyIP = $this->Config()->getDecrypted('plugin', 'proxy_ip', ''); - $sMsg = "ProxyIP: " . $sProxyIP; - $oLogger->Write($sMsg, $sLevel, $sPrefix); + /* create master user login from remote user header and settings */ + $sEmail = $sRemoteUser . $sMasterSeparator . $sMasterUser; + $sPassword = new \SnappyMail\SensitiveString(\trim($this->Config()->getDecrypted('plugin', 'master_password', ''))); - $sProxyCheck = (bool) $this->Config()->Get('plugin', 'check_proxy', false); - $sClientIPs = $this->Manager()->Actions()->Http()->GetClientIP(true); - - /* make sure that remote user is only set by authorized proxy to avoid security risks */ - if ($sProxyCheck) { - $sProxyRequest = false; - $sMsg = "checking client IPs: " . $sClientIPs; - $oLogger->Write($sMsg, $sLevel, $sPrefix); - - $sClientIPs = explode(", ", $sClientIPs); - if (is_array($sClientIPs)) { - foreach ($sClientIPs as &$sIP) { - $sMsg = "checking client IP: " . $sIP; - $oLogger->Write($sMsg, $sLevel, $sPrefix); - - if ($this->ip_in_range($sIP, $sProxyIP)) { - $sProxyRequest = true; - } - } - } else { - $sMsg = "checking client IP: " . $sClientIPs; - $oLogger->Write($sMsg, $sLevel, $sPrefix); - - if ($this->ip_in_range($sClientIPs, $sProxyIP)) { - $sProxyRequest = true; - } - } - } else { - $sProxyRequest = true; + try + { + static::$login = true; + $oAccount = $oActions->LoginProcess($sEmail, $sPassword); } - - if ($sProxyRequest) { - /* create master user login from remote user header and settings */ - $sEmail = $sRemoteUser . $sMasterSeparator . $sMasterUser; - $sPassword = new \SnappyMail\SensitiveString(\trim($this->Config()->getDecrypted('plugin', 'master_password', ''))); - - try - { - static::$login = true; - $oAccount = $oActions->LoginProcess($sEmail, $sPassword); - } - catch (\Throwable $oException) - { - $oLogger = $oActions->Logger(); - $oLogger && $oLogger->WriteException($oException); - } - - \MailSo\Base\Http::Location('./'); - return true; + catch (\Throwable $oException) + { + $oLogger = $oActions->Logger(); + $oLogger && $oLogger->WriteException($oException); } \MailSo\Base\Http::Location('./'); @@ -188,17 +128,6 @@ class ProxyAuthPlugin extends \RainLoop\Plugins\AbstractPlugin ->SetDescription('Name of header containing username') ->SetDefaultValue('Remote-User') ->SetEncrypted(), - \RainLoop\Plugins\Property::NewInstance('check_proxy') - ->SetLabel('Check Proxy') - ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) - ->SetDescription('Activates check if proxy is connecting') - ->SetDefaultValue(true), - \RainLoop\Plugins\Property::NewInstance('proxy_ip') - ->SetLabel('Proxy IPNet') - ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT) - ->SetDescription('IP or Subnet of proxy, auth header will only be accepted from this address') - ->SetDefaultValue('10.1.0.0/24') - ->SetEncrypted(), \RainLoop\Plugins\Property::NewInstance('auto_login') ->SetAllowedInJs(true) ->SetLabel('Activate automatic login')