Add better Docker development and prod support

This commit is contained in:
Floris Westerman 2020-11-26 15:13:55 +01:00
parent e0462af9da
commit fcf2c2468d
No known key found for this signature in database
GPG key ID: E2AED138B92702B0
27 changed files with 1453 additions and 66 deletions

0
.docker/mail/setup.sh → .docker/dev/mail/setup.sh Executable file → Normal file
View file

View file

@ -1,4 +1,3 @@
server {
listen 80 default;
listen 443 ssl;

View file

@ -1,6 +0,0 @@
FROM node:10.16.0-alpine
RUN apk add --no-cache git
RUN yarn global add gulp
CMD ["node", "--version"]

View file

@ -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"]

View file

@ -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>/$UPLOAD_MAX_SIZE/g" /usr/local/etc/php-fpm.d/php-fpm.conf /etc/nginx/nginx.conf
sed -i "s/<MEMORY_LIMIT>/$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'

View file

@ -0,0 +1,5 @@
/snappymail/data/_data_/_default_/logs/* {
size 10M
rotate 0
missingok
}

View file

@ -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 <UPLOAD_MAX_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;
}
}
}

View file

@ -0,0 +1,23 @@
<?php
$stdIn = STDIN;
$stdOut = STDOUT;
fwrite($stdOut, "READY\n");
while(true) {
if (false == $line = trim(fgets($stdIn))) {
continue;
}
$match = null;
if (preg_match('/eventname:(.*?) /', $line, $match)) {
if (in_array($match[1], ['PROCESS_STATE_EXITED', 'PROCESS_STATE_STOPPED', 'PROCESS_STATE_FATAL'])) {
exec('kill -15 '.file_get_contents('/run/supervisord.pid'));
}
}
fwrite($stdOut, "RESULT 2\nOK");
sleep(1);
fwrite($stdOut, "READY\n");
}

View file

@ -0,0 +1,113 @@
#!/bin/bash
# ----------------------------------------------------------------------
# Simple script to invoke logrotate at regular intervals
# ----------------------------------------------------------------------
# from https://github.com/misho-kr/docker-appliances/blob/master/nginx-nodejs/logrotate-loop.sh
LOGROTATE_BIN="logrotate"
STATE="$HOME/logrotate.state"
CONF="/etc/logrotate.d/snappymail"
export LOGROTATE_BIN STATE CONF
RUN_INTERVAL="3600" # every hour
# helper functions for logging
export FMT="%a %b %d %Y %H:%M:%S GMT%z (%Z)"
function log_date() {
echo "$(date +"$FMT"): $*"
}
# ----------------------------------------------------------------------
# Main loop of the logrotate service:
#
# while True:
# sleep N seconds
# run logrotate
#
# ----------------------------------------------------------------------
function logrotate_loop() {
trap on_terminate TERM INT
local interval="${1}"
log_date "===================================================="
log_date
log_date "logrotate service starting (pid=$$)"
log_date "logrotate process will run every ${interval} seconds"
while true; do
current_time=$(date "+%s")
next_run_time=$(( current_time + interval ))
while (( current_time < next_run_time ))
do
logrotate_sleep $(( next_run_time - current_time ))
current_time=$(date "+%s")
done
logrotate_run
done
}
# helper function to execute logrotate and pass it the right parameters
function logrotate_run() {
log_date "logrotate will run now"
${LOGROTATE_BIN} -s ${STATE} ${CONF}
}
# ----------------------------------------------------------------------
# Procedure to idle the execution for a number of seconds
#
# There are two requirements:
#
# - export the PID of the sleep command so that it can be terminated
# in case the logrotate service is being shutdown
# - keep this (bash) process responsive to SIGTERM while in sleep
# mode (normally the signal will be masked and will not be delivered
# until the subprocess completes)
# ----------------------------------------------------------------------
proc_sleep_pid=""
function logrotate_sleep() {
local sleep_interval=${1}
log_date "logrotate will sleep for ${sleep_interval} seconds"
( exec -a "logrotate: sleep" sleep ${sleep_interval} )&
proc_sleep_pid=$!
wait ${proc_sleep_pid}
}
# ----------------------------------------------------------------------
# Signal handler for logrotate service to make sure the process exits:
#
# - properly by terminating the sleep process that is used to idle
# the service
# - gracefully by writing a message in the log
# ----------------------------------------------------------------------
function on_terminate() {
log_date "logrotate will terminate"
log_date
kill -TERM ${proc_sleep_pid}
exit 0
}
# ----------------------------------------------------------------------
# main
# ----------------------------------------------------------------------
logrotate_loop ${1:-RUN_INTERVAL}

View file

@ -0,0 +1,60 @@
[supervisord]
nodaemon=true
[program:nginx]
command=nginx -c /etc/nginx/nginx.conf -g 'daemon off;'
process_name=%(program_name)s_%(process_num)02d
user=php-cli
numprocs=1
autostart=true
autorestart=false
startsecs=0
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:php-fpm]
command=php-fpm -F
process_name=%(program_name)s_%(process_num)02d
user=php-cli
numprocs=1
autostart=true
autorestart=false
startsecs=0
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
; reads snappymail logs
[program:snappymail-auth]
command=tail -f /snappymail/data/_data_/_default_/logs/auth.log
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:snappymail-errors]
command=tail -f /snappymail/data/_data_/_default_/logs/errors.log
# everything is an error
stdout_logfile=/dev/stderr
stdout_logfile_maxbytes=0
redirect_stderr=true
[program:logrotate]
command=/logrotate-loop.sh
autorestart=true
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[eventlistener:subprocess-stopped]
command=php /listener.php
process_name=%(program_name)s_%(process_num)02d
user=php-cli
numprocs=1
events=PROCESS_STATE_EXITED,PROCESS_STATE_STOPPED,PROCESS_STATE_FATAL
autostart=true
autorestart=unexpected

View file

@ -0,0 +1,19 @@
[global]
daemonize = no
[default]
listen = /tmp/php-fpm.sock
pm = ondemand
pm.max_children = 30
pm.process_idle_timeout = 10s
pm.max_requests = 500
catch_workers_output = yes
chdir = /
php_admin_value[error_log] = /tmp/php_error.log
php_admin_value[log_errors] = On
php_admin_value[expose_php] = Off
php_admin_value[display_errors] = Off
php_admin_value[date.timezone] = UTC
php_admin_value[post_max_size] = <UPLOAD_MAX_SIZE>
php_admin_value[upload_max_filesize] = <UPLOAD_MAX_SIZE>
php_admin_value[memory_limit] = <MEMORY_LIMIT>

View file

@ -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"

View file

@ -0,0 +1,342 @@
<?php
class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
{
/**
* @var string
*/
private $sEngine = 'MySQL';
/**
* @var string
*/
private $sHost = '127.0.0.1';
/**
* @var int
*/
private $iPort = 3306;
/**
* @var string
*/
private $sDatabase = 'postfixadmin';
/**
* @var string
*/
private $sTable = 'mailbox';
/**
* @var string
*/
private $sUsercol = 'username';
/**
* @var string
*/
private $sPasscol = 'password';
/**
* @var string
*/
private $sUser = 'postfixadmin';
/**
* @var string
*/
private $sPassword = '';
/**
* @var string
*/
private $sEncrypt = '';
/**
* @var string
*/
private $sAllowedEmails = '';
/**
* @var \MailSo\Log\Logger
*/
private $oLogger = null;
/**
* @param string $sEngine
*
* @return \ChangePasswordPostfixAdminDriver
*/
public function SetEngine($sEngine)
{
$this->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;
}
}

