mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-03 06:27:02 +03:00
* Cleanup OpenPgpImportPopupView code * update polish translation * small fix * Added Import S/MIME certificate popup And much better handling of the sign and encrypt options * bugfix: store in Passphrases * Resolve #1448 * pre-verify S/MIME opaque signed messages so we have a body to view * Fix timestampToString() for future dates * Move php8.php to /app/libraries/polyfill/ * Improved Settings handling to prevent bugs in outer code * Changed AbstractProvider::IsActive() to be abstract * Example for #1449 * bugfix: previous IsActive() commit * OpenSSL required due to S/MIME * Use get_debug_type() instead of gettype() * update polish translation * Make all Enumerations classes abstract * Added search functionality in Admin -> Config And removed the unused ['capa']['quota'] * Cleanup Quota handling * OPEN_PGP should be OPENPGP as it is one word * Improve Capa handling * Resolve #1451 * Bugfix TypeError: b64Encode(...).match(...) is null * Small StorageType change * Bugfix: mailvelope editor failed * Bugfix: undefined getMailvelopePrivateKeyFor() * Bugfix: MIME parser RegExp didn't escape `boundary` which caused issues * Return detailed info on PgpImportKey * Show GnuPG verify error * Sort PGP keys by email and id * Sort S/MIME certificates on emailAddress else validTo * S/MIME import from signature use `BEGIN PKCS7` * Optionally use existing private key to generate S/MIME certificate * Chaned some error_log() to MailSo Logger() * Force reload of S/MIME certificates list on import * Make better use of SnappyMail\SensitiveString * Fix view PGP key button * Mask all POST data that has a key which contains `pass` * v2.35.1 * Resolve #1455 * Improved GnuPG error handling * Update pt/pt-PT translation * update Polish translation * Drop support for gnupg pecl extension as it fails with "no passphrase" issues * Resolve #1456 * Resolve #1458 * v2.35.2 * fix changelog * Resolve #1461 * Update pt/pt-PT translation * compact-composer plugin v1.0.0 * Resolve #1462 * Fix decrypt error message * `new Error()` to `Error()` * Resolve #1463 * Show url for #1466 * Simplify SignMe/Remember me code * Simplify language Notifications * Bugfix: SetPassword expects \SnappyMail\SensitiveString * https://github.com/the-djmaze/snappymail/issues/1450#issuecomment-1972147950 * improve: fire the 'squire2-toolbar' event after more props are added * improve: add dark theme support and use 'button' element as menu trigger for consistent styling * fix: use compact template in non-destructive way (do not replace the PopupsCompose template if a different wysiwyg is used) * Update admin.json * Update user.json * CSS rainloopErrorTip location * Improved error handling on PGP and S/MIME decrypt * KnockoutJS remove unused `beforeRemove` * KnockoutJS drop unused `as` * KnockoutJS simplify renderMode because only 1 option is used * KnoutJS cleanup templating.js a bit * KnockoutJS drop unused `bindingRewriteValidators` * KnockoutJS drop the twoWayBindings code * KnockoutJS simplify virtualElements binding check * KnockoutJS simplify applyBindingsToNodeInternal * KnockoutJS use Array.isArray * KnockoutJS drop alias `textinput` for `textInput` * KnockoutJS scramble `createChildContext` * KnockoutJS scramble `controlsDescendantBindings` * KnockoutJS scramble `exportDependencies` * KnockoutJS drop unused `throttleEvaluation` * KnockoutJS drop unused `valueAllowUnset` * KnockoutJS drop unused `templateNodes` * KnockoutJS drop unused `optionsCaption` * KnockoutJS drop unused `dontLimitMoves` * KnockoutJS drop unused `uniqueName` * KnockoutJS drop IE leftovers * KnockoutJS drop unused `preprocess` * KnockoutJS drop unused "disposeWhenNodeIsRemoved" and "disposeWhen" * KnockoutJS don't scramble exportDependencies. controlsDescendantBindings, createChildContext * KnockoutJS drop unused `$parentContext` and `$parents` * KnockoutJS drop unused `$rawData` * Knockoutjs built latest * KnockoutJS drop unused template options `nodes`, `if`, `ifnot` * KnockoutJS use more Array.isArray * KnockoutJS cleanup code a bit * KnockoutJS primitiveTypes can just be checked with Object() * KnockoutJS rebuilt * Verify S/MIME signed automatically and log Exception * Automatically verify PGP and S/MIME signed messages * `new Error` to `Error` * By default throw AccountNotAllowed as confused in #1478 * GPG use pinentries for decrypt, sign and export * Better GPG error handling * GPG show error on view/export * OpenPGP fix handling of importing keys * Make "verify signatures automatically" optional, as it requires more IMAP fetching * S/MIME don't post identity key and certificate, just fetch from server * Show error to old browsers, instead of crashing * Automatically verify S/MIME decrypted signed message --------- Co-authored-by: the-djmaze <> Co-authored-by: tinola <tinola@poczta.onet.pl> Co-authored-by: Maarten <3752035+the-djmaze@users.noreply.github.com> Co-authored-by: lmperfis <joint.striker@gmail.com> Co-authored-by: Sergey Mosin <sergey@srgdev.com> Co-authored-by: hguilbert <51283484+hguilbert@users.noreply.github.com>
309 lines
9.2 KiB
PHP
309 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace RainLoop\Model;
|
|
|
|
use RainLoop\Utils;
|
|
use RainLoop\Notifications;
|
|
use RainLoop\Exceptions\ClientException;
|
|
use SnappyMail\SensitiveString;
|
|
|
|
abstract class Account implements \JsonSerializable
|
|
{
|
|
private string $sName = '';
|
|
|
|
private string $sEmail = '';
|
|
|
|
private string $sLogin = '';
|
|
|
|
private ?SensitiveString $oPassword = null;
|
|
|
|
private string $sSmtpLogin = '';
|
|
|
|
private ?SensitiveString $sSmtpPassword = null;
|
|
|
|
private string $sProxyAuthUser = '';
|
|
|
|
private ?SensitiveString $sProxyAuthPassword = null;
|
|
|
|
private Domain $oDomain;
|
|
|
|
public function Email() : string
|
|
{
|
|
return $this->sEmail;
|
|
}
|
|
|
|
public function Name() : string
|
|
{
|
|
return $this->sName;
|
|
}
|
|
|
|
public function IncLogin() : string
|
|
{
|
|
return $this->oDomain->IncShortLogin()
|
|
? \MailSo\Base\Utils::GetAccountNameFromEmail($this->sLogin)
|
|
: $this->sLogin;
|
|
}
|
|
|
|
public function IncPassword() : string
|
|
{
|
|
return $this->oPassword ? $this->oPassword->getValue() : '';
|
|
}
|
|
|
|
public function OutLogin() : string
|
|
{
|
|
$sSmtpLogin = $this->sSmtpLogin ?: $this->sLogin;
|
|
return $this->oDomain->OutShortLogin() ? \MailSo\Base\Utils::GetAccountNameFromEmail($sSmtpLogin) : $sSmtpLogin;
|
|
}
|
|
|
|
public function Domain() : Domain
|
|
{
|
|
return $this->oDomain;
|
|
}
|
|
|
|
public function Hash() : string
|
|
{
|
|
return \sha1(\implode(APP_SALT, [
|
|
$this->sEmail,
|
|
$this->sLogin,
|
|
// \json_encode($this->Domain()),
|
|
// $this->oPassword
|
|
]));
|
|
}
|
|
|
|
public function SetPassword(SensitiveString $oPassword) : void
|
|
{
|
|
$this->oPassword = $oPassword;
|
|
}
|
|
|
|
public function SetSmtpPassword(
|
|
#[\SensitiveParameter]
|
|
string $sPassword
|
|
) : void
|
|
{
|
|
$this->sSmtpPassword = new SensitiveString($sPassword);
|
|
}
|
|
|
|
public function SetProxyAuthUser(string $sProxyAuthUser) : void
|
|
{
|
|
$this->sProxyAuthUser = $sProxyAuthUser;
|
|
}
|
|
|
|
public function SetProxyAuthPassword(
|
|
#[\SensitiveParameter]
|
|
string $sProxyAuthPassword
|
|
) : void
|
|
{
|
|
$this->sProxyAuthPassword = new SensitiveString($sProxyAuthPassword);
|
|
}
|
|
|
|
#[\ReturnTypeWillChange]
|
|
public function jsonSerialize()
|
|
{
|
|
$result = [
|
|
'email' => $this->sEmail,
|
|
'login' => $this->sLogin,
|
|
'pass' => $this->IncPassword(),
|
|
'name' => $this->sName
|
|
];
|
|
if ($this->sSmtpLogin && $this->sSmtpPassword) {
|
|
$result['smtp'] = [
|
|
'user' => $this->sSmtpLogin,
|
|
'pass' => $this->sSmtpPassword->getValue()
|
|
];
|
|
}
|
|
if ($this->sProxyAuthUser && $this->sProxyAuthPassword) {
|
|
$result['proxy'] = [
|
|
'user' => $this->sProxyAuthUser,
|
|
'pass' => $this->sProxyAuthPassword->getValue()
|
|
];
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
public static function NewInstanceFromCredentials(\RainLoop\Actions $oActions,
|
|
string $sEmail, string $sLogin,
|
|
SensitiveString $oPassword,
|
|
bool $bThrowException = false): ?self
|
|
{
|
|
$oAccount = null;
|
|
if ($sEmail && $sLogin && \strlen($oPassword)) {
|
|
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
|
if ($oDomain) {
|
|
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
|
|
$oAccount = new static;
|
|
|
|
$oAccount->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
|
$oAccount->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin);
|
|
$oAccount->SetPassword($oPassword);
|
|
$oAccount->oDomain = $oDomain;
|
|
|
|
$oActions->Plugins()->RunHook('filter.account', array($oAccount));
|
|
|
|
if ($bThrowException && !$oAccount) {
|
|
throw new ClientException(Notifications::AccountFilterError);
|
|
}
|
|
} else if ($bThrowException) {
|
|
throw new ClientException(Notifications::AccountNotAllowed);
|
|
}
|
|
} else if ($bThrowException) {
|
|
throw new ClientException(Notifications::DomainNotAllowed);
|
|
}
|
|
}
|
|
|
|
return $oAccount;
|
|
}
|
|
|
|
/**
|
|
* Converts old numeric array to new associative array
|
|
*/
|
|
public static function convertArray(array $aAccount) : array
|
|
{
|
|
if (isset($aAccount['email'])) {
|
|
return $aAccount;
|
|
}
|
|
if (empty($aAccount[0]) || 'account' != $aAccount[0] || 7 > \count($aAccount)) {
|
|
return [];
|
|
}
|
|
$aResult = [
|
|
'email' => $aAccount[1] ?: '',
|
|
'login' => $aAccount[2] ?: '',
|
|
'pass' => $aAccount[3] ?: ''
|
|
];
|
|
if ($aAccount[5] && $aAccount[6]) {
|
|
$aResult['proxy'] = [
|
|
'user' => $aAccount[5],
|
|
'pass' => $aAccount[6]
|
|
];
|
|
}
|
|
return $aResult;
|
|
}
|
|
|
|
public static function NewInstanceFromTokenArray(
|
|
\RainLoop\Actions $oActions,
|
|
array $aAccountHash,
|
|
bool $bThrowExceptionOnFalse = false): ?self
|
|
{
|
|
$oAccount = null;
|
|
$aAccountHash = static::convertArray($aAccountHash);
|
|
if (!empty($aAccountHash['email']) && 3 <= \count($aAccountHash)) {
|
|
$oAccount = static::NewInstanceFromCredentials(
|
|
$oActions,
|
|
$aAccountHash['email'],
|
|
$aAccountHash['login'],
|
|
new SensitiveString($aAccountHash['pass']),
|
|
$bThrowExceptionOnFalse
|
|
);
|
|
if ($oAccount) {
|
|
if (isset($aAccountHash['name'])) {
|
|
$oAccount->sName = $aAccountHash['name'];
|
|
}
|
|
// init smtp user/password
|
|
if (isset($aAccountHash['smtp'])) {
|
|
$oAccount->sSmtpLogin = $aAccountHash['smtp']['user'];
|
|
$oAccount->SetSmtpPassword($aAccountHash['smtp']['pass']);
|
|
}
|
|
// init proxy user/password
|
|
if (isset($aAccountHash['proxy'])) {
|
|
$oAccount->sProxyAuthUser = $aAccountHash['proxy']['user'];
|
|
$oAccount->SetProxyAuthPassword($aAccountHash['proxy']['pass']);
|
|
}
|
|
}
|
|
}
|
|
return $oAccount;
|
|
}
|
|
|
|
public function ImapConnectAndLogin(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Imap\ImapClient $oImapClient, \RainLoop\Config\Application $oConfig) : bool
|
|
{
|
|
$oSettings = $this->Domain()->ImapSettings();
|
|
$oSettings->timeout = \max($oSettings->timeout, (int) $oConfig->Get('imap', 'timeout', $oSettings->timeout));
|
|
$oSettings->Login = $this->IncLogin();
|
|
|
|
$oSettings->expunge_all_on_delete |= !!$oConfig->Get('imap', 'use_expunge_all_on_delete', false);
|
|
$oSettings->fast_simple_search = !(!$oSettings->fast_simple_search || !$oConfig->Get('imap', 'message_list_fast_simple_search', true));
|
|
$oSettings->fetch_new_messages = !(!$oSettings->fetch_new_messages || !$oConfig->Get('imap', 'fetch_new_messages', true));
|
|
$oSettings->force_select |= !!$oConfig->Get('imap', 'use_force_selection', false);
|
|
$oSettings->message_all_headers |= !!$oConfig->Get('imap', 'message_all_headers', false);
|
|
$oSettings->search_filter = $oSettings->search_filter ?: \trim($oConfig->Get('imap', 'message_list_permanent_filter', ''));
|
|
// $oSettings->body_text_limit = \min($oSettings->body_text_limit, (int) $oConfig->Get('imap', 'body_text_limit', 50));
|
|
// $oSettings->thread_limit = \min($oSettings->thread_limit, (int) $oConfig->Get('imap', 'large_thread_limit', 50));
|
|
|
|
$oImapClient->Settings = $oSettings;
|
|
|
|
$oPlugins->RunHook('imap.before-connect', array($this, $oImapClient, $oSettings));
|
|
$oImapClient->Connect($oSettings);
|
|
$oPlugins->RunHook('imap.after-connect', array($this, $oImapClient, $oSettings));
|
|
|
|
$oSettings->Password = $this->oPassword;
|
|
return $this->netClientLogin($oImapClient, $oPlugins);
|
|
}
|
|
|
|
public function SmtpConnectAndLogin(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Smtp\SmtpClient $oSmtpClient) : bool
|
|
{
|
|
$oSettings = $this->Domain()->SmtpSettings();
|
|
$oSettings->Login = $this->OutLogin();
|
|
$oSettings->Ehlo = \MailSo\Smtp\SmtpClient::EhloHelper();
|
|
|
|
$oSmtpClient->Settings = $oSettings;
|
|
|
|
$oPlugins->RunHook('smtp.before-connect', array($this, $oSmtpClient, $oSettings));
|
|
if ($oSettings->usePhpMail) {
|
|
$oSettings->useAuth = false;
|
|
return true;
|
|
}
|
|
$oSmtpClient->Connect($oSettings);
|
|
$oPlugins->RunHook('smtp.after-connect', array($this, $oSmtpClient, $oSettings));
|
|
/*
|
|
if ($this->oDomain->OutAskCredentials() && !($this->sSmtpPassword && $this->sSmtpLogin)) {
|
|
throw new RequireCredentialsException
|
|
}
|
|
*/
|
|
$oSettings->Password = $this->sSmtpPassword ?: $this->oPassword;
|
|
return $this->netClientLogin($oSmtpClient, $oPlugins);
|
|
}
|
|
|
|
public function SieveConnectAndLogin(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Sieve\SieveClient $oSieveClient, \RainLoop\Config\Application $oConfig)
|
|
{
|
|
$oSettings = $this->Domain()->SieveSettings();
|
|
$oSettings->Login = $this->IncLogin();
|
|
|
|
$oSieveClient->Settings = $oSettings;
|
|
|
|
$oPlugins->RunHook('sieve.before-connect', array($this, $oSieveClient, $oSettings));
|
|
$oSieveClient->Connect($oSettings);
|
|
$oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $oSettings));
|
|
|
|
$oSettings->Password = $this->oPassword;
|
|
return $this->netClientLogin($oSieveClient, $oPlugins);
|
|
}
|
|
|
|
private function netClientLogin(\MailSo\Net\NetClient $oClient, \RainLoop\Plugins\Manager $oPlugins) : bool
|
|
{
|
|
/*
|
|
$encrypted = !empty(\stream_get_meta_data($oClient->ConnectionResource())['crypto']);
|
|
[crypto] => Array(
|
|
[protocol] => TLSv1.3
|
|
[cipher_name] => TLS_AES_256_GCM_SHA384
|
|
[cipher_bits] => 256
|
|
[cipher_version] => TLSv1.3
|
|
)
|
|
*/
|
|
$oSettings = $oClient->Settings;
|
|
$oSettings->ProxyAuthUser = $this->sProxyAuthUser;
|
|
$oSettings->ProxyAuthPassword = $this->sProxyAuthPassword;
|
|
|
|
$client_name = \strtolower($oClient->getLogName());
|
|
|
|
$oPlugins->RunHook("{$client_name}.before-login", array($this, $oClient, $oSettings));
|
|
$bResult = !$oSettings->useAuth || $oClient->Login($oSettings);
|
|
$oPlugins->RunHook("{$client_name}.after-login", array($this, $oClient, $bResult, $oSettings));
|
|
return $bResult;
|
|
}
|
|
|
|
/*
|
|
// Stores settings in AdditionalAccount else MainAccount
|
|
public function settingsLocal() : \RainLoop\Settings
|
|
{
|
|
return \RainLoop\Api::Actions()->SettingsProvider(true)->Load($this);
|
|
}
|
|
*/
|
|
}
|