mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-10 06:58:27 +03:00
Improved user authentication system by supporting OpenSSL aes-256-cbc-hmac-sha1 encryption
This commit is contained in:
parent
99c72ad9e6
commit
8ca043b6e4
16 changed files with 468 additions and 450 deletions
|
|
@ -18,30 +18,22 @@ namespace MailSo\Base;
|
|||
class Crypt
|
||||
{
|
||||
|
||||
public static function Encrypt(string $sString, string $sKey, string $sCipher = '') : string
|
||||
public static function Encrypt(string $sString, string $sKey) : string
|
||||
{
|
||||
if (!\strlen($sString)) {
|
||||
return '';
|
||||
}
|
||||
if ($sCipher && \is_callable('openssl_encrypt')) {
|
||||
$iv = \str_pad('', \openssl_cipher_iv_length($sCipher), \sha1($sKey));
|
||||
return \openssl_encrypt($sString, $sCipher, $sKey, OPENSSL_RAW_DATA, $iv);
|
||||
}
|
||||
if (\is_callable('xxtea_encrypt')) {
|
||||
return \xxtea_encrypt($sString, $sKey);
|
||||
}
|
||||
return Xxtea::encrypt($sString, $sKey);
|
||||
}
|
||||
|
||||
public static function Decrypt(string $sString, string $sKey, string $sCipher = '') : string
|
||||
public static function Decrypt(string $sString, string $sKey) : string
|
||||
{
|
||||
if (!\strlen($sString)) {
|
||||
return '';
|
||||
}
|
||||
if ($sCipher && \is_callable('openssl_encrypt')) {
|
||||
$iv = \str_pad('', \openssl_cipher_iv_length($sCipher), \sha1($sKey));
|
||||
return \openssl_decrypt($sString, $sCipher, $sKey, OPENSSL_RAW_DATA, $iv);
|
||||
}
|
||||
if (\is_callable('xxtea_decrypt')) {
|
||||
return \xxtea_decrypt($sString, $sKey);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,20 @@ class Actions
|
|||
use Actions\Themes;
|
||||
|
||||
const AUTH_MAILTO_TOKEN_KEY = 'smmailtoauth';
|
||||
|
||||
/**
|
||||
* This 30 days cookie contains decrypt data,
|
||||
* to decrypt a \RainLoop\Model\Account which is stored at
|
||||
* /_data_/.../storage/DOMAIN/LOCAL/.sign_me/*
|
||||
* Gets refreshed on each login
|
||||
*/
|
||||
const AUTH_SIGN_ME_TOKEN_KEY = 'smremember';
|
||||
const AUTH_SPEC_TOKEN_KEY = 'smspecauth';
|
||||
|
||||
/**
|
||||
* This session cookie contains a \RainLoop\Model\Account
|
||||
*/
|
||||
const AUTH_SPEC_TOKEN_KEY = 'smaccount';
|
||||
|
||||
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'smspeclogout';
|
||||
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'smspeclogoutcmk';
|
||||
|
||||
|
|
@ -130,7 +142,6 @@ class Actions
|
|||
$this->oAddressBookProvider = null;
|
||||
$this->oSuggestionsProvider = null;
|
||||
|
||||
$this->sSpecAuthToken = '';
|
||||
$this->bIsJson = false;
|
||||
|
||||
$oConfig = $this->Config();
|
||||
|
|
@ -309,7 +320,6 @@ class Actions
|
|||
|
||||
if (false !== \strpos($sLine, '{imap:') || false !== \strpos($sLine, '{smtp:')) {
|
||||
if (!$oAccount) {
|
||||
$this->getAuthAccountHash();
|
||||
$oAccount = $this->getAccountFromToken(false);
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +373,6 @@ class Actions
|
|||
|
||||
if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine)) {
|
||||
if (!$oAccount) {
|
||||
$this->getAuthAccountHash();
|
||||
$oAccount = $this->getAccountFromToken(false);
|
||||
}
|
||||
|
||||
|
|
@ -871,7 +880,8 @@ class Actions
|
|||
$aResult['IncLogin'] = $oAccount->IncLogin();
|
||||
$aResult['OutLogin'] = $oAccount->OutLogin();
|
||||
$aResult['AccountHash'] = $oAccount->Hash();
|
||||
$aResult['AccountSignMe'] = $oAccount->SignMe();
|
||||
// $aResult['AccountSignMe'] = $oAccount->SignMe();
|
||||
$aResult['AccountSignMe'] = false;
|
||||
$aResult['ContactsIsAllowed'] = $oAddressBookProvider->IsActive();
|
||||
$aResult['ContactsSyncIsAllowed'] = (bool)$oConfig->Get('contacts', 'allow_sync', false);
|
||||
$aResult['ContactsSyncInterval'] = (int)$oConfig->Get('contacts', 'sync_interval', 20);
|
||||
|
|
@ -1066,17 +1076,6 @@ class Actions
|
|||
}
|
||||
}
|
||||
|
||||
public function AuthToken(Model\Account $oAccount): void
|
||||
{
|
||||
$this->SetAuthToken($oAccount);
|
||||
|
||||
$aAccounts = $this->GetAccounts($oAccount);
|
||||
if (isset($aAccounts[$oAccount->Email()])) {
|
||||
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
|
||||
$this->SetAccounts($oAccount, $aAccounts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
|
|
@ -1202,7 +1201,7 @@ class Actions
|
|||
{
|
||||
$iResult = 0;
|
||||
|
||||
$oAccount = $this->GetAccountFromCustomToken($sHash, false);
|
||||
$oAccount = $this->GetAccountFromCustomToken($sHash);
|
||||
if ($oAccount) {
|
||||
try {
|
||||
$oMailClient = new \MailSo\Mail\MailClient();
|
||||
|
|
|
|||
|
|
@ -73,9 +73,9 @@ trait Accounts
|
|||
|
||||
$oAccountToChange = null;
|
||||
if ($oAccount->Email() === $sEmailToDelete && !empty($aAccounts[$sParentEmail])) {
|
||||
$oAccountToChange = $this->GetAccountFromCustomToken($aAccounts[$sParentEmail], false, false);
|
||||
$oAccountToChange = $this->GetAccountFromCustomToken($aAccounts[$sParentEmail], false);
|
||||
if ($oAccountToChange) {
|
||||
$this->AuthToken($oAccountToChange);
|
||||
$this->SetAuthToken($oAccountToChange);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -707,15 +707,12 @@ trait Messages
|
|||
$this->Plugins()->RunHook('filter.smtp-hidden-rcpt', array($oAccount, $oMessage, &$aHiddenRcpt));
|
||||
}
|
||||
|
||||
$bUsePhpMail = $oAccount->Domain()->OutUsePhpMail();
|
||||
|
||||
$oSmtpClient = new \MailSo\Smtp\SmtpClient();
|
||||
$oSmtpClient->SetLogger($this->Logger());
|
||||
$oSmtpClient->SetTimeOuts(10, (int) \RainLoop\Api::Config()->Get('labs', 'smtp_timeout', 60));
|
||||
|
||||
$oAccount->OutConnectAndLoginHelper(
|
||||
$this->Plugins(), $oSmtpClient, $this->Config(), $bUsePhpMail
|
||||
);
|
||||
$bUsePhpMail = false;
|
||||
$oAccount->OutConnectAndLoginHelper($this->Plugins(), $oSmtpClient, $this->Config(), $bUsePhpMail);
|
||||
|
||||
if ($bUsePhpMail)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,24 +23,16 @@ trait User
|
|||
$sEmail = \MailSo\Base\Utils::Trim($this->GetActionParam('Email', ''));
|
||||
$sPassword = $this->GetActionParam('Password', '');
|
||||
$sLanguage = $this->GetActionParam('Language', '');
|
||||
$bSignMe = '1' === (string) $this->GetActionParam('SignMe', '0');
|
||||
$bSignMe = !empty($this->GetActionParam('SignMe', 0));
|
||||
|
||||
$oAccount = null;
|
||||
|
||||
$this->Logger()->AddSecret($sPassword);
|
||||
|
||||
try
|
||||
{
|
||||
$oAccount = $this->LoginProcess($sEmail, $sPassword,
|
||||
$bSignMe ? $this->generateSignMeToken($sEmail) : '');
|
||||
$this->Plugins()->RunHook('login.success', array($oAccount));
|
||||
}
|
||||
catch (ClientException $oException)
|
||||
{
|
||||
throw $oException;
|
||||
}
|
||||
$oAccount = $this->LoginProcess($sEmail, $sPassword, $bSignMe);
|
||||
$this->Plugins()->RunHook('login.success', array($oAccount));
|
||||
|
||||
$this->AuthToken($oAccount);
|
||||
$this->SetAuthToken($oAccount);
|
||||
|
||||
if ($oAccount && \strlen($sLanguage))
|
||||
{
|
||||
|
|
@ -211,13 +203,9 @@ trait User
|
|||
$oAccount = $this->getAccountFromToken(false);
|
||||
if ($oAccount)
|
||||
{
|
||||
if ($oAccount->SignMe())
|
||||
{
|
||||
$this->ClearSignMeData($oAccount);
|
||||
}
|
||||
|
||||
if (!$oAccount->IsAdditionalAccount())
|
||||
{
|
||||
$this->ClearSignMeData();
|
||||
Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
|
||||
}
|
||||
}
|
||||
|
|
@ -515,11 +503,6 @@ trait User
|
|||
$this->SettingsProvider()->Save($oAccount, $oSettings) : false);
|
||||
}
|
||||
|
||||
private function generateSignMeToken(string $sEmail) : string
|
||||
{
|
||||
return \MailSo\Base\Utils::Sha1Rand(APP_SALT.$sEmail);
|
||||
}
|
||||
|
||||
private function getMimeFileByHash(\RainLoop\Model\Account $oAccount, string $sHash) : array
|
||||
{
|
||||
$aValues = $this->getDecodedRawKeyValue($sHash);
|
||||
|
|
|
|||
|
|
@ -10,21 +10,20 @@ use \RainLoop\Exceptions\ClientException;
|
|||
|
||||
trait UserAuth
|
||||
{
|
||||
/*
|
||||
const AUTH_SIGN_ME_TOKEN_KEY = 'smremember';
|
||||
const AUTH_SPEC_TOKEN_KEY = 'smspecauth';
|
||||
const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'smspeclogout';
|
||||
const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'smspeclogoutcmk';
|
||||
*/
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sSpecAuthToken;
|
||||
private $sSpecAuthToken = null;
|
||||
|
||||
/**
|
||||
* Or use 'aes-256-xts' ?
|
||||
*/
|
||||
private static $sCipher = 'aes-256-cbc-hmac-sha1';
|
||||
|
||||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function LoginProcess(string &$sEmail, string &$sPassword, string $sSignMeToken = ''): Account
|
||||
public function LoginProcess(string &$sEmail, string &$sPassword, bool $bSignMe = false): Account
|
||||
{
|
||||
$sInputEmail = $sEmail;
|
||||
|
||||
|
|
@ -117,7 +116,7 @@ trait UserAuth
|
|||
$oAccount = null;
|
||||
$sClientCert = \trim($this->Config()->Get('ssl', 'client_cert', ''));
|
||||
try {
|
||||
$oAccount = $this->LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken, $sClientCert, true);
|
||||
$oAccount = Account::NewInstanceByLogin($this, $sEmail, $sLogin, $sPassword, $sClientCert, true);
|
||||
|
||||
if (!$oAccount) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
|
|
@ -130,6 +129,7 @@ trait UserAuth
|
|||
|
||||
try {
|
||||
$this->CheckMailConnection($oAccount, true);
|
||||
$bSignMe && $this->SetSignMeToken($oAccount);
|
||||
} catch (\Throwable $oException) {
|
||||
$this->loginErrorDelay();
|
||||
|
||||
|
|
@ -142,107 +142,27 @@ trait UserAuth
|
|||
/**
|
||||
* @throws \RainLoop\Exceptions\ClientException
|
||||
*/
|
||||
public function GetAccountFromCustomToken(string $sToken, bool $bThrowExceptionOnFalse = true, bool $bValidateShortToken = true, bool $bQ = false): ?Account
|
||||
public function GetAccountFromCustomToken(string $sToken, bool $bValidateShortToken = true, bool $bQ = false, bool $bThrowExceptionOnFalse = false): ?Account
|
||||
{
|
||||
$oResult = null;
|
||||
if (!empty($sToken)) {
|
||||
$aAccountHash = $bQ ? Utils::DecodeKeyValuesQ($sToken) : Utils::DecodeKeyValues($sToken);
|
||||
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && // simple token validation
|
||||
8 <= \count($aAccountHash) && // length checking
|
||||
!empty($aAccountHash[7]) && // does short token exist
|
||||
(!$bValidateShortToken || Utils::GetShortToken() === $aAccountHash[7] || // check short token if needed
|
||||
(isset($aAccountHash[10]) && 0 < $aAccountHash[10] && \time() < $aAccountHash[10]))
|
||||
) {
|
||||
$oAccount = $this->LoginProvide($aAccountHash[1], $aAccountHash[2], $aAccountHash[3],
|
||||
empty($aAccountHash[5]) ? '' : $aAccountHash[5], empty($aAccountHash[11]) ? '' : $aAccountHash[11], $bThrowExceptionOnFalse);
|
||||
|
||||
if ($oAccount) {
|
||||
// init proxy user/password
|
||||
if (!empty($aAccountHash[8]) && !empty($aAccountHash[9])) {
|
||||
$oAccount->SetProxyAuthUser($aAccountHash[8]);
|
||||
$oAccount->SetProxyAuthPassword($aAccountHash[9]);
|
||||
}
|
||||
|
||||
$this->Logger()->AddSecret($oAccount->Password());
|
||||
$this->Logger()->AddSecret($oAccount->ProxyAuthPassword());
|
||||
|
||||
$oAccount->SetParentEmail($aAccountHash[6]);
|
||||
$oResult = $oAccount;
|
||||
}
|
||||
} else if ($bThrowExceptionOnFalse) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
}
|
||||
$aToken = $bQ ? Utils::DecodeKeyValuesQ($sToken) : Utils::DecodeKeyValues($sToken);
|
||||
return Account::NewInstanceFromTokenArray(
|
||||
$this,
|
||||
$bQ ? Utils::DecodeKeyValuesQ($sToken) : Utils::DecodeKeyValues($sToken),
|
||||
$bThrowExceptionOnFalse
|
||||
);
|
||||
}
|
||||
|
||||
if ($bThrowExceptionOnFalse && !$oResult) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
if ($bThrowExceptionOnFalse) {
|
||||
throw new ClientException(\RainLoop\Notifications::AuthError);
|
||||
}
|
||||
|
||||
return $oResult;
|
||||
}
|
||||
|
||||
protected function LoginProvide(string $sEmail, string $sLogin, string $sPassword, string $sSignMeToken = '', string $sClientCert = '', bool $bThrowProvideException = false): ?Account
|
||||
{
|
||||
$oAccount = null;
|
||||
if (\strlen($sEmail) && \strlen($sLogin) && \strlen($sPassword)) {
|
||||
$oDomain = $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
||||
if ($oDomain) {
|
||||
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
|
||||
$oAccount = new Account($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken, '', '', $sClientCert);
|
||||
$this->Plugins()->RunHook('filter.account', array($oAccount));
|
||||
|
||||
if ($bThrowProvideException && !$oAccount) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
}
|
||||
} else if ($bThrowProvideException) {
|
||||
throw new ClientException(Notifications::AccountNotAllowed);
|
||||
}
|
||||
} else if ($bThrowProvideException) {
|
||||
throw new ClientException(Notifications::DomainNotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
|
||||
public function GetSpecAuthTokenCookie(): string
|
||||
{
|
||||
return Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, '');
|
||||
}
|
||||
|
||||
// rlspecauth / AuthAccountHash
|
||||
public function getAuthAccountHash() : string
|
||||
{
|
||||
if ('' === $this->sSpecAuthToken && !\strlen($this->GetSpecAuthLogoutTokenWithDeletion())) {
|
||||
$sAuthAccountHash = $this->GetSpecAuthTokenCookie() ?: $this->GetSpecAuthToken();
|
||||
if (empty($sAuthAccountHash)) {
|
||||
$oAccount = $this->GetAccountFromSignMeToken();
|
||||
if ($oAccount) try
|
||||
{
|
||||
$this->CheckMailConnection($oAccount);
|
||||
$this->AuthToken($oAccount);
|
||||
$sAuthAccountHash = $this->GetSpecAuthToken();
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$oException = null;
|
||||
$this->ClearSignMeData($oAccount);
|
||||
}
|
||||
}
|
||||
$this->SetSpecAuthToken($sAuthAccountHash);
|
||||
}
|
||||
return $this->GetSpecAuthToken();
|
||||
}
|
||||
|
||||
private function getLocalAuthToken(): string
|
||||
{
|
||||
$sToken = $this->GetSpecAuthToken();
|
||||
return !empty($sToken) && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : '';
|
||||
return null;
|
||||
}
|
||||
|
||||
public function GetShortLifeSpecAuthToken(int $iLife = 60): string
|
||||
{
|
||||
$aAccountHash = Utils::DecodeKeyValues($this->getLocalAuthToken());
|
||||
$aAccountHash = Utils::DecodeKeyValues($this->sSpecAuthToken);
|
||||
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0]) {
|
||||
$aAccountHash[10] = \time() + $iLife;
|
||||
return Utils::EncodeKeyValues($aAccountHash);
|
||||
|
|
@ -256,126 +176,141 @@ trait UserAuth
|
|||
*/
|
||||
public function getAccountFromToken(bool $bThrowExceptionOnFalse = true): ?Account
|
||||
{
|
||||
return $this->GetAccountFromCustomToken($this->getLocalAuthToken(), $bThrowExceptionOnFalse, true, true);
|
||||
}
|
||||
|
||||
public function GetSpecAuthToken(): string
|
||||
{
|
||||
return $this->sSpecAuthToken;
|
||||
}
|
||||
|
||||
public function SetSpecAuthToken(string $sSpecAuthToken): self
|
||||
{
|
||||
$this->sSpecAuthToken = $sSpecAuthToken;
|
||||
|
||||
return $this;
|
||||
if (!\is_string($this->sSpecAuthToken)) {
|
||||
$this->sSpecAuthToken = '';
|
||||
if (isset($_COOKIE[self::AUTH_SPEC_LOGOUT_TOKEN_KEY])) {
|
||||
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY);
|
||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||
// Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
|
||||
} else {
|
||||
$sAuthAccountHash = Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY);
|
||||
if (empty($sAuthAccountHash)) {
|
||||
$oAccount = $this->GetAccountFromSignMeToken();
|
||||
if ($oAccount) {
|
||||
$this->SetAuthToken($oAccount);
|
||||
$sAuthAccountHash = $this->sSpecAuthToken;
|
||||
}
|
||||
}
|
||||
$this->sSpecAuthToken = $sAuthAccountHash ?: '';
|
||||
}
|
||||
}
|
||||
return $this->GetAccountFromCustomToken($this->sSpecAuthToken, true, true, $bThrowExceptionOnFalse);
|
||||
}
|
||||
|
||||
public function SetAuthToken(Account $oAccount): void
|
||||
{
|
||||
$sSpecAuthToken = '_' . $oAccount->GetAuthTokenQ();
|
||||
$sSpecAuthToken = $oAccount->GetAuthTokenQ();
|
||||
|
||||
$this->SetSpecAuthToken($sSpecAuthToken);
|
||||
$this->sSpecAuthToken = $sSpecAuthToken;
|
||||
Utils::SetCookie(self::AUTH_SPEC_TOKEN_KEY, $sSpecAuthToken);
|
||||
|
||||
if ($oAccount->SignMe()) {
|
||||
$this->SetSignMeToken($oAccount);
|
||||
if (isset($aAccounts[$oAccount->Email()])) {
|
||||
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
|
||||
$this->SetAccounts($oAccount, $aAccounts);
|
||||
}
|
||||
}
|
||||
|
||||
private static function GetSignMeToken(): ?array
|
||||
{
|
||||
$sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY, '');
|
||||
return empty($sSignMeToken) ? null : Utils::DecodeKeyValuesQ($sSignMeToken);
|
||||
$sSignMeToken = Utils::GetCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||
if (empty($sSignMeToken)) {
|
||||
return null;
|
||||
}
|
||||
$aResult = \SnappyMail\Crypt::DecryptCookie($sSignMeToken);
|
||||
return \is_array($aResult) ? $aResult : null;
|
||||
}
|
||||
|
||||
private static function SetSignMeTokenCookie(array $aData): void
|
||||
{
|
||||
Utils::SetCookie(
|
||||
self::AUTH_SIGN_ME_TOKEN_KEY,
|
||||
\SnappyMail\Crypt::EncryptCookie($aData),
|
||||
\time() + 3600 * 24 * 30 // 30 days
|
||||
);
|
||||
}
|
||||
|
||||
private function SetSignMeToken(Account $oAccount): void
|
||||
{
|
||||
$this->ClearSignMeData($oAccount);
|
||||
$this->ClearSignMeData();
|
||||
|
||||
$uuid = \SnappyMail\UUID::generate();
|
||||
$salt = \sha1(\random_bytes(16));
|
||||
Utils::SetCookie(self::AUTH_SIGN_ME_TOKEN_KEY,
|
||||
Utils::EncodeKeyValuesQ(array(
|
||||
$data = \SnappyMail\Crypt::EncryptRaw($oAccount);
|
||||
|
||||
if ($data['xxtea']) {
|
||||
static::SetSignMeTokenCookie(array(
|
||||
'e' => $oAccount->Email(),
|
||||
'u' => $uuid,
|
||||
's' => $salt
|
||||
)),
|
||||
\time() + 3600 * 24 * 30); // 30 days
|
||||
's' => \base64_encode($data[1])
|
||||
));
|
||||
} else {
|
||||
static::SetSignMeTokenCookie(array(
|
||||
'e' => $oAccount->Email(),
|
||||
'u' => $uuid,
|
||||
'i' => \base64_encode($data[1])
|
||||
));
|
||||
}
|
||||
|
||||
$this->StorageProvider()->Put($oAccount,
|
||||
$this->StorageProvider()->Put(
|
||||
$oAccount,
|
||||
StorageType::SIGN_ME,
|
||||
$uuid,
|
||||
Utils::EncryptString($oAccount->GetAuthToken(), $salt)
|
||||
$data[2]
|
||||
);
|
||||
}
|
||||
|
||||
public function GetAccountFromSignMeToken(): ?Account
|
||||
{
|
||||
$oAccount = null;
|
||||
|
||||
$aTokenData = static::GetSignMeToken();
|
||||
if (!empty($aTokenData)) {
|
||||
if (!empty($aTokenData['e']) && !empty($aTokenData['u']) && !empty($aTokenData['s']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
|
||||
$sAuthToken = $this->StorageProvider()->Get($aTokenData['e'],
|
||||
$oAccount = null;
|
||||
if (!empty($aTokenData['e']) && !empty($aTokenData['u']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
|
||||
$sAuthToken = $this->StorageProvider()->Get(
|
||||
$aTokenData['e'],
|
||||
StorageType::SIGN_ME,
|
||||
$aTokenData['u']
|
||||
);
|
||||
if (empty($sAuthToken)) {
|
||||
return null;
|
||||
}
|
||||
$sAuthToken = Utils::DecryptString($sAuthToken, $aTokenData['s']);
|
||||
if (!empty($sAuthToken)) {
|
||||
$oAccount = $this->GetAccountFromCustomToken($sAuthToken, false, false, true);
|
||||
if (!empty($aTokenData['s'])) {
|
||||
$aAccountHash = \SnappyMail\Crypt::XxteaDecrypt($sAuthToken, \base64_decode($aTokenData['s']));
|
||||
} else if (!empty($aTokenData['i'])) {
|
||||
$aAccountHash = \SnappyMail\Crypt::OpenSSLDecrypt($sAuthToken, \base64_decode($aTokenData['i']));
|
||||
}
|
||||
} else if (!empty($aTokenData['e']) && !empty($aTokenData['t'])) {
|
||||
// This is old, see https://github.com/the-djmaze/snappymail/issues/126
|
||||
$sTokenSettings = $this->StorageProvider()->Get($aTokenData['e'],
|
||||
StorageType::CONFIG,
|
||||
'sign_me'
|
||||
);
|
||||
if (!empty($sTokenSettings)) {
|
||||
$aSignMeData = Utils::DecodeKeyValuesQ($sTokenSettings);
|
||||
if (!empty($aSignMeData['AuthToken']) &&
|
||||
!empty($aSignMeData['SignMeToken']) &&
|
||||
$aSignMeData['SignMeToken'] === $aTokenData['t']) {
|
||||
$oAccount = $this->GetAccountFromCustomToken($aSignMeData['AuthToken'], false, false, true);
|
||||
if (!empty($aAccountHash) && \is_array($aAccountHash)) {
|
||||
$oAccount = Account::NewInstanceFromTokenArray($this, $aAccountHash);
|
||||
if ($oAccount) {
|
||||
try
|
||||
{
|
||||
$this->CheckMailConnection($oAccount);
|
||||
// Update lifetime
|
||||
$this->SetSignMeToken($oAccount);
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($oAccount) {
|
||||
// Update lifetime
|
||||
$this->SetSignMeToken($oAccount);
|
||||
}
|
||||
} else {
|
||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
$this->ClearSignMeData();
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function ClearSignMeData(Account $oAccount) : void
|
||||
protected function ClearSignMeData() : void
|
||||
{
|
||||
if ($oAccount) {
|
||||
if (isset($_COOKIE[self::AUTH_SIGN_ME_TOKEN_KEY])) {
|
||||
$aTokenData = static::GetSignMeToken();
|
||||
if (!empty($aTokenData['u']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
|
||||
$this->StorageProvider()->Clear($oAccount, StorageType::SIGN_ME, $aTokenData['u']);
|
||||
if (!empty($aTokenData['e']) && !empty($aTokenData['u']) && \SnappyMail\UUID::isValid($aTokenData['u'])) {
|
||||
$this->StorageProvider()->Clear($aTokenData['e'], StorageType::SIGN_ME, $aTokenData['u']);
|
||||
}
|
||||
Utils::ClearCookie(self::AUTH_SIGN_ME_TOKEN_KEY);
|
||||
$this->StorageProvider()->Clear($oAccount, StorageType::CONFIG, 'sign_me');
|
||||
}
|
||||
}
|
||||
|
||||
public function GetSpecAuthLogoutTokenWithDeletion(): string
|
||||
{
|
||||
$sResult = Utils::GetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, '');
|
||||
if (\strlen($sResult)) {
|
||||
Utils::ClearCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY);
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
public function SetAuthLogoutToken(): void
|
||||
{
|
||||
\header('X-RainLoop-Action: Logout');
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
namespace RainLoop\Model;
|
||||
|
||||
class Account
|
||||
use \RainLoop\Utils;
|
||||
use \RainLoop\Exceptions\ClientException;
|
||||
|
||||
class Account implements \JsonSerializable
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
|
@ -22,23 +25,18 @@ class Account
|
|||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sProxyAuthUser;
|
||||
private $sProxyAuthUser = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sProxyAuthPassword;
|
||||
private $sProxyAuthPassword = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sClientCert;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sSignMeToken;
|
||||
|
||||
/**
|
||||
* @var \RainLoop\Model\Domain
|
||||
*/
|
||||
|
|
@ -47,21 +45,7 @@ class Account
|
|||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sParentEmail;
|
||||
|
||||
function __construct(string $sEmail, string $sLogin, string $sPassword, Domain $oDomain,
|
||||
string $sSignMeToken = '', string $sProxyAuthUser = '', string $sProxyAuthPassword = '', string $sClientCert = '')
|
||||
{
|
||||
$this->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||
$this->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin);
|
||||
$this->sPassword = $sPassword;
|
||||
$this->oDomain = $oDomain;
|
||||
$this->sSignMeToken = $sSignMeToken;
|
||||
$this->sProxyAuthUser = $sProxyAuthUser;
|
||||
$this->sProxyAuthPassword = $sProxyAuthPassword;
|
||||
$this->sClientCert = $sClientCert;
|
||||
$this->sParentEmail = '';
|
||||
}
|
||||
private $sParentEmail = '';
|
||||
|
||||
public function Email() : string
|
||||
{
|
||||
|
|
@ -85,12 +69,12 @@ class Account
|
|||
|
||||
public function ParentEmailHelper() : string
|
||||
{
|
||||
return \strlen($this->sParentEmail) ? $this->sParentEmail : $this->sEmail;
|
||||
return $this->sParentEmail ?: $this->sEmail;
|
||||
}
|
||||
|
||||
public function IsAdditionalAccount() : string
|
||||
public function IsAdditionalAccount() : bool
|
||||
{
|
||||
return \strlen($this->sParentEmail);
|
||||
return !empty($this->sParentEmail);
|
||||
}
|
||||
|
||||
public function IncLogin() : string
|
||||
|
|
@ -135,16 +119,6 @@ class Account
|
|||
return $this->sClientCert;
|
||||
}
|
||||
|
||||
public function SignMe() : bool
|
||||
{
|
||||
return \strlen($this->sSignMeToken);
|
||||
}
|
||||
|
||||
public function SignMeToken() : string
|
||||
{
|
||||
return $this->sSignMeToken;
|
||||
}
|
||||
|
||||
public function Domain() : Domain
|
||||
{
|
||||
return $this->oDomain;
|
||||
|
|
@ -152,8 +126,13 @@ class Account
|
|||
|
||||
public function Hash() : string
|
||||
{
|
||||
return \md5(APP_SALT.$this->Email().APP_SALT.$this->DomainIncHost().
|
||||
APP_SALT.$this->DomainIncPort().APP_SALT.$this->Password().APP_SALT.'0'.APP_SALT.$this->ParentEmail().APP_SALT);
|
||||
return \md5(\implode(APP_SALT, [
|
||||
$this->sEmail,
|
||||
$this->Domain()->IncHost(),
|
||||
$this->Domain()->IncPort(),
|
||||
$this->sPassword,
|
||||
$this->sParentEmail
|
||||
]));
|
||||
}
|
||||
|
||||
public function SetPassword(string $sPassword) : void
|
||||
|
|
@ -176,106 +155,108 @@ class Account
|
|||
$this->sProxyAuthPassword = $sProxyAuthPassword;
|
||||
}
|
||||
|
||||
public function DomainIncHost() : string
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->Domain()->IncHost();
|
||||
}
|
||||
|
||||
public function DomainIncPort() : int
|
||||
{
|
||||
return $this->Domain()->IncPort();
|
||||
}
|
||||
|
||||
public function DomainIncSecure() : int
|
||||
{
|
||||
return $this->Domain()->IncSecure();
|
||||
}
|
||||
|
||||
public function DomainOutHost() : string
|
||||
{
|
||||
return $this->Domain()->OutHost();
|
||||
}
|
||||
|
||||
public function DomainOutPort() : int
|
||||
{
|
||||
return $this->Domain()->OutPort();
|
||||
}
|
||||
|
||||
public function DomainOutSecure() : int
|
||||
{
|
||||
return $this->Domain()->OutSecure();
|
||||
}
|
||||
|
||||
public function DomainOutAuth() : bool
|
||||
{
|
||||
return $this->Domain()->OutAuth();
|
||||
}
|
||||
|
||||
public function DomainSieveHost() : string
|
||||
{
|
||||
return $this->Domain()->SieveHost();
|
||||
}
|
||||
|
||||
public function DomainSievePort() : int
|
||||
{
|
||||
return $this->Domain()->SievePort();
|
||||
}
|
||||
|
||||
public function DomainSieveSecure() : int
|
||||
{
|
||||
return $this->Domain()->SieveSecure();
|
||||
return array(
|
||||
'account', // 0
|
||||
$this->sEmail, // 1
|
||||
$this->sLogin, // 2
|
||||
$this->sPassword, // 3
|
||||
$this->sClientCert, // 4
|
||||
$this->sParentEmail, // 5
|
||||
$this->sProxyAuthUser, // 6
|
||||
$this->sProxyAuthPassword // 7
|
||||
);
|
||||
}
|
||||
|
||||
public function GetAuthToken() : string
|
||||
{
|
||||
return \RainLoop\Utils::EncodeKeyValues(array(
|
||||
'token', // 0
|
||||
$this->sEmail, // 1
|
||||
$this->sLogin, // 2
|
||||
$this->sPassword, // 3
|
||||
\RainLoop\Utils::Fingerprint(), // 4
|
||||
$this->sSignMeToken, // 5
|
||||
$this->sParentEmail, // 6
|
||||
\RainLoop\Utils::GetShortToken(), // 7
|
||||
$this->sProxyAuthUser, // 8
|
||||
$this->sProxyAuthPassword, // 9
|
||||
0, // 10 // lifetime
|
||||
$this->sClientCert // 11
|
||||
));
|
||||
return Utils::EncodeKeyValues($this->jsonSerialize());
|
||||
}
|
||||
|
||||
public function GetAuthTokenQ() : string
|
||||
{
|
||||
return \RainLoop\Utils::EncodeKeyValuesQ(array(
|
||||
'token', // 0
|
||||
$this->sEmail, // 1
|
||||
$this->sLogin, // 2
|
||||
$this->sPassword, // 3
|
||||
\RainLoop\Utils::Fingerprint(), // 4
|
||||
$this->sSignMeToken, // 5
|
||||
$this->sParentEmail, // 6
|
||||
\RainLoop\Utils::GetShortToken(), // 7
|
||||
$this->sProxyAuthUser, // 8
|
||||
$this->sProxyAuthPassword, // 9
|
||||
0, // 10 // lifetime
|
||||
$this->sClientCert // 11
|
||||
));
|
||||
return Utils::EncodeKeyValuesQ($this->jsonSerialize());
|
||||
return \MailSo\Base\Utils::UrlSafeBase64Encode(
|
||||
\MailSo\Base\Crypt::Encrypt(\json_encode($this), \sha1(APP_SALT))
|
||||
);
|
||||
}
|
||||
|
||||
public static function NewInstanceByLogin(\RainLoop\Actions $oActions, string $sEmail, string $sLogin, string $sPassword, string $sClientCert = '', bool $bThrowException = false): ?self
|
||||
{
|
||||
$oAccount = null;
|
||||
if (\strlen($sEmail) && \strlen($sLogin) && \strlen($sPassword)) {
|
||||
$oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true);
|
||||
if ($oDomain) {
|
||||
if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) {
|
||||
$oAccount = new self;
|
||||
|
||||
$oAccount->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true);
|
||||
$oAccount->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin);
|
||||
$oAccount->sPassword = $sPassword;
|
||||
$oAccount->oDomain = $oDomain;
|
||||
$oAccount->sClientCert = $sClientCert;
|
||||
|
||||
$oActions->Plugins()->RunHook('filter.account', array($oAccount));
|
||||
|
||||
if ($bThrowException && !$oAccount) {
|
||||
throw new ClientException(Notifications::AuthError);
|
||||
}
|
||||
} else if ($bThrowException) {
|
||||
throw new ClientException(Notifications::AccountNotAllowed);
|
||||
}
|
||||
} else if ($bThrowException) {
|
||||
throw new ClientException(Notifications::DomainNotAllowed);
|
||||
}
|
||||
}
|
||||
|
||||
return $oAccount;
|
||||
}
|
||||
|
||||
public static function NewInstanceFromTokenArray(
|
||||
\RainLoop\Actions $oActions,
|
||||
array $aAccountHash,
|
||||
bool $bThrowExceptionOnFalse = false): ?self
|
||||
{
|
||||
if (!empty($aAccountHash[0]) && 'account' === $aAccountHash[0] && 8 === \count($aAccountHash)) {
|
||||
$oAccount = static::NewInstanceByLogin(
|
||||
$oActions,
|
||||
$aAccountHash[1],
|
||||
$aAccountHash[2],
|
||||
$aAccountHash[3],
|
||||
$aAccountHash[4] ?: '',
|
||||
$bThrowExceptionOnFalse
|
||||
);
|
||||
|
||||
if ($oAccount) {
|
||||
// init proxy user/password
|
||||
if (!empty($aAccountHash[6]) && !empty($aAccountHash[7])) {
|
||||
$oAccount->SetProxyAuthUser($aAccountHash[6]);
|
||||
$oAccount->SetProxyAuthPassword($aAccountHash[7]);
|
||||
}
|
||||
|
||||
$oActions->Logger()->AddSecret($oAccount->Password());
|
||||
$oActions->Logger()->AddSecret($oAccount->ProxyAuthPassword());
|
||||
|
||||
$oAccount->SetParentEmail($aAccountHash[5]);
|
||||
return $oAccount;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function IncConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Mail\MailClient $oMailClient, \RainLoop\Config\Application $oConfig) : bool
|
||||
{
|
||||
$oImapClient = $oMailClient->ImapClient();
|
||||
|
||||
$aImapCredentials = array(
|
||||
'UseConnect' => true,
|
||||
'UseAuth' => true,
|
||||
'Host' => $this->DomainIncHost(),
|
||||
'Port' => $this->DomainIncPort(),
|
||||
'Secure' => $this->DomainIncSecure(),
|
||||
'Login' => $this->IncLogin(),
|
||||
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
|
||||
'ClientCert' => $this->ClientCert(),
|
||||
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
|
||||
$aImapCredentials = \array_merge(
|
||||
$this->Domain()->ImapSettings(),
|
||||
array(
|
||||
'Login' => $this->IncLogin(),
|
||||
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
|
||||
'ClientCert' => $this->ClientCert(),
|
||||
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
|
||||
)
|
||||
);
|
||||
|
||||
$oPlugins->RunHook('imap.before-connect', array($this, $oImapClient, &$aImapCredentials));
|
||||
|
|
@ -292,17 +273,15 @@ class Account
|
|||
|
||||
public function OutConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Smtp\SmtpClient $oSmtpClient, \RainLoop\Config\Application $oConfig, bool &$bUsePhpMail = false) : bool
|
||||
{
|
||||
$aSmtpCredentials = array(
|
||||
'UseConnect' => !$bUsePhpMail,
|
||||
'UseAuth' => $this->DomainOutAuth(),
|
||||
'UsePhpMail' => $bUsePhpMail,
|
||||
'Ehlo' => \MailSo\Smtp\SmtpClient::EhloHelper(),
|
||||
'Host' => $this->DomainOutHost(),
|
||||
'Port' => $this->DomainOutPort(),
|
||||
'Secure' => $this->DomainOutSecure(),
|
||||
'Login' => $this->OutLogin(),
|
||||
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
|
||||
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
|
||||
$aSmtpCredentials = \array_merge(
|
||||
$this->Domain()->SmtpSettings(),
|
||||
array(
|
||||
'UseConnect' => !$bUsePhpMail,
|
||||
'UsePhpMail' => $bUsePhpMail,
|
||||
'Login' => $this->OutLogin(),
|
||||
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
|
||||
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true)
|
||||
)
|
||||
);
|
||||
|
||||
$oPlugins->RunHook('smtp.before-connect', array($this, $oSmtpClient, &$aSmtpCredentials));
|
||||
|
|
@ -321,16 +300,14 @@ class Account
|
|||
|
||||
public function SieveConnectAndLoginHelper(\RainLoop\Plugins\Manager $oPlugins, \MailSo\Sieve\ManageSieveClient $oSieveClient, \RainLoop\Config\Application $oConfig)
|
||||
{
|
||||
$aSieveCredentials = array(
|
||||
'UseConnect' => true,
|
||||
'UseAuth' => true,
|
||||
'Host' => $this->DomainSieveHost(),
|
||||
'Port' => $this->DomainSievePort(),
|
||||
'Secure' => $this->DomainSieveSecure(),
|
||||
'Login' => $this->IncLogin(),
|
||||
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
|
||||
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true),
|
||||
'InitialAuthPlain' => !!$oConfig->Get('ssl', 'sieve_auth_plain_initial', true)
|
||||
$aSieveCredentials = \array_merge(
|
||||
$this->Domain()->SieveSettings(),
|
||||
array(
|
||||
'Login' => $this->IncLogin(),
|
||||
'VerifySsl' => !!$oConfig->Get('ssl', 'verify_certificate', false),
|
||||
'AllowSelfSigned' => !!$oConfig->Get('ssl', 'allow_self_signed', true),
|
||||
'InitialAuthPlain' => !!$oConfig->Get('ssl', 'sieve_auth_plain_initial', true)
|
||||
)
|
||||
);
|
||||
|
||||
$oPlugins->RunHook('sieve.before-connect', array($this, $oSieveClient, &$aSieveCredentials));
|
||||
|
|
|
|||
|
|
@ -396,27 +396,62 @@ class Domain implements \JsonSerializable
|
|||
return true;
|
||||
}
|
||||
|
||||
public function ImapSettings() : array
|
||||
{
|
||||
return array(
|
||||
'UseConnect' => true,
|
||||
'UseAuth' => true,
|
||||
'Host' => $this->sIncHost,
|
||||
'Port' => $this->iIncPort,
|
||||
'Secure' => $this->iIncSecure,
|
||||
);
|
||||
}
|
||||
|
||||
public function SmtpSettings() : array
|
||||
{
|
||||
return array(
|
||||
'UseConnect' => !$this->bOutUsePhpMail,
|
||||
'UseAuth' => $this->bOutAuth,
|
||||
'Host' => $this->sOutHost,
|
||||
'Port' => $this->iOutPort,
|
||||
'Secure' => $this->iOutSecure,
|
||||
'Ehlo' => \MailSo\Smtp\SmtpClient::EhloHelper(),
|
||||
'UsePhpMail' => $this->bOutUsePhpMail
|
||||
);
|
||||
}
|
||||
|
||||
public function SieveSettings() : array
|
||||
{
|
||||
return array(
|
||||
'UseConnect' => true,
|
||||
'UseAuth' => true,
|
||||
'Host' => $this->sSieveHost,
|
||||
'Port' => $this->iSievePort,
|
||||
'Secure' => $this->iSieveSecure
|
||||
);
|
||||
}
|
||||
|
||||
public function ToSimpleJSON() : array
|
||||
{
|
||||
return array(
|
||||
'Name' => $this->Name(),
|
||||
'IncHost' => $this->IncHost(),
|
||||
'IncPort' => $this->IncPort(),
|
||||
'IncSecure' => $this->IncSecure(),
|
||||
'IncShortLogin' => $this->IncShortLogin(),
|
||||
'UseSieve' => $this->UseSieve(),
|
||||
'SieveHost' => $this->SieveHost(),
|
||||
'SievePort' => $this->SievePort(),
|
||||
'SieveSecure' => $this->SieveSecure(),
|
||||
'OutHost' => $this->OutHost(),
|
||||
'OutPort' => $this->OutPort(),
|
||||
'OutSecure' => $this->OutSecure(),
|
||||
'OutShortLogin' => $this->OutShortLogin(),
|
||||
'OutAuth' => $this->OutAuth(),
|
||||
'OutSetSender' => $this->OutSetSender(),
|
||||
'OutUsePhpMail' => $this->OutUsePhpMail(),
|
||||
'WhiteList' => $this->WhiteList(),
|
||||
'AliasName' => $this->AliasName()
|
||||
'Name' => $this->sName,
|
||||
'IncHost' => $this->sIncHost,
|
||||
'IncPort' => $this->iIncPort,
|
||||
'IncSecure' => $this->iIncSecure,
|
||||
'IncShortLogin' => $this->bIncShortLogin,
|
||||
'UseSieve' => $this->bUseSieve,
|
||||
'SieveHost' => $this->sSieveHost,
|
||||
'SievePort' => $this->iSievePort,
|
||||
'SieveSecure' => $this->iSieveSecure,
|
||||
'OutHost' => $this->sOutHost,
|
||||
'OutPort' => $this->iOutPort,
|
||||
'OutSecure' => $this->iOutSecure,
|
||||
'OutShortLogin' => $this->bOutShortLogin,
|
||||
'OutAuth' => $this->bOutAuth,
|
||||
'OutSetSender' => $this->bOutSetSender,
|
||||
'OutUsePhpMail' => $this->bOutUsePhpMail,
|
||||
'WhiteList' => $this->sWhiteList,
|
||||
'AliasName' => $this->sAliasName
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -424,24 +459,24 @@ class Domain implements \JsonSerializable
|
|||
{
|
||||
return array(
|
||||
// '@Object' => 'Object/Domain',
|
||||
'Name' => \MailSo\Base\Utils::IdnToUtf8($this->Name()),
|
||||
'IncHost' => \MailSo\Base\Utils::IdnToUtf8($this->IncHost()),
|
||||
'IncPort' => $this->IncPort(),
|
||||
'IncSecure' => $this->IncSecure(),
|
||||
'IncShortLogin' => $this->IncShortLogin(),
|
||||
'UseSieve' => $this->UseSieve(),
|
||||
'SieveHost' => \MailSo\Base\Utils::IdnToUtf8($this->SieveHost()),
|
||||
'SievePort' => $this->SievePort(),
|
||||
'SieveSecure' => $this->SieveSecure(),
|
||||
'OutHost' => \MailSo\Base\Utils::IdnToUtf8($this->OutHost()),
|
||||
'OutPort' => $this->OutPort(),
|
||||
'OutSecure' => $this->OutSecure(),
|
||||
'OutShortLogin' => $this->OutShortLogin(),
|
||||
'OutAuth' => $this->OutAuth(),
|
||||
'OutSetSender' => $this->OutSetSender(),
|
||||
'OutUsePhpMail' => $this->OutUsePhpMail(),
|
||||
'WhiteList' => $this->WhiteList(),
|
||||
'AliasName' => $this->AliasName()
|
||||
'Name' => \MailSo\Base\Utils::IdnToUtf8($this->sName),
|
||||
'IncHost' => \MailSo\Base\Utils::IdnToUtf8($this->sIncHost),
|
||||
'IncPort' => $this->iIncPort,
|
||||
'IncSecure' => $this->iIncSecure,
|
||||
'IncShortLogin' => $this->bIncShortLogin,
|
||||
'UseSieve' => $this->bUseSieve,
|
||||
'SieveHost' => \MailSo\Base\Utils::IdnToUtf8($this->sSieveHost),
|
||||
'SievePort' => $this->iSievePort,
|
||||
'SieveSecure' => $this->iSieveSecure,
|
||||
'OutHost' => \MailSo\Base\Utils::IdnToUtf8($this->sOutHost),
|
||||
'OutPort' => $this->iOutPort,
|
||||
'OutSecure' => $this->iOutSecure,
|
||||
'OutShortLogin' => $this->bOutShortLogin,
|
||||
'OutAuth' => $this->bOutAuth,
|
||||
'OutSetSender' => $this->bOutSetSender,
|
||||
'OutUsePhpMail' => $this->bOutUsePhpMail,
|
||||
'WhiteList' => $this->sWhiteList,
|
||||
'AliasName' => $this->sAliasName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,54 +29,54 @@ class Storage extends \RainLoop\Providers\AbstractProvider
|
|||
}
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|string|null $oAccount
|
||||
* @param \RainLoop\Model\Account|string|null $mAccount
|
||||
* @param mixed $sValue
|
||||
*/
|
||||
public function Put($oAccount, int $iStorageType, string $sKey, string $sValue) : bool
|
||||
public function Put($mAccount, int $iStorageType, string $sKey, string $sValue) : bool
|
||||
{
|
||||
if (!$this->verifyAccount($oAccount, $iStorageType))
|
||||
if (!$this->verifyAccount($mAccount, $iStorageType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->oDriver->Put($oAccount, $iStorageType, $sKey, $sValue);
|
||||
return $this->oDriver->Put($mAccount, $iStorageType, $sKey, $sValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|string|null $oAccount
|
||||
* @param \RainLoop\Model\Account|string|null $mAccount
|
||||
* @param mixed $mDefault = false
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function Get($oAccount, int $iStorageType, string $sKey, $mDefault = false)
|
||||
public function Get($mAccount, int $iStorageType, string $sKey, $mDefault = false)
|
||||
{
|
||||
if (!$this->verifyAccount($oAccount, $iStorageType))
|
||||
if (!$this->verifyAccount($mAccount, $iStorageType))
|
||||
{
|
||||
return $mDefault;
|
||||
}
|
||||
|
||||
return $this->oDriver->Get($oAccount, $iStorageType, $sKey, $mDefault);
|
||||
return $this->oDriver->Get($mAccount, $iStorageType, $sKey, $mDefault);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|string|null $oAccount
|
||||
* @param \RainLoop\Model\Account|string|null $mAccount
|
||||
*/
|
||||
public function Clear($oAccount, int $iStorageType, string $sKey) : bool
|
||||
public function Clear($mAccount, int $iStorageType, string $sKey) : bool
|
||||
{
|
||||
if (!$this->verifyAccount($oAccount, $iStorageType))
|
||||
if (!$this->verifyAccount($mAccount, $iStorageType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->oDriver->Clear($oAccount, $iStorageType, $sKey);
|
||||
return $this->oDriver->Clear($mAccount, $iStorageType, $sKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|string $oAccount
|
||||
* @param \RainLoop\Model\Account|string $mAccount
|
||||
*/
|
||||
public function DeleteStorage($oAccount) : bool
|
||||
public function DeleteStorage($mAccount) : bool
|
||||
{
|
||||
return $this->oDriver->DeleteStorage($oAccount);
|
||||
return $this->oDriver->DeleteStorage($mAccount);
|
||||
}
|
||||
|
||||
public function IsActive() : bool
|
||||
|
|
|
|||
|
|
@ -29,24 +29,24 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
|||
}
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|string|null $oAccount
|
||||
* @param \RainLoop\Model\Account|string|null $mAccount
|
||||
*/
|
||||
public function Put($oAccount, int $iStorageType, string $sKey, string $sValue) : bool
|
||||
public function Put($mAccount, int $iStorageType, string $sKey, string $sValue) : bool
|
||||
{
|
||||
$sFileName = $this->generateFileName($oAccount, $iStorageType, $sKey, true);
|
||||
$sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey, true);
|
||||
return $sFileName && false !== \file_put_contents($sFileName, $sValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|string|null $oAccount
|
||||
* @param \RainLoop\Model\Account|string|null $mAccount
|
||||
* @param mixed $mDefault = false
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function Get($oAccount, int $iStorageType, string $sKey, $mDefault = false)
|
||||
public function Get($mAccount, int $iStorageType, string $sKey, $mDefault = false)
|
||||
{
|
||||
$mValue = false;
|
||||
$sFileName = $this->generateFileName($oAccount, $iStorageType, $sKey);
|
||||
$sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey);
|
||||
if ($sFileName && \file_exists($sFileName)) {
|
||||
$mValue = \file_get_contents($sFileName);
|
||||
}
|
||||
|
|
@ -55,12 +55,12 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
|||
}
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|string|null $oAccount
|
||||
* @param \RainLoop\Model\Account|string|null $mAccount
|
||||
*/
|
||||
public function Clear($oAccount, int $iStorageType, string $sKey) : bool
|
||||
public function Clear($mAccount, int $iStorageType, string $sKey) : bool
|
||||
{
|
||||
$mResult = true;
|
||||
$sFileName = $this->generateFileName($oAccount, $iStorageType, $sKey);
|
||||
$sFileName = $this->generateFileName($mAccount, $iStorageType, $sKey);
|
||||
if ($sFileName && \file_exists($sFileName)) {
|
||||
$mResult = \unlink($sFileName);
|
||||
}
|
||||
|
|
@ -69,11 +69,11 @@ class FileStorage implements \RainLoop\Providers\Storage\IStorage
|
|||
}
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|string $oAccount
|
||||
* @param \RainLoop\Model\Account|string $mAccount
|
||||
*/
|
||||
public function DeleteStorage($oAccount) : bool
|
||||
public function DeleteStorage($mAccount) : bool
|
||||
{
|
||||
$sPath = $this->generateFileName($oAccount, StorageType::CONFIG, '', false, true);
|
||||
$sPath = $this->generateFileName($mAccount, StorageType::CONFIG, '', false, true);
|
||||
if ($sPath && \is_dir($sPath)) {
|
||||
\MailSo\Base\Utils::RecRmDir($sPath);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,22 @@ namespace RainLoop\Providers\Storage;
|
|||
interface IStorage
|
||||
{
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|null $oAccount
|
||||
* @param \RainLoop\Model\Account|null $mAccount
|
||||
*/
|
||||
public function Put($oAccount, int $iStorageType, string $sKey, string $sValue) : bool;
|
||||
public function Put($mAccount, int $iStorageType, string $sKey, string $sValue) : bool;
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|null $oAccount
|
||||
* @param \RainLoop\Model\Account|null $mAccount
|
||||
* @param mixed $mDefault = false
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function Get($oAccount, int $iStorageType, string $sKey, $mDefault = false);
|
||||
public function Get($mAccount, int $iStorageType, string $sKey, $mDefault = false);
|
||||
|
||||
/**
|
||||
* @param \RainLoop\Model\Account|null $oAccount
|
||||
* @param \RainLoop\Model\Account|null $mAccount
|
||||
*/
|
||||
public function Clear($oAccount, int $iStorageType, string $sKey) : bool;
|
||||
public function Clear($mAccount, int $iStorageType, string $sKey) : bool;
|
||||
|
||||
public function IsLocal() : bool;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,8 +96,6 @@ class Service
|
|||
$bAdmin = true;
|
||||
}
|
||||
|
||||
$bAdmin || $this->oActions->getAuthAccountHash();
|
||||
|
||||
if ($this->oHttp->IsPost())
|
||||
{
|
||||
$this->oHttp->ServerNoCache();
|
||||
|
|
|
|||
|
|
@ -684,7 +684,7 @@ class ServiceActions
|
|||
{
|
||||
$oAccount = $this->oActions->LoginProcess($sEmail, $sPassword);
|
||||
|
||||
if ($oAccount instanceof Model\Account && $aAdditionalOptions)
|
||||
if ($aAdditionalOptions)
|
||||
{
|
||||
$bNeedToSettings = false;
|
||||
|
||||
|
|
@ -711,7 +711,7 @@ class ServiceActions
|
|||
}
|
||||
}
|
||||
|
||||
$this->oActions->AuthToken($oAccount);
|
||||
$this->oActions->SetAuthToken($oAccount);
|
||||
|
||||
$bLogout = !($oAccount instanceof Model\Account);
|
||||
}
|
||||
|
|
@ -746,7 +746,7 @@ class ServiceActions
|
|||
try
|
||||
{
|
||||
$oAccount = $this->oActions->LoginProcess($sEmail, $sPassword);
|
||||
$this->oActions->AuthToken($oAccount);
|
||||
$this->oActions->SetAuthToken($oAccount);
|
||||
$bLogout = !($oAccount instanceof Model\Account);
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
|
|
@ -824,13 +824,13 @@ class ServiceActions
|
|||
$aAccounts = $this->oActions->GetAccounts($oAccount);
|
||||
if (isset($aAccounts[$sEmail]))
|
||||
{
|
||||
$oAccountToLogin = $this->oActions->GetAccountFromCustomToken($aAccounts[$sEmail], false, false);
|
||||
$oAccountToLogin = $this->oActions->GetAccountFromCustomToken($aAccounts[$sEmail], false);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oAccountToLogin)
|
||||
{
|
||||
$this->oActions->AuthToken($oAccountToLogin);
|
||||
$this->oActions->SetAuthToken($oAccountToLogin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,17 @@ class Utils
|
|||
|
||||
const
|
||||
/**
|
||||
* 30 days cookie
|
||||
* Used by: ServiceProxyExternal, compileLogParams, GetCsrfToken
|
||||
* To preven CSRF attacks on all requests.
|
||||
*/
|
||||
CONNECTION_TOKEN = 'rltoken',
|
||||
CONNECTION_TOKEN = 'smtoken',
|
||||
|
||||
/**
|
||||
* Session cookie
|
||||
* Used by: GetAuthToken, GetAuthTokenQ, GetAccountFromCustomToken, EncryptStringQ, DecryptStringQ
|
||||
*/
|
||||
SHORT_TOKEN = 'rlsession';
|
||||
SHORT_TOKEN = 'smsession';
|
||||
|
||||
public static function EncryptString(string $sString, string $sKey) : string
|
||||
{
|
||||
|
|
@ -96,14 +98,13 @@ class Utils
|
|||
|
||||
public static function Fingerprint() : string
|
||||
{
|
||||
return \md5($_SERVER['HTTP_USER_AGENT'] ?: 'RainLoopFingerprint');
|
||||
return \sha1(\preg_replace('/[^a-z]+/i', '', \explode(')', $_SERVER['HTTP_USER_AGENT'])[0]));
|
||||
}
|
||||
|
||||
public static function GetShortToken() : string
|
||||
{
|
||||
$sToken = static::GetCookie(self::SHORT_TOKEN, null);
|
||||
if (!$sToken)
|
||||
{
|
||||
if (!$sToken) {
|
||||
$sToken = \MailSo\Base\Utils::Sha1Rand(APP_SALT);
|
||||
static::SetCookie(self::SHORT_TOKEN, $sToken, 0);
|
||||
}
|
||||
|
|
|
|||
100
snappymail/v/0.0.0/app/libraries/snappymail/crypt.php
Normal file
100
snappymail/v/0.0.0/app/libraries/snappymail/crypt.php
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace SnappyMail;
|
||||
|
||||
abstract class Crypt
|
||||
{
|
||||
/**
|
||||
* Or use 'aes-256-xts' ?
|
||||
*/
|
||||
const CIPHER = 'aes-256-cbc-hmac-sha1';
|
||||
|
||||
private static function Passphrase() : string
|
||||
{
|
||||
return \sha1(\preg_replace('/[^a-z]+/i', '', \explode(')', $_SERVER['HTTP_USER_AGENT'])[0]) . APP_SALT, true);
|
||||
}
|
||||
|
||||
public static function DecryptCookie($sData)
|
||||
{
|
||||
return \json_decode(
|
||||
\zlib_decode(
|
||||
\MailSo\Base\Crypt::Decrypt(
|
||||
\MailSo\Base\Utils::UrlSafeBase64Decode($sData),
|
||||
static::Passphrase()
|
||||
)
|
||||
),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public static function EncryptCookie($mData) : string
|
||||
{
|
||||
return \MailSo\Base\Utils::UrlSafeBase64Encode(
|
||||
\MailSo\Base\Crypt::Encrypt(
|
||||
\zlib_encode(
|
||||
\json_encode($mData),
|
||||
ZLIB_ENCODING_RAW,
|
||||
9
|
||||
),
|
||||
static::Passphrase()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static function EncryptRaw($data) : array
|
||||
{
|
||||
if (\is_callable('openssl_encrypt')) {
|
||||
$iv = \random_bytes(\openssl_cipher_iv_length(static::CIPHER));
|
||||
$data = \openssl_encrypt(
|
||||
\json_encode($data),
|
||||
static::CIPHER,
|
||||
static::Passphrase(),
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
);
|
||||
return [static::CIPHER, $iv, $data];
|
||||
}
|
||||
|
||||
$salt = \random_bytes(16);
|
||||
return ['xxtea', $salt, static::XxteaEncrypt($data, $salt)];
|
||||
}
|
||||
|
||||
public static function OpenSSLDecrypt(string $data, string $iv)
|
||||
{
|
||||
if (!$data || !$iv || !\is_callable('openssl_decrypt')) {
|
||||
return null;
|
||||
}
|
||||
return \json_decode(\openssl_decrypt(
|
||||
$data,
|
||||
static::CIPHER,
|
||||
static::Passphrase(),
|
||||
OPENSSL_RAW_DATA,
|
||||
$iv
|
||||
), true);
|
||||
}
|
||||
|
||||
public static function XxteaDecrypt(string $data, string $salt)
|
||||
{
|
||||
if (!$data || !$salt) {
|
||||
return null;
|
||||
}
|
||||
$key = $salt . static::Passphrase();
|
||||
return \json_decode(\is_callable('xxtea_decrypt')
|
||||
? \xxtea_decrypt($data, $key)
|
||||
: \MailSo\Base\Xxtea::decrypt($data, $key)
|
||||
, true);
|
||||
}
|
||||
|
||||
public static function XxteaEncrypt($data, string $salt) : string
|
||||
{
|
||||
if (!$data || !$salt) {
|
||||
return null;
|
||||
}
|
||||
$data = \json_encode($data);
|
||||
$key = $salt . static::Passphrase();
|
||||
return \is_callable('xxtea_encrypt')
|
||||
? \xxtea_encrypt($data, $key)
|
||||
: \MailSo\Base\Xxtea::encrypt($data, $key);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
'imagick' => extension_loaded('imagick'),
|
||||
'intl' => function_exists('idn_to_ascii'),
|
||||
'ldap' => extension_loaded('ldap'),
|
||||
'OpenSSL' => extension_loaded('openssl'),
|
||||
'mysql' => extension_loaded('pdo_mysql'),
|
||||
'pgsql' => extension_loaded('pdo_pgsql'),
|
||||
'sqlite' => extension_loaded('pdo_sqlite'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue