Get('security', 'max_sys_getloadavg', 0)); if ($max && \is_callable('sys_getloadavg')) { $load = \sys_getloadavg(); if ($load && $load[0] > $max) { \header('HTTP/1.1 503 Service Unavailable', true, 503); \header('Retry-After: 120'); exit("Mailserver too busy ({$load[0]}). Please try again later."); } } $this->aReplaceEnv = null; if ((isset($_ENV) && \is_array($_ENV) && \count($_ENV)) || (isset($_SERVER) && \is_array($_SERVER) && \count($_SERVER))) { $sEnvNames = $this->Get('labs', 'replace_env_in_configuration', ''); if (\strlen($sEnvNames)) { $this->aReplaceEnv = \explode(',', $sEnvNames); if (\is_array($this->aReplaceEnv)) { $this->aReplaceEnv = \array_map('trim', $this->aReplaceEnv); $this->aReplaceEnv = \array_map('strtolower', $this->aReplaceEnv); } } } if (!\is_array($this->aReplaceEnv) || !\count($this->aReplaceEnv)) { $this->aReplaceEnv = null; } $sCipher = $this->Get('security', 'encrypt_cipher', ''); if (!$sCipher || !\SnappyMail\Crypt::cipherSupported($sCipher)) { $sCipher && \SnappyMail\Log::warning('Crypt', "OpenSSL no support for cipher '{$sCipher}'"); $aCiphers = \SnappyMail\Crypt::listCiphers(); $sCipher2 = $aCiphers ? $aCiphers[\array_rand($aCiphers)] : ''; if ($sCipher !== $sCipher2) { $this->Set('security', 'encrypt_cipher', $sCipher2); $this->Save(); } } return $bResult; } /** * @param mixed $mDefault = null * * @return mixed */ public function Get(string $sSection, string $sName, $mDefault = null) { $mResult = parent::Get($sSection, $sName, $mDefault); if ($this->aReplaceEnv && \is_string($mResult)) { $sKey = \strtolower($sSection.'.'.$sName); if (\in_array($sKey, $this->aReplaceEnv) && false !== strpos($mResult, '$')) { $mResult = \preg_replace_callback('/\$([^\s]+)/', function($aMatch) { if (!empty($aMatch[0]) && !empty($aMatch[1])) { if (!empty($_ENV[$aMatch[1]])) { return $_ENV[$aMatch[1]]; } if (!empty($_SERVER[$aMatch[1]])) { return $_SERVER[$aMatch[1]]; } return $aMatch[0]; } return ''; }, $mResult); } } return $mResult; } public function Set(string $sSectionKey, string $sParamKey, $mParamValue) : void { // Workarounds for the changed application structure if ('labs' === $sSectionKey) { if (\str_starts_with($sParamKey, 'imap_')) { $sSectionKey = 'imap'; $sParamKey = \str_replace('imap_', '', $sParamKey); } if (\str_starts_with($sParamKey, 'use_app_debug_')) { $sSectionKey = 'debug'; $sParamKey = \str_replace('use_app_debug_js', 'javascript', $sParamKey); $sParamKey = \str_replace('use_app_debug_css', 'css', $sParamKey); } if ('cache_system_data' === $sParamKey) { $sSectionKey = 'cache'; $sParamKey = 'system_data'; } if ('force_https' === $sParamKey) { $sSectionKey = 'security'; } if ('check_new_messages' === $sParamKey) { $sSectionKey = 'imap'; $sParamKey = 'fetch_new_messages'; } } parent::Set($sSectionKey, $sParamKey, $mParamValue); } public function SetPassword(string $sPassword) : void { $this->Set('security', 'admin_password', \password_hash($sPassword, PASSWORD_DEFAULT)); } public function ValidatePassword(string $sPassword) : bool { $sConfigPassword = (string) $this->Get('security', 'admin_password', ''); if (32 == \strlen($sPassword) && \md5(APP_SALT.$sPassword.APP_SALT) === $sConfigPassword) { $this->SetPassword($sPassword); return true; } return \strlen($sPassword) && \password_verify($sPassword, $sConfigPassword); } public function Save() : bool { $this->Set('version', 'current', APP_VERSION); $this->Set('version', 'saved', \gmdate('r')); return parent::Save(); } protected function defaultValues() : array { $value = \ini_get('upload_max_filesize'); $upload_max_filesize = \intval($value); switch (\strtoupper(\substr($value, -1))) { case 'G': $upload_max_filesize *= 1024; case 'M': $upload_max_filesize *= 1024; case 'K': $upload_max_filesize *= 1024; } $upload_max_filesize = $upload_max_filesize / 1024 / 1024; return array( 'webmail' => array( 'title' => array('SnappyMail Webmail', 'Text displayed as page title'), 'loading_description' => array('SnappyMail', 'Text displayed on startup'), 'favicon_url' => array(''), 'app_path' => array(''), 'theme' => array('Default', 'Theme used by default'), 'allow_themes' => array(true, 'Allow theme selection on settings screen'), 'allow_user_background' => array(false), 'language' => array('en', 'Language used by default'), 'language_admin' => array('en', 'Admin Panel interface language'), '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'), 'message_read_delay' => array(5, 'Mark message read after N seconds'), 'attachment_size_limit' => array(\min($upload_max_filesize, 25), 'File size limit (MB) for file upload on compose screen 0 for unlimited.'), 'compress_output' => array(false, 'brotli or gzip compress the output. Warning: only enable when server does not do this, else double compression errors occur') ), 'interface' => array( 'show_attachment_thumbnail' => array(true) ), 'contacts' => array( 'enable' => array(false, 'Enable contacts'), 'allow_sync' => array(false), 'sync_interval' => array(20), 'type' => array('sqlite'), 'pdo_dsn' => array('host=127.0.0.1;port=3306;dbname=snappymail'), 'pdo_user' => array('root'), 'pdo_password' => array(''), 'mysql_ssl_ca' => array('', 'PEM format certificate'), 'mysql_ssl_verify' => array(true), 'mysql_ssl_ciphers' => array('', 'HIGH'), 'suggestions_limit' => array(20) ), 'security' => array( 'custom_server_signature' => array('SnappyMail'), 'x_xss_protection_header' => array('1; mode=block'), 'openpgp' => array(false), 'allow_admin_panel' => array(true, 'Access settings'), 'admin_login' => array('admin', 'Login and password for web admin panel'), 'admin_password' => array(''), 'admin_totp' => array(''), 'admin_panel_host' => array(''), 'admin_panel_key' => array('admin'), 'force_https' => array(false), 'hide_x_mailer_header' => array(true), 'max_sys_getloadavg' => array(0.0, 'https://en.m.wikipedia.org/wiki/Load_(computing)'), 'content_security_policy' => array('', 'For example to allow all images use "img-src https:". More info at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives'), 'csp_report' => array(false, 'Report CSP errors to PHP and/or SnappyMail Log'), 'encrypt_cipher' => array('aes-256-cbc-hmac-sha1', 'A valid cipher method from https://php.net/openssl_get_cipher_methods'), 'cookie_samesite' => array('Strict', 'Strict, Lax or None'), 'secfetch_allow' => array('', 'Additional allowed Sec-Fetch combinations separated by ";". For example: * Allow iframe on same domain in any mode: dest=iframe,site=same-origin * Allow navigate to iframe on same domain: mode=navigate,dest=iframe,site=same-origin * Allow navigate to iframe on (sub)domain: mode=navigate,dest=iframe,site=same-site * Allow navigate to iframe from any domain: mode=navigate,dest=iframe,site=cross-site Default is "site=same-origin;site=none"') ), 'admin_panel' => array( /* 'enabled' => array(true, 'Access settings'), 'login' => array('admin', 'Login and password for web admin panel'), 'password' => array(''), 'totp' => array(''), 'host' => array(''), 'key' => array('admin'), */ 'allow_update' => array(false) ), 'ssl' => array( 'verify_certificate' => array(true, 'Require verification of SSL certificate used.'), 'allow_self_signed' => array(false, 'Allow self-signed certificates. Requires verify_certificate.'), 'security_level' => array(1, 'https://www.openssl.org/docs/man1.1.1/man3/SSL_CTX_set_security_level.html'), 'cafile' => array('', 'Location of Certificate Authority file on local filesystem (/etc/ssl/certs/ca-certificates.crt)'), 'capath' => array('', 'capath must be a correctly hashed certificate directory. (/etc/ssl/certs/)'), 'local_cert' => array('', 'Location of client certificate file (pem format with private key) on local filesystem'), 'disable_compression' => array(true, 'This can help mitigate the CRIME attack vector.') ), 'capa' => array( 'quota' => array(true), 'dangerous_actions' => array(true, 'Allow clear folder and delete messages without moving to trash'), 'attachments_actions' => array(true, 'Allow download attachments as Zip (and optionally others)') ), 'login' => array( 'default_domain' => array('', 'If someone logs in without "@domain.tld", this value will be used When this value is HTTP_HOST, the $_SERVER["HTTP_HOST"] value is used. When this value is SERVER_NAME, the $_SERVER["SERVER_NAME"] value is used. When this value is gethostname, the gethostname() value is used. '), 'allow_languages_on_login' => array(true, 'Allow language selection on webmail login screen'), 'determine_user_language' => array(true, 'Detect language from browser header `Accept-Language`'), 'determine_user_domain' => array(false, 'Like default_domain but then HTTP_HOST/SERVER_NAME without www.'), 'login_lowercase' => array(true), 'sign_me_auto' => array(\RainLoop\Enumerations\SignMeType::DEFAULT_OFF, 'This option allows webmail to remember the logged in user once they closed the browser window. Values: "DefaultOff" - can be used, disabled by default; "DefaultOn" - can be used, enabled by default; "Unused" - cannot be used') ), 'plugins' => array( 'enable' => array(false, 'Enable plugin support'), 'enabled_list' => array('', 'Comma-separated list of enabled plugins'), ), 'defaults' => array( 'view_editor_type' => array('Html', 'Editor mode used by default (Plain, Html)'), 'view_layout' => array(1, 'layout: 0 - no preview, 1 - side preview, 2 - bottom preview'), 'view_use_checkboxes' => array(true), 'autologout' => array(30), 'view_html' => array(true), 'show_images' => array(false), 'contacts_autosave' => array(true), 'mail_use_threads' => array(false), 'allow_draft_autosave' => array(true), 'mail_reply_same_folder' => array(false), 'msg_default_action' => array(1, '1 - reply, 2 - reply all'), ), 'logs' => array( 'enable' => array(false, 'Enable logging'), 'path' => array('', 'Path where log files will be stored'), 'level' => array(4, 'Log messages of set RFC 5424 section 6.2.1 Severity level and higher (0 = highest, 7 = lowest). 0 = Emergency 1 = Alert 2 = Critical 3 = Error 4 = Warning 5 = Notice 6 = Informational 7 = Debug'), 'hide_passwords' => array(true, 'Required for development purposes only. Disabling this option is not recommended.'), 'time_zone' => array('UTC'), 'filename' => array('log-{date:Y-m-d}.txt', 'Log filename. For security reasons, some characters are removed from filename. Allows for pattern-based folder creation (see examples below). Patterns: {date:Y-m-d} - Replaced by pattern-based date Detailed info: http://www.php.net/manual/en/function.date.php {user:email} - Replaced by user\'s email address If user is not logged in, value is set to "unknown" {user:login} - Replaced by user\'s login (the user part of an email) If user is not logged in, value is set to "unknown" {user:domain} - Replaced by user\'s domain name (the domain part of an email) If user is not logged in, value is set to "unknown" {user:uid} - Replaced by user\'s UID regardless of account currently used {user:ip} {request:ip} - Replaced by user\'s IP address Others: {imap:login} {imap:host} {imap:port} {smtp:login} {smtp:host} {smtp:port} Examples: filename = "log-{date:Y-m-d}.txt" filename = "{date:Y-m-d}/{user:domain}/{user:email}_{user:uid}.log" filename = "{user:email}-{date:Y-m-d}.txt" filename = "syslog" filename = "stderr"'), 'auth_logging' => array(false, 'Enable auth logging in a separate file (for fail2ban)'), 'auth_logging_filename' => array('fail2ban/auth-{date:Y-m-d}.txt'), 'auth_logging_format' => array('[{date:Y-m-d H:i:s}] Auth failed: ip={request:ip} user={imap:login} host={imap:host} port={imap:port}'), 'auth_syslog' => array(false, 'Enable auth logging to syslog for fail2ban') ), 'debug' => array( 'enable' => array(false, 'Special option required for development purposes'), // use_app_debug_js 'javascript' => array(false), // use_app_debug_css 'css' => array(false) ), 'cache' => array( 'enable' => array(true, 'The section controls caching of the entire application. Enables caching in the system'), 'path' => array('', 'Path where cache files will be stored'), 'index' => array('v1', 'Additional caching key. If changed, cache is purged'), 'fast_cache_driver' => array('files', 'Can be: files, APCU, memcache, redis (beta)'), 'fast_cache_index' => array('v1', 'Additional caching key. If changed, fast cache is purged'), 'http' => array(true, 'Browser-level cache. If enabled, caching is maintainted without using files'), 'http_expires' => array(3600, 'Browser-level cache time (seconds, Expires header)'), 'server_uids' => array(true, 'Caching message UIDs when searching and sorting (threading)'), 'system_data' => array(true) ), 'imap' => array( 'use_force_selection' => array(false), 'use_expunge_all_on_delete' => array(false), 'message_list_fast_simple_search' => array(true), 'message_list_permanent_filter' => array(''), 'message_all_headers' => array(false), 'show_login_alert' => array(true), 'fetch_new_messages' => array(true), ), 'labs' => array( 'date_from_headers' => array(true, 'Display message RFC 2822 date and time header, instead of the arrival internal date.'), 'allow_message_append' => array(false), 'login_fault_delay' => array(5, 'When login fails, wait N seconds before responding'), 'log_ajax_response_write_limit' => array(300), 'smtp_show_server_errors' => array(false), 'sieve_auth_plain_initial' => array(true), 'sieve_allow_fileinto_inbox' => array(false), 'mail_func_clear_headers' => array(true, 'PHP mail() remove To and Subject headers'), 'mail_func_additional_parameters' => array(false, 'PHP mail() set -f emailaddress'), 'folders_spec_limit' => array(50), 'curl_proxy' => array(''), 'curl_proxy_auth' => array(''), 'custom_login_link' => array(''), 'custom_logout_link' => array(''), 'http_client_ip_check_proxy' => array(false), 'fast_cache_memcache_host' => array('127.0.0.1'), 'fast_cache_memcache_port' => array(11211), 'fast_cache_redis_host' => array('127.0.0.1'), 'fast_cache_redis_port' => array(6379), 'use_local_proxy_for_external_images' => array(true), 'image_exif_auto_rotate' => array(false), 'cookie_default_path' => array(''), 'cookie_default_secure' => array(false), 'replace_env_in_configuration' => array(''), 'boundary_prefix' => array(''), 'dev_email' => array(''), 'dev_password' => array('') ), 'version' => array( 'current' => array(''), 'saved' => array('') ) ); } }