diff --git a/plugins/login-override/LICENSE b/plugins/login-override/LICENSE new file mode 100644 index 000000000..4a4ca8d81 --- /dev/null +++ b/plugins/login-override/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/login-override/index.php b/plugins/login-override/index.php new file mode 100644 index 000000000..91ef58e1d --- /dev/null +++ b/plugins/login-override/index.php @@ -0,0 +1,63 @@ +addHook('imap.before-login', 'MapImapCredentials'); + $this->addHook('smtp.before-login', 'MapSmtpCredentials'); + } + + public function MapImapCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Imap\ImapClient $oSmtpClient, \MailSo\Imap\Settings $oSettings) + { + static::MapCredentials($oAccount, $oSettings, $this->Config()->getDecrypted('plugin', 'imap_mapping', '')); + } + + public function MapSmtpCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Smtp\SmtpClient $oSmtpClient, \MailSo\Smtp\Settings $oSettings) + { + static::MapCredentials($oAccount, $oSettings, $this->Config()->getDecrypted('plugin', 'smtp_mapping', '')); + } + + private static function MapCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Net\ConnectSettings $oSettings, string $mapping) + { + $sEmail = $oAccount->Email(); + $aList = \preg_split('/\\R/', $mapping); + foreach ($aList as $line) { + $line = \explode(':', $line, 3); + if (!empty($line[0]) && $line[0] === $sEmail) { + if (!empty($line[1])) { + $oSettings->Login = $line[1]; + } + if (!empty($line[2])) { + $oSettings->Password = $line[2]; + } + } + } + } + + protected function configMapping() : array + { + return array( + \RainLoop\Plugins\Property::NewInstance('imap_mapping') + ->SetLabel('IMAP mapping') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT) + ->SetDescription('Each line as email:loginname:password, loginname or password may be empty to use default') + ->SetDefaultValue('user@example.com:user1:password1') + ->SetEncrypted(), + \RainLoop\Plugins\Property::NewInstance('smtp_mapping') + ->SetLabel('SMTP mapping') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT) + ->SetDescription('Each line as email:loginname:password, loginname or password may be empty to use default') + ->SetDefaultValue('user@example.com:user1:password1') + ->SetEncrypted() + ); + } +} diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminExtensions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminExtensions.php index f142e7f22..6c1bd4c48 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminExtensions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminExtensions.php @@ -122,12 +122,16 @@ trait AdminExtensions $aSettings = (array) $this->GetActionParam('settings', []); foreach ($aMap as $oItem) { $sKey = $oItem->Name(); - $sValue = $aSettings[$sKey] ?? $oConfig->Get('plugin', $sKey); - if (PluginPropertyType::PASSWORD !== $oItem->Type() || static::APP_DUMMY !== $sValue) { - $oItem->SetValue($sValue); - $mResultValue = $oItem->Value(); - if (null !== $mResultValue) { - $oConfig->Set('plugin', $sKey, $mResultValue); + $mValue = $aSettings[$sKey] ?? $oConfig->Get('plugin', $sKey); + if (PluginPropertyType::PASSWORD !== $oItem->Type() || static::APP_DUMMY !== $mValue) { + $oItem->SetValue($mValue); + $mValue = $oItem->Value(); + if (null !== $mValue) { + if ($oItem->encrypted) { + $oConfig->setEncrypted('plugin', $sKey, $mValue); + } else { + $oConfig->Set('plugin', $sKey, $mValue); + } } } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/AbstractConfig.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/AbstractConfig.php index bda1035bc..0e4ed05c2 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/AbstractConfig.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/AbstractConfig.php @@ -4,30 +4,15 @@ namespace RainLoop\Config; abstract class AbstractConfig implements \JsonSerializable { - /** - * @var string - */ - private $sFile; + private string $sFile; - /** - * @var string - */ - private $sAdditionalFile = ''; + private string $sAdditionalFile = ''; - /** - * @var array - */ - private $aData; + private array $aData; - /** - * @var bool - */ - private $bUseApcCache; + private bool $bUseApcCache; - /** - * @var string - */ - private $sFileHeader; + private string $sFileHeader; public function __construct(string $sFileName, string $sFileHeader = '', string $sAdditionalFileName = '') { @@ -78,8 +63,7 @@ abstract class AbstractConfig implements \JsonSerializable */ public function Set(string $sSectionKey, string $sParamKey, $mParamValue) : void { - if (isset($this->aData[$sSectionKey][$sParamKey][0])) - { + if (isset($this->aData[$sSectionKey][$sParamKey][0])) { if (!\is_scalar($mParamValue)) { $mParamValue = null; } @@ -99,13 +83,26 @@ abstract class AbstractConfig implements \JsonSerializable $this->aData[$sSectionKey][$sParamKey][0] = (string) $mParamValue; break; } - } - else if ('custom' === $sSectionKey) - { + } else if ('custom' === $sSectionKey) { $this->aData[$sSectionKey][$sParamKey] = array((string) $mParamValue); } } + public function getDecrypted(string $sSection, string $sName, $mDefault = null) + { + // $salt = \basename($this->sFile) not possible due to RainLoop\Plugins\Property + return isset($this->aData[$sSection][$sName][0]) + ? \SnappyMail\Crypt::DecryptFromJSON($this->aData[$sSection][$sName][0], \APP_SALT) + : $mDefault; + } + + public function setEncrypted(string $sSectionKey, string $sParamKey, $mParamValue) : void + { + // $salt = \basename($this->sFile) not possible due to RainLoop\Plugins\Property + $mParamValue = \SnappyMail\Crypt::EncryptToJSON($mParamValue, \APP_SALT); + $this->Set($sSectionKey, $sParamKey, $mParamValue); + } + private function cacheKey() : string { return 'config:'.\sha1($this->sFile).':'.\sha1($this->sAdditionalFile).':'; @@ -113,24 +110,20 @@ abstract class AbstractConfig implements \JsonSerializable private function loadDataFromCache() : bool { - if ($this->bUseApcCache) - { + if ($this->bUseApcCache) { $iMTime = \filemtime($this->sFile); $iMTime = \is_int($iMTime) && 0 < $iMTime ? $iMTime : 0; $iATime = $this->sAdditionalFile ? \filemtime($this->sAdditionalFile) : 0; $iATime = \is_int($iATime) && 0 < $iATime ? $iATime : 0; - if (0 < $iMTime) - { + if (0 < $iMTime) { $sKey = $this->cacheKey(); $sTimeHash = \apcu_fetch($sKey.'time'); - if ($sTimeHash && $sTimeHash === \md5($iMTime.'/'.$iATime)) - { + if ($sTimeHash && $sTimeHash === \md5($iMTime.'/'.$iATime)) { $aFetchData = \apcu_fetch($sKey.'data'); - if (\is_array($aFetchData)) - { + if (\is_array($aFetchData)) { $this->aData = $aFetchData; return true; } @@ -143,16 +136,14 @@ abstract class AbstractConfig implements \JsonSerializable private function storeDataToCache() : bool { - if ($this->bUseApcCache) - { + if ($this->bUseApcCache) { $iMTime = \filemtime($this->sFile); $iMTime = \is_int($iMTime) && 0 < $iMTime ? $iMTime : 0; $iATime = $this->sAdditionalFile ? \filemtime($this->sAdditionalFile) : 0; $iATime = \is_int($iATime) && 0 < $iATime ? $iATime : 0; - if (0 < $iMTime) - { + if (0 < $iMTime) { $sKey = $this->cacheKey(); \apcu_store($sKey.'time', \md5($iMTime.'/'.$iATime)); @@ -167,8 +158,7 @@ abstract class AbstractConfig implements \JsonSerializable private function clearCache() : bool { - if ($this->bUseApcCache) - { + if ($this->bUseApcCache) { $sKey = $this->cacheKey(); \apcu_delete($sKey.'time'); @@ -186,10 +176,8 @@ abstract class AbstractConfig implements \JsonSerializable if (!\file_exists($sFile) && \str_ends_with($sFile, '.json')) { $sFile = \str_replace('.json', '.ini', $sFile); } - if (\file_exists($sFile) && \is_readable($sFile)) - { - if ($this->loadDataFromCache()) - { + if (\file_exists($sFile) && \is_readable($sFile)) { + if ($this->loadDataFromCache()) { return true; } @@ -198,14 +186,10 @@ abstract class AbstractConfig implements \JsonSerializable } else { $aData = \parse_ini_file($sFile, true); } - if ($aData && \count($aData)) - { - foreach ($aData as $sSectionKey => $aSectionValue) - { - if (\is_array($aSectionValue)) - { - foreach ($aSectionValue as $sParamKey => $mParamValue) - { + if ($aData && \count($aData)) { + foreach ($aData as $sSectionKey => $aSectionValue) { + if (\is_array($aSectionValue)) { + foreach ($aSectionValue as $sParamKey => $mParamValue) { $this->Set($sSectionKey, $sParamKey, $mParamValue); } } @@ -213,21 +197,16 @@ abstract class AbstractConfig implements \JsonSerializable unset($aData); - if (\file_exists($this->sAdditionalFile) && \is_readable($this->sAdditionalFile)) - { + if (\file_exists($this->sAdditionalFile) && \is_readable($this->sAdditionalFile)) { if (\str_ends_with($this->sAdditionalFile, '.json')) { $aData = \json_decode(\file_get_contents($this->sAdditionalFile), true); } else { $aData = \parse_ini_file($this->sAdditionalFile, true); } - if ($aData && \count($aData)) - { - foreach ($aData as $sSectionKey => $aSectionValue) - { - if (\is_array($aSectionValue)) - { - foreach ($aSectionValue as $sParamKey => $mParamValue) - { + if ($aData && \count($aData)) { + foreach ($aData as $sSectionKey => $aSectionValue) { + if (\is_array($aSectionValue)) { + foreach ($aSectionValue as $sParamKey => $mParamValue) { $this->Set($sSectionKey, $sParamKey, $mParamValue); } } @@ -248,8 +227,7 @@ abstract class AbstractConfig implements \JsonSerializable public function Save() : bool { - if (\file_exists($this->sFile) && !\is_writable($this->sFile)) - { + if (\file_exists($this->sFile) && !\is_writable($this->sFile)) { return false; } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Property.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Property.php index 054bc50bf..62e2bdb12 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Property.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Plugins/Property.php @@ -30,6 +30,8 @@ class Property implements \JsonSerializable private string $sPlaceholder = ''; + public bool $encrypted = false; + function __construct(string $sName) { $this->sName = $sName; @@ -43,7 +45,6 @@ class Property implements \JsonSerializable public function SetType(int $iType) : self { $this->iType = (int) $iType; - return $this; } @@ -93,42 +94,42 @@ class Property implements \JsonSerializable } else { $this->mDefaultValue = $mDefaultValue; } - return $this; } public function SetOptions(array $aOptions) : self { $this->aOptions = $aOptions; - return $this; } public function SetPlaceholder(string $sPlaceholder) : self { $this->sPlaceholder = $sPlaceholder; - return $this; } public function SetLabel(string $sLabel) : self { $this->sLabel = $sLabel; - return $this; } public function SetDescription(string $sDesc) : self { $this->sDesc = $sDesc; - return $this; } public function SetAllowedInJs(bool $bValue = true) : self { $this->bAllowedInJs = $bValue; + return $this; + } + public function SetEncrypted(bool $bValue = true) : self + { + $this->encrypted = $bValue; return $this; } @@ -188,7 +189,7 @@ class Property implements \JsonSerializable { return array( '@Object' => 'Object/PluginProperty', - 'value' => $this->mValue, + 'value' => $this->encrypted ? \SnappyMail\Crypt::DecryptFromJSON($this->mValue, \APP_SALT) : $this->mValue, 'placeholder' => $this->sPlaceholder, 'name' => $this->sName, 'type' => $this->iType,