Added Allow/Disallow multiple identities

This commit is contained in:
RainLoop Team 2015-05-18 00:38:20 +04:00
parent 28d93f70ae
commit 2f482a6288
49 changed files with 32740 additions and 32243 deletions

View file

@ -16,7 +16,7 @@ It's not recommended to use in production environment.
## License
RainLoop Webmail (**Community edition**) is released under
**RainLoop Webmail (Community edition)** is released under
**GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (AGPL)**.
http://www.gnu.org/licenses/agpl-3.0.html

View file

@ -225,7 +225,6 @@
{
var
kn = require('Knoin/Knoin'),
sStartupUrl = Utils.pString(Settings.settingsGet('StartupUrl')),
sCustomLogoutLink = Utils.pString(Settings.settingsGet('CustomLogoutLink')),
bInIframe = !!Settings.settingsGet('InIframe')
;
@ -235,7 +234,6 @@
if (bLogout)
{
sStartupUrl = '';
this.clearClientSideToken();
}
@ -244,11 +242,6 @@
window.close();
}
if (bAdmin)
{
sStartupUrl = '';
}
sCustomLogoutLink = sCustomLogoutLink || (bAdmin ? Links.rootAdmin() : Links.rootUser());
if (bLogout && window.location.href !== sCustomLogoutLink)
@ -267,7 +260,7 @@
else
{
kn.routeOff();
kn.setHash(Links.root(sStartupUrl), true);
kn.setHash(Links.root(), true);
kn.routeOff();
_.delay(function () {

View file

@ -1273,6 +1273,7 @@
self = this,
$LAB = require('$LAB'),
sJsHash = Settings.settingsGet('JsHash'),
sStartupUrl = Utils.pString(Settings.settingsGet('StartupUrl')),
iContactsSyncInterval = Utils.pInt(Settings.settingsGet('ContactsSyncInterval')),
bGoogle = Settings.settingsGet('AllowGoogleSocial'),
bFacebook = Settings.settingsGet('AllowFacebookSocial'),
@ -1314,6 +1315,13 @@
if (bValue)
{
if ('' !== sStartupUrl)
{
kn.routeOff();
kn.setHash(Links.root(sStartupUrl), true);
kn.routeOn();
}
if ($LAB && window.crypto && window.crypto.getRandomValues && Settings.capa(Enums.Capa.OpenPGP))
{
var fOpenpgpCallback = function (openpgp) {

View file

@ -79,7 +79,8 @@
'AttachmentThumbnails': 'ATTACHMENT_THUMBNAILS',
'Templates': 'TEMPLATES',
'AutoLogout': 'AUTOLOGOUT',
'AdditionalAccounts': 'ADDITIONAL_ACCOUNTS'
'AdditionalAccounts': 'ADDITIONAL_ACCOUNTS',
'Identities': 'IDENTITIES'
};
/**

View file

@ -56,9 +56,12 @@
'SettingsContacts', 'SETTINGS_LABELS/LABEL_CONTACTS_NAME', 'contacts');
}
if (Settings.capa(Enums.Capa.AdditionalAccounts) || Settings.capa(Enums.Capa.Identities))
{
kn.addSettingsViewModel(require('Settings/User/Accounts'), 'SettingsAccounts',
Settings.capa(Enums.Capa.AdditionalAccounts) ?
'SETTINGS_LABELS/LABEL_ACCOUNTS_NAME' : 'SETTINGS_LABELS/LABEL_IDENTITIES_NAME', 'accounts');
}
if (Settings.capa(Enums.Capa.Sieve))
{

View file

@ -37,6 +37,7 @@
this.capaUserBackground = CapaAdminStore.userBackground;
this.capaGravatar = CapaAdminStore.gravatar;
this.capaAdditionalAccounts = CapaAdminStore.additionalAccounts;
this.capaIdentities = CapaAdminStore.identities;
this.capaAttachmentThumbnails = CapaAdminStore.attachmentThumbnails;
this.capaTemplates = CapaAdminStore.templates;
@ -136,6 +137,12 @@
});
});
self.capaIdentities.subscribe(function (bValue) {
Remote.saveAdminConfig(null, {
'CapaIdentities': bValue ? '1' : '0'
});
});
self.capaTemplates.subscribe(function (bValue) {
Remote.saveAdminConfig(null, {
'CapaTemplates': bValue ? '1' : '0'

View file

@ -24,7 +24,7 @@
function AccountsUserSettings()
{
this.allowAdditionalAccount = Settings.capa(Enums.Capa.AdditionalAccounts);
this.allowIdentities = true;
this.allowIdentities = Settings.capa(Enums.Capa.Identities);;
this.accounts = AccountStore.accounts;
this.identities = IdentityStore.identities;

View file

@ -16,9 +16,12 @@
AppStore = require('Stores/User/App'),
LanguageStore = require('Stores/Language'),
SettingsStore = require('Stores/User/Settings'),
IdentityStore = require('Stores/User/Identity'),
NotificationStore = require('Stores/User/Notification'),
MessageStore = require('Stores/User/Message'),
Settings = require('Storage/Settings'),
Remote = require('Remote/User/Ajax')
;
@ -62,6 +65,20 @@
this.isAnimationSupported = Globals.bAnimationSupported;
this.identities = IdentityStore.identities;
this.identityMain = ko.computed(function () {
var aList = this.identities();
return Utils.isArray(aList) ? _.find(aList, function (oItem) {
return oItem && '' === oItem.id() ? true : false;
}) : null;
}, this);
this.identityMainDesc = ko.computed(function () {
var oIdentity = this.identityMain();
return oIdentity ? oIdentity.formattedName() : '---';
}, this);
this.editorDefaultTypes = ko.computed(function () {
Translator.trigger();
return [
@ -82,6 +99,15 @@
}, this);
}
GeneralUserSettings.prototype.editMainIdentity = function ()
{
var oIdentity = this.identityMain();
if (oIdentity)
{
require('Knoin/Knoin').showScreenPopup(require('View/Popup/Identity'), [oIdentity]);
}
};
GeneralUserSettings.prototype.testSoundNotification = function ()
{
NotificationStore.playSoundNotification(true);

View file

@ -17,6 +17,7 @@
function CapaAdminStore()
{
this.additionalAccounts = ko.observable(false);
this.identities = ko.observable(false);
this.gravatar = ko.observable(false);
this.attachmentThumbnails = ko.observable(false);
this.sieve = ko.observable(false);
@ -32,6 +33,7 @@
CapaAdminStore.prototype.populate = function()
{
this.additionalAccounts(Settings.capa(Enums.Capa.AdditionalAccounts));
this.identities(Settings.capa(Enums.Capa.Identities));
this.gravatar(Settings.capa(Enums.Capa.Gravatar));
this.attachmentThumbnails(Settings.capa(Enums.Capa.AttachmentThumbnails));
this.sieve(Settings.capa(Enums.Capa.Sieve));

View file

@ -899,6 +899,20 @@ END;
return $sResult;
}
/**
* @param string $sDomain
*
* @return string
*/
public static function GetClearDomainName($sDomain)
{
$sResultDomain = \preg_replace(
'/^(webmail|email|mail|www|imap4|pop3|imap|pop|demo|client|ssl|secure)\./i',
'', $sDomain);
return false === \strpos($sResultDomain, '.') ? $sDomain : $sResultDomain;
}
/**
* @param string $sFileName
*

View file

@ -194,9 +194,8 @@ class Actions
*/
public function GetShortLifeSpecAuthToken($iLife = 60)
{
$sToken = $this->getAuthToken();
$aAccountHash = \RainLoop\Utils::DecodeKeyValues($sToken);
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && is_array($aAccountHash))
$aAccountHash = \RainLoop\Utils::DecodeKeyValues($this->getLocalAuthToken());
if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && \is_array($aAccountHash))
{
$aAccountHash[10] = \time() + $iLife;
return \RainLoop\Utils::EncodeKeyValues($aAccountHash);
@ -458,6 +457,18 @@ class Actions
$this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine);
}
if (false !== \strpos($sLine, '{request:domain}'))
{
$sLine = \str_replace('{request:domain}',
$this->Http()->GetHost(false, true, true), $sLine);
}
if (false !== \strpos($sLine, '{request:domain-clear}'))
{
$sLine = \str_replace('{request:domain-clear}',
\MailSo\Base\Utils::GetClearDomainName($this->Http()->GetHost(false, true, true)), $sLine);
}
$sLine = \preg_replace('/\{request:([^}]*)\}/i', 'request', $sLine);
}
@ -489,8 +500,12 @@ class Actions
{
$sEmail = $oAccount->Email();
$sLine = \str_replace('{user:email}', $sEmail, $sLine);
$sLine = \str_replace('{user:login}', \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sLine);
$sLine = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sLine);
$sLine = \str_replace('{user:login}',
\MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sLine);
$sLine = \str_replace('{user:domain}',
\MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sLine);
$sLine = \str_replace('{user:domain-clear}',
\MailSo\Base\Utils::GetClearDomainName(\MailSo\Base\Utils::GetDomainFromEmail($sEmail)), $sLine);
}
}
@ -552,7 +567,7 @@ class Actions
{
if ($oAccount)
{
$sSpecAuthToken = '_'.$oAccount->GetAuthToken();
$sSpecAuthToken = '_'.$oAccount->GetAuthTokenQ();
$this->SetSpecAuthToken($sSpecAuthToken);
\RainLoop\Utils::SetCookie(self::AUTH_SPEC_TOKEN_KEY, $sSpecAuthToken, 0, '/', null, null, true);
@ -560,7 +575,7 @@ class Actions
if ($oAccount->SignMe() && 0 < \strlen($oAccount->SignMeToken()))
{
\RainLoop\Utils::SetCookie(self::AUTH_SIGN_ME_TOKEN_KEY,
\RainLoop\Utils::EncodeKeyValues(array(
\RainLoop\Utils::EncodeKeyValuesQ(array(
'e' => $oAccount->Email(),
't' => $oAccount->SignMeToken()
)),
@ -569,9 +584,9 @@ class Actions
$this->StorageProvider()->Put($oAccount,
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
'sign_me',
\RainLoop\Utils::EncodeKeyValues(array(
\RainLoop\Utils::EncodeKeyValuesQ(array(
'Time' => \time(),
'AuthToken' => $oAccount->GetAuthToken(),
'AuthToken' => $oAccount->GetAuthTokenQ(),
'SignMetToken' => $oAccount->SignMeToken()
))
);
@ -618,7 +633,7 @@ class Actions
/**
* @return string
*/
private function getAuthToken()
private function getLocalAuthToken()
{
$sToken = $this->GetSpecAuthToken();
return !empty($sToken) && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : '';
@ -637,7 +652,7 @@ class Actions
*/
public function ClearAdminAuthToken()
{
$aAdminHash = \RainLoop\Utils::DecodeKeyValues($this->getAdminAuthToken());
$aAdminHash = \RainLoop\Utils::DecodeKeyValuesQ($this->getAdminAuthToken());
if (
!empty($aAdminHash[0]) && !empty($aAdminHash[1]) && !empty($aAdminHash[2]) &&
'token' === $aAdminHash[0] && \md5(APP_SALT) === $aAdminHash[1]
@ -1085,7 +1100,7 @@ class Actions
$sRand = '';
}
return '' === $sRand ? '' : \RainLoop\Utils::EncodeKeyValues(array('token', \md5(APP_SALT), $sRand));
return '' === $sRand ? '' : \RainLoop\Utils::EncodeKeyValuesQ(array('token', \md5(APP_SALT), $sRand));
}
/**
@ -1098,7 +1113,7 @@ class Actions
$bResult = false;
if ($this->Config()->Get('security', 'allow_admin_panel', true))
{
$aAdminHash = \RainLoop\Utils::DecodeKeyValues($this->getAdminAuthToken());
$aAdminHash = \RainLoop\Utils::DecodeKeyValuesQ($this->getAdminAuthToken());
if (!empty($aAdminHash[0]) && !empty($aAdminHash[1]) && !empty($aAdminHash[2]) &&
'token' === $aAdminHash[0] && \md5(APP_SALT) === $aAdminHash[1] &&
'' !== $this->Cacher(null, true)->Get(\RainLoop\KeyPathHelper::SessionAdminKey($aAdminHash[2]), '')
@ -1124,7 +1139,7 @@ class Actions
if (!empty($sTo))
{
\RainLoop\Utils::SetCookie(self::AUTH_MAILTO_TOKEN_KEY,
\RainLoop\Utils::EncodeKeyValues(array(
\RainLoop\Utils::EncodeKeyValuesQ(array(
'Time' => \microtime(true),
'MailTo' => 'MailTo',
'To' => $sTo
@ -1177,16 +1192,17 @@ class Actions
* @param string $sToken
* @param bool $bThrowExceptionOnFalse = true
* @param bool $bValidateShortToken = true
* @param bool $bQ = false
*
* @return \RainLoop\Model\Account|bool
* @throws \RainLoop\Exceptions\ClientException
*/
public function GetAccountFromCustomToken($sToken, $bThrowExceptionOnFalse = true, $bValidateShortToken = true)
public function GetAccountFromCustomToken($sToken, $bThrowExceptionOnFalse = true, $bValidateShortToken = true, $bQ = false)
{
$oResult = false;
if (!empty($sToken))
{
$aAccountHash = \RainLoop\Utils::DecodeKeyValues($sToken);
$aAccountHash = $bQ ? \RainLoop\Utils::DecodeKeyValuesQ($sToken) : \RainLoop\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
@ -1236,7 +1252,7 @@ class Actions
$sSignMeToken = \RainLoop\Utils::GetCookie(\RainLoop\Actions::AUTH_SIGN_ME_TOKEN_KEY, '');
if (!empty($sSignMeToken))
{
$aTokenData = \RainLoop\Utils::DecodeKeyValues($sSignMeToken);
$aTokenData = \RainLoop\Utils::DecodeKeyValuesQ($sSignMeToken);
if (\is_array($aTokenData) && !empty($aTokenData['e']) && !empty($aTokenData['t']))
{
$sTokenSettings = $this->StorageProvider()->Get($aTokenData['e'],
@ -1246,13 +1262,13 @@ class Actions
if (!empty($sTokenSettings))
{
$aSignMeData = \RainLoop\Utils::DecodeKeyValues($sTokenSettings);
$aSignMeData = \RainLoop\Utils::DecodeKeyValuesQ($sTokenSettings);
if (\is_array($aSignMeData) &&
!empty($aSignMeData['AuthToken']) &&
!empty($aSignMeData['SignMetToken']) &&
$aSignMeData['SignMetToken'] === $aTokenData['t'])
{
$oAccount = $this->GetAccountFromCustomToken($aSignMeData['AuthToken'], false, false);
$oAccount = $this->GetAccountFromCustomToken($aSignMeData['AuthToken'], false, false, true);
}
}
}
@ -1273,7 +1289,7 @@ class Actions
*/
public function getAccountFromToken($bThrowExceptionOnFalse = true)
{
return $this->GetAccountFromCustomToken($this->getAuthToken(), $bThrowExceptionOnFalse);
return $this->GetAccountFromCustomToken($this->getLocalAuthToken(), $bThrowExceptionOnFalse, true, true);
}
/**
@ -1467,9 +1483,10 @@ class Actions
if (null !== $sToken)
{
\RainLoop\Utils::ClearCookie(self::AUTH_MAILTO_TOKEN_KEY);
$mMailToData = \RainLoop\Utils::DecodeKeyValues($sToken);
if (\is_array($mMailToData) && !empty($mMailToData['MailTo']) && 'MailTo' === $mMailToData['MailTo'] &&
!empty($mMailToData['To']))
$mMailToData = \RainLoop\Utils::DecodeKeyValuesQ($sToken);
if (\is_array($mMailToData) && !empty($mMailToData['MailTo']) &&
'MailTo' === $mMailToData['MailTo'] && !empty($mMailToData['To']))
{
$aResult['MailToEmail'] = $mMailToData['To'];
}
@ -1496,6 +1513,8 @@ class Actions
$aResult['WelcomePageUrl'] = '';
$aResult['WelcomePageDisplay'] = '';
}
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount);
}
else
{
@ -1509,6 +1528,8 @@ class Actions
$aResult['WelcomePageUrl'] = '';
$aResult['WelcomePageDisplay'] = '';
$aResult['StartupUrl'] = '';
}
$aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false);
@ -2109,51 +2130,27 @@ class Actions
*/
private function clientRsaDecryptHelper($sEncryptedData)
{
$aMatch = array();
if ('rsa:xxx:' === substr($sEncryptedData, 0, 8) && $this->Config()->Get('security', 'use_rsa_encryption', false))
{
$oLogger = $this->Logger();
$oLogger->Write('Trying to decode encrypted data', \MailSo\Log\Enumerations\Type::INFO, 'RSA');
$sPrivateKey = file_exists(APP_PRIVATE_DATA.'rsa/private') ?
\file_get_contents(APP_PRIVATE_DATA.'rsa/private') : '';
if (!empty($sPrivateKey))
{
$sData = \trim(\substr($sEncryptedData, 8));
if (!\class_exists('Crypt_RSA'))
{
\set_include_path(\get_include_path().PATH_SEPARATOR.APP_VERSION_ROOT_PATH.'app/libraries/phpseclib');
include_once 'Crypt/RSA.php';
\defined('CRYPT_RSA_MODE') || \define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
}
$oLogger->HideErrorNotices(true);
$oRsa = new \Crypt_RSA();
$oRsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$oRsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
$oRsa->setPrivateKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$oRsa->loadKey($sPrivateKey, CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
$sData = $oRsa->decrypt(\base64_decode($sData));
if (\preg_match('/^[a-z0-9]{32}:(.+):[a-z0-9]{32}$/', $sData, $aMatch) && isset($aMatch[1]))
{
$sEncryptedData = $aMatch[1];
}
else
{
$oLogger->Write('Invalid decrypted data', \MailSo\Log\Enumerations\Type::WARNING, 'RSA');
}
$oLogger->HideErrorNotices(false);
}
else
{
$oLogger->Write('Private key is not found', \MailSo\Log\Enumerations\Type::WARNING, 'RSA');
}
}
// $aMatch = array();
// if ('rsa:xxx:' === \substr($sEncryptedData, 0, 8) && $this->Config()->Get('security', 'use_rsa_encryption', false))
// {
// $oLogger = $this->Logger();
// $oLogger->Write('Trying to decode encrypted data', \MailSo\Log\Enumerations\Type::INFO, 'RSA');
// $oLogger->HideErrorNotices(true);
//
// $sData = \trim(\substr($sEncryptedData, 8));
// $sData = \RainLoop\Utils::DecryptStringRSA(\base64_decode($sData));
//
// if (false !== $sData && \preg_match('/^[a-z0-9]{32}:(.+):[a-z0-9]{32}$/', $sData, $aMatch) && isset($aMatch[1]))
// {
// $sEncryptedData = $aMatch[1];
// }
// else
// {
// $oLogger->Write('Invalid decrypted data', \MailSo\Log\Enumerations\Type::WARNING, 'RSA');
// }
//
// $oLogger->HideErrorNotices(false);
// }
return $sEncryptedData;
}
@ -2290,6 +2287,7 @@ class Actions
1 < \count($aOrder['Accounts']))
{
$aAccounts = \array_merge(\array_flip($aOrder['Accounts']), $aAccounts);
$aAccounts = \array_filter($aAccounts, function ($sHash) {
return 5 < \strlen($sHash);
});
@ -2390,10 +2388,11 @@ class Actions
/**
* @param \RainLoop\Model\Account $oAccount
* @param bool $bAllowIdentities = false
*
* @return array
*/
public function GetIdentities($oAccount)
public function GetIdentities($oAccount, $bAllowIdentities = false)
{
$aIdentities = array();
if ($oAccount)
@ -2421,14 +2420,14 @@ class Actions
if ($oItem && $oItem->Validate())
{
if ('' === $oItem->Id())
if ($oItem->IsAccountIdentities())
{
$oItem->SetEmail($oAccount->Email());
$bHasAccountIdentity = true;
\array_unshift($aIdentities, $oItem);
\array_push($aIdentities, $oItem);
}
else
else if ($bAllowIdentities)
{
\array_push($aIdentities, $oItem);
}
@ -2442,7 +2441,7 @@ class Actions
\RainLoop\Model\Identity::NewInstanceFromAccount($oAccount));
}
if (1 < \count($aIdentities))
if (1 < \count($aIdentities) && $bAllowIdentities)
{
$sOrder = $this->StorageProvider()->Get($oAccount,
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG,
@ -2454,8 +2453,16 @@ class Actions
1 < \count($aOrder['Identities']))
{
$aList = $aOrder['Identities'];
foreach ($aList as $iIndex => $sItem)
{
if ('' === $sItem)
{
$aList[$iIndex] = '---';
}
}
\usort($aIdentities, function ($a, $b) use ($aList) {
return \array_search($a->Id(), $aList) < \array_search($b->Id(), $aList) ? -1 : 1;
return \array_search($a->Id(true), $aList) < \array_search($b->Id(true), $aList) ? -1 : 1;
});
}
}
@ -2527,14 +2534,20 @@ class Actions
/**
* @param \RainLoop\Model\Account $oAccount
* @param array $aIdentities = array()
* @param bool $bAllowIdentities = false
*
* @return array
*/
public function SetIdentities($oAccount, $aIdentities = array())
public function SetIdentities($oAccount, $aIdentities = array(), $bAllowIdentities = false)
{
$aResult = array();
foreach ($aIdentities as $oItem)
{
if (!$bAllowIdentities && $oItem && !$oItem->IsAccountIdentities())
{
continue;
}
$aResult[] = $oItem->ToSimpleJSON(false);
}
@ -2675,7 +2688,7 @@ class Actions
$oNewAccount->SetParentEmail($sParentEmail);
$aAccounts[$oNewAccount->Email()] = $oNewAccount->GetAuthToken();
if (0 === \strlen($oAccount->ParentEmail()))
if (!$oAccount->IsAdditionalAccount())
{
$aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken();
}
@ -2824,7 +2837,7 @@ class Actions
$mResult = array(
'Files' => array(array(
'FileName' => 'attachments.zip',
'Hash' => \RainLoop\Utils::EncodeKeyValues(array(
'Hash' => \RainLoop\Utils::EncodeKeyValuesQ(array(
'V' => APP_VERSION,
'Account' => $oAccount ? \md5($oAccount->Hash()) : '',
'FileName' => 'attachments.zip',
@ -2912,7 +2925,7 @@ class Actions
{
$mResult['Files'][] = array(
'FileName' => isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat',
'Hash' => \RainLoop\Utils::EncodeKeyValues($aItem)
'Hash' => \RainLoop\Utils::EncodeKeyValuesQ($aItem)
);
}
@ -2943,20 +2956,34 @@ class Actions
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument);
}
$aIdentitiesForSave = array();
$aIdentities = $this->GetIdentities($oAccount,
$this->GetCapa(false, \RainLoop\Enumerations\Capa::IDENTITIES, $oAccount));
$aIdentities = $this->GetIdentities($oAccount);
$bAdded = false;
$aIdentitiesForSave = array();
foreach ($aIdentities as $oItem)
{
if ($oItem && $oItem->Id() !== $oIdentity->Id())
if ($oItem)
{
if ($oItem->Id() === $oIdentity->Id())
{
$aIdentitiesForSave[] = $oIdentity;
$bAdded = true;
}
else
{
$aIdentitiesForSave[] = $oItem;
}
}
}
if (!$bAdded)
{
$aIdentitiesForSave[] = $oIdentity;
}
return $this->DefaultResponse(__FUNCTION__, $this->SetIdentities($oAccount, $aIdentitiesForSave));
return $this->DefaultResponse(__FUNCTION__, $this->SetIdentities($oAccount, $aIdentitiesForSave,
$this->GetCapa(false, \RainLoop\Enumerations\Capa::IDENTITIES, $oAccount)));
}
/**
@ -2968,6 +2995,11 @@ class Actions
{
$oAccount = $this->getAccountFromToken();
if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::IDENTITIES, $oAccount))
{
return $this->FalseResponse(__FUNCTION__);
}
$sId = \trim($this->GetActionParam('IdToDelete', ''));
if (empty($sId))
{
@ -2975,7 +3007,7 @@ class Actions
}
$aNew = array();
$aIdentities = $this->GetIdentities($oAccount);
$aIdentities = $this->GetIdentities($oAccount, $this->GetCapa(false, \RainLoop\Enumerations\Capa::IDENTITIES, $oAccount));
foreach ($aIdentities as $oItem)
{
if ($oItem && $sId !== $oItem->Id())
@ -2984,7 +3016,8 @@ class Actions
}
}
return $this->DefaultResponse(__FUNCTION__, $this->SetIdentities($oAccount, $aNew));
return $this->DefaultResponse(__FUNCTION__, $this->SetIdentities($oAccount, $aNew,
$this->GetCapa(false, \RainLoop\Enumerations\Capa::IDENTITIES, $oAccount)));
}
/**
@ -3110,7 +3143,7 @@ class Actions
$aAccounts = $this->GetActionParam('Accounts', null);
$aIdentities = $this->GetActionParam('Identities', null);
if (!\is_array($aAccounts) || !\is_array($aIdentities))
if (!\is_array($aAccounts) && !\is_array($aIdentities))
{
return $this->FalseResponse(__FUNCTION__);
}
@ -3118,8 +3151,8 @@ class Actions
return $this->DefaultResponse(__FUNCTION__, $this->StorageProvider()->Put($oAccount,
\RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'accounts_identities_order',
\json_encode(array(
'Accounts' => $aAccounts,
'Identities' => $aIdentities
'Accounts' => \is_array($aAccounts) ? $aAccounts : array(),
'Identities' => \is_array($aIdentities) ? $aIdentities : array()
))
));
}
@ -3134,6 +3167,7 @@ class Actions
$oAccount = $this->getAccountFromToken();
$mAccounts = false;
if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount))
{
$mAccounts = $this->GetAccounts($oAccount);
@ -3147,7 +3181,8 @@ class Actions
return $this->DefaultResponse(__FUNCTION__, array(
'Accounts' => $mAccounts,
'Identities' => $this->GetIdentities($oAccount)
'Identities' => $this->GetIdentities($oAccount,
$this->GetCapa(false, \RainLoop\Enumerations\Capa::IDENTITIES, $oAccount))
));
}
@ -3448,6 +3483,9 @@ class Actions
case \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS:
$this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_accounts', 'bool');
break;
case \RainLoop\Enumerations\Capa::IDENTITIES:
$this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_identities', 'bool');
break;
case \RainLoop\Enumerations\Capa::TEMPLATES:
$this->setConfigFromParams($oConfig, $sParamName, 'capa', 'templates', 'bool');
break;
@ -3560,6 +3598,7 @@ class Actions
});
$this->setCapaFromParams($oConfig, 'CapaAdditionalAccounts', \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS);
$this->setCapaFromParams($oConfig, 'CapaIdentities', \RainLoop\Enumerations\Capa::IDENTITIES);
$this->setCapaFromParams($oConfig, 'CapaTemplates', \RainLoop\Enumerations\Capa::TEMPLATES);
$this->setCapaFromParams($oConfig, 'CapaTwoFactorAuth', \RainLoop\Enumerations\Capa::TWO_FACTOR);
$this->setCapaFromParams($oConfig, 'CapaTwoFactorAuthForce', \RainLoop\Enumerations\Capa::TWO_FACTOR_FORCE);
@ -7128,19 +7167,19 @@ class Actions
try
{
$aAttachments = $this->GetActionParam('Attachments', array());
if (is_array($aAttachments) && 0 < count($aAttachments))
if (\is_array($aAttachments) && 0 < \count($aAttachments))
{
$mResult = array();
foreach ($aAttachments as $sAttachment)
{
$aValues = \RainLoop\Utils::DecodeKeyValues($sAttachment);
if (is_array($aValues))
$aValues = \RainLoop\Utils::DecodeKeyValuesQ($sAttachment);
if (\is_array($aValues))
{
$sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : '';
$iUid = (int) isset($aValues['Uid']) ? $aValues['Uid'] : 0;
$sMimeIndex = (string) isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : '';
$sTempName = md5($sAttachment);
$sTempName = \md5($sAttachment);
if (!$this->FilesProvider()->FileExists($oAccount, $sTempName))
{
$this->MailClient()->MessageMimeStream(
@ -7738,7 +7777,7 @@ class Actions
$aParams = $this->GetActionParam('Params', null);
$this->Http()->ServerNoCache();
$aData = \RainLoop\Utils::DecodeKeyValues($sRawKey);
$aData = \RainLoop\Utils::DecodeKeyValuesQ($sRawKey);
if (isset($aParams[0], $aParams[1], $aParams[2]) &&
'Raw' === $aParams[0] && 'FramedView' === $aParams[2] && isset($aData['Framed']) && $aData['Framed'] && $aData['FileName'])
{
@ -7842,6 +7881,11 @@ class Actions
$aResult[] = \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS;
}
if ($oConfig->Get('webmail', 'allow_additional_identities', false))
{
$aResult[] = \RainLoop\Enumerations\Capa::IDENTITIES;
}
if ($oConfig->Get('security', 'allow_two_factor_auth', false) &&
($bAdmin || ($oAccount && !$oAccount->IsAdditionalAccount())))
{
@ -8399,7 +8443,7 @@ class Actions
$bResult = false;
if (!empty($sRawKey))
{
$aValues = \RainLoop\Utils::DecodeKeyValues($sRawKey);
$aValues = \RainLoop\Utils::DecodeKeyValuesQ($sRawKey);
if (is_array($aValues))
{
$bResult = $aValues;
@ -9205,7 +9249,7 @@ class Actions
$sSubject = $mResult['Subject'];
$mResult['Hash'] = \md5($mResult['Folder'].$mResult['Uid']);
$mResult['RequestHash'] = \RainLoop\Utils::EncodeKeyValues(array(
$mResult['RequestHash'] = \RainLoop\Utils::EncodeKeyValuesQ(array(
'V' => APP_VERSION,
'Account' => $oAccount ? \md5($oAccount->Hash()) : '',
'Folder' => $mResult['Folder'],
@ -9270,7 +9314,7 @@ class Actions
if (!!$this->Config()->Get('labs', 'use_local_proxy_for_external_images', false))
{
$fAdditionalExternalFilter = function ($sUrl) {
return './?/ProxyExternal/'.\RainLoop\Utils::EncodeKeyValues(array(
return './?/ProxyExternal/'.\RainLoop\Utils::EncodeKeyValuesQ(array(
'Rnd' => \md5(\microtime(true)),
'Token' => \RainLoop\Utils::GetConnectionToken(),
'Url' => $sUrl
@ -9421,7 +9465,7 @@ class Actions
$mResult['IsThumbnail'] = $this->isFileHasThumbnail($mResult['FileName']);
}
$mResult['Download'] = \RainLoop\Utils::EncodeKeyValues(array(
$mResult['Download'] = \RainLoop\Utils::EncodeKeyValuesQ(array(
'V' => APP_VERSION,
'Account' => $oAccount ? \md5($oAccount->Hash()) : '',
'Folder' => $mResult['Folder'],

View file

@ -139,7 +139,8 @@ class Api
{
$sSsoHash = \MailSo\Base\Utils::Sha1Rand($sEmail.$sPassword);
return \RainLoop\Api::Actions()->Cacher()->Set(\RainLoop\KeyPathHelper::SsoCacherKey($sSsoHash), \RainLoop\Utils::EncodeKeyValues(array(
return \RainLoop\Api::Actions()->Cacher()->Set(\RainLoop\KeyPathHelper::SsoCacherKey($sSsoHash),
\RainLoop\Utils::EncodeKeyValuesQ(array(
'Email' => $sEmail,
'Password' => $sPassword,
'AdditionalOptions' => $aAdditionalOptions,

View file

@ -70,6 +70,7 @@ class Application extends \RainLoop\Config\AbstractConfig
'allow_languages_on_settings' => array(true, 'Allow language selection on settings screen'),
'allow_additional_accounts' => array(true, ''),
'allow_additional_identities' => array(true, ''),
'messages_per_page' => array(20, ' Number of messages displayed on page by default'),

View file

@ -16,6 +16,7 @@ class Capa
const FILTERS = 'FILTERS';
const ATTACHMENT_THUMBNAILS = 'ATTACHMENT_THUMBNAILS';
const ADDITIONAL_ACCOUNTS = 'ADDITIONAL_ACCOUNTS';
const IDENTITIES = 'IDENTITIES';
const TEMPLATES = 'TEMPLATES';
const AUTOLOGOUT = 'AUTOLOGOUT';
}

View file

@ -366,6 +366,26 @@ class Account extends \RainLoop\Account // for backward compatibility
));
}
/**
* @return string
*/
public function GetAuthTokenQ()
{
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 // timelife
));
}
/**
* @param \RainLoop\Plugins\Manager $oPlugins
* @param \MailSo\Mail\MailClient $oMailClient

View file

@ -75,11 +75,13 @@ class Identity
}
/**
* @param bool $bFillOnEmpty = false
*
* @return string
*/
public function Id()
public function Id($bFillOnEmpty = false)
{
return $this->sId;
return $bFillOnEmpty ? ('' === $this->sId ? '---' : $this->sId) : $this->sId;
}
/**
@ -192,4 +194,12 @@ class Identity
{
return !empty($this->sEmail);
}
/**
* @return bool
*/
public function IsAccountIdentities()
{
return '' === $this->Id();
}
}

View file

@ -429,7 +429,7 @@ class ServiceActions
{
$this->oActions->verifyCacheByKey($sData);
$aData = \RainLoop\Utils::DecodeKeyValues($sData);
$aData = \RainLoop\Utils::DecodeKeyValuesQ($sData);
if (\is_array($aData) && !empty($aData['Token']) && !empty($aData['Url']) && $aData['Token'] === \RainLoop\Utils::GetConnectionToken())
{
$iCode = 404;
@ -892,7 +892,7 @@ class ServiceActions
$sSsoSubData = $this->Cacher()->Get(\RainLoop\KeyPathHelper::SsoCacherKey($sSsoHash));
if (!empty($sSsoSubData))
{
$mData = \RainLoop\Utils::DecodeKeyValues($sSsoSubData);
$mData = \RainLoop\Utils::DecodeKeyValuesQ($sSsoSubData);
$this->Cacher()->Delete(\RainLoop\KeyPathHelper::SsoCacherKey($sSsoHash));
if (\is_array($mData) && !empty($mData['Email']) && isset($mData['Password'], $mData['Time']) &&

View file

@ -6,6 +6,10 @@ class Utils
{
static $Cookies = null;
static $RSA = null;
static $RsaKey = null;
/**
* @return void
*/
@ -31,6 +35,145 @@ class Utils
return false;
}
/**
* @return \Crypt_RSA|null
*/
static public function CryptRSA()
{
if (null === \RainLoop\Utils::$RSA)
{
if (!\defined('_phpseclib_'))
{
\set_include_path(\get_include_path().PATH_SEPARATOR.APP_VERSION_ROOT_PATH.'app/libraries/phpseclib');
define('_phpseclib_', true);
}
if (!\class_exists('Crypt_RSA'))
{
include_once 'Crypt/RSA.php';
\defined('CRYPT_RSA_MODE') || \define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
}
if (\class_exists('Crypt_RSA'))
{
$oRsa = new \Crypt_RSA();
$oRsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$oRsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
$oRsa->setPrivateKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$sPrivateKey = \file_exists(APP_PRIVATE_DATA.'rsa/private') ?
\file_get_contents(APP_PRIVATE_DATA.'rsa/private') : '';
if (!empty($sPrivateKey))
{
$oRsa->loadKey($sPrivateKey, CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
$oRsa->loadKey($oRsa->getPublicKey(), CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
\RainLoop\Utils::$RSA = $oRsa;
}
}
}
return \RainLoop\Utils::$RSA;
}
/**
* @return string
*/
static public function RsaPrivateKey()
{
if (!empty(\RainLoop\Utils::$RsaKey))
{
return \RainLoop\Utils::$RsaKey;
}
\RainLoop\Utils::$RsaKey = \file_exists(APP_PRIVATE_DATA.'rsa/private') ?
\file_get_contents(APP_PRIVATE_DATA.'rsa/private') : '';
\RainLoop\Utils::$RsaKey = \is_string(\RainLoop\Utils::$RsaKey) ? \RainLoop\Utils::$RsaKey : '';
}
/**
* @param string $sString
* @param string $sKey = ''
*
* @return string|false
*/
static public function EncryptStringRSA($sString, $sKey = '')
{
$sResult = '';
$sKey = \md5($sKey);
$sPrivateKey = \RainLoop\Utils::RsaPrivateKey();
if (!empty($sPrivateKey))
{
$oPrivKey = \openssl_pkey_get_private($sPrivateKey);
$oKeyDetails = \openssl_pkey_get_details($oPrivKey);
if (!empty($oKeyDetails['key']) && !empty($oKeyDetails['bits']))
{
$oPubKey = \openssl_pkey_get_public($oKeyDetails['key']);
$iC = (($oKeyDetails['bits'] / 8) - 15);
$aString = \str_split($sString, $iC);
foreach ($aString as $iIndex => $sLine)
{
$sEncrypted = '';
\openssl_public_encrypt($sLine, $sEncrypted, $oPubKey);
$aString[$iIndex] = $sEncrypted;
}
$aString[] = $sKey;
$sResult = @\serialize($aString);
\openssl_free_key($oPubKey);
}
\openssl_free_key($oPrivKey);
}
return $sResult;
}
/**
* @param string $sString
* @param string $sKey = ''
*
* @return string|false
*/
static public function DecryptStringRSA($sString, $sKey = '')
{
$sResult = '';
$sKey = \md5($sKey);
$sPrivateKey = \RainLoop\Utils::RsaPrivateKey();
if (!empty($sPrivateKey) && !empty($sString))
{
$oPrivKey = \openssl_pkey_get_private($sPrivateKey);
$aString = @\unserialize($sString);
if (\is_array($aString))
{
if ($sKey === \array_pop($aString))
{
foreach ($aString as $iIndex => $sLine)
{
$sDecrypted = '';
\openssl_private_decrypt($sLine, $sDecrypted, $oPrivKey);
$aString[$iIndex] = $sDecrypted;
}
$sResult = \implode('', $aString);
}
}
\openssl_free_key($oPrivKey);
}
return $sResult;
}
/**
* @param string $sString
@ -54,6 +197,42 @@ class Utils
return \MailSo\Base\Crypt::XxteaDecrypt($sEncriptedString, $sKey);
}
/**
* @param string $sString
* @param string $sKey
*
* @return string
*/
static public function EncryptStringQ($sString, $sKey)
{
// if (\MailSo\Base\Utils::FunctionExistsAndEnabled('openssl_pkey_get_private'))
// {
// return \RainLoop\Utils::EncryptStringRSA($sString,
// $sKey.'Q'.\RainLoop\Utils::GetShortToken());
// }
return \MailSo\Base\Crypt::XxteaEncrypt($sString,
$sKey.'Q'.\RainLoop\Utils::GetShortToken());
}
/**
* @param string $sEncriptedString
* @param string $sKey
*
* @return string
*/
static public function DecryptStringQ($sEncriptedString, $sKey)
{
// if (\MailSo\Base\Utils::FunctionExistsAndEnabled('openssl_pkey_get_private'))
// {
// return \RainLoop\Utils::DecryptStringRSA($sEncriptedString,
// $sKey.'Q'.\RainLoop\Utils::GetShortToken());
// }
return \MailSo\Base\Crypt::XxteaDecrypt($sEncriptedString,
$sKey.'Q'.\RainLoop\Utils::GetShortToken());
}
/**
* @param array $aValues
* @param string $sCustomKey = ''
@ -63,7 +242,7 @@ class Utils
static public function EncodeKeyValues(array $aValues, $sCustomKey = '')
{
return \MailSo\Base\Utils::UrlSafeBase64Encode(
\RainLoop\Utils::EncryptString(\serialize($aValues), \md5(APP_SALT.$sCustomKey)));
\RainLoop\Utils::EncryptString(@\serialize($aValues), \md5(APP_SALT.$sCustomKey)));
}
/**
@ -74,13 +253,41 @@ class Utils
*/
static public function DecodeKeyValues($sEncodedValues, $sCustomKey = '')
{
$aResult = \unserialize(
$aResult = @\unserialize(
\RainLoop\Utils::DecryptString(
\MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedValues), \md5(APP_SALT.$sCustomKey)));
return \is_array($aResult) ? $aResult : array();
}
/**
* @param array $aValues
* @param string $sCustomKey = ''
*
* @return string
*/
static public function EncodeKeyValuesQ(array $aValues, $sCustomKey = '')
{
return \MailSo\Base\Utils::UrlSafeBase64Encode(
\RainLoop\Utils::EncryptStringQ(
@\serialize($aValues), \md5(APP_SALT.$sCustomKey)));
}
/**
* @param string $sEncodedValues
* @param string $sCustomKey = ''
*
* @return array
*/
static public function DecodeKeyValuesQ($sEncodedValues, $sCustomKey = '')
{
$aResult = @\unserialize(
\RainLoop\Utils::DecryptStringQ(
\MailSo\Base\Utils::UrlSafeBase64Decode($sEncodedValues), \md5(APP_SALT.$sCustomKey)));
return \is_array($aResult) ? $aResult : array();
}
/**
* @return string
*/
@ -296,33 +503,33 @@ class Utils
*/
public static function GetCookie($sName, $mDefault = null)
{
if (null === self::$Cookies)
if (null === \RainLoop\Utils::$Cookies)
{
self::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
\RainLoop\Utils::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
}
return isset(self::$Cookies[$sName]) ? self::$Cookies[$sName] : $mDefault;
return isset(\RainLoop\Utils::$Cookies[$sName]) ? \RainLoop\Utils::$Cookies[$sName] : $mDefault;
}
public static function SetCookie($sName, $sValue = '', $iExpire = 0, $sPath = '/', $sDomain = '', $sSecure = false, $bHttpOnly = false)
{
if (null === self::$Cookies)
if (null === \RainLoop\Utils::$Cookies)
{
self::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
\RainLoop\Utils::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
}
self::$Cookies[$sName] = $sValue;
\RainLoop\Utils::$Cookies[$sName] = $sValue;
@\setcookie($sName, $sValue, $iExpire, $sPath, $sDomain, $sSecure, $bHttpOnly);
}
public static function ClearCookie($sName)
{
if (null === self::$Cookies)
if (null === \RainLoop\Utils::$Cookies)
{
self::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
\RainLoop\Utils::$Cookies = is_array($_COOKIE) ? $_COOKIE : array();
}
unset(self::$Cookies[$sName]);
unset(\RainLoop\Utils::$Cookies[$sName]);
@\setcookie($sName, '', \time() - 3600 * 24 * 30, '/');
}

View file

@ -56,7 +56,7 @@
* @category Crypt
* @package Crypt_AES
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVIII Jim Wigginton
* @copyright 2008 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -152,4 +152,56 @@ class Crypt_AES extends Crypt_Rijndael
{
return;
}
/**
* Sets the key length
*
* Valid key lengths are 128, 192, and 256. If the length is less than 128, it will be rounded up to
* 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount.
*
* @see Crypt_Rijndael:setKeyLength()
* @access public
* @param Integer $length
*/
function setKeyLength($length)
{
switch ($length) {
case 160:
$length = 192;
break;
case 224:
$length = 256;
}
parent::setKeyLength($length);
}
/**
* Sets the key.
*
* Rijndael supports five different key lengths, AES only supports three.
*
* @see Crypt_Rijndael:setKey()
* @see setKeyLength()
* @access public
* @param String $key
*/
function setKey($key)
{
parent::setKey($key);
if (!$this->explicit_key_length) {
$length = strlen($key);
switch (true) {
case $length <= 16:
$this->key_size = 16;
break;
case $length <= 24:
$this->key_size = 24;
break;
default:
$this->key_size = 32;
}
$this->_setupEngine();
}
}
}

View file

@ -47,7 +47,7 @@
* @package Crypt_Base
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -48,7 +48,7 @@
* @package Crypt_Blowfish
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -53,7 +53,7 @@
* @category Crypt
* @package Crypt_DES
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -5,7 +5,7 @@
*
* Uses hash() or mhash() if available and an internal implementation, otherwise. Currently supports the following:
*
* md2, md5, md5-96, sha1, sha1-96, sha256, sha384, and sha512
* md2, md5, md5-96, sha1, sha1-96, sha256, sha256-96, sha384, and sha512, sha512-96
*
* If {@link Crypt_Hash::setKey() setKey()} is called, {@link Crypt_Hash::hash() hash()} will return the HMAC as opposed to
* the hash. If no valid algorithm is provided, sha1 will be used.
@ -49,7 +49,7 @@
* @category Crypt
* @package Crypt_Hash
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -207,6 +207,9 @@ class Crypt_Hash
switch ($hash) {
case 'md5-96':
case 'sha1-96':
case 'sha256-96':
case 'sha512-96':
$hash = substr($hash, 0, -3);
$this->l = 12; // 96 / 8 = 12
break;
case 'md2':
@ -243,14 +246,12 @@ class Crypt_Hash
case CRYPT_HASH_MODE_MHASH:
switch ($hash) {
case 'md5':
case 'md5-96':
$this->hash = MHASH_MD5;
break;
case 'sha256':
$this->hash = MHASH_SHA256;
break;
case 'sha1':
case 'sha1-96':
default:
$this->hash = MHASH_SHA1;
}
@ -258,7 +259,6 @@ class Crypt_Hash
case CRYPT_HASH_MODE_HASH:
switch ($hash) {
case 'md5':
case 'md5-96':
$this->hash = 'md5';
return;
case 'md2':
@ -268,7 +268,6 @@ class Crypt_Hash
$this->hash = $hash;
return;
case 'sha1':
case 'sha1-96':
default:
$this->hash = 'sha1';
}
@ -281,7 +280,6 @@ class Crypt_Hash
$this->hash = array($this, '_md2');
break;
case 'md5':
case 'md5-96':
$this->b = 64;
$this->hash = array($this, '_md5');
break;
@ -295,7 +293,6 @@ class Crypt_Hash
$this->hash = array($this, '_sha512');
break;
case 'sha1':
case 'sha1-96':
default:
$this->b = 64;
$this->hash = array($this, '_sha1');

View file

@ -55,7 +55,7 @@
* @category Crypt
* @package Crypt_RC4
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -62,7 +62,7 @@
* @category Crypt
* @package Crypt_RSA
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMIX Jim Wigginton
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -493,14 +493,13 @@ class Crypt_RSA
$this->configFile = CRYPT_RSA_OPENSSL_CONFIG;
if ( !defined('CRYPT_RSA_MODE') ) {
switch (true) {
// Math/BigInteger's openssl requirements are a little less stringent than Crypt/RSA's. in particular,
// Math/BigInteger doesn't require an openssl.cfg file whereas Crypt/RSA does. so if Math/BigInteger
// can't use OpenSSL it can be pretty trivially assumed, then, that Crypt/RSA can't either.
if ( defined('MATH_BIGINTEGER_OPENSSL_DISABLE') ) {
case defined('MATH_BIGINTEGER_OPENSSL_DISABLE'):
define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
}
switch ( !defined('CRYPT_RSA_MODE') ) { // ie. only run this if the above didn't set CRYPT_RSA_MODE already
break;
// openssl_pkey_get_details - which is used in the only place Crypt/RSA.php uses OpenSSL - was introduced in PHP 5.2.0
case !function_exists('openssl_pkey_get_details'):
define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
@ -517,7 +516,14 @@ class Crypt_RSA
$versions = array();
if (!empty($matches[1])) {
for ($i = 0; $i < count($matches[1]); $i++) {
$versions[$matches[1][$i]] = trim(str_replace('=>', '', strip_tags($matches[2][$i])));
$fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i])));
// Remove letter part in OpenSSL version
if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) {
$versions[$matches[1][$i]] = $fullVersion;
} else {
$versions[$matches[1][$i]] = $m[0];
}
}
}
@ -533,7 +539,7 @@ class Crypt_RSA
define('MATH_BIGINTEGER_OPENSSL_DISABLE', true);
}
break;
case true:
default:
define('CRYPT_RSA_MODE', CRYPT_RSA_MODE_INTERNAL);
}
}
@ -743,17 +749,18 @@ class Crypt_RSA
*/
function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients)
{
$signed = $this->privateKeyFormat != CRYPT_RSA_PRIVATE_FORMAT_XML;
$num_primes = count($primes);
$raw = array(
'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi
'modulus' => $n->toBytes(true),
'publicExponent' => $e->toBytes(true),
'privateExponent' => $d->toBytes(true),
'prime1' => $primes[1]->toBytes(true),
'prime2' => $primes[2]->toBytes(true),
'exponent1' => $exponents[1]->toBytes(true),
'exponent2' => $exponents[2]->toBytes(true),
'coefficient' => $coefficients[2]->toBytes(true)
'modulus' => $n->toBytes($signed),
'publicExponent' => $e->toBytes($signed),
'privateExponent' => $d->toBytes($signed),
'prime1' => $primes[1]->toBytes($signed),
'prime2' => $primes[2]->toBytes($signed),
'exponent1' => $exponents[1]->toBytes($signed),
'exponent2' => $exponents[2]->toBytes($signed),
'coefficient' => $coefficients[2]->toBytes($signed)
);
// if the format in question does not support multi-prime rsa and multi-prime rsa was used,
@ -942,8 +949,10 @@ class Crypt_RSA
*/
function _convertPublicKey($n, $e)
{
$modulus = $n->toBytes(true);
$publicExponent = $e->toBytes(true);
$signed = $this->publicKeyFormat != CRYPT_RSA_PUBLIC_FORMAT_XML;
$modulus = $n->toBytes($signed);
$publicExponent = $e->toBytes($signed);
switch ($this->publicKeyFormat) {
case CRYPT_RSA_PUBLIC_FORMAT_RAW:

View file

@ -38,7 +38,7 @@
* @category Crypt
* @package Crypt_Random
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -65,7 +65,7 @@
* @category Crypt
* @package Crypt_Rijndael
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVIII Jim Wigginton
* @copyright 2008 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -702,9 +702,15 @@ class Crypt_Rijndael extends Crypt_Base
case $length <= 16:
$this->key_size = 16;
break;
case $length <= 20:
$this->key_size = 20;
break;
case $length <= 24:
$this->key_size = 24;
break;
case $length <= 28:
$this->key_size = 28;
break;
default:
$this->key_size = 32;
}

View file

@ -47,7 +47,7 @@
* @category Crypt
* @package Crypt_TripleDES
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -48,7 +48,7 @@
* @package Crypt_Twofish
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -31,7 +31,7 @@
* @category File
* @package File_ANSI
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMXII Jim Wigginton
* @copyright 2012 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -34,7 +34,7 @@
* @category File
* @package File_ASN1
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMXII Jim Wigginton
* @copyright 2012 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -272,7 +272,8 @@ class File_ASN1
}
$this->encoded = $encoded;
return $this->_decode_ber($encoded);
// encapsulate in an array for BC with the old decodeBER
return array($this->_decode_ber($encoded));
}
/**
@ -287,11 +288,8 @@ class File_ASN1
* @return Array
* @access private
*/
function _decode_ber(&$encoded, $start = 0)
function _decode_ber($encoded, $start = 0)
{
$decoded = array();
while ( strlen($encoded) ) {
$current = array('start' => $start);
$type = ord($this->_string_shift($encoded));
@ -317,16 +315,13 @@ class File_ASN1
if ( $length == 0x80 ) { // indefinite length
// "[A sender shall] use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all
// immediately available." -- paragraph 8.1.3.2.c
//if ( !$constructed ) {
// return false;
//}
$length = strlen($encoded);
} elseif ( $length & 0x80 ) { // definite length, long form
// technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
// support it up to four.
$length&= 0x7F;
$temp = $this->_string_shift($encoded, $length);
// tags of indefinite length don't really have a header length; this length includes the tag
// tags of indefinte length don't really have a header length; this length includes the tag
$current+= array('headerlength' => $length + 2);
$start+= $length;
extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
@ -334,12 +329,10 @@ class File_ASN1
$current+= array('headerlength' => 2);
}
// End-of-content, see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
if (!$type && !$length) {
return $decoded;
}
$content = $this->_string_shift($encoded, $length);
// at this point $length can be overwritten. it's only accurate for definite length things as is
/* Class is UNIVERSAL, APPLICATION, PRIVATE, or CONTEXT-SPECIFIC. The UNIVERSAL class is restricted to the ASN.1
built-in types. It defines an application-independent data type that must be distinguishable from all other
data types. The other three classes are user defined. The APPLICATION class distinguishes data types that
@ -354,14 +347,36 @@ class File_ASN1
case FILE_ASN1_CLASS_APPLICATION:
case FILE_ASN1_CLASS_PRIVATE:
case FILE_ASN1_CLASS_CONTEXT_SPECIFIC:
$decoded[] = array(
if (!$constructed) {
return array(
'type' => $class,
'constant' => $tag,
'content' => $constructed ? $this->_decode_ber($content, $start) : $content,
'content' => $content,
'length' => $length + $start - $current['start']
) + $current;
);
}
$newcontent = array();
if (strlen($content)) {
$newcontent = $this->_decode_ber($content, $start);
$length = $newcontent['length'];
if (substr($content, $length, 2) == "\0\0") {
$length+= 2;
}
$start+= $length;
continue 2;
$newcontent = array($newcontent);
}
return array(
'type' => $class,
'constant' => $tag,
// the array encapsulation is for BC with the old format
'content' => $newcontent,
// the only time when $content['headerlength'] isn't defined is when the length is indefinite.
// the absence of $content['headerlength'] is how we know if something is indefinite or not.
// technically, it could be defined to be 2 and then another indicator could be used but whatever.
'length' => $start - $current['start']
) + $current;
}
$current+= array('type' => $tag);
@ -409,16 +424,21 @@ class File_ASN1
if (!$constructed) {
$current['content'] = $content;
} else {
$temp = $this->_decode_ber($content, $start);
$length-= strlen($content);
for ($i = 0, $size = count($temp); $i < $size; $i++) {
$current['content'] = '';
$length = 0;
while (substr($content, 0, 2) != "\0\0") {
$temp = $this->_decode_ber($content, $length + $start);
$this->_string_shift($content, $temp['length']);
// all subtags should be octet strings
//if ($temp[$i]['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
//if ($temp['type'] != FILE_ASN1_TYPE_OCTET_STRING) {
// return false;
//}
$current['content'].= $temp[$i]['content'];
$current['content'].= $temp['content'];
$length+= $temp['length'];
}
if (substr($content, 0, 2) == "\0\0") {
$length+= 2; // +2 for the EOC
}
// $length =
}
break;
case FILE_ASN1_TYPE_NULL:
@ -429,7 +449,20 @@ class File_ASN1
break;
case FILE_ASN1_TYPE_SEQUENCE:
case FILE_ASN1_TYPE_SET:
$current['content'] = $this->_decode_ber($content, $start);
$offset = 0;
$current['content'] = array();
while (strlen($content)) {
// if indefinite length construction was used and we have an end-of-content string next
// see paragraphs 8.1.1.3, 8.1.3.2, 8.1.3.6, 8.1.5, and (for an example) 8.6.4.2
if (!isset($current['headerlength']) && substr($content, 0, 2) == "\0\0") {
$length = $offset + 2; // +2 for the EOC
break 2;
}
$temp = $this->_decode_ber($content, $start + $offset);
$this->_string_shift($content, $temp['length']);
$current['content'][] = $temp;
$offset+= $temp['length'];
}
break;
case FILE_ASN1_TYPE_OBJECT_IDENTIFIER:
$temp = ord($this->_string_shift($content));
@ -484,18 +517,16 @@ class File_ASN1
case FILE_ASN1_TYPE_GENERALIZED_TIME:
$current['content'] = $this->_decodeTime($content, $tag);
default:
}
$start+= $length;
$decoded[] = $current + array('length' => $start - $current['start']);
}
return $decoded;
// ie. length is the length of the full TLV encoding - it's not just the length of the value
return $current + array('length' => $start - $current['start']);
}
/**
* ASN.1 Decode
* ASN.1 Map
*
* Provides an ASN.1 semantic mapping ($mapping) from a parsed BER-encoding to a human readable format.
*
@ -805,16 +836,6 @@ class File_ASN1
return $this->_encode_der($source, $mapping, null, $special);
}
/**
* ASN.1 Encode (Helper function)
*
* @param String $source
* @param Array $mapping
* @param Integer $idx
* @param Array $special
* @return String
* @access private
*/
/**
* ASN.1 Encode (Helper function)
*

View file

@ -37,7 +37,7 @@
* @category File
* @package File_X509
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMXII Jim Wigginton
* @copyright 2012 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/

View file

@ -63,7 +63,7 @@
* @category Math
* @package Math_BigInteger
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVI Jim Wigginton
* @copyright 2006 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://pear.php.net/package/Math_BigInteger
*/
@ -278,7 +278,14 @@ class Math_BigInteger
$versions = array();
if (!empty($matches[1])) {
for ($i = 0; $i < count($matches[1]); $i++) {
$versions[$matches[1][$i]] = trim(str_replace('=>', '', strip_tags($matches[2][$i])));
$fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i])));
// Remove letter part in OpenSSL version
if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) {
$versions[$matches[1][$i]] = $fullVersion;
} else {
$versions[$matches[1][$i]] = $m[0];
}
}
}
@ -329,7 +336,10 @@ class Math_BigInteger
switch ( MATH_BIGINTEGER_MODE ) {
case MATH_BIGINTEGER_MODE_GMP:
if (is_resource($x) && get_resource_type($x) == 'GMP integer') {
switch (true) {
case is_resource($x) && get_resource_type($x) == 'GMP integer':
// PHP 5.6 switched GMP from using resources to objects
case is_object($x) && get_class($x) == 'GMP':
$this->value = $x;
return;
}
@ -912,7 +922,7 @@ class Math_BigInteger
$value = $x_value;
}
$value[] = 0; // just in case the carry adds an extra digit
$value[count($value)] = 0; // just in case the carry adds an extra digit
$carry = 0;
for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) {
@ -2134,7 +2144,7 @@ class Math_BigInteger
if ($this->_compare($result, false, $temp[MATH_BIGINTEGER_VALUE], $temp[MATH_BIGINTEGER_SIGN]) < 0) {
$corrector_value = $this->_array_repeat(0, $n_length + 1);
$corrector_value[] = 1;
$corrector_value[count($corrector_value)] = 1;
$result = $this->_add($result, false, $corrector_value, false);
$result = $result[MATH_BIGINTEGER_VALUE];
}
@ -2904,7 +2914,7 @@ class Math_BigInteger
$leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3);
$this->_base256_lshift($leading_ones, $current_bits);
$temp = str_pad($temp, ceil($this->bits / 8), chr(0), STR_PAD_LEFT);
$temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT);
return $this->_normalize(new Math_BigInteger($leading_ones | $temp, 256));
}
@ -3479,7 +3489,7 @@ class Math_BigInteger
}
if ( $carry ) {
$this->value[] = $carry;
$this->value[count($this->value)] = $carry;
}
while ($num_digits--) {

View file

@ -44,7 +44,7 @@
* @category Net
* @package Net_SCP
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMX Jim Wigginton
* @copyright 2010 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -129,7 +129,7 @@ class Net_SCP
}
switch (strtolower(get_class($ssh))) {
case'net_ssh2':
case 'net_ssh2':
$this->mode = NET_SCP_SSH2;
break;
case 'net_ssh1':
@ -170,7 +170,7 @@ class Net_SCP
return false;
}
if (!$this->ssh->exec('scp -t "' . $remote_file . '"', false)) { // -t = to
if (!$this->ssh->exec('scp -t ' . escapeshellarg($remote_file), false)) { // -t = to
return false;
}
@ -195,7 +195,6 @@ class Net_SCP
$fp = @fopen($data, 'rb');
if (!$fp) {
fclose($fp);
return false;
}
$size = filesize($data);
@ -245,7 +244,7 @@ class Net_SCP
return false;
}
if (!$this->ssh->exec('scp -f "' . $remote_file . '"', false)) { // -f = from
if (!$this->ssh->exec('scp -f ' . escapeshellarg($remote_file), false)) { // -f = from
return false;
}

View file

@ -48,7 +48,7 @@
* @category Net
* @package Net_SFTP
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMIX Jim Wigginton
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -1061,12 +1061,14 @@ class Net_SFTP extends Net_SSH2
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
$temp = &$this->stat_cache;
foreach ($dirs as $dir) {
$max = count($dirs) - 1;
foreach ($dirs as $i=>$dir) {
if (!isset($temp[$dir])) {
$temp[$dir] = array();
}
if ($dir == end($dirs)) {
if ($i === $max) {
$temp[$dir] = $value;
break;
}
$temp = &$temp[$dir];
}
@ -1084,8 +1086,9 @@ class Net_SFTP extends Net_SSH2
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
$temp = &$this->stat_cache;
foreach ($dirs as $dir) {
if ($dir == end($dirs)) {
$max = count($dirs) - 1;
foreach ($dirs as $i=>$dir) {
if ($i === $max) {
unset($temp[$dir]);
return true;
}
@ -1501,7 +1504,7 @@ class Net_SFTP extends Net_SSH2
return false;
}
$i = 0;
$entries = $this->_list($path, true, false);
$entries = $this->_list($path, true);
if ($entries === false) {
return $this->_setstat($path, $attr, false);
@ -1513,11 +1516,8 @@ class Net_SFTP extends Net_SSH2
return false;
}
unset($entries['.'], $entries['..']);
foreach ($entries as $filename=>$props) {
if ($filename == '.' || $filename == '..') {
continue;
}
if (!isset($props['type'])) {
return false;
}
@ -1757,6 +1757,8 @@ class Net_SFTP extends Net_SSH2
* contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
* large $remote_file will be, as well.
*
* If $data is a resource then it'll be used as a resource instead.
*
* Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
* care of that, yourself.
*
@ -1778,7 +1780,7 @@ class Net_SFTP extends Net_SSH2
* Setting $local_start to > 0 or $mode | NET_SFTP_RESUME_START doesn't do anything unless $mode | NET_SFTP_LOCAL_FILE.
*
* @param String $remote_file
* @param String $data
* @param String|resource $data
* @param optional Integer $mode
* @param optional Integer $start
* @param optional Integer $local_start
@ -1834,7 +1836,12 @@ class Net_SFTP extends Net_SSH2
}
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
if ($mode & NET_SFTP_LOCAL_FILE) {
switch (true) {
case is_resource($data):
$mode = $mode & ~NET_SFTP_LOCAL_FILE;
$fp = $data;
break;
case $mode & NET_SFTP_LOCAL_FILE:
if (!is_file($data)) {
user_error("$data is not a valid file");
return false;
@ -1843,7 +1850,11 @@ class Net_SFTP extends Net_SSH2
if (!$fp) {
return false;
}
$size = filesize($data);
}
if (isset($fp)) {
$stat = fstat($fp);
$size = $stat['size'];
if ($local_start >= 0) {
fseek($fp, $local_start);
@ -1864,11 +1875,13 @@ class Net_SFTP extends Net_SSH2
$sftp_packet_size-= strlen($handle) + 25;
$i = 0;
while ($sent < $size) {
$temp = $mode & NET_SFTP_LOCAL_FILE ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
$temp = isset($fp) ? fread($fp, $sftp_packet_size) : substr($data, $sent, $sftp_packet_size);
$subtemp = $offset + $sent;
$packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
if ($mode & NET_SFTP_LOCAL_FILE) {
fclose($fp);
}
return false;
}
$sent+= strlen($temp);
@ -2003,6 +2016,12 @@ class Net_SFTP extends Net_SSH2
return false;
}
if (is_resource($local_file)) {
$fp = $local_file;
$stat = fstat($fp);
$res_offset = $stat['size'];
} else {
$res_offset = 0;
if ($local_file !== false) {
$fp = fopen($local_file, 'wb');
if (!$fp) {
@ -2011,13 +2030,16 @@ class Net_SFTP extends Net_SSH2
} else {
$content = '';
}
}
$fclose_check = $local_file !== false && !is_resource($local_file);
$start = $offset;
$size = $this->max_sftp_packet < $length || $length < 0 ? $this->max_sftp_packet : $length;
while (true) {
$packet = pack('Na*N3', strlen($handle), $handle, $offset / 4294967296, $offset, $size);
if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
if ($local_file !== false) {
if ($fclose_check) {
fclose($fp);
}
return false;
@ -2040,7 +2062,7 @@ class Net_SFTP extends Net_SSH2
break 2;
default:
user_error('Expected SSH_FXP_DATA or SSH_FXP_STATUS');
if ($local_file !== false) {
if ($fclose_check) {
fclose($fp);
}
return false;
@ -2055,11 +2077,11 @@ class Net_SFTP extends Net_SSH2
if ($local_file === false) {
$content = substr($content, 0, $length);
} else {
ftruncate($fp, $length);
ftruncate($fp, $length + $res_offset);
}
}
if ($local_file !== false) {
if ($fclose_check) {
fclose($fp);
}
@ -2135,7 +2157,7 @@ class Net_SFTP extends Net_SSH2
return false;
}
$i = 0;
$entries = $this->_list($path, true, false);
$entries = $this->_list($path, true);
// normally $entries would have at least . and .. but it might not if the directories
// permissions didn't allow reading
@ -2143,11 +2165,8 @@ class Net_SFTP extends Net_SSH2
return false;
}
unset($entries['.'], $entries['..']);
foreach ($entries as $filename=>$props) {
if ($filename == '.' || $filename == '..') {
continue;
}
if (!isset($props['type'])) {
return false;
}
@ -2205,7 +2224,7 @@ class Net_SFTP extends Net_SSH2
$result = $this->_query_stat_cache($path);
if (isset($result)) {
// return true if $result is an array or if it's int(1)
// return true if $result is an array or if it's an stdClass object
return $result !== false;
}
}
@ -2348,7 +2367,7 @@ class Net_SFTP extends Net_SSH2
}
switch ($type) {
case NET_SFTP_BLOCK_DEVICE: return 'block';
case NET_SFTP_TYPE_BLOCK_DEVICE: return 'block';
case NET_SFTP_TYPE_CHAR_DEVICE: return 'char';
case NET_SFTP_TYPE_DIRECTORY: return 'dir';
case NET_SFTP_TYPE_FIFO: return 'fifo';
@ -2454,14 +2473,13 @@ class Net_SFTP extends Net_SSH2
foreach ($this->attributes as $key => $value) {
switch ($flags & $key) {
case NET_SFTP_ATTR_SIZE: // 0x00000001
// size is represented by a 64-bit integer, so we perhaps ought to be doing the following:
// $attr['size'] = new Math_BigInteger($this->_string_shift($response, 8), 256);
// of course, you shouldn't be using Net_SFTP to transfer files that are in excess of 4GB
// (0xFFFFFFFF bytes), anyway. as such, we'll just represent all file sizes that are bigger than
// 4GB as being 4GB.
extract(unpack('Nupper/Nsize', $this->_string_shift($response, 8)));
$attr['size'] = $upper ? 4294967296 * $upper : 0;
$attr['size']+= $size < 0 ? ($size & 0x7FFFFFFF) + 0x80000000 : $size;
// The size attribute is defined as an unsigned 64-bit integer.
// The following will use floats on 32-bit platforms, if necessary.
// As can be seen in the BigInteger class, floats are generally
// IEEE 754 binary64 "double precision" on such platforms and
// as such can represent integers of at least 2^50 without loss
// of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
$attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8)));
break;
case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
$attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
@ -2521,7 +2539,7 @@ class Net_SFTP extends Net_SSH2
case 0020000: // character special
return NET_SFTP_TYPE_CHAR_DEVICE;
case 0060000: // block special
return NET_SFTP_BLOCK_DEVICE;
return NET_SFTP_TYPE_BLOCK_DEVICE;
case 0140000: // socket
return NET_SFTP_TYPE_SOCKET;
case 0160000: // whiteout

View file

@ -28,7 +28,7 @@
* @category Net
* @package Net_SFTP_Stream
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMXIII Jim Wigginton
* @copyright 2013 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -48,7 +48,6 @@ class Net_SFTP_Stream
* Rather than re-create the connection we re-use instances if possible
*
* @var Array
* @access static
*/
static $instances;

View file

@ -59,7 +59,7 @@
* @category Net
* @package Net_SSH1
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -1128,6 +1128,7 @@ class Net_SSH1
$padding_length = 8 - ($temp['length'] & 7);
$length = $temp['length'] + $padding_length;
$raw = '';
while ($length > 0) {
$temp = fread($this->fsock, $length);

View file

@ -61,7 +61,7 @@
* @category Net
* @package Net_SSH2
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
@ -77,7 +77,7 @@ define('NET_SSH2_MASK_CONNECTED', 0x00000002);
define('NET_SSH2_MASK_LOGIN_REQ', 0x00000004);
define('NET_SSH2_MASK_LOGIN', 0x00000008);
define('NET_SSH2_MASK_SHELL', 0x00000010);
define('NET_SSH2_MASK_WINDOW_ADJUST', 0X00000020);
define('NET_SSH2_MASK_WINDOW_ADJUST', 0x00000020);
/**#@-*/
/**#@+
@ -191,100 +191,100 @@ class Net_SSH2
* Server Identifier
*
* @see Net_SSH2::getServerIdentification()
* @var String
* @var mixed false or Array
* @access private
*/
var $server_identifier = '';
var $server_identifier = false;
/**
* Key Exchange Algorithms
*
* @see Net_SSH2::getKexAlgorithims()
* @var Array
* @var mixed false or Array
* @access private
*/
var $kex_algorithms;
var $kex_algorithms = false;
/**
* Server Host Key Algorithms
*
* @see Net_SSH2::getServerHostKeyAlgorithms()
* @var Array
* @var mixed false or Array
* @access private
*/
var $server_host_key_algorithms;
var $server_host_key_algorithms = false;
/**
* Encryption Algorithms: Client to Server
*
* @see Net_SSH2::getEncryptionAlgorithmsClient2Server()
* @var Array
* @var mixed false or Array
* @access private
*/
var $encryption_algorithms_client_to_server;
var $encryption_algorithms_client_to_server = false;
/**
* Encryption Algorithms: Server to Client
*
* @see Net_SSH2::getEncryptionAlgorithmsServer2Client()
* @var Array
* @var mixed false or Array
* @access private
*/
var $encryption_algorithms_server_to_client;
var $encryption_algorithms_server_to_client = false;
/**
* MAC Algorithms: Client to Server
*
* @see Net_SSH2::getMACAlgorithmsClient2Server()
* @var Array
* @var mixed false or Array
* @access private
*/
var $mac_algorithms_client_to_server;
var $mac_algorithms_client_to_server = false;
/**
* MAC Algorithms: Server to Client
*
* @see Net_SSH2::getMACAlgorithmsServer2Client()
* @var Array
* @var mixed false or Array
* @access private
*/
var $mac_algorithms_server_to_client;
var $mac_algorithms_server_to_client = false;
/**
* Compression Algorithms: Client to Server
*
* @see Net_SSH2::getCompressionAlgorithmsClient2Server()
* @var Array
* @var mixed false or Array
* @access private
*/
var $compression_algorithms_client_to_server;
var $compression_algorithms_client_to_server = false;
/**
* Compression Algorithms: Server to Client
*
* @see Net_SSH2::getCompressionAlgorithmsServer2Client()
* @var Array
* @var mixed false or Array
* @access private
*/
var $compression_algorithms_server_to_client;
var $compression_algorithms_server_to_client = false;
/**
* Languages: Server to Client
*
* @see Net_SSH2::getLanguagesServer2Client()
* @var Array
* @var mixed false or Array
* @access private
*/
var $languages_server_to_client;
var $languages_server_to_client = false;
/**
* Languages: Client to Server
*
* @see Net_SSH2::getLanguagesClient2Server()
* @var Array
* @var mixed false or Array
* @access private
*/
var $languages_client_to_server;
var $languages_client_to_server = false;
/**
* Block Size for Server to Client Encryption
@ -949,6 +949,12 @@ class Net_SSH2
*/
function _connect()
{
if ($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR) {
return false;
}
$this->bitmap |= NET_SSH2_MASK_CONSTRUCTOR;
$timeout = $this->connectionTimeout;
$host = $this->host . ':' . $this->port;
@ -965,7 +971,7 @@ class Net_SSH2
$timeout-= $elapsed;
if ($timeout <= 0) {
user_error(rtrim("Cannot connect to $host. Timeout error"));
user_error("Cannot connect to $host. Timeout error");
return false;
}
@ -978,7 +984,7 @@ class Net_SSH2
// on windows this returns a "Warning: Invalid CRT parameters detected" error
// the !count() is done as a workaround for <https://bugs.php.net/42682>
if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
user_error(rtrim("Cannot connect to $host. Banner timeout"));
user_error("Cannot connect to $host. Banner timeout");
return false;
}
@ -1038,7 +1044,7 @@ class Net_SSH2
return false;
}
$this->bitmap = NET_SSH2_MASK_CONNECTED;
$this->bitmap|= NET_SSH2_MASK_CONNECTED;
return true;
}
@ -1098,7 +1104,7 @@ class Net_SSH2
'arcfour256',
'arcfour128',
'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
//'arcfour', // OPTIONAL the ARCFOUR stream cipher with a 128-bit key
// CTR modes from <http://tools.ietf.org/html/rfc4344#section-4>:
'aes128-ctr', // RECOMMENDED AES (Rijndael) in SDCTR mode, with 128-bit key
@ -1126,7 +1132,7 @@ class Net_SSH2
'3des-ctr', // RECOMMENDED Three-key 3DES in SDCTR mode
'3des-cbc', // REQUIRED three-key 3DES in CBC mode
'none' // OPTIONAL no encryption; NOT RECOMMENDED
//'none' // OPTIONAL no encryption; NOT RECOMMENDED
);
if (phpseclib_resolve_include_path('Crypt/RC4.php') === false) {
@ -1163,11 +1169,14 @@ class Net_SSH2
}
$mac_algorithms = array(
// from <http://www.ietf.org/rfc/rfc6668.txt>:
'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20)
'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16)
'none' // OPTIONAL no MAC; NOT RECOMMENDED
//'none' // OPTIONAL no MAC; NOT RECOMMENDED
);
static $compression_algorithms = array(
@ -1692,6 +1701,10 @@ class Net_SSH2
$createKeyLength = 0; // ie. $mac_algorithms[$i] == 'none'
switch ($mac_algorithms[$i]) {
case 'hmac-sha2-256':
$this->hmac_create = new Crypt_Hash('sha256');
$createKeyLength = 32;
break;
case 'hmac-sha1':
$this->hmac_create = new Crypt_Hash('sha1');
$createKeyLength = 20;
@ -1718,6 +1731,11 @@ class Net_SSH2
$checkKeyLength = 0;
$this->hmac_size = 0;
switch ($mac_algorithms[$i]) {
case 'hmac-sha2-256':
$this->hmac_check = new Crypt_Hash('sha256');
$checkKeyLength = 32;
$this->hmac_size = 32;
break;
case 'hmac-sha1':
$this->hmac_check = new Crypt_Hash('sha1');
$checkKeyLength = 20;
@ -1799,7 +1817,6 @@ class Net_SSH2
function _login($username)
{
if (!($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR)) {
$this->bitmap |= NET_SSH2_MASK_CONSTRUCTOR;
if (!$this->_connect()) {
return false;
}
@ -2062,7 +2079,6 @@ class Net_SSH2
if (!count($responses) && $num_prompts) {
$this->last_interactive_response = $orig;
$this->bitmap |= NET_SSH_MASK_LOGIN_INTERACTIVE;
return false;
}
@ -2245,7 +2261,7 @@ class Net_SSH2
/**
* Execute Command
*
* If $block is set to false then Net_SSH2::_get_channel_packet(NET_SSH2_CHANNEL_EXEC) will need to be called manually.
* If $callback is set to false then Net_SSH2::_get_channel_packet(NET_SSH2_CHANNEL_EXEC) will need to be called manually.
* In all likelihood, this is not a feature you want to be taking advantage of.
*
* @param String $command
@ -2654,11 +2670,12 @@ class Net_SSH2
/**
* Is the connection still active?
*
* @return boolean
* @access public
*/
function isConnected()
{
return $this->bitmap & NET_SSH2_MASK_LOGIN;
return (bool) ($this->bitmap & NET_SSH2_MASK_CONNECTED);
}
/**
@ -2968,7 +2985,7 @@ class Net_SSH2
extract(unpack('Ctype/Nchannel', $this->_string_shift($response, 5)));
$this->window_size_server_to_client[$channel]-= strlen($response) + 4;
$this->window_size_server_to_client[$channel]-= strlen($response);
// resize the window, if appropriate
if ($this->window_size_server_to_client[$channel] < 0) {
@ -3258,26 +3275,22 @@ class Net_SSH2
*/
function _send_channel_packet($client_channel, $data)
{
while (strlen($data)) {
if (!$this->window_size_client_to_server[$client_channel]) {
$this->bitmap^= NET_SSH2_MASK_WINDOW_ADJUST;
// using an invalid channel will let the buffers be built up for the valid channels
$this->_get_channel_packet(-1);
$this->bitmap^= NET_SSH2_MASK_WINDOW_ADJUST;
}
/* The maximum amount of data allowed is determined by the maximum
packet size for the channel, and the current window size, whichever
is smaller.
-- http://tools.ietf.org/html/rfc4254#section-5.2 */
$max_size = min(
$this->packet_size_client_to_server[$client_channel],
$this->window_size_client_to_server[$client_channel]
) - 4;
while (strlen($data) > $max_size) {
if (!$this->window_size_client_to_server[$client_channel]) {
$this->bitmap^= NET_SSH2_MASK_WINDOW_ADJUST;
// using an invalid channel will let the buffers be built up for the valid channels
$output = $this->_get_channel_packet(-1);
$this->bitmap^= NET_SSH2_MASK_WINDOW_ADJUST;
$max_size = min(
$this->packet_size_client_to_server[$client_channel],
$this->window_size_client_to_server[$client_channel]
) - 4;
}
);
$temp = $this->_string_shift($data, $max_size);
$packet = pack('CN2a*',
@ -3286,27 +3299,13 @@ class Net_SSH2
strlen($temp),
$temp
);
$this->window_size_client_to_server[$client_channel]-= strlen($temp) + 4;
$this->window_size_client_to_server[$client_channel]-= strlen($temp);
if (!$this->_send_binary_packet($packet)) {
return false;
}
}
if (strlen($data) >= $this->window_size_client_to_server[$client_channel] - 4) {
$this->bitmap^= NET_SSH2_MASK_WINDOW_ADJUST;
$this->_get_channel_packet(-1);
$this->bitmap^= NET_SSH2_MASK_WINDOW_ADJUST;
}
$this->window_size_client_to_server[$client_channel]-= strlen($data) + 4;
return $this->_send_binary_packet(pack('CN2a*',
NET_SSH2_MSG_CHANNEL_DATA,
$this->server_channels[$client_channel],
strlen($data),
$data));
return true;
}
/**
@ -3355,7 +3354,7 @@ class Net_SSH2
*/
function _disconnect($reason)
{
if ($this->bitmap) {
if ($this->bitmap & NET_SSH2_MASK_CONNECTED) {
$data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, '');
$this->_send_binary_packet($data);
$this->bitmap = 0;
@ -3509,6 +3508,8 @@ class Net_SSH2
*/
function getServerIdentification()
{
$this->_connect();
return $this->server_identifier;
}
@ -3520,6 +3521,8 @@ class Net_SSH2
*/
function getKexAlgorithms()
{
$this->_connect();
return $this->kex_algorithms;
}
@ -3531,6 +3534,8 @@ class Net_SSH2
*/
function getServerHostKeyAlgorithms()
{
$this->_connect();
return $this->server_host_key_algorithms;
}
@ -3542,6 +3547,8 @@ class Net_SSH2
*/
function getEncryptionAlgorithmsClient2Server()
{
$this->_connect();
return $this->encryption_algorithms_client_to_server;
}
@ -3553,6 +3560,8 @@ class Net_SSH2
*/
function getEncryptionAlgorithmsServer2Client()
{
$this->_connect();
return $this->encryption_algorithms_server_to_client;
}
@ -3564,6 +3573,8 @@ class Net_SSH2
*/
function getMACAlgorithmsClient2Server()
{
$this->_connect();
return $this->mac_algorithms_client_to_server;
}
@ -3575,6 +3586,8 @@ class Net_SSH2
*/
function getMACAlgorithmsServer2Client()
{
$this->_connect();
return $this->mac_algorithms_server_to_client;
}
@ -3586,6 +3599,8 @@ class Net_SSH2
*/
function getCompressionAlgorithmsClient2Server()
{
$this->_connect();
return $this->compression_algorithms_client_to_server;
}
@ -3597,6 +3612,8 @@ class Net_SSH2
*/
function getCompressionAlgorithmsServer2Client()
{
$this->_connect();
return $this->compression_algorithms_server_to_client;
}
@ -3608,6 +3625,8 @@ class Net_SSH2
*/
function getLanguagesServer2Client()
{
$this->_connect();
return $this->languages_server_to_client;
}
@ -3619,6 +3638,8 @@ class Net_SSH2
*/
function getLanguagesClient2Server()
{
$this->_connect();
return $this->languages_client_to_server;
}
@ -3648,7 +3669,6 @@ class Net_SSH2
function getServerPublicHostKey()
{
if (!($this->bitmap & NET_SSH2_MASK_CONSTRUCTOR)) {
$this->bitmap |= NET_SSH2_MASK_CONSTRUCTOR;
if (!$this->_connect()) {
return false;
}
@ -3731,8 +3751,9 @@ class Net_SSH2
$e = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
$temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
$n = new Math_BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
$nLength = $temp['length'];
$rawN = $this->_string_shift($server_public_host_key, $temp['length']);
$n = new Math_BigInteger($rawN, -256);
$nLength = strlen(ltrim($rawN, "\0"));
/*
$temp = unpack('Nlength', $this->_string_shift($signature, 4));
@ -3769,7 +3790,7 @@ class Net_SSH2
$s = $s->toBytes();
$h = pack('N4H*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, sha1($this->exchange_hash));
$h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 3 - strlen($h)) . $h;
$h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h;
if ($s != $h) {
user_error('Bad server signature');

View file

@ -43,7 +43,7 @@
* @category System
* @package System_SSH_Agent
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMXIV Jim Wigginton
* @copyright 2014 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
* @internal See http://api.libssh.org/rfc/PROTOCOL.agent

View file

@ -30,7 +30,7 @@
* @category System
* @package System_SSH_Agent
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMXIV Jim Wigginton
* @copyright 2014 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
* @internal See http://api.libssh.org/rfc/PROTOCOL.agent

View file

@ -138,6 +138,13 @@
value: capaAdditionalAccounts
}
}"></div>
<div data-bind="component: {
name: 'Checkbox',
params: {
label: 'TAB_GENERAL/LABEL_ALLOW_IDENTITIES',
value: capaIdentities
}
}"></div>
</div>
</div>
<!--

View file

@ -3,6 +3,20 @@
<div class="legend">
<span class="i18n i18n-animation" data-i18n="SETTINGS_GENERAL/LEGEND_GENERAL"></span>
</div>
<div class="control-group">
<label class="control-label">
<span class="i18n" data-i18n="SETTINGS_GENERAL/LABEL_IDENTITY"></span>
</label>
<div class="controls">
<div style="display: inline-block; margin-top: 5px">
<b data-bind="text: identityMainDesc"></b>
&nbsp;&nbsp;&nbsp;
<span data-bind="click: editMainIdentity" style="cursor: pointer">
<i class="icon-cog"></i>
</span>
</div>
</div>
</div>
<div class="control-group" data-bind="visible: allowLanguagesOnSettings">
<label class="control-label">
<span class="i18n" data-i18n="SETTINGS_GENERAL/LABEL_LANGUAGE"></span>

View file

@ -34,6 +34,7 @@ LABEL_ALLOW_GRAVATAR = "Gravatar erlauben"
LEGEND_MAIN = "Haupt"
LABEL_ATTACHMENT_SIZE_LIMIT = "Größenlimit für Anhänge"
LABEL_ALLOW_ADDITIONAL_ACCOUNTS = "Zusätzliche Konten erlauben"
LABEL_ALLOW_IDENTITIES = "Allow multiple identities"
LABEL_ALLOW_TEMPLATES = "Vorlagen erlauben"
ALERT_WARNING = "Warnung!"
HTML_ALERT_WEAK_PASSWORD = "Sie verwenden das Standard-Admin-Passwort.

View file

@ -34,6 +34,7 @@ LABEL_ALLOW_GRAVATAR = "Allow Gravatar"
LEGEND_MAIN = "Main"
LABEL_ATTACHMENT_SIZE_LIMIT = "Attachment size limit"
LABEL_ALLOW_ADDITIONAL_ACCOUNTS = "Allow additional accounts"
LABEL_ALLOW_IDENTITIES = "Allow multiple identities"
LABEL_ALLOW_TEMPLATES = "Allow templates"
ALERT_WARNING = "Warning!"
HTML_ALERT_WEAK_PASSWORD = "You are using the default admin password.

View file

@ -34,6 +34,7 @@ LABEL_ALLOW_GRAVATAR = "Permitir Gravatar"
LEGEND_MAIN = "Principal"
LABEL_ATTACHMENT_SIZE_LIMIT = "Tamanho limite de anexo"
LABEL_ALLOW_ADDITIONAL_ACCOUNTS = "Permitir contas adicionais"
LABEL_ALLOW_IDENTITIES = "Allow multiple identities"
LABEL_ALLOW_TEMPLATES = "Permitir templates"
ALERT_WARNING = "Aviso!"
HTML_ALERT_WEAK_PASSWORD = "Você está usando a senha administrativa padrão.

View file

@ -34,6 +34,7 @@ LABEL_ALLOW_GRAVATAR = "Разрешить граватары"
LEGEND_MAIN = "Основное"
LABEL_ATTACHMENT_SIZE_LIMIT = "Предельный размер вложений"
LABEL_ALLOW_ADDITIONAL_ACCOUNTS = "Разрешить дополнительные аккаунты"
LABEL_ALLOW_IDENTITIES = "Разрешить множественные профили"
LABEL_ALLOW_TEMPLATES = "Разрешить шаблоны"
ALERT_WARNING = "Внимание!"
HTML_ALERT_WEAK_PASSWORD = "Вы используете пароль администратора по умолчанию.

View file

@ -481,6 +481,7 @@ AUTOLOGIN_MINUTES_OPTION_NAME = "%MINUTES% minute(s)"
[SETTINGS_GENERAL]
LEGEND_GENERAL = "General"
LABEL_LANGUAGE = "Language"
LABEL_IDENTITY = "Identity"
LABEL_LAYOUT = "Layout"
LABEL_LAYOUT_NO_SPLIT = "No Split"
LABEL_LAYOUT_VERTICAL_SPLIT = "Vertical Split"