From fcf2c2468dffaeef29819284be9c55c0295d26ad Mon Sep 17 00:00:00 2001 From: Floris Westerman Date: Thu, 26 Nov 2020 15:13:55 +0100 Subject: [PATCH] Add better Docker development and prod support --- .../mail/config/dovecot-quotas.cf} | 0 .docker/{ => dev}/mail/setup.sh | 0 .docker/{ => dev}/nginx/default.conf | 1 - .docker/{ => dev}/nginx/ssl.sh | 0 .docker/{ => dev}/php/Dockerfile | 0 .docker/{ => dev}/php/snappymail.ini | 0 .docker/node/Dockerfile | 6 - .docker/release/Dockerfile | 48 +++ .docker/release/files/entrypoint.sh | 77 ++++ .../release/files/etc/logrotate.d/snappymail | 5 + .docker/release/files/etc/nginx/nginx.conf | 106 ++++++ .docker/release/files/listener.php | 23 ++ .docker/release/files/logrotate-loop.sh | 113 ++++++ .docker/release/files/supervisor.conf | 60 +++ .../usr/local/etc/php-fpm.d/php-fpm.conf | 19 + .../files/usr/local/include/application.ini | 359 ++++++++++++++++++ .../ChangePasswordPostfixAdminDriver.php | 342 +++++++++++++++++ .../postfixadmin-change-password/LICENSE | 20 + .../postfixadmin-change-password/README | 1 + .../postfixadmin-change-password/VERSION | 1 + .../postfixadmin-change-password/index.php | 100 +++++ .../postfixadmin-change-password/md5crypt.php | 139 +++++++ .docker/tx/Dockerfile | 5 - .gitignore | 3 +- docker-compose.yml | 76 ++-- release.php | 13 + template.env | 2 + 27 files changed, 1453 insertions(+), 66 deletions(-) rename .docker/{nginx/ssl/.gitempty => dev/mail/config/dovecot-quotas.cf} (100%) rename .docker/{ => dev}/mail/setup.sh (100%) mode change 100755 => 100644 rename .docker/{ => dev}/nginx/default.conf (99%) rename .docker/{ => dev}/nginx/ssl.sh (100%) rename .docker/{ => dev}/php/Dockerfile (100%) rename .docker/{ => dev}/php/snappymail.ini (100%) delete mode 100644 .docker/node/Dockerfile create mode 100644 .docker/release/Dockerfile create mode 100644 .docker/release/files/entrypoint.sh create mode 100644 .docker/release/files/etc/logrotate.d/snappymail create mode 100644 .docker/release/files/etc/nginx/nginx.conf create mode 100644 .docker/release/files/listener.php create mode 100644 .docker/release/files/logrotate-loop.sh create mode 100644 .docker/release/files/supervisor.conf create mode 100644 .docker/release/files/usr/local/etc/php-fpm.d/php-fpm.conf create mode 100644 .docker/release/files/usr/local/include/application.ini create mode 100644 .docker/release/files/usr/local/include/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php create mode 100644 .docker/release/files/usr/local/include/postfixadmin-change-password/LICENSE create mode 100644 .docker/release/files/usr/local/include/postfixadmin-change-password/README create mode 100644 .docker/release/files/usr/local/include/postfixadmin-change-password/VERSION create mode 100644 .docker/release/files/usr/local/include/postfixadmin-change-password/index.php create mode 100644 .docker/release/files/usr/local/include/postfixadmin-change-password/md5crypt.php delete mode 100644 .docker/tx/Dockerfile create mode 100644 template.env diff --git a/.docker/nginx/ssl/.gitempty b/.docker/dev/mail/config/dovecot-quotas.cf similarity index 100% rename from .docker/nginx/ssl/.gitempty rename to .docker/dev/mail/config/dovecot-quotas.cf diff --git a/.docker/mail/setup.sh b/.docker/dev/mail/setup.sh old mode 100755 new mode 100644 similarity index 100% rename from .docker/mail/setup.sh rename to .docker/dev/mail/setup.sh diff --git a/.docker/nginx/default.conf b/.docker/dev/nginx/default.conf similarity index 99% rename from .docker/nginx/default.conf rename to .docker/dev/nginx/default.conf index 802d50431..aaf702abc 100644 --- a/.docker/nginx/default.conf +++ b/.docker/dev/nginx/default.conf @@ -1,4 +1,3 @@ - server { listen 80 default; listen 443 ssl; diff --git a/.docker/nginx/ssl.sh b/.docker/dev/nginx/ssl.sh similarity index 100% rename from .docker/nginx/ssl.sh rename to .docker/dev/nginx/ssl.sh diff --git a/.docker/php/Dockerfile b/.docker/dev/php/Dockerfile similarity index 100% rename from .docker/php/Dockerfile rename to .docker/dev/php/Dockerfile diff --git a/.docker/php/snappymail.ini b/.docker/dev/php/snappymail.ini similarity index 100% rename from .docker/php/snappymail.ini rename to .docker/dev/php/snappymail.ini diff --git a/.docker/node/Dockerfile b/.docker/node/Dockerfile deleted file mode 100644 index af8934b75..000000000 --- a/.docker/node/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM node:10.16.0-alpine - -RUN apk add --no-cache git -RUN yarn global add gulp - -CMD ["node", "--version"] diff --git a/.docker/release/Dockerfile b/.docker/release/Dockerfile new file mode 100644 index 000000000..ebda05a40 --- /dev/null +++ b/.docker/release/Dockerfile @@ -0,0 +1,48 @@ +# Inspired by the original Rainloop dockerfile from youtous on GitLab +FROM php:7.3-fpm-buster + +ARG FILES_ZIP +LABEL org.label-schema.description="SnappyMail webmail client image using nginx, php-fpm based on Debian Buster" + +ENV UID=991 GID=991 UPLOAD_MAX_SIZE=50M LOG_TO_STDERR=true MEMORY_LIMIT=128M SECURE_COOKIES=true +ENV fpm.pool.clear_env=false + +# Install dependencies such as nginx +RUN mkdir -p /usr/share/man/man1/ /usr/share/man/man3/ /usr/share/man/man7/ && \ + apt-get update -q --fix-missing && \ + apt-get -y upgrade && \ + apt-get install --no-install-recommends -y \ + apt-transport-https gnupg openssl wget curl ca-certificates nginx supervisor sudo \ + unzip libzip-dev libxml2-dev libldb-dev libldap2-dev \ + sqlite3 libsqlite3-dev libsqlite3-0 libpq-dev postgresql-client mariadb-client logrotate \ + zip mlocate libmcrypt-dev libpcre3-dev libicu-dev \ + build-essential chrpath libssl-dev \ + libxft-dev libfreetype6 libfreetype6-dev \ + libpng-dev libjpeg62-turbo-dev \ + libfontconfig1 libfontconfig1-dev \ + && \ + rm -rf /var/lib/apt/lists/* + +# Install PHP extensions +RUN php -m && \ + docker-php-ext-configure ldap --with-libdir=lib/x86_64-linux-gnu/ && \ + docker-php-ext-configure intl && \ + docker-php-ext-configure gd --with-freetype-dir=/usr/include --with-jpeg-dir=/usr/include/ && \ + docker-php-ext-install ldap opcache pdo_mysql pdo_pgsql zip intl gd && \ + php -m + +# Install snappymail +WORKDIR /tmp +COPY ${FILES_ZIP} . +RUN mkdir /snappymail && \ + unzip -q ${FILES_ZIP} -d /snappymail && \ + find /snappymail -type d -exec chmod 755 {} \; && \ + find /snappymail -type f -exec chmod 644 {} \; && \ + rm -rf ${FILES_ZIP} + +# Install other content +COPY files / +RUN chmod +x /entrypoint.sh && chmod +x /logrotate-loop.sh +VOLUME /snappymail/data +EXPOSE 8888 +CMD ["/entrypoint.sh"] \ No newline at end of file diff --git a/.docker/release/files/entrypoint.sh b/.docker/release/files/entrypoint.sh new file mode 100644 index 000000000..21e23f8f6 --- /dev/null +++ b/.docker/release/files/entrypoint.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +# Create not root user +groupadd --gid "$GID" php-cli -f +adduser --uid "$UID" --disabled-password --gid "$GID" --shell /bin/bash --home /home/php-cli php-cli --force --gecos "" + + +# Set attachment size limit +sed -i "s//$UPLOAD_MAX_SIZE/g" /usr/local/etc/php-fpm.d/php-fpm.conf /etc/nginx/nginx.conf +sed -i "s//$MEMORY_LIMIT/g" /usr/local/etc/php-fpm.d/php-fpm.conf + +# Remove postfixadmin-change-password plugin if exist +if [ -d "/snappymail/data/_data_/_default_/plugins/postfixadmin-change-password" ]; then + rm -rf /snappymail/data/_data_/_default_/plugins/postfixadmin-change-password +fi + +# Set log output to STDERR if wanted (LOG_TO_STDERR=true) +if [ "$LOG_TO_STDERR" = true ]; then + echo "[INFO] Logging to stderr activated" + sed -i "s/.*error_log.*$/error_log \/dev\/stderr warn;/" /etc/nginx/nginx.conf + sed -i "s/.*error_log.*$/php_admin_value[error_log] = \/dev\/stderr/" /usr/local/etc/php-fpm.d/php-fpm.conf +fi + +# Secure cookies +if [ "${SECURE_COOKIES}" = true ]; then + echo "[INFO] Secure cookies activated" + { + echo 'session.cookie_httponly = On'; + echo 'session.cookie_secure = On'; + echo 'session.use_only_cookies = On'; + } > /usr/local/etc/php/conf.d/cookies.ini; +fi + +# Add postfixadmin-change-password plugin +mkdir -p /snappymail/data/_data_/_default_/plugins/ +cp -r /usr/local/include/postfixadmin-change-password /snappymail/data/_data_/_default_/plugins/ + +# Copy snappymail default config if absent +SNAPPYMAIL_CONFIG_FILE=/snappymail/data/_data_/_default_/configs/application.ini +if [ ! -f "$SNAPPYMAIL_CONFIG_FILE" ]; then + echo "[INFO] Creating default Snappymail configuration" + mkdir -p $(dirname $SNAPPYMAIL_CONFIG_FILE) + cp /usr/local/include/application.ini $SNAPPYMAIL_CONFIG_FILE +fi + +# Enable output of snappymail logs +if [ "${LOG_TO_STDERR}" = true ]; then + sed -z 's/\; Enable logging\nenable = Off/\; Enable logging\nenable = On/' -i $SNAPPYMAIL_CONFIG_FILE + sed 's/^filename = .*/filename = "errors.log"/' -i $SNAPPYMAIL_CONFIG_FILE + sed 's/^write_on_error_only = .*/write_on_error_only = Off/' -i $SNAPPYMAIL_CONFIG_FILE + sed 's/^write_on_php_error_only = .*/write_on_php_error_only = On/' -i $SNAPPYMAIL_CONFIG_FILE +else + sed -z 's/\; Enable logging\nenable = On/\; Enable logging\nenable = Off/' -i $SNAPPYMAIL_CONFIG_FILE +fi +# Always enable snappymail Auth logging +sed 's/^auth_logging = .*/auth_logging = On/' -i $SNAPPYMAIL_CONFIG_FILE +sed 's/^auth_logging_filename = .*/auth_logging_filename = "auth.log"/' -i $SNAPPYMAIL_CONFIG_FILE +sed 's/^auth_logging_format = .*/auth_logging_format = "[{date:Y-m-d H:i:s}] Auth failed: ip={request:ip} user={imap:login} host={imap:host} port={imap:port}"/' -i $SNAPPYMAIL_CONFIG_FILE +# Redirect snappymail logs to stderr /stdout +mkdir -p /snappymail/data/_data_/_default_/logs/ +# empty logs +cp /dev/null /snappymail/data/_data_/_default_/logs/errors.log +cp /dev/null /snappymail/data/_data_/_default_/logs/auth.log +chown -R php-cli:php-cli /snappymail/data/ + +# Fix permissions +chown -R $UID:$GID /snappymail/data /var/log /var/lib/nginx +chmod o+w /dev/stdout +chmod o+w /dev/stderr + + +# Touch supervisord PID file in order to fix permissions +touch /run/supervisord.pid +chown php-cli:php-cli /run/supervisord.pid + +# RUN ! +exec sudo -u php-cli -g php-cli /usr/bin/supervisord -c '/supervisor.conf' --pidfile '/run/supervisord.pid' diff --git a/.docker/release/files/etc/logrotate.d/snappymail b/.docker/release/files/etc/logrotate.d/snappymail new file mode 100644 index 000000000..701631d98 --- /dev/null +++ b/.docker/release/files/etc/logrotate.d/snappymail @@ -0,0 +1,5 @@ +/snappymail/data/_data_/_default_/logs/* { + size 10M + rotate 0 + missingok +} \ No newline at end of file diff --git a/.docker/release/files/etc/nginx/nginx.conf b/.docker/release/files/etc/nginx/nginx.conf new file mode 100644 index 000000000..1bb643f23 --- /dev/null +++ b/.docker/release/files/etc/nginx/nginx.conf @@ -0,0 +1,106 @@ +worker_processes auto; +pid /tmp/nginx.pid; + +events { + worker_connections 1024; + use epoll; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + access_log off; + error_log /tmp/ngx_error.log error; + + sendfile on; + keepalive_timeout 15; + keepalive_disable msie6; + keepalive_requests 100; + tcp_nopush on; + tcp_nodelay on; + server_tokens off; + + fastcgi_temp_path /tmp/fastcgi 1 2; + client_body_temp_path /tmp/client_body 1 2; + proxy_temp_path /tmp/proxy 1 2; + uwsgi_temp_path /tmp/uwsgi 1 2; + scgi_temp_path /tmp/scgi 1 2; + + gzip on; + gzip_comp_level 5; + gzip_min_length 512; + gzip_buffers 4 8k; + gzip_proxied any; + gzip_vary on; + gzip_disable "msie6"; + gzip_types + text/css + text/javascript + text/xml + text/plain + text/x-component + application/javascript + application/x-javascript + application/json + application/xml + application/rss+xml + application/vnd.ms-fontobject + font/truetype + font/opentype + image/svg+xml; + + server { + listen 8888; + root /snappymail; + index index.php; + charset utf-8; + + client_max_body_size ; + + location ^~ /data { + deny all; + } + + location / { + try_files $uri $uri/ index.php; + } + + # Assets cache control + # -------------------------------------- + location ~* \.(?:html|xml|json)$ { + expires -1; + } + + location ~* \.(?:css|js)$ { + expires 7d; + add_header Pragma public; + add_header Cache-Control "public"; + } + + location ~* \.(?:gif|jpe?g|png|ico|otf|eot|svg|ttf|woff|woff2)$ { + expires 30d; + log_not_found off; + add_header Pragma public; + add_header Cache-Control "public"; + } + + # PHP Backend + # -------------------------------------- + location ~* \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTP_PROXY ""; + fastcgi_index index.php; + fastcgi_pass unix:/tmp/php-fpm.sock; + fastcgi_intercept_errors on; + fastcgi_request_buffering off; + fastcgi_param REMOTE_ADDR $http_x_real_ip; + } + + } + +} diff --git a/.docker/release/files/listener.php b/.docker/release/files/listener.php new file mode 100644 index 000000000..0d44ab5a9 --- /dev/null +++ b/.docker/release/files/listener.php @@ -0,0 +1,23 @@ + +php_admin_value[upload_max_filesize] = +php_admin_value[memory_limit] = diff --git a/.docker/release/files/usr/local/include/application.ini b/.docker/release/files/usr/local/include/application.ini new file mode 100644 index 000000000..f70bfcf4a --- /dev/null +++ b/.docker/release/files/usr/local/include/application.ini @@ -0,0 +1,359 @@ +; SnappyMail Webmail configuration file +; Please don't add custom parameters here, those will be overwritten + +[webmail] +; Text displayed as page title +title = "SnappyMail Webmail" + +; Text displayed on startup +loading_description = "SnappyMail" +favicon_url = "" + +; Theme used by default +theme = "Default" + +; Allow theme selection on settings screen +allow_themes = On +allow_user_background = Off + +; Language used by default +language = "en" + +; Admin Panel interface language +language_admin = "en" + +; Allow language selection on settings screen +allow_languages_on_settings = On +allow_additional_accounts = On +allow_additional_identities = On + +; Number of messages displayed on page by default +messages_per_page = 20 + +; File size limit (MB) for file upload on compose screen +; 0 for unlimited. +attachment_size_limit = 25 + +[interface] +show_attachment_thumbnail = On +use_native_scrollbars = Off +new_move_to_folder_button = On + +[branding] +login_logo = "" +login_background = "" +login_desc = "" +login_css = "" +user_css = "" +user_logo = "" +user_logo_title = "" +user_logo_message = "" +user_iframe_message = "" +welcome_page_url = "" +welcome_page_display = "none" + +[contacts] +; Enable contacts +enable = Off +allow_sync = On +sync_interval = 20 +type = "sqlite" +pdo_dsn = "mysql:host=127.0.0.1;port=3306;dbname=snappymail" +pdo_user = "root" +pdo_password = "" +suggestions_limit = 30 + +[security] +; Enable CSRF protection (http://en.wikipedia.org/wiki/Cross-site_request_forgery) +csrf_protection = On +custom_server_signature = "SnappyMail" +x_frame_options_header = "" +x_xss_protection_header = "1; mode=block" +openpgp = Off + +; Login and password for web admin panel +admin_login = "admin" +admin_password = "12345" + +; Access settings +allow_admin_panel = On +allow_two_factor_auth = Off +force_two_factor_auth = Off +hide_x_mailer_header = Off +admin_panel_host = "" +admin_panel_key = "admin" +content_security_policy = "" +core_install_access_domain = "" + +[ssl] +; Require verification of SSL certificate used. +verify_certificate = Off + +; Allow self-signed certificates. Requires verify_certificate. +allow_self_signed = On + +; Location of Certificate Authority file on local filesystem (/etc/ssl/certs/ca-certificates.crt) +cafile = "" + +; capath must be a correctly hashed certificate directory. (/etc/ssl/certs/) +capath = "" + +; Location of client certificate file (pem format with private key) on local filesystem +client_cert = "" + +[capa] +folders = On +composer = On +contacts = On +settings = On +quota = On +help = On +reload = On +search = On +search_adv = On +filters = On +x-templates = Off +dangerous_actions = On +message_actions = On +messagelist_actions = On +attachments_actions = On + +[login] +default_domain = "" + +; Allow language selection on webmail login screen +allow_languages_on_login = On +determine_user_language = On +determine_user_domain = Off +welcome_page = Off +hide_submit_button = On +forgot_password_link_url = "" +registration_link_url = "" +login_lowercase = On + +; 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 +sign_me_auto = "DefaultOff" + +[plugins] +; Enable plugin support +enable = Off + +; List of enabled plugins +enabled_list = "" + +[defaults] +; Editor mode used by default (Plain, Html, HtmlForced or PlainForced) +view_editor_type = "Html" + +; layout: 0 - no preview, 1 - side preview, 2 - bottom preview +view_layout = 1 +view_use_checkboxes = On +autologout = 30 +show_images = Off +contacts_autosave = On +mail_use_threads = Off +allow_draft_autosave = On +mail_reply_same_folder = Off + +[logs] +; Enable logging +enable = Off + +; Logs entire request only if error occured (php requred) +write_on_error_only = Off + +; Logs entire request only if php error occured +write_on_php_error_only = Off + +; Logs entire request only if request timeout (in seconds) occured. +write_on_timeout_only = 0 + +; Required for development purposes only. +; Disabling this option is not recommended. +hide_passwords = On +time_offset = "0" +session_filter = "" +sentry_dsn = "" + +; 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 = "log-{date:Y-m-d}.txt" + +; Enable auth logging in a separate file (for fail2ban) +auth_logging = Off +auth_logging_filename = "fail2ban/auth-{date:Y-m-d}.txt" +auth_logging_format = "[{date:Y-m-d H:i:s}] Auth failed: ip={request:ip} user={imap:login} host={imap:host} port={imap:port}" + +[debug] +; Special option required for development purposes +enable = Off + +[social] +; Google +google_enable = Off +google_enable_auth = Off +google_enable_auth_gmail = Off +google_enable_drive = Off +google_enable_preview = Off +google_client_id = "" +google_client_secret = "" +google_api_key = "" + +; Facebook +fb_enable = Off +fb_app_id = "" +fb_app_secret = "" + +; Twitter +twitter_enable = Off +twitter_consumer_key = "" +twitter_consumer_secret = "" + +; Dropbox +dropbox_enable = Off +dropbox_api_key = "" + +[cache] +; The section controls caching of the entire application. +; +; Enables caching in the system +enable = On + +; Additional caching key. If changed, cache is purged +index = "v1" + +; Can be: files, APC, memcache, redis (beta) +fast_cache_driver = "files" + +; Additional caching key. If changed, fast cache is purged +fast_cache_index = "v1" + +; Browser-level cache. If enabled, caching is maintainted without using files +http = On + +; Browser-level cache time (seconds, Expires header) +http_expires = 3600 + +; Caching message UIDs when searching and sorting (threading) +server_uids = On + +[labs] +; Experimental settings. Handle with care. +; +allow_mobile_version = On +ignore_folders_subscription = Off +check_new_password_strength = On +update_channel = "stable" +allow_gravatar = On +allow_prefetch = On +allow_smart_html_links = On +cache_system_data = On +date_from_headers = On +autocreate_system_folders = On +allow_message_append = Off +disable_iconv_if_mbstring_supported = Off +login_fault_delay = 1 +log_ajax_response_write_limit = 300 +allow_html_editor_source_button = Off +allow_html_editor_biti_buttons = Off +allow_ctrl_enter_on_compose = On +try_to_detect_hidden_images = Off +hide_dangerous_actions = Off +use_app_debug_js = Off +use_mobile_version_for_tablets = Off +use_app_debug_css = Off +use_imap_sort = On +use_imap_force_selection = Off +use_imap_list_subscribe = On +use_imap_thread = On +use_imap_move = Off +use_imap_expunge_all_on_delete = Off +imap_forwarded_flag = "$Forwarded" +imap_read_receipt_flag = "$ReadReceipt" +imap_body_text_limit = 555000 +imap_message_list_fast_simple_search = On +imap_message_list_count_limit_trigger = 0 +imap_message_list_date_filter = 0 +imap_message_list_permanent_filter = "" +imap_message_all_headers = Off +imap_large_thread_limit = 50 +imap_folder_list_limit = 200 +imap_show_login_alert = On +imap_use_auth_plain = On +imap_use_auth_cram_md5 = Off +smtp_show_server_errors = Off +smtp_use_auth_plain = On +smtp_use_auth_cram_md5 = Off +sieve_allow_raw_script = Off +sieve_utf8_folder_name = On +sieve_auth_plain_initial = On +sieve_allow_fileinto_inbox = Off +imap_timeout = 300 +smtp_timeout = 60 +sieve_timeout = 10 +domain_list_limit = 99 +mail_func_clear_headers = On +mail_func_additional_parameters = Off +favicon_status = On +folders_spec_limit = 50 +owncloud_save_folder = "Attachments" +owncloud_suggestions = On +curl_proxy = "" +curl_proxy_auth = "" +in_iframe = Off +force_https = Off +custom_login_link = "" +custom_logout_link = "" +allow_external_login = Off +allow_external_sso = Off +external_sso_key = "" +http_client_ip_check_proxy = Off +fast_cache_memcache_host = "127.0.0.1" +fast_cache_memcache_port = 11211 +fast_cache_redis_host = "127.0.0.1" +fast_cache_redis_port = 6379 +use_local_proxy_for_external_images = Off +detect_image_exif_orientation = On +cookie_default_path = "" +cookie_default_secure = Off +check_new_messages = On +replace_env_in_configuration = "" +startup_url = "" +strict_html_parser = Off +allow_cmd = Off +dev_email = "" +dev_password = "" + +[version] +current = "1.14.0" +saved = "Wed, 08 Apr 2020 16:37:27 +0000" \ No newline at end of file diff --git a/.docker/release/files/usr/local/include/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php b/.docker/release/files/usr/local/include/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php new file mode 100644 index 000000000..d52d13c11 --- /dev/null +++ b/.docker/release/files/usr/local/include/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php @@ -0,0 +1,342 @@ +sEngine = $sEngine; + return $this; + } + + /** + * @param string $sHost + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetHost($sHost) + { + $this->sHost = $sHost; + return $this; + } + + /** + * @param int $iPort + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetPort($iPort) + { + $this->iPort = (int) $iPort; + return $this; + } + + /** + * @param string $sDatabase + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetDatabase($sDatabase) + { + $this->sDatabase = $sDatabase; + return $this; + } + + /** + * @param string $sTable + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetTable($sTable) + { + $this->sTable = $sTable; + return $this; + } + + /** + * @param string $sUsercol + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetUserColumn($sUsercol) + { + $this->sUsercol = $sUsercol; + return $this; + } + + /** + * @param string $sPasscol + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetPasswordColumn($sPasscol) + { + $this->sPasscol = $sPasscol; + return $this; + } + + /** + * @param string $sUser + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetUser($sUser) + { + $this->sUser = $sUser; + return $this; + } + + /** + * @param string $sPassword + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetPassword($sPassword) + { + $this->sPassword = $sPassword; + return $this; + } + + /** + * @param string $sEncrypt + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetEncrypt($sEncrypt) + { + $this->sEncrypt = $sEncrypt; + return $this; + } + + /** + * @param string $sAllowedEmails + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetAllowedEmails($sAllowedEmails) + { + $this->sAllowedEmails = $sAllowedEmails; + return $this; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \ChangePasswordPostfixAdminDriver + */ + public function SetLogger($oLogger) + { + if ($oLogger instanceof \MailSo\Log\Logger) + { + $this->oLogger = $oLogger; + } + + return $this; + } + + /** + * @param \RainLoop\Model\Account $oAccount + * + * @return bool + */ + public function PasswordChangePossibility($oAccount) + { + return $oAccount && $oAccount->Email() && + \RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param string $sPrevPassword + * @param string $sNewPassword + * + * @return bool + */ + public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword) + { + if ($this->oLogger) + { + $this->oLogger->Write('Postfix: Try to change password for '.$oAccount->Email()); + } + + unset($sPrevPassword); + + $bResult = false; + + if (0 < \strlen($sNewPassword)) + { + try + { + $sDsn = ''; + switch($this->sEngine){ + case 'MySQL': + $sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase; + break; + case 'PostgreSQL': + $sDsn = 'pgsql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase; + break; + default: + $sDsn = 'mysql:host='.$this->sHost.';port='.$this->iPort.';dbname='.$this->sDatabase; + break; + } + + + $oPdo = new \PDO($sDsn, $this->sUser, $this->sPassword); + $oPdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + $sUpdatePassword = $this->cryptPassword($sNewPassword, $oPdo); + if (0 < \strlen($sUpdatePassword)) + { + $oStmt = $oPdo->prepare("UPDATE {$this->sTable} SET {$this->sPasscol} = ? WHERE {$this->sUsercol} = ?"); + $bResult = (bool) $oStmt->execute(array($sUpdatePassword, $oAccount->Email())); + } + else + { + if ($this->oLogger) + { + $this->oLogger->Write('Postfix: Encrypted password is empty', + \MailSo\Log\Enumerations\Type::ERROR); + } + } + + $oPdo = null; + } + catch (\Exception $oException) + { + if ($this->oLogger) + { + $this->oLogger->WriteException($oException); + } + } + } + + return $bResult; + } + + /** + * @param string $sPassword + * @param \PDO $oPdo + * + * @return string + */ + private function cryptPassword($sPassword, $oPdo) + { + $sResult = ''; + if (function_exists('random_bytes')) { + $sSalt = substr(base64_encode(random_bytes(32)), 0, 16); + } else { + $sSalt = substr(str_shuffle('./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), 0, 16); + } + switch (strtolower($this->sEncrypt)) + { + default: + case 'plain': + case 'cleartext': + $sResult = '{PLAIN}' . $sPassword; + break; + + case 'md5crypt': + include_once __DIR__.'/md5crypt.php'; + $sResult = '{MD5-CRYPT}' . md5crypt($sPassword); + break; + + case 'md5': + $sResult = '{PLAIN-MD5}' . md5($sPassword); + break; + + case 'system': + $sResult = '{CRYPT}' . crypt($sPassword); + break; + + case 'sha256-crypt': + $sResult = '{SHA256-CRYPT}' . crypt($sPassword,'$5$'.$sSalt); + break; + + case 'sha512-crypt': + $sResult = '{SHA512-CRYPT}' . crypt($sPassword,'$6$'.$sSalt); + break; + + case 'mysql_encrypt': + if($this->sEngine == 'MySQL'){ + $oStmt = $oPdo->prepare('SELECT ENCRYPT(?) AS encpass'); + if ($oStmt->execute(array($sPassword))) + { + $aFetchResult = $oStmt->fetchAll(\PDO::FETCH_ASSOC); + if (\is_array($aFetchResult) && isset($aFetchResult[0]['encpass'])) + { + $sResult = $aFetchResult[0]['encpass']; + } + } + }else{ + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CouldNotSaveNewPassword); + } + break; + } + + return $sResult; + } +} diff --git a/.docker/release/files/usr/local/include/postfixadmin-change-password/LICENSE b/.docker/release/files/usr/local/include/postfixadmin-change-password/LICENSE new file mode 100644 index 000000000..c6cb63dbf --- /dev/null +++ b/.docker/release/files/usr/local/include/postfixadmin-change-password/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 RainLoop Team, @zaffkea + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.docker/release/files/usr/local/include/postfixadmin-change-password/README b/.docker/release/files/usr/local/include/postfixadmin-change-password/README new file mode 100644 index 000000000..342f4d4b5 --- /dev/null +++ b/.docker/release/files/usr/local/include/postfixadmin-change-password/README @@ -0,0 +1 @@ +Plugin that adds functionality to change the email account password (PostfixAdmin). \ No newline at end of file diff --git a/.docker/release/files/usr/local/include/postfixadmin-change-password/VERSION b/.docker/release/files/usr/local/include/postfixadmin-change-password/VERSION new file mode 100644 index 000000000..7e32cd569 --- /dev/null +++ b/.docker/release/files/usr/local/include/postfixadmin-change-password/VERSION @@ -0,0 +1 @@ +1.3 diff --git a/.docker/release/files/usr/local/include/postfixadmin-change-password/index.php b/.docker/release/files/usr/local/include/postfixadmin-change-password/index.php new file mode 100644 index 000000000..c0f9e40a7 --- /dev/null +++ b/.docker/release/files/usr/local/include/postfixadmin-change-password/index.php @@ -0,0 +1,100 @@ +addHook('main.fabrica', 'MainFabrica'); + } + + /** + * @return string + */ + public function Supported() + { + if (!extension_loaded('pdo') || !class_exists('PDO')) + { + return 'The PHP extension PDO must be installed to use this plugin'; + } + + $aDrivers = \PDO::getAvailableDrivers(); + if (!is_array($aDrivers) || (!in_array('mysql', $aDrivers) && !in_array('pgsql', $aDrivers))) + { + return 'The PHP extension PDO (mysql or pgsql) must be installed to use this plugin'; + } + + return ''; + } + + /** + * @param string $sName + * @param mixed $oProvider + */ + public function MainFabrica($sName, &$oProvider) + { + switch ($sName) + { + case 'change-password': + + include_once __DIR__.'/ChangePasswordPostfixAdminDriver.php'; + + $oProvider = new ChangePasswordPostfixAdminDriver(); + + $oProvider + ->SetEngine($this->Config()->Get('plugin', 'engine','')) + ->SetHost($this->Config()->Get('plugin', 'host', '')) + ->SetPort((int) $this->Config()->Get('plugin', 'port', 3306)) + ->SetDatabase($this->Config()->Get('plugin', 'database', '')) + ->SetTable($this->Config()->Get('plugin', 'table', '')) + ->SetUserColumn($this->Config()->Get('plugin', 'usercol', '')) + ->SetPasswordColumn($this->Config()->Get('plugin', 'passcol', '')) + ->SetUser($this->Config()->Get('plugin', 'user', '')) + ->SetPassword($this->Config()->Get('plugin', 'password', '')) + ->SetEncrypt($this->Config()->Get('plugin', 'encrypt', '')) + ->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', '')))) + ->SetLogger($this->Manager()->Actions()->Logger()) + ; + + break; + } + } + + /** + * @return array + */ + public function configMapping() + { + return array( + \RainLoop\Plugins\Property::NewInstance('engine')->SetLabel('Engine') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array('MySQL', 'PostgreSQL')) + ->SetDescription('Database Engine'), + \RainLoop\Plugins\Property::NewInstance('host')->SetLabel('Host') + ->SetDefaultValue('127.0.0.1'), + \RainLoop\Plugins\Property::NewInstance('port')->SetLabel('Port') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) + ->SetDefaultValue(3306), + \RainLoop\Plugins\Property::NewInstance('database')->SetLabel('Database') + ->SetDefaultValue('postfixadmin'), + \RainLoop\Plugins\Property::NewInstance('table')->SetLabel('table') + ->SetDefaultValue('mailbox'), + \RainLoop\Plugins\Property::NewInstance('usercol')->SetLabel('username column') + ->SetDefaultValue('username'), + \RainLoop\Plugins\Property::NewInstance('passcol')->SetLabel('password column') + ->SetDefaultValue('password'), + \RainLoop\Plugins\Property::NewInstance('user')->SetLabel('User') + ->SetDefaultValue('postfixadmin'), + \RainLoop\Plugins\Property::NewInstance('password')->SetLabel('Password') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD) + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('encrypt')->SetLabel('Encrypt') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array('md5crypt', 'md5', 'system', 'cleartext', 'mysql_encrypt', 'SHA256-CRYPT', 'SHA512-CRYPT')) + ->SetDescription('In what way do you want the passwords to be crypted ?'), + \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT) + ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') + ->SetDefaultValue('*') + ); + } +} diff --git a/.docker/release/files/usr/local/include/postfixadmin-change-password/md5crypt.php b/.docker/release/files/usr/local/include/postfixadmin-change-password/md5crypt.php new file mode 100644 index 000000000..13f878eca --- /dev/null +++ b/.docker/release/files/usr/local/include/postfixadmin-change-password/md5crypt.php @@ -0,0 +1,139 @@ + 0; $i -= 16) + { + if ($i > 16) + { + $ctx .= substr($final,0,16); + } + else + { + $ctx .= substr($final,0,$i); + } + } + + $i = strlen($pw); + + while ($i > 0) + { + if ($i & 1) + { + $ctx .= chr(0); + } + else + { + $ctx .= $pw[0]; + } + + $i = $i >> 1; + } + + $final = hex2bin(md5($ctx)); + + for ($i=0; $i<1000; $i++) + { + $ctx1 = ""; + if ($i & 1) + { + $ctx1 .= $pw; + } + else + { + $ctx1 .= substr($final,0,16); + } + if ($i % 3) + { + $ctx1 .= $salt; + } + if ($i % 7) + { + $ctx1 .= $pw; + } + if ($i & 1) + { + $ctx1 .= substr($final, 0, 16); + } + else + { + $ctx1 .= $pw; + } + + $final = hex2bin(md5($ctx1)); + } + + $passwd = ""; + $passwd .= to64(((ord($final[0]) << 16) | (ord($final[6]) << 8) | (ord($final[12]))), 4); + $passwd .= to64(((ord($final[1]) << 16) | (ord($final[7]) << 8) | (ord($final[13]))), 4); + $passwd .= to64(((ord($final[2]) << 16) | (ord($final[8]) << 8) | (ord($final[14]))), 4); + $passwd .= to64(((ord($final[3]) << 16) | (ord($final[9]) << 8) | (ord($final[15]))), 4); + $passwd .= to64(((ord($final[4]) << 16) | (ord($final[10]) << 8) | (ord($final[5]))), 4); + $passwd .= to64(ord($final[11]), 2); + + return $magic.$salt.'$'.$passwd; +} + +function create_salt() +{ + srand((double) microtime() * 1000000); + return substr(md5(rand(0,9999999)), 0, 8); +} + +// PHP around 5.3.8 includes hex2bin as native function - http://php.net/hex2bin +if (!function_exists('hex2bin')) +{ + function hex2bin($str) + { + $len = strlen($str); + $nstr = ""; + for ($i = 0; $i < $len; $i += 2) + { + $num = sscanf(substr($str, $i, 2), "%x"); + $nstr .= chr($num[0]); + } + + return $nstr; + } +} + +function to64($v, $n) +{ + $ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + $ret = ""; + + while (($n - 1) >= 0) + { + $n--; + $ret .= $ITOA64[$v & 0x3f]; + $v = $v >> 6; + } + + return $ret; +} \ No newline at end of file diff --git a/.docker/tx/Dockerfile b/.docker/tx/Dockerfile deleted file mode 100644 index ca1abc1ee..000000000 --- a/.docker/tx/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM python:3.6-alpine - -RUN pip install transifex-client - -CMD ["tx", "--version"] diff --git a/.gitignore b/.gitignore index 1cca1db71..c9fd10a31 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ /data /MULTIPLY /include.php -.idea/ \ No newline at end of file +.idea/ +.env \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 370cae9a3..23d28c6ee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,18 +1,16 @@ -version: '2' +version: '3.0' + services: + + # Mail server running on separate image mail: image: tvial/docker-mailserver:latest - hostname: mail - container_name: rl.mail - domainname: domain.com restart: always - ports: - - 25:25 - - 143:143 + domainname: example.com volumes: - maildata:/var/mail - mailstate:/var/mail-state - - ./.docker/mail/config/:/tmp/docker-mailserver/ + - ./.docker/dev/mail/config/:/tmp/docker-mailserver/ environment: - ENABLE_SPAMASSASSIN=0 - ENABLE_CLAMAV=0 @@ -24,70 +22,44 @@ services: cap_add: - NET_ADMIN - SYS_PTRACE + + # MySQL database db: image: mysql:5.7 hostname: db - container_name: rl.db restart: always environment: - MYSQL_ROOT_PASSWORD: root - MYSQL_USER: snappymail - MYSQL_PASSWORD: snappymail - MYSQL_DATABASE: snappymail + - MYSQL_ROOT_PASSWORD=root + - MYSQL_USER=snappymail + - MYSQL_PASSWORD=snappymail + - MYSQL_DATABASE=snappymail volumes: - mysql:/var/lib/mysql - - tmp:/tmp + + # PHP FPM Server php: build: - context: ./.docker/php - hostname: php - container_name: rl.php - expose: - - 9000 + context: ./.docker/dev/php depends_on: - db - mail volumes: - ./:/var/www - # - ./build/local/:/var/www - - ./.docker/php/snappymail.ini:/usr/local/etc/php/conf.d/snappymail.ini - - tmp:/tmp - node: - build: - context: ./.docker/node - hostname: node - container_name: rl.node - working_dir: /var/www - command: sh -c 'yarn --version' - volumes: - - ./:/var/www - - tmp:/tmp + - ./.docker/dev/php/snappymail.ini:/usr/local/etc/php/conf.d/snappymail.ini + + # Nginx load balancer translating to PHP FPM nginx: image: nginx:latest hostname: nginx - container_name: rl.nginx depends_on: - php ports: - - 443:443 - - 80:80 + - "${HTTPS_PORT}:443" + - "${HTTP_PORT}:80" volumes: - ./:/var/www - # - ./build/local/:/var/www - - ./.docker/nginx/ssl:/etc/nginx/ssl - - ./.docker/nginx/default.conf:/etc/nginx/conf.d/default.conf - - tmp:/tmp - tx: - build: - context: ./.docker/tx - hostname: tx - container_name: rl.tx - working_dir: /var/www - command: sh -c 'tx --version' - volumes: - - ./:/var/www - - ./.docker/.cache/tx/root:/root - - tmp:/tmp + - ./.docker/dev/nginx/ssl:/etc/nginx/ssl + - ./.docker/dev/nginx/default.conf:/etc/nginx/conf.d/default.conf volumes: mysql: @@ -95,6 +67,4 @@ volumes: maildata: driver: local mailstate: - driver: local - tmp: - driver: local + driver: local \ No newline at end of file diff --git a/release.php b/release.php index 457a030ba..21dd88337 100755 --- a/release.php +++ b/release.php @@ -11,6 +11,7 @@ $package = json_decode(file_get_contents('package.json')); $zip_destination = "snappymail-{$package->version}.zip"; $tar_destination = "snappymail-{$package->version}.tar"; +$docker_zip = "./.docker/release/snappymail-{$package->version}.zip"; @unlink($zip_destination); @unlink($tar_destination); @@ -110,3 +111,15 @@ $tar->compress(Phar::GZ); unlink($tar_destination); echo "\n{$zip_destination} created\n{$tar_destination}.gz created\n"; + +// Docker build +if(readline("Build Docker image? (Y/N): ") === "Y") { + copy($zip_destination, $docker_zip); + + $docker = trim(`which docker`); + if(!$docker) { + exit("Docker not installed!"); + } + + passthru("{$docker} build " . __DIR__ . "/.docker/release/ --build-arg FILES_ZIP={$zip_destination} -t snappymail:{$package->version}"); +} \ No newline at end of file diff --git a/template.env b/template.env new file mode 100644 index 000000000..92dfbf460 --- /dev/null +++ b/template.env @@ -0,0 +1,2 @@ +HTTP_PORT=8080 +HTTPS_PORT=8443 \ No newline at end of file