diff --git a/.gitignore b/.gitignore index b8558fdb..9de43f40 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ data/conf/rspamd/override.d/* !data/conf/nginx/site.conf data/conf/nginx/*.conf data/conf/dovecot/extra.conf +data/conf/rspamd/custom/* diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh index 616a6b81..c6bf76ca 100755 --- a/data/Dockerfiles/acme/docker-entrypoint.sh +++ b/data/Dockerfiles/acme/docker-entrypoint.sh @@ -79,7 +79,7 @@ while true; do while read domain; do SQL_DOMAIN_ARR+=("${domain}") - done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs) + done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0" -Bs) while read alias_domain; do SQL_DOMAIN_ARR+=("${alias_domain}") done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) diff --git a/data/Dockerfiles/clamd/Dockerfile b/data/Dockerfiles/clamd/Dockerfile index 48edeae1..aa50b807 100644 --- a/data/Dockerfiles/clamd/Dockerfile +++ b/data/Dockerfiles/clamd/Dockerfile @@ -15,6 +15,8 @@ RUN apk add --update \ && chmod 750 /run/clamav \ && sed -i '/Foreground yes/s/^#//g' /etc/clamav/clamd.conf \ && sed -i '/TCPSocket 3310/s/^#//g' /etc/clamav/clamd.conf \ + && sed -i 's/#PhishingSignatures yes/PhishingSignatures no/g' /etc/clamav/clamd.conf \ + && sed -i 's/#PhishingScanURLs yes/PhishingScanURLs no/g' /etc/clamav/clamd.conf \ && sed -i '/Foreground yes/s/^#//g' /etc/clamav/freshclam.conf # Port provision diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 33f64382..27413ed0 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -40,6 +40,7 @@ RUN apt-get update && apt-get -y install \ libtest-pod-perl \ libtest-simple-perl \ libunicode-string-perl \ + libproc-processtable-perl \ liburi-perl \ lzma-dev \ make \ @@ -67,7 +68,7 @@ RUN curl https://www.dovecot.org/releases/2.2/dovecot-$DOVECOT_VERSION.tar.gz | RUN cpanm Data::Uniqid Mail::IMAPClient String::Util RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync -RUN echo '30 3 * * * vmail /usr/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync +RUN echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY imapsync /usr/local/bin/imapsync diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index a07a3896..4e9fe14b 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -74,4 +74,7 @@ chown -R vmail:vmail /var/vmail/sieve # Fix more than 1 hardlink issue touch /etc/crontab /etc/cron.*/* +# Clean old PID if any +[[ -f /usr/local/var/run/dovecot/master.pid ]] && rm /usr/local/var/run/dovecot/master.pid + exec "$@" diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_cron.pl index 27419aac..26b5ad67 100755 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_cron.pl @@ -2,11 +2,20 @@ use DBI; use LockFile::Simple qw(lock trylock unlock); +use Proc::ProcessTable; use Data::Dumper qw(Dumper); use IPC::Run 'run'; use String::Util 'trim'; use File::Temp; +my $t = Proc::ProcessTable->new; +my $imapsync_running = grep { $_->{cmndline} =~ /^\/usr\/bin\/perl \/usr\/local\/bin\/imapsync\s/ } @{$t->table}; +if ($imapsync_running eq 1) +{ + print "imapsync is active, exiting..."; + exit; +} + $DBNAME = ''; $DBUSER = ''; $DBPASS = ''; @@ -16,7 +25,10 @@ $dsn = "DBI:mysql:database=" . $DBNAME . ";host=mysql"; $lock_file = $run_dir . "/imapsync_busy"; $lockmgr = LockFile::Simple->make(-autoclean => 1, -max => 1); $lockmgr->lock($lock_file) || die "can't lock ${lock_file}"; -$dbh = DBI->connect($dsn, $DBUSER, $DBPASS); +$dbh = DBI->connect($dsn, $DBUSER, $DBPASS, { + mysql_auto_reconnect => 1, + mysql_enable_utf8mb4 => 1 +}); open my $file, '<', "/etc/sogo/sieve.creds"; my $creds = <$file>; close $file; diff --git a/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh b/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh index 4ad5ab32..ab066d89 100755 --- a/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh +++ b/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh @@ -6,7 +6,7 @@ while read QUERY; do echo "500 dunno" continue fi - result=$(curl -s http://nginx:8081/forwardinghosts.php?host=${QUERY[1]}) + result=$(curl -s http://172.22.1.251:8081/forwardinghosts.php?host=${QUERY[1]}) logger -t whitelist_forwardinghosts -p mail.info "Look up ${QUERY[1]} on whitelist, result $result" echo ${result} done diff --git a/data/Dockerfiles/rspamd/milter_headers.lua b/data/Dockerfiles/rspamd/milter_headers.lua deleted file mode 100644 index 6ad7957a..00000000 --- a/data/Dockerfiles/rspamd/milter_headers.lua +++ /dev/null @@ -1,387 +0,0 @@ ---[[ -Copyright (c) 2016, Andrew Lewis -Copyright (c) 2016, Vsevolod Stakhov - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -]]-- - -if confighelp then - return -end - --- A plugin that provides common header manipulations - -local logger = require "rspamd_logger" -local util = require "rspamd_util" -local N = 'milter_headers' -local E = {} - -local HOSTNAME = util.get_hostname() - -local settings = { - skip_local = false, - skip_authenticated = false, - routines = { - ['x-spamd-result'] = { - header = 'X-Spamd-Result', - remove = 1, - }, - ['x-rspamd-server'] = { - header = 'X-Rspamd-Server', - remove = 1, - }, - ['x-rspamd-queue-id'] = { - header = 'X-Rspamd-Queue-Id', - remove = 1, - }, - ['spam-header'] = { - header = 'Deliver-To', - value = 'Junk', - remove = 1, - }, - ['x-virus'] = { - header = 'X-Virus', - remove = 1, - symbols = {}, -- needs config - }, - ['x-spamd-bar'] = { - header = 'X-Spamd-Bar', - positive = '+', - negative = '-', - neutral = '/', - remove = 1, - }, - ['x-spam-level'] = { - header = 'X-Spam-Level', - char = '*', - remove = 1, - }, - ['x-spam-status'] = { - header = 'X-Spam-Status', - remove = 1, - }, - ['authentication-results'] = { - header = 'Authentication-Results', - remove = 1, - spf_symbols = { - pass = 'R_SPF_ALLOW', - fail = 'R_SPF_FAIL', - softfail = 'R_SPF_SOFTFAIL', - neutral = 'R_SPF_NEUTRAL', - temperror = 'R_SPF_DNSFAIL', - none = 'R_SPF_NA', - permerror = 'R_SPF_PERMFAIL', - }, - dkim_symbols = { - pass = 'R_DKIM_ALLOW', - fail = 'R_DKIM_REJECT', - temperror = 'R_DKIM_TEMPFAIL', - none = 'R_DKIM_NA', - permerror = 'R_DKIM_PERMFAIL', - }, - dmarc_symbols = { - pass = 'DMARC_POLICY_ALLOW', - permerror = 'DMARC_BAD_POLICY', - temperror = 'DMARC_DNSFAIL', - none = 'DMARC_NA', - reject = 'DMARC_POLICY_REJECT', - softfail = 'DMARC_POLICY_SOFTFAIL', - quarantine = 'DMARC_POLICY_QUARANTINE', - }, - }, - }, -} - -local active_routines = {} -local custom_routines = {} - -local function milter_headers(task) - - if settings.skip_local then - local ip = task:get_ip() - if (ip and ip:is_local()) then return end - end - - if settings.skip_authenticated then - if task:get_user() ~= nil then return end - end - - local routines, common, add, remove = {}, {}, {}, {} - - routines['x-spamd-result'] = function() - if not common.symbols then - common.symbols = task:get_symbols_all() - common['metric_score'] = task:get_metric_score('default') - common['metric_action'] = task:get_metric_action('default') - end - if settings.routines['x-spamd-result'].remove then - remove[settings.routines['x-spamd-result'].header] = settings.routines['x-spamd-result'].remove - end - local buf = {} - table.insert(buf, table.concat({ - 'default: ', (common['metric_action'] == 'reject') and 'True' or 'False', ' [', - common['metric_score'][1], ' / ', common['metric_score'][2], ']' - })) - for _, s in ipairs(common.symbols) do - if not s.options then s.options = {} end - table.insert(buf, table.concat({ - ' ', s.name, ' (', s.score, ') [', table.concat(s.options, ','), ']', - })) - end - add[settings.routines['x-spamd-result'].header] = table.concat(buf, '\n') - end - - routines['x-rspamd-queue-id'] = function() - if common.queue_id ~= false then - common.queue_id = task:get_queue_id() - if not common.queue_id then - common.queue_id = false - end - end - if settings.routines['x-rspamd-queue-id'].remove then - remove[settings.routines['x-rspamd-queue-id'].header] = settings.routines['x-rspamd-queue-id'].remove - end - if common.queue_id then - add[settings.routines['x-rspamd-queue-id'].header] = common.queue_id - end - end - - routines['x-rspamd-server'] = function() - if settings.routines['x-rspamd-server'].remove then - remove[settings.routines['x-rspamd-server'].header] = settings.routines['x-rspamd-server'].remove - end - add[settings.routines['x-rspamd-server'].header] = HOSTNAME - end - - routines['x-spamd-bar'] = function() - if not common['metric_score'] then - common['metric_score'] = task:get_metric_score('default') - end - local score = common['metric_score'][1] - local spambar - if score <= -1 then - spambar = string.rep(settings.routines['x-spamd-bar'].negative, score*-1) - elseif score >= 1 then - spambar = string.rep(settings.routines['x-spamd-bar'].positive, score) - else - spambar = settings.routines['x-spamd-bar'].neutral - end - if settings.routines['x-spamd-bar'].remove then - remove[settings.routines['x-spamd-bar'].header] = settings.routines['x-spamd-bar'].remove - end - if spambar ~= '' then - add[settings.routines['x-spamd-bar'].header] = spambar - end - end - - routines['x-spam-level'] = function() - if not common['metric_score'] then - common['metric_score'] = task:get_metric_score('default') - end - local score = common['metric_score'][1] - if score < 1 then - return nil, {}, {} - end - if settings.routines['x-spam-level'].remove then - remove[settings.routines['x-spam-level'].header] = settings.routines['x-spam-level'].remove - end - add[settings.routines['x-spam-level'].header] = string.rep(settings.routines['x-spam-level'].char, score) - end - - routines['spam-header'] = function() - if not common['metric_action'] then - common['metric_action'] = task:get_metric_action('default') - end - if settings.routines['spam-header'].remove then - remove[settings.routines['spam-header'].header] = settings.routines['spam-header'].remove - end - local action = common['metric_action'] - if action ~= 'no action' and action ~= 'greylist' then - add[settings.routines['spam-header'].header] = settings.routines['spam-header'].value - end - end - - routines['x-virus'] = function() - if not common.symbols then - common.symbols = {} - end - if settings.routines['x-virus'].remove then - remove[settings.routines['x-virus'].header] = settings.routines['x-virus'].remove - end - local virii = {} - for _, sym in ipairs(settings.routines['x-virus'].symbols) do - if not (common.symbols[sym] == false) then - local s = task:get_symbol(sym) - if not s then - common.symbols[sym] = false - else - common.symbols[sym] = s - if (((s or E)[1] or E).options or E)[1] then - table.insert(virii, s[1].options[1]) - else - table.insert(virii, 'unknown') - end - end - end - end - if #virii > 0 then - add[settings.routines['x-virus'].header] = table.concat(virii, ',') - end - end - - routines['x-spam-status'] = function() - if not common['metric_score'] then - common['metric_score'] = task:get_metric_score('default') - end - if not common['metric_action'] then - common['metric_action'] = task:get_metric_action('default') - end - local score = common['metric_score'][1] - local action = common['metric_action'] - local is_spam - local spamstatus - if action ~= 'no action' and action ~= 'greylist' then - is_spam = 'Yes' - else - is_spam = 'No' - end - spamstatus = is_spam .. ', score=' .. string.format('%.2f', score) - if settings.routines['x-spam-status'].remove then - remove[settings.routines['x-spam-status'].header] = settings.routines['x-spam-status'].remove - end - add[settings.routines['x-spam-status'].header] = spamstatus - end - - routines['authentication-results'] = function() - local ar = require "auth_results" - - if settings.routines['authentication-results'].remove then - remove[settings.routines['authentication-results'].header] = - settings.routines['authentication-results'].remove - end - - local res = ar.gen_auth_results(task, - settings.routines['authentication-results']) - - if res then - add[settings.routines['authentication-results'].header] = res - end - end - - for _, n in ipairs(active_routines) do - local ok, err - if custom_routines[n] then - local to_add, to_remove, common_in - ok, err, to_add, to_remove, common_in = pcall(custom_routines[n], task, common) - if ok then - for k, v in pairs(to_add) do - add[k] = v - end - for k, v in pairs(to_remove) do - add[k] = v - end - for k, v in pairs(common_in) do - if type(v) == 'table' then - if not common[k] then - common[k] = {} - end - for kk, vv in pairs(v) do - common[k][kk] = vv - end - else - common[k] = v - end - end - end - else - ok, err = pcall(routines[n]) - end - if not ok then - logger.errx(task, 'call to %s failed: %s', n, err) - end - end - - if not next(add) then add = nil end - if not next(remove) then remove = nil end - if add or remove then - task:set_milter_reply({ - add_headers = add, - remove_headers = remove - }) - end -end - -local opts = rspamd_config:get_all_opt(N) or rspamd_config:get_all_opt('rmilter_headers') -if not opts then return end - -if type(opts['use']) == 'string' then - opts['use'] = {opts['use']} -elseif (type(opts['use']) == 'table' and not opts['use'][1]) then - logger.debugm(N, rspamd_config, 'no functions are enabled') - return -end -if type(opts['use']) ~= 'table' then - logger.errx(rspamd_config, 'unexpected type for "use" option: %s', type(opts['use'])) - return -end -if type(opts['custom']) == 'table' then - for k, v in pairs(opts['custom']) do - local f, err = load(v) - if not f then - logger.errx(rspamd_config, 'could not load "%s": %s', k, err) - else - custom_routines[k] = f() - end - end -end -local have_routine = {} -local function activate_routine(s) - if settings.routines[s] or custom_routines[s] then - have_routine[s] = true - table.insert(active_routines, s) - if (opts.routines and opts.routines[s]) then - for k, v in pairs(opts.routines[s]) do - settings.routines[s][k] = v - end - end - else - logger.errx(rspamd_config, 'routine "%s" does not exist', s) - end -end -if opts['extended_spam_headers'] then - activate_routine('x-spamd-result') - activate_routine('x-rspamd-server') - activate_routine('x-rspamd-queue-id') -end -if opts['skip_local'] then - settings.skip_local = true -end -if opts['skip_authenticated'] then - settings.skip_authenticated = true -end -for _, s in ipairs(opts['use']) do - if not have_routine[s] then - activate_routine(s) - end -end -if (#active_routines < 1) then - logger.errx(rspamd_config, 'no active routines') - return -end -logger.infox(rspamd_config, 'active routines [%s]', table.concat(active_routines, ',')) -rspamd_config:register_symbol({ - name = 'MILTER_HEADERS', - type = 'postfilter', - callback = milter_headers, - priority = 10 -}) diff --git a/data/Dockerfiles/rspamd/settings.conf b/data/Dockerfiles/rspamd/settings.conf index 4449f091..3c1c9c16 100644 --- a/data/Dockerfiles/rspamd/settings.conf +++ b/data/Dockerfiles/rspamd/settings.conf @@ -1 +1 @@ -settings = "http://nginx:8081/settings.php"; +settings = "http://172.22.1.251:8081/settings.php"; diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 0fc4675f..a9cefc47 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -17,6 +17,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ syslog-ng-core \ syslog-ng-mod-redis \ dirmngr \ + netcat \ + psmisc \ wget \ && rm -rf /var/lib/apt/lists/* \ && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ @@ -37,7 +39,7 @@ RUN mkdir /usr/share/doc/sogo \ && echo '0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds' >> /etc/cron.d/sogo \ && touch /etc/default/locale -COPY ./reconf-domains.sh / +COPY ./bootstrap-sogo.sh / COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY supervisord.conf /etc/supervisor/supervisord.conf diff --git a/data/Dockerfiles/sogo/reconf-domains.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh similarity index 96% rename from data/Dockerfiles/sogo/reconf-domains.sh rename to data/Dockerfiles/sogo/bootstrap-sogo.sh index ec0d4c93..92acb591 100755 --- a/data/Dockerfiles/sogo/reconf-domains.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -3,6 +3,13 @@ # Wait for MySQL to warm-up while mysqladmin ping --host 172.22.1.250 --silent; do +# Wait until port becomes free and send sig +until ! nc -z sogo-mailcow 20000; +do + killall -TERM sogod + sleep 3 +done + # Recreate view mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view" @@ -93,8 +100,6 @@ echo ' chown sogo:sogo -R /var/lib/sogo/ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist -supervisorctl restart sogo - -sleep 99999 +exec gosu sogo /usr/sbin/sogod done diff --git a/data/Dockerfiles/sogo/supervisord.conf b/data/Dockerfiles/sogo/supervisord.conf index 30392e3b..06eb4985 100644 --- a/data/Dockerfiles/sogo/supervisord.conf +++ b/data/Dockerfiles/sogo/supervisord.conf @@ -22,14 +22,16 @@ user=sogo autorestart=true priority=4 -[program:reconf-domains] -command=/reconf-domains.sh +[program:bootstrap-sogo] +command=/bootstrap-sogo.sh stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 priority=3 +startretries=10 autorestart=true +stopwaitsecs=120 [program:sogo] command="/usr/sbin/sogod" @@ -38,8 +40,9 @@ stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 -autorestart = unexpected +autorestart = true autostart = false +stopwaitsecs=120 priority=5 [inet_http_server] diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf index 2d416d21..e478f0b2 100644 --- a/data/conf/dovecot/dovecot.conf +++ b/data/conf/dovecot/dovecot.conf @@ -13,7 +13,7 @@ disable_plaintext_auth = yes login_log_format_elements = "user=<%u> method=%m rip=%r lip=%l mpid=%e %c %k" mail_home = /var/vmail/%d/%n mail_location = maildir:~/ -mail_plugins = quota acl zlib #mail_crypt +mail_plugins = quota acl zlib listescape #mail_crypt ssl_protocols = !SSLv3 ssl_prefer_server_ciphers = yes ssl_cipher_list = EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA256:EECDH:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA128-SHA:AES128-SHA @@ -214,10 +214,10 @@ userdb { driver = sql } protocol imap { - mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve #mail_crypt + mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape #mail_crypt } protocol lmtp { - mail_plugins = quota sieve acl zlib #mail_crypt + mail_plugins = quota sieve acl zlib listescape #mail_crypt auth_socket_path = /usr/local/var/run/dovecot/auth-master } protocol sieve { @@ -248,6 +248,7 @@ plugin { sieve_max_script_size = 1M sieve_quota_max_scripts = 0 sieve_quota_max_storage = 0 + listescape_char = "\\" #mail_crypt_global_private_key =
-