View file

@ -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.

View file

@ -0,0 +1 @@
Plugin that adds functionality to change the email account password (PostfixAdmin).

View file

@ -0,0 +1,100 @@
<?php
class PostfixadminChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{
public function Init()
{
$this->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('*')
);
}
}

View file

@ -0,0 +1,139 @@
<?php
// md5crypt
// Action: Creates MD5 encrypted password
// Call: md5crypt (string cleartextpassword)
function md5crypt($pw, $salt = "", $magic = "")
{
$MAGIC = "$1$";
if ($magic == "")
{
$magic = $MAGIC;
}
if ($salt == "")
{
$salt = create_salt();
}
$slist = explode("$", $salt);
if (isset($slist[0]) && $slist[0] == "1")
{
$salt = $slist[1];
}
$salt = substr($salt, 0, 8);
$ctx = $pw.$magic.$salt;
$final = hex2bin(md5($pw.$salt.$pw));
for ($i = strlen($pw); $i > 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;
}

View file

@ -1,5 +0,0 @@
FROM python:3.6-alpine
RUN pip install transifex-client
CMD ["tx", "--version"]

3
.gitignore vendored
View file

@ -15,4 +15,5 @@
/data
/MULTIPLY
/include.php
.idea/
.idea/
.env

View file

@ -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

View file

@ -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}");
}

2
template.env Normal file
View file

@ -0,0 +1,2 @@
HTTP_PORT=8080
HTTPS_PORT=8443