diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 70d08eb09..22fc31c69 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -7,13 +7,14 @@ use RainLoop\Enumerations\UploadClientError; class Actions { + use Actions\Admin; + const AUTH_TFA_SIGN_ME_TOKEN_KEY = 'rltfasmauth'; const AUTH_SIGN_ME_TOKEN_KEY = 'rlsmauth'; const AUTH_MAILTO_TOKEN_KEY = 'rlmailtoauth'; const AUTH_SPEC_TOKEN_KEY = 'rlspecauth'; const AUTH_SPEC_LOGOUT_TOKEN_KEY = 'rlspeclogout'; const AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY = 'rlspeclogoutcmk'; - const AUTH_ADMIN_TOKEN_KEY = 'rlaauth'; const RL_MOBILE_TYPE = 'rlmobiletype'; /** @@ -587,36 +588,12 @@ class Actions Utils::SetCookie(self::AUTH_SPEC_LOGOUT_CUSTOM_MSG_KEY, $sMessage, 0); } - private function setAdminAuthToken(string $sToken) : void - { - Utils::SetCookie(self::AUTH_ADMIN_TOKEN_KEY, $sToken, 0); - } - private function getLocalAuthToken() : string { $sToken = $this->GetSpecAuthToken(); return !empty($sToken) && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : ''; } - private function getAdminAuthToken() : string - { - return Utils::GetCookie(self::AUTH_ADMIN_TOKEN_KEY, ''); - } - - public function ClearAdminAuthToken() : void - { - $aAdminHash = 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)->Delete(KeyPathHelper::SessionAdminKey($aAdminHash[2])); - } - - Utils::ClearCookie(self::AUTH_ADMIN_TOKEN_KEY); - } - /** * @throws \RainLoop\Exceptions\ClientException */ @@ -991,43 +968,6 @@ class Actions } } - private function getAdminToken() : string - { - $sRand = \MailSo\Base\Utils::Md5Rand(); - if (!$this->Cacher(null, true)->Set(KeyPathHelper::SessionAdminKey($sRand), \time())) - { - $this->oLogger->Write('Cannot store an admin token', - \MailSo\Log\Enumerations\Type::WARNING); - - $sRand = ''; - } - - return '' === $sRand ? '' : Utils::EncodeKeyValuesQ(array('token', \md5(APP_SALT), $sRand)); - } - - public function IsAdminLoggined(bool $bThrowExceptionOnFalse = true) : bool - { - $bResult = false; - if ($this->Config()->Get('security', 'allow_admin_panel', true)) - { - $aAdminHash = 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(KeyPathHelper::SessionAdminKey($aAdminHash[2]), '') - ) - { - $bResult = true; - } - } - - if (!$bResult && $bThrowExceptionOnFalse) - { - throw new Exceptions\ClientException(Notifications::AuthError); - } - - return $bResult; - } - public function SetMailtoRequest(string $sTo) : void { if (!empty($sTo)) @@ -2962,40 +2902,6 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack } } - private function setCapaFromParams(Config\Application $oConfig, string $sParamName, string $sCapa) : void - { - switch ($sCapa) - { - case Enumerations\Capa::ADDITIONAL_ACCOUNTS: - $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_accounts', 'bool'); - break; - case Enumerations\Capa::IDENTITIES: - $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_identities', 'bool'); - break; - case Enumerations\Capa::TEMPLATES: - $this->setConfigFromParams($oConfig, $sParamName, 'capa', 'x-templates', 'bool'); - break; - case Enumerations\Capa::TWO_FACTOR: - $this->setConfigFromParams($oConfig, $sParamName, 'security', 'allow_two_factor_auth', 'bool'); - break; - case Enumerations\Capa::TWO_FACTOR_FORCE: - $this->setConfigFromParams($oConfig, $sParamName, 'security', 'force_two_factor_auth', 'bool'); - break; - case Enumerations\Capa::ATTACHMENT_THUMBNAILS: - $this->setConfigFromParams($oConfig, $sParamName, 'interface', 'show_attachment_thumbnail', 'bool'); - break; - case Enumerations\Capa::THEMES: - $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_themes', 'bool'); - break; - case Enumerations\Capa::USER_BACKGROUND: - $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_user_background', 'bool'); - break; - case Enumerations\Capa::OPEN_PGP: - $this->setConfigFromParams($oConfig, $sParamName, 'security', 'openpgp', 'bool'); - break; - } - } - private function setSettingsFromParams(Settings $oSettings, string $sConfigName, string $sType = 'string', ?callable $mStringCallback = null) : void { if ($this->HasActionParam($sConfigName)) @@ -3026,921 +2932,6 @@ NewThemeLink IncludeCss LoadingDescriptionEsc TemplatesLink LangLink IncludeBack } } - public function DoAdminSettingsUpdate() : array - { -// sleep(3); -// return $this->DefaultResponse(__FUNCTION__, false); - - $this->IsAdminLoggined(); - - $oConfig = $this->Config(); - - $self = $this; - - $this->setConfigFromParams($oConfig, 'Language', 'webmail', 'language', 'string', function ($sLanguage) use ($self) { - return $self->ValidateLanguage($sLanguage, '', false); - }); - - $this->setConfigFromParams($oConfig, 'LanguageAdmin', 'webmail', 'language_admin', 'string', function ($sLanguage) use ($self) { - return $self->ValidateLanguage($sLanguage, '', true); - }); - - $this->setConfigFromParams($oConfig, 'Theme', 'webmail', 'theme', 'string', function ($sTheme) use ($self) { - return $self->ValidateTheme($sTheme); - }); - - $this->setConfigFromParams($oConfig, 'VerifySslCertificate', 'ssl', 'verify_certificate', 'bool'); - $this->setConfigFromParams($oConfig, 'AllowSelfSigned', 'ssl', 'allow_self_signed', 'bool'); - - $this->setConfigFromParams($oConfig, 'UseLocalProxyForExternalImages', 'labs', 'use_local_proxy_for_external_images', 'bool'); - - $this->setConfigFromParams($oConfig, 'NewMoveToFolder', 'interface', 'new_move_to_folder_button', 'bool'); - - $this->setConfigFromParams($oConfig, 'AllowLanguagesOnSettings', 'webmail', 'allow_languages_on_settings', 'bool'); - $this->setConfigFromParams($oConfig, 'AllowLanguagesOnLogin', 'login', 'allow_languages_on_login', 'bool'); - $this->setConfigFromParams($oConfig, 'AttachmentLimit', 'webmail', 'attachment_size_limit', 'int'); - - $this->setConfigFromParams($oConfig, 'LoginDefaultDomain', 'login', 'default_domain', 'string'); - - $this->setConfigFromParams($oConfig, 'ContactsEnable', 'contacts', 'enable', 'bool'); - $this->setConfigFromParams($oConfig, 'ContactsSync', 'contacts', 'allow_sync', 'bool'); - $this->setConfigFromParams($oConfig, 'ContactsPdoDsn', 'contacts', 'pdo_dsn', 'string'); - $this->setConfigFromParams($oConfig, 'ContactsPdoUser', 'contacts', 'pdo_user', 'string'); - $this->setConfigFromParams($oConfig, 'ContactsPdoPassword', 'contacts', 'pdo_password', 'dummy'); - - $this->setConfigFromParams($oConfig, 'ContactsPdoType', 'contacts', 'type', 'string', function ($sType) use ($self) { - return $self->ValidateContactPdoType($sType); - }); - - $this->setCapaFromParams($oConfig, 'CapaAdditionalAccounts', Enumerations\Capa::ADDITIONAL_ACCOUNTS); - $this->setCapaFromParams($oConfig, 'CapaIdentities', Enumerations\Capa::IDENTITIES); - $this->setCapaFromParams($oConfig, 'CapaTemplates', Enumerations\Capa::TEMPLATES); - $this->setCapaFromParams($oConfig, 'CapaTwoFactorAuth', Enumerations\Capa::TWO_FACTOR); - $this->setCapaFromParams($oConfig, 'CapaTwoFactorAuthForce', Enumerations\Capa::TWO_FACTOR_FORCE); - $this->setCapaFromParams($oConfig, 'CapaOpenPGP', Enumerations\Capa::OPEN_PGP); - $this->setCapaFromParams($oConfig, 'CapaThemes', Enumerations\Capa::THEMES); - $this->setCapaFromParams($oConfig, 'CapaUserBackground', Enumerations\Capa::USER_BACKGROUND); - $this->setCapaFromParams($oConfig, 'CapaAttachmentThumbnails', Enumerations\Capa::ATTACHMENT_THUMBNAILS); - - $this->setConfigFromParams($oConfig, 'DetermineUserLanguage', 'login', 'determine_user_language', 'bool'); - $this->setConfigFromParams($oConfig, 'DetermineUserDomain', 'login', 'determine_user_domain', 'bool'); - - $this->setConfigFromParams($oConfig, 'Title', 'webmail', 'title', 'string'); - $this->setConfigFromParams($oConfig, 'LoadingDescription', 'webmail', 'loading_description', 'string'); - $this->setConfigFromParams($oConfig, 'FaviconUrl', 'webmail', 'favicon_url', 'string'); - - $this->setConfigFromParams($oConfig, 'TokenProtection', 'security', 'csrf_protection', 'bool'); - $this->setConfigFromParams($oConfig, 'EnabledPlugins', 'plugins', 'enable', 'bool'); - - if ($this && $this->HasOneOfActionParams(array( - 'LoginLogo', 'LoginBackground', 'LoginDescription', 'LoginCss', - 'UserLogo', 'UserLogoTitle', 'UserLogoMessage', 'UserIframeMessage', 'UserCss' - ))) - { - $this->setConfigFromParams($oConfig, 'LoginLogo', 'branding', 'login_logo', 'string'); - $this->setConfigFromParams($oConfig, 'LoginBackground', 'branding', 'login_background', 'string'); - $this->setConfigFromParams($oConfig, 'LoginDescription', 'branding', 'login_desc', 'string'); - $this->setConfigFromParams($oConfig, 'LoginCss', 'branding', 'login_css', 'string'); - - $this->setConfigFromParams($oConfig, 'UserLogo', 'branding', 'user_logo', 'string'); - $this->setConfigFromParams($oConfig, 'UserLogoTitle', 'branding', 'user_logo_title', 'string'); - $this->setConfigFromParams($oConfig, 'UserLogoMessage', 'branding', 'user_logo_message', 'string'); - $this->setConfigFromParams($oConfig, 'UserIframeMessage', 'branding', 'user_iframe_message', 'string'); - $this->setConfigFromParams($oConfig, 'UserCss', 'branding', 'user_css', 'string'); - } - - return $this->DefaultResponse(__FUNCTION__, $oConfig->Save()); - } - - /** - * @throws \MailSo\Base\Exceptions\Exception - */ - public function DoAdminLogin() : array - { - $sLogin = trim($this->GetActionParam('Login', '')); - $sPassword = $this->GetActionParam('Password', ''); - - $this->Logger()->AddSecret($sPassword); - - if (0 === strlen($sLogin) || 0 === strlen($sPassword) || - !$this->Config()->Get('security', 'allow_admin_panel', true) || - $sLogin !== $this->Config()->Get('security', 'admin_login', '') || - !$this->Config()->ValidatePassword($sPassword)) - { - $this->loginErrorDelay(); - $this->LoggerAuthHelper(null, $this->getAdditionalLogParamsByUserLogin($sLogin, true)); - if ($this->Config()->Get('logs', 'auth_logging', false) - && $this->Config()->Get('security', 'allow_admin_panel', true) - && \openlog('rainloop', 0, \LOG_AUTHPRIV)) - { - \syslog(\LOG_ERR, $this->compileLogParams('Admin Auth failed: ip={request:ip} user='.$sLogin)); - \closelog(); - } - throw new Exceptions\ClientException(Notifications::AuthError); - } - - $sToken = $this->getAdminToken(); - $this->setAdminAuthToken($sToken); - - return $this->DefaultResponse(__FUNCTION__, $sToken ? true : false); - } - - public function DoAdminLogout() : array - { - $this->ClearAdminAuthToken(); - return $this->TrueResponse(__FUNCTION__); - } - - public function DoAdminPing() : array - { - $this->IsAdminLoggined(); - - return $this->DefaultResponse(__FUNCTION__, true); - } - - public function DoAdminContactsTest() : array - { - $this->IsAdminLoggined(); - - $oConfig = $this->Config(); - - $this->setConfigFromParams($oConfig, 'ContactsPdoDsn', 'contacts', 'pdo_dsn', 'string'); - $this->setConfigFromParams($oConfig, 'ContactsPdoUser', 'contacts', 'pdo_user', 'string'); - $this->setConfigFromParams($oConfig, 'ContactsPdoPassword', 'contacts', 'pdo_password', 'dummy'); - - $self = $this; - $this->setConfigFromParams($oConfig, 'ContactsPdoType', 'contacts', 'type', 'string', function ($sType) use ($self) { - return $self->ValidateContactPdoType($sType); - }); - - $sTestMessage = $this->AddressBookProvider(null, true)->Test(); - return $this->DefaultResponse(__FUNCTION__, array( - 'Result' => '' === $sTestMessage, - 'Message' => \MailSo\Base\Utils::Utf8Clear($sTestMessage, '?') - )); - } - - public function DoAdminPasswordUpdate() : array - { - $this->IsAdminLoggined(); - - $bResult = false; - $oConfig = $this->Config(); - - $sLogin = \trim($this->GetActionParam('Login', '')); - $sPassword = $this->GetActionParam('Password', ''); - $this->Logger()->AddSecret($sPassword); - - $sNewPassword = $this->GetActionParam('NewPassword', ''); - if (0 < \strlen(\trim($sNewPassword))) - { - $this->Logger()->AddSecret($sNewPassword); - } - - if ($oConfig->ValidatePassword($sPassword)) - { - if (0 < \strlen($sLogin)) - { - $oConfig->Set('security', 'admin_login', $sLogin); - } - - if (0 < \strlen(\trim($sNewPassword))) - { - $oConfig->SetPassword($sNewPassword); - } - - $bResult = true; - } - - return $this->DefaultResponse(__FUNCTION__, $bResult ? - ($oConfig->Save() ? array('Weak' => $oConfig->ValidatePassword('12345')) : false) : false); - } - - public function DoAdminDomainLoad() : array - { - $this->IsAdminLoggined(); - - return $this->DefaultResponse(__FUNCTION__, - $this->DomainProvider()->Load($this->GetActionParam('Name', ''), false, false)); - } - - public function DoAdminDomainList() : array - { - $this->IsAdminLoggined(); - - $iOffset = (int) $this->GetActionParam('Offset', 0); - $iLimit = (int) $this->GetActionParam('Limit', 20); - $sSearch = (string) $this->GetActionParam('Search', ''); - $bIncludeAliases = '1' === (string) $this->GetActionParam('IncludeAliases', '1'); - - $iOffset = 0; - $sSearch = ''; - $iLimit = $this->Config()->Get('labs', 'domain_list_limit', 99); - - return $this->DefaultResponse(__FUNCTION__, - $this->DomainProvider()->GetList($iOffset, $iLimit, $sSearch, $bIncludeAliases)); - } - - public function DoAdminDomainDelete() : array - { - $this->IsAdminLoggined(); - - return $this->DefaultResponse(__FUNCTION__, - $this->DomainProvider()->Delete((string) $this->GetActionParam('Name', ''))); - } - - public function DoAdminDomainDisable() : array - { - $this->IsAdminLoggined(); - - return $this->DefaultResponse(__FUNCTION__, $this->DomainProvider()->Disable( - (string) $this->GetActionParam('Name', ''), - '1' === (string) $this->GetActionParam('Disabled', '0') - )); - } - - public function DoAdminDomainSave() : array - { - $this->IsAdminLoggined(); - - $oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this); - - return $this->DefaultResponse(__FUNCTION__, - $oDomain ? $this->DomainProvider()->Save($oDomain) : false); - } - - public function DoAdminDomainAliasSave() : array - { - $this->IsAdminLoggined(); - - return $this->DefaultResponse(__FUNCTION__, $this->DomainProvider()->SaveAlias( - (string) $this->GetActionParam('Name', ''), - (string) $this->GetActionParam('Alias', '') - )); - } - - public function DoAdminDomainTest() : array - { - $this->IsAdminLoggined(); - - $bImapResult = false; - $sImapErrorDesc = ''; - $bSmtpResult = false; - $sSmtpErrorDesc = ''; - $bSieveResult = false; - $sSieveErrorDesc = ''; - - $iImapTime = 0; - $iSmtpTime = 0; - $iSieveTime = 0; - - $iConnectionTimeout = 5; - - $oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this, 'domain-test-connection.de'); - if ($oDomain) - { - try - { - $oImapClient = new \MailSo\Imap\ImapClient(); - $oImapClient->SetLogger($this->Logger()); - $oImapClient->SetTimeOuts($iConnectionTimeout); - - $iTime = \microtime(true); - $oImapClient->Connect($oDomain->IncHost(), $oDomain->IncPort(), $oDomain->IncSecure(), - !!$this->Config()->Get('ssl', 'verify_certificate', false), - !!$this->Config()->Get('ssl', 'allow_self_signed', true), - $this->Config()->Get('ssl', 'client_cert', '') - ); - - $iImapTime = \microtime(true) - $iTime; - $oImapClient->Disconnect(); - $bImapResult = true; - } - catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) - { - $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); - $sImapErrorDesc = $oException->getSocketMessage(); - if (empty($sImapErrorDesc)) - { - $sImapErrorDesc = $oException->getMessage(); - } - } - catch (\Throwable $oException) - { - $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); - $sImapErrorDesc = $oException->getMessage(); - } - - if ($oDomain->OutUsePhpMail()) - { - $bSmtpResult = \MailSo\Base\Utils::FunctionExistsAndEnabled('mail'); - if (!$bSmtpResult) - { - $sSmtpErrorDesc = 'PHP: mail() function is undefined'; - } - } - else - { - try - { - $oSmtpClient = new \MailSo\Smtp\SmtpClient(); - $oSmtpClient->SetLogger($this->Logger()); - $oSmtpClient->SetTimeOuts($iConnectionTimeout); - - $iTime = \microtime(true); - $oSmtpClient->Connect($oDomain->OutHost(), $oDomain->OutPort(), $oDomain->OutSecure(), - !!$this->Config()->Get('ssl', 'verify_certificate', false), - !!$this->Config()->Get('ssl', 'allow_self_signed', true), - '', \MailSo\Smtp\SmtpClient::EhloHelper() - ); - - $iSmtpTime = \microtime(true) - $iTime; - $oSmtpClient->Disconnect(); - $bSmtpResult = true; - } - catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) - { - $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); - $sSmtpErrorDesc = $oException->getSocketMessage(); - if (empty($sSmtpErrorDesc)) - { - $sSmtpErrorDesc = $oException->getMessage(); - } - } - catch (\Throwable $oException) - { - $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); - $sSmtpErrorDesc = $oException->getMessage(); - } - } - - if ($oDomain->UseSieve()) - { - try - { - $oSieveClient = new \MailSo\Sieve\ManageSieveClient(); - $oSieveClient->SetLogger($this->Logger()); - $oSieveClient->SetTimeOuts($iConnectionTimeout); - $oSieveClient->__USE_INITIAL_AUTH_PLAIN_COMMAND = !!$this->Config()->Get('labs', 'sieve_auth_plain_initial', true); - - $iTime = \microtime(true); - $oSieveClient->Connect($oDomain->SieveHost(), $oDomain->SievePort(), $oDomain->SieveSecure(), - !!$this->Config()->Get('ssl', 'verify_certificate', false), - !!$this->Config()->Get('ssl', 'allow_self_signed', true) - ); - - $iSieveTime = \microtime(true) - $iTime; - $oSieveClient->Disconnect(); - $bSieveResult = true; - } - catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) - { - $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); - $sSieveErrorDesc = $oException->getSocketMessage(); - if (empty($sSieveErrorDesc)) - { - $sSieveErrorDesc = $oException->getMessage(); - } - } - catch (\Throwable $oException) - { - $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); - $sSieveErrorDesc = $oException->getMessage(); - } - } - else - { - $bSieveResult = true; - } - } - - return $this->DefaultResponse(__FUNCTION__, array( - 'Imap' => $bImapResult ? true : $sImapErrorDesc, - 'Smtp' => $bSmtpResult ? true : $sSmtpErrorDesc, - 'Sieve' => $bSieveResult ? true : $sSieveErrorDesc - )); - } - - private function rainLoopRepo() : string - { - $sUrl = APP_REPOSITORY_PATH; - if ('' !== $sUrl) - { - $sUrl = rtrim($sUrl, '\\/').'/'; - } - - return $sUrl; - } - - private function rainLoopUpdatable() : bool - { - return file_exists(APP_INDEX_ROOT_PATH.'index.php') && - is_writable(APP_INDEX_ROOT_PATH.'index.php') && - is_writable(APP_INDEX_ROOT_PATH.'rainloop/') && - APP_VERSION !== APP_DEV_VERSION - ; - } - - private function getRepositoryDataByUrl(string $sRepo, bool &$bReal = false) : array - { - $bReal = false; - $aRep = null; - - $sRep = ''; - $sRepoFile = 'repository.json'; - $iRepTime = 0; - - $oHttp = \MailSo\Base\Http::SingletonInstance(); - - $sCacheKey = KeyPathHelper::RepositoryCacheFile($sRepo, $sRepoFile); - $sRep = $this->Cacher()->Get($sCacheKey); - if ('' !== $sRep) - { - $iRepTime = $this->Cacher()->GetTimer($sCacheKey); - } - - if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime) - { - $iCode = 0; - $sContentType = ''; - - $sRepPath = $sRepo.$sRepoFile; - $sRep = '' !== $sRepo ? $oHttp->GetUrlAsString($sRepPath, 'RainLoop', $sContentType, $iCode, $this->Logger(), 10, - $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')) : false; - - if (false !== $sRep) - { - $aRep = \json_decode($sRep); - $bReal = \is_array($aRep) && 0 < \count($aRep); - - if ($bReal) - { - $this->Cacher()->Set($sCacheKey, $sRep); - $this->Cacher()->SetTimer($sCacheKey); - } - } - else - { - $this->Logger()->Write('Cannot read remote repository file: '.$sRepPath, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - } - } - else if ('' !== $sRep) - { - $aRep = \json_decode($sRep, false, 10); - $bReal = \is_array($aRep) && 0 < \count($aRep); - } - - $aResult = array(); - if (\is_array($aRep)) - { - foreach ($aRep as $oItem) - { - if ($oItem && isset($oItem->type, $oItem->id, $oItem->name, - $oItem->version, $oItem->release, $oItem->file, $oItem->description)) - { - if (!empty($oItem->required) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->required, '<')) - { - continue; - } - - if (!empty($oItem->depricated) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->depricated, '>=')) - { - continue; - } - - if ('plugin' === $oItem->type) - { - $aResult[] = array( - 'type' => $oItem->type, - 'id' => $oItem->id, - 'name' => $oItem->name, - 'installed' => '', - 'version' => $oItem->version, - 'file' => $oItem->file, - 'release' => $oItem->release, - 'desc' => $oItem->description - ); - } - } - } - } - - return $aResult; - } - - private function getCoreChannel() : string - { - $sChannel = \trim(\strtolower($this->Config()->Get('labs', 'update_channel', 'stable'))); - if (empty($sChannel) || !\in_array($sChannel, array('stable', 'beta'))) - { - $sChannel = 'stable'; - } - - return $sChannel; - } - - private function getRepositoryData(bool &$bReal, bool &$bRainLoopUpdatable) : array - { - $bRainLoopUpdatable = $this->rainLoopUpdatable(); - - $aResult = $this->getRepositoryDataByUrl($this->rainLoopRepo(), $bReal); - - $aSub = array(); - foreach ($aResult as $aItem) - { - if ('plugin' === $aItem['type']) - { - $aSub[] = $aItem; - } - } - $aResult = $aSub; - unset($aSub); - - $aInstalled = $this->Plugins()->InstalledPlugins(); - foreach ($aResult as &$aItem) - { - if ('plugin' === $aItem['type']) - { - foreach ($aInstalled as &$aSubItem) - { - if (\is_array($aSubItem) && isset($aSubItem[0]) && $aSubItem[0] === $aItem['id']) - { - $aSubItem[2] = true; - $aItem['installed'] = $aSubItem[1]; - } - } - } - } - - foreach ($aInstalled as $aSubItemSec) - { - if ($aSubItemSec && !isset($aSubItemSec[2])) - { - \array_push($aResult, array( - 'type' => 'plugin', - 'id' => $aSubItemSec[0], - 'name' => $aSubItemSec[0], - 'installed' => $aSubItemSec[1], - 'version' => '', - 'file' => '', - 'release' => '', - 'desc' => '' - )); - } - } - - foreach ($aResult as &$aItem) - { - $aItem['compare'] = \version_compare($aItem['installed'], $aItem['version'], '<'); - $aItem['canBeDeleted'] = '' !== $aItem['installed'] && 'plugin' === $aItem['type']; - $aItem['canBeUpdated'] = $aItem['compare']; - $aItem['canBeInstalled'] = true; - } - - return $aResult; - } - - public function DoAdminPackagesList() : array - { - $this->IsAdminLoggined(); - - $bReal = false; - $bRainLoopUpdatable = false; - $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); - - return $this->DefaultResponse(__FUNCTION__, array( - 'Real' => $bReal, - 'MainUpdatable' => $bRainLoopUpdatable, - 'List' => $aList - )); - } - - public function DoAdminPackageDelete() : array - { - $this->IsAdminLoggined(); - - $sId = $this->GetActionParam('Id', ''); - - $bReal = false; - $bRainLoopUpdatable = false; - $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); - - $sResultId = ''; - foreach ($aList as $oItem) - { - if ($oItem && 'plugin' === $oItem['type'] && $sId === $oItem['id']) - { - $sResultId = $sId; - break; - } - } - - $bResult = false; - if ('' !== $sResultId) - { - $bResult = \MailSo\Base\Utils::RecRmDir(APP_PLUGINS_PATH.$sResultId); - if ($bResult) - { - $this->pluginEnable($sResultId, false); - } - } - - return $this->DefaultResponse(__FUNCTION__, $bResult); - } - - public function downloadRemotePackageByUrl(string $sUrl) : string - { - $bResult = false; - $sTmp = APP_PRIVATE_DATA.\md5(\microtime(true).$sUrl).'.zip'; - $pDest = \fopen($sTmp, 'w+b'); - if ($pDest) - { - $iCode = 0; - $sContentType = ''; - - \set_time_limit(120); - - $oHttp = \MailSo\Base\Http::SingletonInstance(); - $bResult = $oHttp->SaveUrlToFile($sUrl, $pDest, $sTmp, $sContentType, $iCode, $this->Logger(), 60, - $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')); - - if (!$bResult) - { - $this->Logger()->Write('Cannot save url to temp file: ', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - $this->Logger()->Write($sUrl.' -> '.$sTmp, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - } - - \fclose($pDest); - } - else - { - $this->Logger()->Write('Cannot create temp file: '.$sTmp, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - } - - return $bResult ? $sTmp : ''; - } - - public function DoAdminPackageInstall() : array - { - $this->IsAdminLoggined(); - - $sType = $this->GetActionParam('Type', ''); - $sId = $this->GetActionParam('Id', ''); - $sFile = $this->GetActionParam('File', ''); - - $this->Logger()->Write('Start package install: '.$sFile.' ('.$sType.')', \MailSo\Log\Enumerations\Type::INFO, 'INSTALLER'); - - $sRealFile = ''; - - $bReal = false; - $bRainLoopUpdatable = false; - $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); - - if ('plugin' === $sType) - { - foreach ($aList as $oItem) - { - if ($oItem && $sFile === $oItem['file'] && $sId === $oItem['id']) - { - $sRealFile = $sFile; - break; - } - } - } - - $sTmp = ''; - $bResult = false; - if ('' !== $sRealFile) - { - $sUrl = $this->rainLoopRepo().$sRealFile; - $sTmp = $this->downloadRemotePackageByUrl($sUrl); - } - - if ('' !== $sTmp) - { - $oArchive = new \ZipArchive(); - $oArchive->open($sTmp); - if ('plugin' === $sType) - { - $bResult = true; - if (\is_dir(APP_PLUGINS_PATH.$sId)) - { - $bResult = \MailSo\Base\Utils::RecRmDir(APP_PLUGINS_PATH.$sId); - if (!$bResult) - { - $this->Logger()->Write('Cannot remove previous plugin folder: '.$sId, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - } - } - - if ($bResult) - { - $bResult = $oArchive->extractTo(APP_PLUGINS_PATH); - if (!$bResult) - { - $this->Logger()->Write('Cannot extract package files: '.$oArchive->getStatusString(), \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); - } - } - } - $oArchive->close(); - - \unlink($sTmp); - } - - return $this->DefaultResponse(__FUNCTION__, $bResult ? - ('plugin' !== $sType ? array('Reload' => true) : true) : false); - } - - public function DoAdminPluginList() : array - { - $this->IsAdminLoggined(); - - $aResult = array(); - - $sEnabledPlugins = $this->Config()->Get('plugins', 'enabled_list', ''); - $aEnabledPlugins = \explode(',', \strtolower($sEnabledPlugins)); - $aEnabledPlugins = \array_map('trim', $aEnabledPlugins); - - $aList = $this->Plugins()->InstalledPlugins(); - foreach ($aList as $aItem) - { - $aResult[] = array( - 'Name' => $aItem[0], - 'Enabled' => \in_array(\strtolower($aItem[0]), $aEnabledPlugins), - 'Configured' => false - ); - } - - return $this->DefaultResponse(__FUNCTION__, $aResult); - } - - private function pluginEnable(string $sName, bool $bEnable = true) : bool - { - if (0 === \strlen($sName)) - { - return false; - } - - $oConfig = $this->Config(); - - $sEnabledPlugins = $oConfig->Get('plugins', 'enabled_list', ''); - $aEnabledPlugins = \explode(',', \strtolower($sEnabledPlugins)); - $aEnabledPlugins = \array_map('trim', $aEnabledPlugins); - - $aNewEnabledPlugins = array(); - if ($bEnable) - { - $aNewEnabledPlugins = $aEnabledPlugins; - $aNewEnabledPlugins[] = $sName; - } - else - { - foreach ($aEnabledPlugins as $sPlugin) - { - if ($sName !== $sPlugin && 0 < \strlen($sPlugin)) - { - $aNewEnabledPlugins[] = $sPlugin; - } - } - } - - $aNewEnabledPlugins = \array_unique($aNewEnabledPlugins); - $oConfig->Set('plugins', 'enabled_list', \trim(\implode(',', $aNewEnabledPlugins), ' ,')); - - return $oConfig->Save(); - } - - public function DoAdminPluginDisable() : array - { - $this->IsAdminLoggined(); - - $sName = (string) $this->GetActionParam('Name', ''); - $bDisable = '1' === (string) $this->GetActionParam('Disabled', '1'); - - if (!$bDisable) - { - $oPlugin = $this->Plugins()->CreatePluginByName($sName); - if ($oPlugin) - { - $sValue = $oPlugin->Supported(); - if (0 < \strlen($sValue)) - { - return $this->FalseResponse(__FUNCTION__, Notifications::UnsupportedPluginPackage, $sValue); - } - } - else - { - return $this->FalseResponse(__FUNCTION__, Notifications::InvalidPluginPackage); - } - } - - return $this->DefaultResponse(__FUNCTION__, $this->pluginEnable($sName, !$bDisable)); - } - - public function DoAdminPluginLoad() : array - { - $this->IsAdminLoggined(); - - $mResult = false; - $sName = (string) $this->GetActionParam('Name', ''); - - if (!empty($sName)) - { - $oPlugin = $this->Plugins()->CreatePluginByName($sName); - if ($oPlugin) - { - $mResult = array( - 'Name' => $sName, - 'Readme' => file_exists($oPlugin->Path().'/README') ? file_get_contents($oPlugin->Path().'/README') : '', - 'Config' => array() - ); - - $aMap = $oPlugin->ConfigMap(); - $oConfig = $oPlugin->Config(); - if (is_array($aMap)) - { - foreach ($aMap as $oItem) - { - if ($oItem && ($oItem instanceof Plugins\Property)) - { - $aItem = $oItem->ToArray(); - $aItem[0] = $oConfig->Get('plugin', $oItem->Name(), ''); - if (Enumerations\PluginPropertyType::PASSWORD === $oItem->Type()) - { - $aItem[0] = APP_DUMMY; - } - - $mResult['Config'][] = $aItem; - } - } - } - } - } - - return $this->DefaultResponse(__FUNCTION__, $mResult); - } - - public function DoAdminPluginSettingsUpdate() : array - { - $this->IsAdminLoggined(); - - $mResult = false; - $sName = (string) $this->GetActionParam('Name', ''); - - if (!empty($sName)) - { - $oPlugin = $this->Plugins()->CreatePluginByName($sName); - if ($oPlugin) - { - $oConfig = $oPlugin->Config(); - $aMap = $oPlugin->ConfigMap(); - if (is_array($aMap)) - { - foreach ($aMap as $oItem) - { - $sValue = $this->GetActionParam('_'.$oItem->Name(), $oConfig->Get('plugin', $oItem->Name())); - if (Enumerations\PluginPropertyType::PASSWORD !== $oItem->Type() || APP_DUMMY !== $sValue) - { - $mResultValue = null; - switch ($oItem->Type()) { - case Enumerations\PluginPropertyType::INT: - $mResultValue = (int) $sValue; - break; - case Enumerations\PluginPropertyType::BOOL: - $mResultValue = '1' === (string) $sValue; - break; - case Enumerations\PluginPropertyType::SELECTION: - if (is_array($oItem->DefaultValue()) && in_array($sValue, $oItem->DefaultValue())) - { - $mResultValue = (string) $sValue; - } - break; - case Enumerations\PluginPropertyType::PASSWORD: - case Enumerations\PluginPropertyType::STRING: - case Enumerations\PluginPropertyType::STRING_TEXT: - $mResultValue = (string) $sValue; - break; - } - - if (null !== $mResultValue) - { - $oConfig->Set('plugin', $oItem->Name(), $mResultValue); - } - } - } - } - - $mResult = $oConfig->Save(); - } - } - - if (!$mResult) - { - throw new Exceptions\ClientException(Notifications::CantSavePluginSettings); - } - - return $this->DefaultResponse(__FUNCTION__, true); - } - public function DoSettingsUpdate() : array { $oAccount = $this->getAccountFromToken(); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php new file mode 100644 index 000000000..3ce4c688a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php @@ -0,0 +1,1015 @@ +Config()->Get('security', 'allow_admin_panel', true)) + { + $aAdminHash = 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(KeyPathHelper::SessionAdminKey($aAdminHash[2]), '') + ) + { + $bResult = true; + } + } + + if (!$bResult && $bThrowExceptionOnFalse) + { + throw new ClientException(Notifications::AuthError); + } + + return $bResult; + } + + private function getAdminAuthToken() : string + { + return Utils::GetCookie(static::$AUTH_ADMIN_TOKEN_KEY, ''); + } + + private function setAdminAuthToken(string $sToken) : void + { + Utils::SetCookie(static::$AUTH_ADMIN_TOKEN_KEY, $sToken, 0); + } + + public function ClearAdminAuthToken() : void + { + $aAdminHash = 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)->Delete(KeyPathHelper::SessionAdminKey($aAdminHash[2])); + } + + Utils::ClearCookie(static::$AUTH_ADMIN_TOKEN_KEY); + } + + private function getAdminToken() : string + { + $sRand = \MailSo\Base\Utils::Md5Rand(); + if (!$this->Cacher(null, true)->Set(KeyPathHelper::SessionAdminKey($sRand), \time())) + { + $this->oLogger->Write('Cannot store an admin token', + \MailSo\Log\Enumerations\Type::WARNING); + + $sRand = ''; + } + + return '' === $sRand ? '' : Utils::EncodeKeyValuesQ(array('token', \md5(APP_SALT), $sRand)); + } + + private function setCapaFromParams(\RainLoop\Config\Application $oConfig, string $sParamName, string $sCapa) : void + { + switch ($sCapa) + { + case Capa::ADDITIONAL_ACCOUNTS: + $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_accounts', 'bool'); + break; + case Capa::IDENTITIES: + $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_identities', 'bool'); + break; + case Capa::TEMPLATES: + $this->setConfigFromParams($oConfig, $sParamName, 'capa', 'x-templates', 'bool'); + break; + case Capa::TWO_FACTOR: + $this->setConfigFromParams($oConfig, $sParamName, 'security', 'allow_two_factor_auth', 'bool'); + break; + case Capa::TWO_FACTOR_FORCE: + $this->setConfigFromParams($oConfig, $sParamName, 'security', 'force_two_factor_auth', 'bool'); + break; + case Capa::ATTACHMENT_THUMBNAILS: + $this->setConfigFromParams($oConfig, $sParamName, 'interface', 'show_attachment_thumbnail', 'bool'); + break; + case Capa::THEMES: + $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_themes', 'bool'); + break; + case Capa::USER_BACKGROUND: + $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_user_background', 'bool'); + break; + case Capa::OPEN_PGP: + $this->setConfigFromParams($oConfig, $sParamName, 'security', 'openpgp', 'bool'); + break; + } + } + + public function DoAdminSettingsUpdate() : array + { +// sleep(3); +// return $this->DefaultResponse(__FUNCTION__, false); + + $this->IsAdminLoggined(); + + $oConfig = $this->Config(); + + $self = $this; + + $this->setConfigFromParams($oConfig, 'Language', 'webmail', 'language', 'string', function ($sLanguage) use ($self) { + return $self->ValidateLanguage($sLanguage, '', false); + }); + + $this->setConfigFromParams($oConfig, 'LanguageAdmin', 'webmail', 'language_admin', 'string', function ($sLanguage) use ($self) { + return $self->ValidateLanguage($sLanguage, '', true); + }); + + $this->setConfigFromParams($oConfig, 'Theme', 'webmail', 'theme', 'string', function ($sTheme) use ($self) { + return $self->ValidateTheme($sTheme); + }); + + $this->setConfigFromParams($oConfig, 'VerifySslCertificate', 'ssl', 'verify_certificate', 'bool'); + $this->setConfigFromParams($oConfig, 'AllowSelfSigned', 'ssl', 'allow_self_signed', 'bool'); + + $this->setConfigFromParams($oConfig, 'UseLocalProxyForExternalImages', 'labs', 'use_local_proxy_for_external_images', 'bool'); + + $this->setConfigFromParams($oConfig, 'NewMoveToFolder', 'interface', 'new_move_to_folder_button', 'bool'); + + $this->setConfigFromParams($oConfig, 'AllowLanguagesOnSettings', 'webmail', 'allow_languages_on_settings', 'bool'); + $this->setConfigFromParams($oConfig, 'AllowLanguagesOnLogin', 'login', 'allow_languages_on_login', 'bool'); + $this->setConfigFromParams($oConfig, 'AttachmentLimit', 'webmail', 'attachment_size_limit', 'int'); + + $this->setConfigFromParams($oConfig, 'LoginDefaultDomain', 'login', 'default_domain', 'string'); + + $this->setConfigFromParams($oConfig, 'ContactsEnable', 'contacts', 'enable', 'bool'); + $this->setConfigFromParams($oConfig, 'ContactsSync', 'contacts', 'allow_sync', 'bool'); + $this->setConfigFromParams($oConfig, 'ContactsPdoDsn', 'contacts', 'pdo_dsn', 'string'); + $this->setConfigFromParams($oConfig, 'ContactsPdoUser', 'contacts', 'pdo_user', 'string'); + $this->setConfigFromParams($oConfig, 'ContactsPdoPassword', 'contacts', 'pdo_password', 'dummy'); + + $this->setConfigFromParams($oConfig, 'ContactsPdoType', 'contacts', 'type', 'string', function ($sType) use ($self) { + return $self->ValidateContactPdoType($sType); + }); + + $this->setCapaFromParams($oConfig, 'CapaAdditionalAccounts', Capa::ADDITIONAL_ACCOUNTS); + $this->setCapaFromParams($oConfig, 'CapaIdentities', Capa::IDENTITIES); + $this->setCapaFromParams($oConfig, 'CapaTemplates', Capa::TEMPLATES); + $this->setCapaFromParams($oConfig, 'CapaTwoFactorAuth', Capa::TWO_FACTOR); + $this->setCapaFromParams($oConfig, 'CapaTwoFactorAuthForce', Capa::TWO_FACTOR_FORCE); + $this->setCapaFromParams($oConfig, 'CapaOpenPGP', Capa::OPEN_PGP); + $this->setCapaFromParams($oConfig, 'CapaThemes', Capa::THEMES); + $this->setCapaFromParams($oConfig, 'CapaUserBackground', Capa::USER_BACKGROUND); + $this->setCapaFromParams($oConfig, 'CapaAttachmentThumbnails', Capa::ATTACHMENT_THUMBNAILS); + + $this->setConfigFromParams($oConfig, 'DetermineUserLanguage', 'login', 'determine_user_language', 'bool'); + $this->setConfigFromParams($oConfig, 'DetermineUserDomain', 'login', 'determine_user_domain', 'bool'); + + $this->setConfigFromParams($oConfig, 'Title', 'webmail', 'title', 'string'); + $this->setConfigFromParams($oConfig, 'LoadingDescription', 'webmail', 'loading_description', 'string'); + $this->setConfigFromParams($oConfig, 'FaviconUrl', 'webmail', 'favicon_url', 'string'); + + $this->setConfigFromParams($oConfig, 'TokenProtection', 'security', 'csrf_protection', 'bool'); + $this->setConfigFromParams($oConfig, 'EnabledPlugins', 'plugins', 'enable', 'bool'); + + if ($this->HasOneOfActionParams(array( + 'LoginLogo', 'LoginBackground', 'LoginDescription', 'LoginCss', + 'UserLogo', 'UserLogoTitle', 'UserLogoMessage', 'UserIframeMessage', 'UserCss' + ))) + { + $this->setConfigFromParams($oConfig, 'LoginLogo', 'branding', 'login_logo', 'string'); + $this->setConfigFromParams($oConfig, 'LoginBackground', 'branding', 'login_background', 'string'); + $this->setConfigFromParams($oConfig, 'LoginDescription', 'branding', 'login_desc', 'string'); + $this->setConfigFromParams($oConfig, 'LoginCss', 'branding', 'login_css', 'string'); + + $this->setConfigFromParams($oConfig, 'UserLogo', 'branding', 'user_logo', 'string'); + $this->setConfigFromParams($oConfig, 'UserLogoTitle', 'branding', 'user_logo_title', 'string'); + $this->setConfigFromParams($oConfig, 'UserLogoMessage', 'branding', 'user_logo_message', 'string'); + $this->setConfigFromParams($oConfig, 'UserIframeMessage', 'branding', 'user_iframe_message', 'string'); + $this->setConfigFromParams($oConfig, 'UserCss', 'branding', 'user_css', 'string'); + } + + return $this->DefaultResponse(__FUNCTION__, $oConfig->Save()); + } + + /** + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoAdminLogin() : array + { + $sLogin = trim($this->GetActionParam('Login', '')); + $sPassword = $this->GetActionParam('Password', ''); + + $this->Logger()->AddSecret($sPassword); + + if (0 === strlen($sLogin) || 0 === strlen($sPassword) || + !$this->Config()->Get('security', 'allow_admin_panel', true) || + $sLogin !== $this->Config()->Get('security', 'admin_login', '') || + !$this->Config()->ValidatePassword($sPassword)) + { + $this->loginErrorDelay(); + $this->LoggerAuthHelper(null, $this->getAdditionalLogParamsByUserLogin($sLogin, true)); + if ($this->Config()->Get('logs', 'auth_logging', false) + && $this->Config()->Get('security', 'allow_admin_panel', true) + && \openlog('rainloop', 0, \LOG_AUTHPRIV)) + { + \syslog(\LOG_ERR, $this->compileLogParams('Admin Auth failed: ip={request:ip} user='.$sLogin)); + \closelog(); + } + throw new ClientException(Notifications::AuthError); + } + + $sToken = $this->getAdminToken(); + $this->setAdminAuthToken($sToken); + + return $this->DefaultResponse(__FUNCTION__, $sToken ? true : false); + } + + public function DoAdminLogout() : array + { + $this->ClearAdminAuthToken(); + return $this->TrueResponse(__FUNCTION__); + } + + public function DoAdminPing() : array + { + $this->IsAdminLoggined(); + + return $this->DefaultResponse(__FUNCTION__, true); + } + + public function DoAdminContactsTest() : array + { + $this->IsAdminLoggined(); + + $oConfig = $this->Config(); + + $this->setConfigFromParams($oConfig, 'ContactsPdoDsn', 'contacts', 'pdo_dsn', 'string'); + $this->setConfigFromParams($oConfig, 'ContactsPdoUser', 'contacts', 'pdo_user', 'string'); + $this->setConfigFromParams($oConfig, 'ContactsPdoPassword', 'contacts', 'pdo_password', 'dummy'); + + $self = $this; + $this->setConfigFromParams($oConfig, 'ContactsPdoType', 'contacts', 'type', 'string', function ($sType) use ($self) { + return $self->ValidateContactPdoType($sType); + }); + + $sTestMessage = $this->AddressBookProvider(null, true)->Test(); + return $this->DefaultResponse(__FUNCTION__, array( + 'Result' => '' === $sTestMessage, + 'Message' => \MailSo\Base\Utils::Utf8Clear($sTestMessage, '?') + )); + } + + public function DoAdminPasswordUpdate() : array + { + $this->IsAdminLoggined(); + + $bResult = false; + $oConfig = $this->Config(); + + $sLogin = \trim($this->GetActionParam('Login', '')); + $sPassword = $this->GetActionParam('Password', ''); + $this->Logger()->AddSecret($sPassword); + + $sNewPassword = $this->GetActionParam('NewPassword', ''); + if (0 < \strlen(\trim($sNewPassword))) + { + $this->Logger()->AddSecret($sNewPassword); + } + + if ($oConfig->ValidatePassword($sPassword)) + { + if (0 < \strlen($sLogin)) + { + $oConfig->Set('security', 'admin_login', $sLogin); + } + + if (0 < \strlen(\trim($sNewPassword))) + { + $oConfig->SetPassword($sNewPassword); + } + + $bResult = true; + } + + return $this->DefaultResponse(__FUNCTION__, $bResult ? + ($oConfig->Save() ? array('Weak' => $oConfig->ValidatePassword('12345')) : false) : false); + } + + public function DoAdminDomainLoad() : array + { + $this->IsAdminLoggined(); + + return $this->DefaultResponse(__FUNCTION__, + $this->DomainProvider()->Load($this->GetActionParam('Name', ''), false, false)); + } + + public function DoAdminDomainList() : array + { + $this->IsAdminLoggined(); + + $iOffset = (int) $this->GetActionParam('Offset', 0); + $iLimit = (int) $this->GetActionParam('Limit', 20); + $sSearch = (string) $this->GetActionParam('Search', ''); + $bIncludeAliases = '1' === (string) $this->GetActionParam('IncludeAliases', '1'); + + $iOffset = 0; + $sSearch = ''; + $iLimit = $this->Config()->Get('labs', 'domain_list_limit', 99); + + return $this->DefaultResponse(__FUNCTION__, + $this->DomainProvider()->GetList($iOffset, $iLimit, $sSearch, $bIncludeAliases)); + } + + public function DoAdminDomainDelete() : array + { + $this->IsAdminLoggined(); + + return $this->DefaultResponse(__FUNCTION__, + $this->DomainProvider()->Delete((string) $this->GetActionParam('Name', ''))); + } + + public function DoAdminDomainDisable() : array + { + $this->IsAdminLoggined(); + + return $this->DefaultResponse(__FUNCTION__, $this->DomainProvider()->Disable( + (string) $this->GetActionParam('Name', ''), + '1' === (string) $this->GetActionParam('Disabled', '0') + )); + } + + public function DoAdminDomainSave() : array + { + $this->IsAdminLoggined(); + + $oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this); + + return $this->DefaultResponse(__FUNCTION__, + $oDomain ? $this->DomainProvider()->Save($oDomain) : false); + } + + public function DoAdminDomainAliasSave() : array + { + $this->IsAdminLoggined(); + + return $this->DefaultResponse(__FUNCTION__, $this->DomainProvider()->SaveAlias( + (string) $this->GetActionParam('Name', ''), + (string) $this->GetActionParam('Alias', '') + )); + } + + public function DoAdminDomainTest() : array + { + $this->IsAdminLoggined(); + + $bImapResult = false; + $sImapErrorDesc = ''; + $bSmtpResult = false; + $sSmtpErrorDesc = ''; + $bSieveResult = false; + $sSieveErrorDesc = ''; + + $iImapTime = 0; + $iSmtpTime = 0; + $iSieveTime = 0; + + $iConnectionTimeout = 5; + + $oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this, 'domain-test-connection.de'); + if ($oDomain) + { + try + { + $oImapClient = new \MailSo\Imap\ImapClient(); + $oImapClient->SetLogger($this->Logger()); + $oImapClient->SetTimeOuts($iConnectionTimeout); + + $iTime = \microtime(true); + $oImapClient->Connect($oDomain->IncHost(), $oDomain->IncPort(), $oDomain->IncSecure(), + !!$this->Config()->Get('ssl', 'verify_certificate', false), + !!$this->Config()->Get('ssl', 'allow_self_signed', true), + $this->Config()->Get('ssl', 'client_cert', '') + ); + + $iImapTime = \microtime(true) - $iTime; + $oImapClient->Disconnect(); + $bImapResult = true; + } + catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sImapErrorDesc = $oException->getSocketMessage(); + if (empty($sImapErrorDesc)) + { + $sImapErrorDesc = $oException->getMessage(); + } + } + catch (\Throwable $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sImapErrorDesc = $oException->getMessage(); + } + + if ($oDomain->OutUsePhpMail()) + { + $bSmtpResult = \MailSo\Base\Utils::FunctionExistsAndEnabled('mail'); + if (!$bSmtpResult) + { + $sSmtpErrorDesc = 'PHP: mail() function is undefined'; + } + } + else + { + try + { + $oSmtpClient = new \MailSo\Smtp\SmtpClient(); + $oSmtpClient->SetLogger($this->Logger()); + $oSmtpClient->SetTimeOuts($iConnectionTimeout); + + $iTime = \microtime(true); + $oSmtpClient->Connect($oDomain->OutHost(), $oDomain->OutPort(), $oDomain->OutSecure(), + !!$this->Config()->Get('ssl', 'verify_certificate', false), + !!$this->Config()->Get('ssl', 'allow_self_signed', true), + '', \MailSo\Smtp\SmtpClient::EhloHelper() + ); + + $iSmtpTime = \microtime(true) - $iTime; + $oSmtpClient->Disconnect(); + $bSmtpResult = true; + } + catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sSmtpErrorDesc = $oException->getSocketMessage(); + if (empty($sSmtpErrorDesc)) + { + $sSmtpErrorDesc = $oException->getMessage(); + } + } + catch (\Throwable $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sSmtpErrorDesc = $oException->getMessage(); + } + } + + if ($oDomain->UseSieve()) + { + try + { + $oSieveClient = new \MailSo\Sieve\ManageSieveClient(); + $oSieveClient->SetLogger($this->Logger()); + $oSieveClient->SetTimeOuts($iConnectionTimeout); + $oSieveClient->__USE_INITIAL_AUTH_PLAIN_COMMAND = !!$this->Config()->Get('labs', 'sieve_auth_plain_initial', true); + + $iTime = \microtime(true); + $oSieveClient->Connect($oDomain->SieveHost(), $oDomain->SievePort(), $oDomain->SieveSecure(), + !!$this->Config()->Get('ssl', 'verify_certificate', false), + !!$this->Config()->Get('ssl', 'allow_self_signed', true) + ); + + $iSieveTime = \microtime(true) - $iTime; + $oSieveClient->Disconnect(); + $bSieveResult = true; + } + catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sSieveErrorDesc = $oException->getSocketMessage(); + if (empty($sSieveErrorDesc)) + { + $sSieveErrorDesc = $oException->getMessage(); + } + } + catch (\Throwable $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sSieveErrorDesc = $oException->getMessage(); + } + } + else + { + $bSieveResult = true; + } + } + + return $this->DefaultResponse(__FUNCTION__, array( + 'Imap' => $bImapResult ? true : $sImapErrorDesc, + 'Smtp' => $bSmtpResult ? true : $sSmtpErrorDesc, + 'Sieve' => $bSieveResult ? true : $sSieveErrorDesc + )); + } + + private function rainLoopRepo() : string + { + $sUrl = APP_REPOSITORY_PATH; + if ('' !== $sUrl) + { + $sUrl = rtrim($sUrl, '\\/').'/'; + } + + return $sUrl; + } + + private function rainLoopUpdatable() : bool + { + return file_exists(APP_INDEX_ROOT_PATH.'index.php') && + is_writable(APP_INDEX_ROOT_PATH.'index.php') && + is_writable(APP_INDEX_ROOT_PATH.'rainloop/') && + APP_VERSION !== APP_DEV_VERSION + ; + } + + private function getRepositoryDataByUrl(string $sRepo, bool &$bReal = false) : array + { + $bReal = false; + $aRep = null; + + $sRep = ''; + $sRepoFile = 'repository.json'; + $iRepTime = 0; + + $oHttp = \MailSo\Base\Http::SingletonInstance(); + + $sCacheKey = KeyPathHelper::RepositoryCacheFile($sRepo, $sRepoFile); + $sRep = $this->Cacher()->Get($sCacheKey); + if ('' !== $sRep) + { + $iRepTime = $this->Cacher()->GetTimer($sCacheKey); + } + + if ('' === $sRep || 0 === $iRepTime || \time() - 3600 > $iRepTime) + { + $iCode = 0; + $sContentType = ''; + + $sRepPath = $sRepo.$sRepoFile; + $sRep = '' !== $sRepo ? $oHttp->GetUrlAsString($sRepPath, 'RainLoop', $sContentType, $iCode, $this->Logger(), 10, + $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')) : false; + + if (false !== $sRep) + { + $aRep = \json_decode($sRep); + $bReal = \is_array($aRep) && 0 < \count($aRep); + + if ($bReal) + { + $this->Cacher()->Set($sCacheKey, $sRep); + $this->Cacher()->SetTimer($sCacheKey); + } + } + else + { + $this->Logger()->Write('Cannot read remote repository file: '.$sRepPath, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + } + } + else if ('' !== $sRep) + { + $aRep = \json_decode($sRep, false, 10); + $bReal = \is_array($aRep) && 0 < \count($aRep); + } + + $aResult = array(); + if (\is_array($aRep)) + { + foreach ($aRep as $oItem) + { + if ($oItem && isset($oItem->type, $oItem->id, $oItem->name, + $oItem->version, $oItem->release, $oItem->file, $oItem->description)) + { + if (!empty($oItem->required) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->required, '<')) + { + continue; + } + + if (!empty($oItem->depricated) && APP_DEV_VERSION !== APP_VERSION && version_compare(APP_VERSION, $oItem->depricated, '>=')) + { + continue; + } + + if ('plugin' === $oItem->type) + { + $aResult[] = array( + 'type' => $oItem->type, + 'id' => $oItem->id, + 'name' => $oItem->name, + 'installed' => '', + 'version' => $oItem->version, + 'file' => $oItem->file, + 'release' => $oItem->release, + 'desc' => $oItem->description + ); + } + } + } + } + + return $aResult; + } + + private function getRepositoryData(bool &$bReal, bool &$bRainLoopUpdatable) : array + { + $bRainLoopUpdatable = $this->rainLoopUpdatable(); + + $aResult = $this->getRepositoryDataByUrl($this->rainLoopRepo(), $bReal); + + $aSub = array(); + foreach ($aResult as $aItem) + { + if ('plugin' === $aItem['type']) + { + $aSub[] = $aItem; + } + } + $aResult = $aSub; + unset($aSub); + + $aInstalled = $this->Plugins()->InstalledPlugins(); + foreach ($aResult as &$aItem) + { + if ('plugin' === $aItem['type']) + { + foreach ($aInstalled as &$aSubItem) + { + if (\is_array($aSubItem) && isset($aSubItem[0]) && $aSubItem[0] === $aItem['id']) + { + $aSubItem[2] = true; + $aItem['installed'] = $aSubItem[1]; + } + } + } + } + + foreach ($aInstalled as $aSubItemSec) + { + if ($aSubItemSec && !isset($aSubItemSec[2])) + { + \array_push($aResult, array( + 'type' => 'plugin', + 'id' => $aSubItemSec[0], + 'name' => $aSubItemSec[0], + 'installed' => $aSubItemSec[1], + 'version' => '', + 'file' => '', + 'release' => '', + 'desc' => '' + )); + } + } + + foreach ($aResult as &$aItem) + { + $aItem['compare'] = \version_compare($aItem['installed'], $aItem['version'], '<'); + $aItem['canBeDeleted'] = '' !== $aItem['installed'] && 'plugin' === $aItem['type']; + $aItem['canBeUpdated'] = $aItem['compare']; + $aItem['canBeInstalled'] = true; + } + + return $aResult; + } + + public function DoAdminPackagesList() : array + { + $this->IsAdminLoggined(); + + $bReal = false; + $bRainLoopUpdatable = false; + $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); + + return $this->DefaultResponse(__FUNCTION__, array( + 'Real' => $bReal, + 'MainUpdatable' => $bRainLoopUpdatable, + 'List' => $aList + )); + } + + public function DoAdminPackageDelete() : array + { + $this->IsAdminLoggined(); + + $sId = $this->GetActionParam('Id', ''); + + $bReal = false; + $bRainLoopUpdatable = false; + $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); + + $sResultId = ''; + foreach ($aList as $oItem) + { + if ($oItem && 'plugin' === $oItem['type'] && $sId === $oItem['id']) + { + $sResultId = $sId; + break; + } + } + + $bResult = false; + if ('' !== $sResultId) + { + $bResult = \MailSo\Base\Utils::RecRmDir(APP_PLUGINS_PATH.$sResultId); + if ($bResult) + { + $this->pluginEnable($sResultId, false); + } + } + + return $this->DefaultResponse(__FUNCTION__, $bResult); + } + + private function downloadRemotePackageByUrl(string $sUrl) : string + { + $bResult = false; + $sTmp = APP_PRIVATE_DATA.\md5(\microtime(true).$sUrl).'.zip'; + $pDest = \fopen($sTmp, 'w+b'); + if ($pDest) + { + $iCode = 0; + $sContentType = ''; + + \set_time_limit(120); + + $oHttp = \MailSo\Base\Http::SingletonInstance(); + $bResult = $oHttp->SaveUrlToFile($sUrl, $pDest, $sTmp, $sContentType, $iCode, $this->Logger(), 60, + $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', '')); + + if (!$bResult) + { + $this->Logger()->Write('Cannot save url to temp file: ', \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + $this->Logger()->Write($sUrl.' -> '.$sTmp, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + } + + \fclose($pDest); + } + else + { + $this->Logger()->Write('Cannot create temp file: '.$sTmp, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + } + + return $bResult ? $sTmp : ''; + } + + public function DoAdminPackageInstall() : array + { + $this->IsAdminLoggined(); + + $sType = $this->GetActionParam('Type', ''); + $sId = $this->GetActionParam('Id', ''); + $sFile = $this->GetActionParam('File', ''); + + $this->Logger()->Write('Start package install: '.$sFile.' ('.$sType.')', \MailSo\Log\Enumerations\Type::INFO, 'INSTALLER'); + + $sRealFile = ''; + + $bReal = false; + $bRainLoopUpdatable = false; + $aList = $this->getRepositoryData($bReal, $bRainLoopUpdatable); + + if ('plugin' === $sType) + { + foreach ($aList as $oItem) + { + if ($oItem && $sFile === $oItem['file'] && $sId === $oItem['id']) + { + $sRealFile = $sFile; + break; + } + } + } + + $sTmp = ''; + $bResult = false; + if ('' !== $sRealFile) + { + $sUrl = $this->rainLoopRepo().$sRealFile; + $sTmp = $this->downloadRemotePackageByUrl($sUrl); + } + + if ('' !== $sTmp) + { + $oArchive = new \ZipArchive(); + $oArchive->open($sTmp); + if ('plugin' === $sType) + { + $bResult = true; + if (\is_dir(APP_PLUGINS_PATH.$sId)) + { + $bResult = \MailSo\Base\Utils::RecRmDir(APP_PLUGINS_PATH.$sId); + if (!$bResult) + { + $this->Logger()->Write('Cannot remove previous plugin folder: '.$sId, \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + } + } + + if ($bResult) + { + $bResult = $oArchive->extractTo(APP_PLUGINS_PATH); + if (!$bResult) + { + $this->Logger()->Write('Cannot extract package files: '.$oArchive->getStatusString(), \MailSo\Log\Enumerations\Type::ERROR, 'INSTALLER'); + } + } + } + $oArchive->close(); + + \unlink($sTmp); + } + + return $this->DefaultResponse(__FUNCTION__, $bResult ? + ('plugin' !== $sType ? array('Reload' => true) : true) : false); + } + + public function DoAdminPluginList() : array + { + $this->IsAdminLoggined(); + + $aResult = array(); + + $sEnabledPlugins = $this->Config()->Get('plugins', 'enabled_list', ''); + $aEnabledPlugins = \explode(',', \strtolower($sEnabledPlugins)); + $aEnabledPlugins = \array_map('trim', $aEnabledPlugins); + + $aList = $this->Plugins()->InstalledPlugins(); + foreach ($aList as $aItem) + { + $aResult[] = array( + 'Name' => $aItem[0], + 'Enabled' => \in_array(\strtolower($aItem[0]), $aEnabledPlugins), + 'Configured' => false + ); + } + + return $this->DefaultResponse(__FUNCTION__, $aResult); + } + + private function pluginEnable(string $sName, bool $bEnable = true) : bool + { + if (0 === \strlen($sName)) + { + return false; + } + + $oConfig = $this->Config(); + + $sEnabledPlugins = $oConfig->Get('plugins', 'enabled_list', ''); + $aEnabledPlugins = \explode(',', \strtolower($sEnabledPlugins)); + $aEnabledPlugins = \array_map('trim', $aEnabledPlugins); + + $aNewEnabledPlugins = array(); + if ($bEnable) + { + $aNewEnabledPlugins = $aEnabledPlugins; + $aNewEnabledPlugins[] = $sName; + } + else + { + foreach ($aEnabledPlugins as $sPlugin) + { + if ($sName !== $sPlugin && 0 < \strlen($sPlugin)) + { + $aNewEnabledPlugins[] = $sPlugin; + } + } + } + + $aNewEnabledPlugins = \array_unique($aNewEnabledPlugins); + $oConfig->Set('plugins', 'enabled_list', \trim(\implode(',', $aNewEnabledPlugins), ' ,')); + + return $oConfig->Save(); + } + + public function DoAdminPluginDisable() : array + { + $this->IsAdminLoggined(); + + $sName = (string) $this->GetActionParam('Name', ''); + $bDisable = '1' === (string) $this->GetActionParam('Disabled', '1'); + + if (!$bDisable) + { + $oPlugin = $this->Plugins()->CreatePluginByName($sName); + if ($oPlugin) + { + $sValue = $oPlugin->Supported(); + if (0 < \strlen($sValue)) + { + return $this->FalseResponse(__FUNCTION__, Notifications::UnsupportedPluginPackage, $sValue); + } + } + else + { + return $this->FalseResponse(__FUNCTION__, Notifications::InvalidPluginPackage); + } + } + + return $this->DefaultResponse(__FUNCTION__, $this->pluginEnable($sName, !$bDisable)); + } + + public function DoAdminPluginLoad() : array + { + $this->IsAdminLoggined(); + + $mResult = false; + $sName = (string) $this->GetActionParam('Name', ''); + + if (!empty($sName)) + { + $oPlugin = $this->Plugins()->CreatePluginByName($sName); + if ($oPlugin) + { + $mResult = array( + 'Name' => $sName, + 'Readme' => file_exists($oPlugin->Path().'/README') ? file_get_contents($oPlugin->Path().'/README') : '', + 'Config' => array() + ); + + $aMap = $oPlugin->ConfigMap(); + $oConfig = $oPlugin->Config(); + if (is_array($aMap)) + { + foreach ($aMap as $oItem) + { + if ($oItem && ($oItem instanceof \RainLoop\Plugins\Property)) + { + $aItem = $oItem->ToArray(); + $aItem[0] = $oConfig->Get('plugin', $oItem->Name(), ''); + if (PluginPropertyType::PASSWORD === $oItem->Type()) + { + $aItem[0] = APP_DUMMY; + } + + $mResult['Config'][] = $aItem; + } + } + } + } + } + + return $this->DefaultResponse(__FUNCTION__, $mResult); + } + + public function DoAdminPluginSettingsUpdate() : array + { + $this->IsAdminLoggined(); + + $mResult = false; + $sName = (string) $this->GetActionParam('Name', ''); + + if (!empty($sName)) + { + $oPlugin = $this->Plugins()->CreatePluginByName($sName); + if ($oPlugin) + { + $oConfig = $oPlugin->Config(); + $aMap = $oPlugin->ConfigMap(); + if (is_array($aMap)) + { + foreach ($aMap as $oItem) + { + $sValue = $this->GetActionParam('_'.$oItem->Name(), $oConfig->Get('plugin', $oItem->Name())); + if (PluginPropertyType::PASSWORD !== $oItem->Type() || APP_DUMMY !== $sValue) + { + $mResultValue = null; + switch ($oItem->Type()) { + case PluginPropertyType::INT: + $mResultValue = (int) $sValue; + break; + case PluginPropertyType::BOOL: + $mResultValue = '1' === (string) $sValue; + break; + case PluginPropertyType::SELECTION: + if (is_array($oItem->DefaultValue()) && in_array($sValue, $oItem->DefaultValue())) + { + $mResultValue = (string) $sValue; + } + break; + case PluginPropertyType::PASSWORD: + case PluginPropertyType::STRING: + case PluginPropertyType::STRING_TEXT: + $mResultValue = (string) $sValue; + break; + } + + if (null !== $mResultValue) + { + $oConfig->Set('plugin', $oItem->Name(), $mResultValue); + } + } + } + } + + $mResult = $oConfig->Save(); + } + } + + if (!$mResult) + { + throw new ClientException(Notifications::CantSavePluginSettings); + } + + return $this->DefaultResponse(__FUNCTION__, true); + } + +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php index af7d6694f..7ea634f9f 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php @@ -99,6 +99,10 @@ class ServiceActions } else if (!empty($sAction)) { + if (0 === stripos($sAction, 'Admin') && 'AdminLogin' !== $sAction && 'AdminLogout' !== $sAction) { + $this->oActions->IsAdminLoggined(); + } + $sMethodName = 'Do'.$sAction; $this->Logger()->Write('Action: '.$sMethodName, \MailSo\Log\Enumerations\Type::NOTE, 'AJAX');