diff --git a/.gitignore b/.gitignore index 2f6a1b85..0cb9e7b6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ data/assets/ejabberd/sqlite/sqlite.db data/assets/ssl-example/* data/assets/ssl/* +data/conf/borgmatic/ data/conf/clamav/whitelist.ign2 data/conf/dovecot/acl_anyone data/conf/dovecot/dovecot-master.passwd @@ -39,12 +40,17 @@ data/conf/postfix/sql data/conf/rspamd/custom/* data/conf/rspamd/local.d/* data/conf/rspamd/override.d/* +data/conf/sogo/custom-theme.js data/conf/sogo/plist_ldap data/conf/sogo/sieve.creds data/conf/sogo/sogo-full.svg data/gitea/ data/gogs/ -data/hooks/ +data/hooks/dovecot +data/hooks/phpfpm +data/hooks/postfix +data/hooks/rspamd +data/hooks/unbound data/web/.well-known/acme-challenge data/web/css/build/0081-custom-mailcow.css data/web/inc/vars.local.inc.php diff --git a/data/Dockerfiles/acme/acme.sh b/data/Dockerfiles/acme/acme.sh index 5d5da1ed..4f5cb803 100755 --- a/data/Dockerfiles/acme/acme.sh +++ b/data/Dockerfiles/acme/acme.sh @@ -155,6 +155,18 @@ while true; do fi if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then log_f "Generating missing Lets Encrypt account key..." + if [[ ! -z ${ACME_CONTACT} ]]; then + if ! verify_email "${ACME_CONTACT}"; then + log_f "Invalid email address, will not start registration!" + sleep 365d + exec $(readlink -f "$0") + else + ACME_CONTACT_PARAMETER="--contact mailto:${ACME_CONTACT}" + log_f "Valid email address, using ${ACME_CONTACT} for registration" + fi + else + ACME_CONTACT_PARAMETER="" + fi openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem else log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem" @@ -207,22 +219,9 @@ while true; do IPV6=$(get_ipv6) log_f "OK: ${IPV4}, ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}" - # Hard-fail on CAA errors for MAILCOW_HOSTNAME - MH_PARENT_DOMAIN=$(echo ${MAILCOW_HOSTNAME} | cut -d. -f2-) - MH_CAAS=( $(dig CAA ${MH_PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) - if [[ ! -z ${MH_CAAS} ]]; then - if [[ ${MH_CAAS[@]} =~ "letsencrypt.org" ]]; then - log_f "Validated CAA for parent domain ${MH_PARENT_DOMAIN}" - else - log_f "Skipping ACME validation: Lets Encrypt disallowed for ${MAILCOW_HOSTNAME} by CAA record, retrying in 1h..." - sleep 1h - exec $(readlink -f "$0") - fi - fi - ######################################### # IP and webroot challenge verification # - SQL_DOMAINS=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0" -Bs) + SQL_DOMAINS=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs) if [[ ! $? -eq 0 ]]; then log_f "Failed to read SQL domains, retrying in 1 minute..." sleep 1m @@ -290,7 +289,7 @@ while true; do VALIDATED_CERTIFICATES+=("${CERT_NAME}") # obtain server certificate if required - DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa + ACME_CONTACT_PARAMETER=${ACME_CONTACT_PARAMETER} DOMAINS=${SERVER_SAN_VALIDATED[@]} /srv/obtain-certificate.sh rsa RETURN="$?" if [[ "$RETURN" == "0" ]]; then # 0 = cert created successfully CERT_AMOUNT_CHANGED=1 diff --git a/data/Dockerfiles/acme/functions.sh b/data/Dockerfiles/acme/functions.sh index 98d86dc6..183be01b 100644 --- a/data/Dockerfiles/acme/functions.sh +++ b/data/Dockerfiles/acme/functions.sh @@ -16,6 +16,15 @@ log_f() { fi } +verify_email(){ + regex="^(([A-Za-z0-9]+((\.|\-|\_|\+)?[A-Za-z0-9]?)*[A-Za-z0-9]+)|[A-Za-z0-9]+)@(([A-Za-z0-9]+)+((\.|\-|\_)?([A-Za-z0-9]+)+)*)+\.([A-Za-z]{2,})+$" + if [[ $1 =~ ${regex} ]]; then + return 0 + else + return 1 + fi +} + verify_hash_match(){ CERT_HASH=$(openssl x509 -in "${1}" -noout -pubkey | openssl md5) KEY_HASH=$(openssl pkey -in "${2}" -pubout | openssl md5) @@ -60,6 +69,17 @@ check_domain(){ DOMAIN=$1 A_DOMAIN=$(dig A ${DOMAIN} +short | tail -n 1) AAAA_DOMAIN=$(dig AAAA ${DOMAIN} +short | tail -n 1) + # Hard-fail on CAA errors for MAILCOW_HOSTNAME + PARENT_DOMAIN=$(echo ${DOMAIN} | cut -d. -f2-) + CAAS=( $(dig CAA ${PARENT_DOMAIN} +short | sed -n 's/\d issue "\(.*\)"/\1/p') ) + if [[ ! -z ${CAAS} ]]; then + if [[ ${CAAS[@]} =~ "letsencrypt.org" ]]; then + log_f "Validated CAA for parent domain ${PARENT_DOMAIN}" + else + log_f "Lets Encrypt disallowed for ${PARENT_DOMAIN} by CAA record" + return 1 + fi + fi # Check if CNAME without v6 enabled target if [[ ! -z ${AAAA_DOMAIN} ]] && [[ -z $(echo ${AAAA_DOMAIN} | grep "^\([0-9a-fA-F]\{0,4\}:\)\{1,7\}[0-9a-fA-F]\{0,4\}$") ]]; then AAAA_DOMAIN= diff --git a/data/Dockerfiles/acme/obtain-certificate.sh b/data/Dockerfiles/acme/obtain-certificate.sh index 8264a2cb..a151dff2 100644 --- a/data/Dockerfiles/acme/obtain-certificate.sh +++ b/data/Dockerfiles/acme/obtain-certificate.sh @@ -93,8 +93,8 @@ until dig letsencrypt.org +time=3 +tries=1 @unbound > /dev/null; do sleep 2 done log_f "Resolver OK" - -ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} \ +log_f "Using command acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} --account-key ${ACME_BASE}/acme/account.pem --disable-check --csr ${CSR} --acme-dir /var/www/acme/" +ACME_RESPONSE=$(acme-tiny ${DIRECTORY_URL} ${ACME_CONTACT_PARAMETER} \ --account-key ${ACME_BASE}/acme/account.pem \ --disable-check \ --csr ${CSR} \ diff --git a/data/Dockerfiles/clamd/Dockerfile b/data/Dockerfiles/clamd/Dockerfile index 4c30cf21..b251d968 100644 --- a/data/Dockerfiles/clamd/Dockerfile +++ b/data/Dockerfiles/clamd/Dockerfile @@ -2,7 +2,7 @@ FROM debian:buster-slim LABEL maintainer "André Peters " -ARG CLAMAV=0.103.0 +ARG CLAMAV=0.103.2 RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile index 219dd14a..5a1df99e 100644 --- a/data/Dockerfiles/dovecot/Dockerfile +++ b/data/Dockerfiles/dovecot/Dockerfile @@ -2,8 +2,9 @@ FROM debian:buster-slim LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive -ARG DOVECOT=2.3.13 +ARG DOVECOT=2.3.14 ENV LC_ALL C +ENV GOSU_VERSION 1.12 # Add groups and users before installing Dovecot to not break compatibility RUN groupadd -g 5000 vmail \ @@ -20,7 +21,6 @@ RUN groupadd -g 5000 vmail \ apt-transport-https \ ca-certificates \ cpanminus \ - cron \ curl \ dnsutils \ dirmngr \ @@ -82,6 +82,11 @@ RUN groupadd -g 5000 vmail \ syslog-ng \ syslog-ng-core \ syslog-ng-mod-redis \ + wget \ + && dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')" \ + && wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch" \ + && chmod +x /usr/local/bin/gosu \ + && gosu nobody true \ && apt-key adv --fetch-keys https://repo.dovecot.org/DOVECOT-REPO-GPG \ && echo "deb https://repo.dovecot.org/ce-${DOVECOT}/debian/buster buster main" > /etc/apt/sources.list.d/dovecot.list \ && apt-get update \ @@ -100,7 +105,7 @@ RUN groupadd -g 5000 vmail \ && apt-get autoremove --purge -y \ && apt-get autoclean \ && rm -rf /var/lib/apt/lists/* \ - && rm -rf /tmp/* /var/tmp/* /etc/cron.daily/* /root/.cache/ + && rm -rf /tmp/* /var/tmp/* /root/.cache/ COPY trim_logs.sh /usr/local/bin/trim_logs.sh COPY clean_q_aged.sh /usr/local/bin/clean_q_aged.sh @@ -108,7 +113,7 @@ COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf COPY syslog-ng-redis_slave.conf /etc/syslog-ng/syslog-ng-redis_slave.conf COPY imapsync /usr/local/bin/imapsync COPY postlogin.sh /usr/local/bin/postlogin.sh -COPY imapsync_cron.pl /usr/local/bin/imapsync_cron.pl +COPY imapsync_runner.pl /usr/local/bin/imapsync_runner.pl COPY report-spam.sieve /usr/lib/dovecot/sieve/report-spam.sieve COPY report-ham.sieve /usr/lib/dovecot/sieve/report-ham.sieve COPY rspamd-pipe-ham /usr/lib/dovecot/sieve/rspamd-pipe-ham diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh index 756dea7e..5df135cf 100755 --- a/data/Dockerfiles/dovecot/docker-entrypoint.sh +++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh @@ -7,7 +7,7 @@ while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${D sleep 2 done -until dig +short mailcow.email @unbound > /dev/null; do +until dig +short mailcow.email > /dev/null; do echo "Waiting for DNS..." sleep 1 done @@ -185,6 +185,12 @@ function script_deinit() end EOF +# Replace patterns in app-passdb.lua +sed -i "s/__DBUSER__/${DBUSER}/g" /etc/dovecot/lua/app-passdb.lua +sed -i "s/__DBPASS__/${DBPASS}/g" /etc/dovecot/lua/app-passdb.lua +sed -i "s/__DBNAME__/${DBNAME}/g" /etc/dovecot/lua/app-passdb.lua + + # Migrate old sieve_after file [[ -f /etc/dovecot/sieve_after ]] && mv /etc/dovecot/sieve_after /etc/dovecot/global_sieve_after # Create global sieve scripts @@ -269,15 +275,10 @@ else rm -f /etc/dovecot/sogo-sso.conf fi -# Hard-code env vars to scripts due to cron not passing them to the scripts -sed -i "s/__DBUSER__/${DBUSER}/g" /usr/local/bin/imapsync_cron.pl /usr/local/bin/quarantine_notify.py /usr/local/bin/clean_q_aged.sh /etc/dovecot/lua/app-passdb.lua -sed -i "s/__DBPASS__/${DBPASS}/g" /usr/local/bin/imapsync_cron.pl /usr/local/bin/quarantine_notify.py /usr/local/bin/clean_q_aged.sh /etc/dovecot/lua/app-passdb.lua -sed -i "s/__DBNAME__/${DBNAME}/g" /usr/local/bin/imapsync_cron.pl /usr/local/bin/quarantine_notify.py /usr/local/bin/clean_q_aged.sh /etc/dovecot/lua/app-passdb.lua -sed -i "s/__MAILCOW_HOSTNAME__/${MAILCOW_HOSTNAME}/g" /usr/local/bin/quarantine_notify.py -sed -i "s/__LOG_LINES__/${LOG_LINES}/g" /usr/local/bin/trim_logs.sh + if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then -# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated -cat <<'EOF' > /usr/local/bin/quota_notify.py + # Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated + cat <<'EOF' > /usr/local/bin/quota_notify.py #!/usr/bin/python3 import sys sys.exit() @@ -311,7 +312,7 @@ chmod g+rw /dev/console chown root:tty /dev/console chmod +x /usr/lib/dovecot/sieve/rspamd-pipe-ham \ /usr/lib/dovecot/sieve/rspamd-pipe-spam \ - /usr/local/bin/imapsync_cron.pl \ + /usr/local/bin/imapsync_runner.pl \ /usr/local/bin/postlogin.sh \ /usr/local/bin/imapsync \ /usr/local/bin/trim_logs.sh \ @@ -322,27 +323,6 @@ chmod +x /usr/lib/dovecot/sieve/rspamd-pipe-ham \ /usr/local/bin/quota_notify.py \ /usr/local/bin/repl_health.sh -if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then -# Setup cronjobs -echo '* * * * * nobody /usr/local/bin/imapsync_cron.pl 2>&1 | /usr/bin/logger' > /etc/cron.d/imapsync -#echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync -echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/console 2>&1' > /etc/cron.d/trim_logs -echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/console 2>&1' > /etc/cron.d/maildir_gc -echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/console 2>&1' > /etc/cron.d/sa-rules -echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot-fts/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize -echo '*/20 * * * * vmail /usr/local/bin/quarantine_notify.py >> /dev/console 2>&1' > /etc/cron.d/quarantine_notify -echo '15 4 * * * vmail /usr/local/bin/clean_q_aged.sh >> /dev/console 2>&1' > /etc/cron.d/clean_q_aged -echo '*/5 * * * * vmail /usr/local/bin/repl_health.sh >> /dev/console 2>&1' > /etc/cron.d/repl_health -else -echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/console 2>&1' > /etc/cron.d/maildir_gc -echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/console 2>&1' > /etc/cron.d/sa-rules -echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot-fts/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize -echo '*/5 * * * * vmail /usr/local/bin/repl_health.sh >> /dev/console 2>&1' > /etc/cron.d/repl_health -fi - -# Fix more than 1 hardlink issue -touch /etc/crontab /etc/cron.*/* - # Prepare environment file for cronjobs printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh diff --git a/data/Dockerfiles/dovecot/imapsync_cron.pl b/data/Dockerfiles/dovecot/imapsync_runner.pl similarity index 96% rename from data/Dockerfiles/dovecot/imapsync_cron.pl rename to data/Dockerfiles/dovecot/imapsync_runner.pl index 746b1d47..0f01a971 100644 --- a/data/Dockerfiles/dovecot/imapsync_cron.pl +++ b/data/Dockerfiles/dovecot/imapsync_runner.pl @@ -36,11 +36,11 @@ sub qqw($) { } $run_dir="/tmp"; -$dsn = 'DBI:mysql:database=__DBNAME__;mysql_socket=/var/run/mysqld/mysqld.sock'; +$dsn = 'DBI:mysql:database=' . $ENV{'DBNAME'} . ';mysql_socket=/var/run/mysqld/mysqld.sock'; $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, $ENV{'DBUSER'}, $ENV{'DBPASS'}, { mysql_auto_reconnect => 1, mysql_enable_utf8mb4 => 1 }); @@ -127,6 +127,7 @@ while ($row = $sth->fetchrow_arrayref()) { my $generated_cmds = [ "/usr/local/bin/imapsync", "--tmpdir", "/tmp", "--nofoldersizes", + "--addheader", ($timeout1 gt "0" ? () : ('--timeout1', $timeout1)), ($timeout2 gt "0" ? () : ('--timeout2', $timeout2)), ($exclude eq "" ? () : ("--exclude", $exclude)), diff --git a/data/Dockerfiles/dovecot/maildir_gc.sh b/data/Dockerfiles/dovecot/maildir_gc.sh index 24c1e461..21358ccb 100755 --- a/data/Dockerfiles/dovecot/maildir_gc.sh +++ b/data/Dockerfiles/dovecot/maildir_gc.sh @@ -1,2 +1,2 @@ -#/bin/bash +#!/bin/bash [ -d /var/vmail/_garbage/ ] && /usr/bin/find /var/vmail/_garbage/ -mindepth 1 -maxdepth 1 -type d -cmin +${MAILDIR_GC_TIME} -exec rm -r {} \; diff --git a/data/Dockerfiles/dovecot/quarantine_notify.py b/data/Dockerfiles/dovecot/quarantine_notify.py index 3ab4430b..acd887a0 100755 --- a/data/Dockerfiles/dovecot/quarantine_notify.py +++ b/data/Dockerfiles/dovecot/quarantine_notify.py @@ -41,7 +41,7 @@ try: break time_now = int(time.time()) - mailcow_hostname = '__MAILCOW_HOSTNAME__' + mailcow_hostname = os.environ.get('MAILCOW_HOSTNAME') max_score = float(r.get('Q_MAX_SCORE') or "9999.0") if max_score == "": @@ -50,7 +50,7 @@ try: def query_mysql(query, headers = True, update = False): while True: try: - cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user='__DBUSER__', passwd='__DBPASS__', database='__DBNAME__', charset="utf8") + cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user=os.environ.get('DBUSER'), passwd=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8") except Exception as ex: print('%s - trying again...' % (ex)) time.sleep(3) diff --git a/data/Dockerfiles/dovecot/sa-rules.sh b/data/Dockerfiles/dovecot/sa-rules.sh index 1bfc8ccb..89911c19 100755 --- a/data/Dockerfiles/dovecot/sa-rules.sh +++ b/data/Dockerfiles/dovecot/sa-rules.sh @@ -2,7 +2,6 @@ # Create temp directories [[ ! -d /tmp/sa-rules-heinlein ]] && mkdir -p /tmp/sa-rules-heinlein -#[[ ! -d /tmp/sa-rules-schaal ]] && mkdir -p /tmp/sa-rules-schaal # Hash current SA rules if [[ ! -f /etc/rspamd/custom/sa-rules ]]; then @@ -12,19 +11,11 @@ else fi # Deploy -## Heinlein -curl --connect-timeout 15 --retry 10 --max-time 30 http://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"').tar.gz --output /tmp/sa-rules-heinlein.tar.gz +curl --connect-timeout 15 --retry 10 --max-time 30 http://www.spamassassin.heinlein-support.de/$(dig txt 1.4.3.spamassassin.heinlein-support.de +short | tr -d '"' | tr -dc '0-9').tar.gz --output /tmp/sa-rules-heinlein.tar.gz if gzip -t /tmp/sa-rules-heinlein.tar.gz; then tar xfvz /tmp/sa-rules-heinlein.tar.gz -C /tmp/sa-rules-heinlein cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules fi -## Schaal -#curl --connect-timeout 15 --max-time 30 http://sa.schaal-it.net/$(dig txt 1.4.3.sa.schaal-it.net +short | tr -d '"').tar.gz --output /tmp/sa-rules-schaal.tar.gz -#if gzip -t /tmp/sa-rules-schaal.tar.gz; then -# tar xfvz /tmp/sa-rules-schaal.tar.gz -C /tmp/sa-rules-schaal -# # Append, do not overwrite -# cat /tmp/sa-rules-schaal/*cf >> /etc/rspamd/custom/sa-rules -#fi sed -i -e 's/\([^\\]\)\$\([^\/]\)/\1\\$\2/g' /etc/rspamd/custom/sa-rules @@ -40,4 +31,3 @@ fi # Cleanup rm -rf /tmp/sa-rules-heinlein /tmp/sa-rules-heinlein.tar.gz -#rm -rf /tmp/sa-rules-schaal /tmp/sa-rules-schaal.tar.gz diff --git a/data/Dockerfiles/dovecot/supervisord.conf b/data/Dockerfiles/dovecot/supervisord.conf index 2d91b55a..a7698640 100644 --- a/data/Dockerfiles/dovecot/supervisord.conf +++ b/data/Dockerfiles/dovecot/supervisord.conf @@ -15,10 +15,6 @@ autostart=true command=/usr/sbin/dovecot -F autorestart=true -[program:cron] -command=/usr/sbin/cron -f -autorestart=true - [eventlistener:processes] command=/usr/local/sbin/stop-supervisor.sh events=PROCESS_STATE_STOPPED, PROCESS_STATE_EXITED, PROCESS_STATE_FATAL diff --git a/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf b/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf index 335cbfe6..f6905092 100644 --- a/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf +++ b/data/Dockerfiles/dovecot/syslog-ng-redis_slave.conf @@ -27,7 +27,7 @@ destination d_redis_f2b_channel { host("`REDIS_SLAVEOF_IP`") persist-name("redis2") port(`REDIS_SLAVEOF_PORT`) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; diff --git a/data/Dockerfiles/dovecot/syslog-ng.conf b/data/Dockerfiles/dovecot/syslog-ng.conf index f0489ea1..bdaca9cb 100644 --- a/data/Dockerfiles/dovecot/syslog-ng.conf +++ b/data/Dockerfiles/dovecot/syslog-ng.conf @@ -27,7 +27,7 @@ destination d_redis_f2b_channel { host("redis-mailcow") persist-name("redis2") port(6379) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; diff --git a/data/Dockerfiles/dovecot/trim_logs.sh b/data/Dockerfiles/dovecot/trim_logs.sh index 2993a4cf..9b0824e1 100755 --- a/data/Dockerfiles/dovecot/trim_logs.sh +++ b/data/Dockerfiles/dovecot/trim_logs.sh @@ -14,12 +14,12 @@ if [[ ! -z ${REDIS_SLAVEOF_IP} ]]; then else REDIS_CMDLINE="redis-cli -h redis -p 6379" fi -catch_non_zero "${REDIS_CMDLINE} LTRIM ACME_LOG 0 __LOG_LINES__" -catch_non_zero "${REDIS_CMDLINE} LTRIM POSTFIX_MAILLOG 0 __LOG_LINES__" -catch_non_zero "${REDIS_CMDLINE} LTRIM DOVECOT_MAILLOG 0 __LOG_LINES__" -catch_non_zero "${REDIS_CMDLINE} LTRIM SOGO_LOG 0 __LOG_LINES__" -catch_non_zero "${REDIS_CMDLINE} LTRIM NETFILTER_LOG 0 __LOG_LINES__" -catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 __LOG_LINES__" -catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 __LOG_LINES__" -catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 __LOG_LINES__" -catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 __LOG_LINES__" +catch_non_zero "${REDIS_CMDLINE} LTRIM ACME_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM POSTFIX_MAILLOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM DOVECOT_MAILLOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM SOGO_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM NETFILTER_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM AUTODISCOVER_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM API_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM RL_LOG 0 ${LOG_LINES}" +catch_non_zero "${REDIS_CMDLINE} LTRIM WATCHDOG_LOG 0 ${LOG_LINES}" diff --git a/data/Dockerfiles/ejabberd/authenticator b/data/Dockerfiles/ejabberd/authenticator index 6f3acd0a..96a6b8eb 100755 --- a/data/Dockerfiles/ejabberd/authenticator +++ b/data/Dockerfiles/ejabberd/authenticator @@ -8,7 +8,7 @@ use LeeSherwood\Ejabberd\CommandExecutors\mailcowCommandExecutor; $logger = new Logger('ejabberdAuth'); -$stdoutHandler = new Monolog\Handler\StreamHandler('/var/www/authentication/auth.log', Logger::DEBUG); +$stdoutHandler = new Monolog\Handler\StreamHandler('/var/www/authentication/auth.log', Logger::INFO); $logger->pushHandler($stdoutHandler); diff --git a/data/Dockerfiles/ejabberd/docker-entrypoint.sh b/data/Dockerfiles/ejabberd/docker-entrypoint.sh index 2289bb40..1777315f 100755 --- a/data/Dockerfiles/ejabberd/docker-entrypoint.sh +++ b/data/Dockerfiles/ejabberd/docker-entrypoint.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -until dig +short mailcow.email @unbound > /dev/null; do +until dig +short mailcow.email > /dev/null; do echo "Waiting for DNS..." sleep 1 done diff --git a/data/Dockerfiles/netfilter/server.py b/data/Dockerfiles/netfilter/server.py index 36565dbc..88fc5f25 100644 --- a/data/Dockerfiles/netfilter/server.py +++ b/data/Dockerfiles/netfilter/server.py @@ -2,6 +2,7 @@ import re import os +import sys import time import atexit import signal @@ -39,6 +40,7 @@ BLACKLIST= [] bans = {} quit_now = False +exit_code = 0 lock = Lock() def log(priority, message): @@ -61,6 +63,7 @@ def logInfo(message): def refreshF2boptions(): global f2boptions global quit_now + global exit_code if not r.get('F2B_OPTIONS'): f2boptions = {} f2boptions['ban_time'] = int @@ -81,10 +84,12 @@ def refreshF2boptions(): except ValueError: print('Error loading F2B options: F2B_OPTIONS is not json') quit_now = True + exit_code = 2 def refreshF2bregex(): global f2bregex global quit_now + global exit_code if not r.get('F2B_REGEX'): f2bregex = {} f2bregex[1] = 'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed' @@ -95,6 +100,7 @@ def refreshF2bregex(): f2bregex[6] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+' f2bregex[7] = 'Rspamd UI: Invalid password by ([0-9a-f\.:]+)' f2bregex[8] = '-login: Aborted login \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' + f2bregex[9] = 'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+' r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False)) else: try: @@ -103,6 +109,7 @@ def refreshF2bregex(): except ValueError: print('Error loading F2B options: F2B_REGEX is not json') quit_now = True + exit_code = 2 if r.exists('F2B_LOG'): r.rename('F2B_LOG', 'NETFILTER_LOG') @@ -110,6 +117,7 @@ if r.exists('F2B_LOG'): def mailcowChainOrder(): global lock global quit_now + global exit_code while not quit_now: time.sleep(10) with lock: @@ -128,9 +136,11 @@ def mailcowChainOrder(): if position > 2: logCrit('Error in %s chain order: MAILCOW on position %d, restarting container' % (chain.name, position)) quit_now = True + exit_code = 2 if not target_found: logCrit('Error in %s chain: MAILCOW target not found, restarting container' % (chain.name)) quit_now = True + exit_code = 2 def ban(address): global lock @@ -300,22 +310,30 @@ def watch(): logInfo('Watching Redis channel F2B_CHANNEL') pubsub.subscribe('F2B_CHANNEL') + global quit_now + global exit_code + while not quit_now: - for item in pubsub.listen(): - refreshF2bregex() - for rule_id, rule_regex in f2bregex.items(): - if item['data'] and item['type'] == 'message': - try: - result = re.search(rule_regex, item['data']) - except re.error: - result = False - if result: - addr = result.group(1) - ip = ipaddress.ip_address(addr) - if ip.is_private or ip.is_loopback: - continue - logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data'])) - ban(addr) + try: + for item in pubsub.listen(): + refreshF2bregex() + for rule_id, rule_regex in f2bregex.items(): + if item['data'] and item['type'] == 'message': + try: + result = re.search(rule_regex, item['data']) + except re.error: + result = False + if result: + addr = result.group(1) + ip = ipaddress.ip_address(addr) + if ip.is_private or ip.is_loopback: + continue + logWarn('%s matched rule id %s (%s)' % (addr, rule_id, item['data'])) + ban(addr) + except Exception as ex: + logWarn('Error reading log line from pubsub') + quit_now = True + exit_code = 2 def snat4(snat_target): global lock @@ -555,3 +573,5 @@ if __name__ == '__main__': while not quit_now: time.sleep(0.5) + + sys.exit(exit_code) diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh index 0a4a2925..9a2b5829 100755 --- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh +++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh @@ -90,6 +90,15 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then ${REDIS_CMDLINE} --raw SET Q_MAX_AGE 365 fi + # Set default password policy - if unset + if [[ -z $(${REDIS_CMDLINE} --raw HGET PASSWD_POLICY length) ]]; then + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY length 6 + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY chars 0 + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY special_chars 0 + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY lowerupper 0 + ${REDIS_CMDLINE} --raw HSET PASSWD_POLICY numbers 0 + fi + # Trigger db init echo "Running DB init..." php -c /usr/local/etc/php -f /web/inc/init_db.inc.php diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh index dffcfd44..753f446e 100755 --- a/data/Dockerfiles/postfix/postfix.sh +++ b/data/Dockerfiles/postfix/postfix.sh @@ -10,7 +10,7 @@ while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${D sleep 2 done -until dig +short mailcow.email @unbound > /dev/null; do +until dig +short mailcow.email > /dev/null; do echo "Waiting for DNS..." sleep 1 done diff --git a/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf b/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf index 609ee55e..40fb1cda 100644 --- a/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf +++ b/data/Dockerfiles/postfix/syslog-ng-redis_slave.conf @@ -28,7 +28,7 @@ destination d_redis_f2b_channel { host("`REDIS_SLAVEOF_IP`") persist-name("redis2") port(`REDIS_SLAVEOF_PORT`) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; diff --git a/data/Dockerfiles/postfix/syslog-ng.conf b/data/Dockerfiles/postfix/syslog-ng.conf index 9e14fe17..8fdc104e 100644 --- a/data/Dockerfiles/postfix/syslog-ng.conf +++ b/data/Dockerfiles/postfix/syslog-ng.conf @@ -28,7 +28,7 @@ destination d_redis_f2b_channel { host("redis-mailcow") persist-name("redis2") port(6379) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; filter f_mail { facility(mail); }; diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile index 1e499651..9cf5f621 100644 --- a/data/Dockerfiles/sogo/Dockerfile +++ b/data/Dockerfiles/sogo/Dockerfile @@ -4,14 +4,13 @@ LABEL maintainer "Andre Peters " ARG DEBIAN_FRONTEND=noninteractive ARG SOGO_DEBIAN_REPOSITORY=http://packages.inverse.ca/SOGo/nightly/5/debian/ ENV LC_ALL C -ENV GOSU_VERSION 1.11 +ENV GOSU_VERSION 1.12 # Prerequisites RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \ && apt-get update && apt-get install -y --no-install-recommends \ apt-transport-https \ ca-certificates \ - cron \ gettext \ gnupg \ mariadb-client \ diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh index fef7958b..d908bb3e 100755 --- a/data/Dockerfiles/sogo/bootstrap-sogo.sh +++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh @@ -249,14 +249,4 @@ rsync -a /usr/lib/GNUstep/SOGo/. /sogo_web/ # Chown backup path chown -R sogo:sogo /sogo_backup -# Creating cronjobs -if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then - echo "* * * * * sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/sieve.creds 2>/dev/null" > /etc/cron.d/sogo - echo "* * * * * sogo /usr/sbin/sogo-tool expire-sessions ${SOGO_EXPIRE_SESSION}" >> /etc/cron.d/sogo - echo "0 0 * * * sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds" >> /etc/cron.d/sogo - echo "0 2 * * * sogo /usr/sbin/sogo-tool backup /sogo_backup ALL" >> /etc/cron.d/sogo -else - rm /etc/cron.d/sogo -fi - exec gosu sogo /usr/sbin/sogod diff --git a/data/Dockerfiles/sogo/supervisord.conf b/data/Dockerfiles/sogo/supervisord.conf index 551a8e12..4946d98c 100644 --- a/data/Dockerfiles/sogo/supervisord.conf +++ b/data/Dockerfiles/sogo/supervisord.conf @@ -11,18 +11,13 @@ stderr_logfile_maxbytes=0 autostart=true priority=1 -[program:cron] -command=/usr/sbin/cron -f -autorestart=true -priority=2 - [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 +priority=2 startretries=10 autorestart=true stopwaitsecs=120 diff --git a/data/Dockerfiles/sogo/syslog-ng-redis_slave.conf b/data/Dockerfiles/sogo/syslog-ng-redis_slave.conf index 9b04c781..5a84b722 100644 --- a/data/Dockerfiles/sogo/syslog-ng-redis_slave.conf +++ b/data/Dockerfiles/sogo/syslog-ng-redis_slave.conf @@ -30,7 +30,7 @@ destination d_redis_f2b_channel { host("`REDIS_SLAVEOF_IP`") persist-name("redis2") port(`REDIS_SLAVEOF_PORT`) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; log { diff --git a/data/Dockerfiles/sogo/syslog-ng.conf b/data/Dockerfiles/sogo/syslog-ng.conf index 0c257d6a..537038ef 100644 --- a/data/Dockerfiles/sogo/syslog-ng.conf +++ b/data/Dockerfiles/sogo/syslog-ng.conf @@ -30,7 +30,7 @@ destination d_redis_f2b_channel { host("redis-mailcow") persist-name("redis2") port(6379) - command("PUBLISH" "F2B_CHANNEL" "$MESSAGE") + command("PUBLISH" "F2B_CHANNEL" "$(sanitize $MESSAGE)") ); }; log { diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh index 177a5304..19c0d2ed 100755 --- a/data/Dockerfiles/watchdog/watchdog.sh +++ b/data/Dockerfiles/watchdog/watchdog.sh @@ -109,7 +109,7 @@ function mail_error() { SUBJECT="${BODY}" BODY="Please see netfilter-mailcow for more details and triggered rules." else - SUBJECT="Watchdog ALERT: ${1}" + SUBJECT="${WATCHDOG_SUBJECT}: ${1}" fi IFS=',' read -r -a MAIL_RCPTS <<< "${WATCHDOG_NOTIFY_EMAIL}" for rcpt in "${MAIL_RCPTS[@]}"; do @@ -210,7 +210,7 @@ external_checks() { sleep 60 else diff_c=0 - sleep $(( ( RANDOM % 20 ) + 120 )) + sleep $(( ( RANDOM % 20 ) + 1800 )) fi done return 1 diff --git a/data/conf/nginx/includes/site-defaults.conf b/data/conf/nginx/includes/site-defaults.conf index 34bd7256..ae4de7b8 100644 --- a/data/conf/nginx/includes/site-defaults.conf +++ b/data/conf/nginx/includes/site-defaults.conf @@ -3,6 +3,8 @@ charset utf-8; override_charset on; + server_tokens off; + ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305; diff --git a/data/conf/nginx/site.conf b/data/conf/nginx/site.conf index d6e6b136..1b46d2b9 100644 --- a/data/conf/nginx/site.conf +++ b/data/conf/nginx/site.conf @@ -1,4 +1,3 @@ -server_tokens off; proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g; server_names_hash_bucket_size 64; diff --git a/data/conf/phpfpm/php-conf.d/other.ini b/data/conf/phpfpm/php-conf.d/other.ini index 379be750..02f59a9c 100644 --- a/data/conf/phpfpm/php-conf.d/other.ini +++ b/data/conf/phpfpm/php-conf.d/other.ini @@ -1,2 +1,3 @@ max_execution_time = 3600 max_input_time = 3600 +memory_limit = 512M diff --git a/data/conf/postfix/main.cf b/data/conf/postfix/main.cf index 7098fcb1..1005b284 100644 --- a/data/conf/postfix/main.cf +++ b/data/conf/postfix/main.cf @@ -15,7 +15,7 @@ smtpd_relay_restrictions = permit_mynetworks, alias_maps = hash:/etc/aliases alias_database = hash:/etc/aliases relayhost = -mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 [fe80::]/10 [fc00::]/7 +mynetworks_style = subnet mailbox_size_limit = 0 recipient_delimiter = + inet_interfaces = all @@ -198,6 +198,3 @@ parent_domain_matches_subdomains = debug_peer_list,fast_flush_domains,mynetworks # DO NOT EDIT ANYTHING BELOW # # User overrides # - -myhostname = mail.gnous.fr - diff --git a/data/conf/rspamd/custom/bulk_header.map b/data/conf/rspamd/custom/bulk_header.map index d747315b..e9dc206f 100644 --- a/data/conf/rspamd/custom/bulk_header.map +++ b/data/conf/rspamd/custom/bulk_header.map @@ -1,5 +1,5 @@ /X-EMV-Platform; .*/i -/.*nur-1-click*/i +/.*nur-1-click.*/i /.*episerver.*/i /.*supergewinne.*/i /List-Unsubscribe.*nbps\.eu/i diff --git a/data/conf/rspamd/local.d/dkim_signing.conf b/data/conf/rspamd/local.d/dkim_signing.conf index 13eb0945..4fac27fd 100644 --- a/data/conf/rspamd/local.d/dkim_signing.conf +++ b/data/conf/rspamd/local.d/dkim_signing.conf @@ -32,4 +32,4 @@ selector_prefix = "DKIM_SELECTORS"; # forwards are arc signed, rejects are dkim signed sign_networks = "/etc/rspamd/custom/dovecot_trusted.map"; use_domain_sign_networks = "header"; -sign_headers = "from:sender:reply-to:subject:date:message-id:to:cc:mime-version:content-type:content-transfer-encoding:resent-to:resent-cc:resent-from:resent-sender:resent-message-id:in-reply-to:references:list-id:list-help:list-owner:list-unsubscribe:list-subscribe:list-post:openpgp:autocrypt"; +sign_headers = "from:sender:reply-to:subject:date:message-id:to:cc:mime-version:content-type:content-transfer-encoding:content-language:resent-to:resent-cc:resent-from:resent-sender:resent-message-id:in-reply-to:references:list-id:list-help:list-owner:list-unsubscribe:list-subscribe:list-post:list-unsubscribe-post:disposition-notification-to:disposition-notification-options:original-recipient:openpgp:autocrypt"; diff --git a/data/conf/rspamd/local.d/multimap.conf b/data/conf/rspamd/local.d/multimap.conf index 0f05bb51..c3a59d85 100644 --- a/data/conf/rspamd/local.d/multimap.conf +++ b/data/conf/rspamd/local.d/multimap.conf @@ -80,7 +80,6 @@ SIEVE_HOST { type = "ip"; map = "${LOCAL_CONFDIR}/custom/dovecot_trusted.map"; symbols_set = ["SIEVE_HOST"]; - score = -15; } RSPAMD_HOST { diff --git a/data/conf/rspamd/local.d/reputation.conf b/data/conf/rspamd/local.d/reputation.conf index 0e3d03eb..c9600b7b 100644 --- a/data/conf/rspamd/local.d/reputation.conf +++ b/data/conf/rspamd/local.d/reputation.conf @@ -3,7 +3,6 @@ rules { selector "ip" { } backend "redis" { - servers = "redis"; } symbol = "IP_REPUTATION"; } diff --git a/data/conf/sogo/custom-theme.js b/data/conf/sogo/custom-theme.js index 30c2a9e1..0df50677 100644 --- a/data/conf/sogo/custom-theme.js +++ b/data/conf/sogo/custom-theme.js @@ -1,86 +1,36 @@ -/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ - +/* EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE - EXAMPLE (function() { 'use strict'; - angular.module('SOGo.Common') .config(configure) - /** - * @ngInject - */ configure.$inject = ['$mdThemingProvider']; function configure($mdThemingProvider) { - - /** - * The SOGo palettes are defined in js/Common/Common.app.js: - * - * - sogo-green - * - sogo-blue - * - sogo-grey - * - * The Material palettes are also available: - * - * - red - * - pink - * - purple - * - deep-purple - * - indigo - * - blue - * - light-blue - * - cyan - * - teal - * - green - * - light-green - * - lime - * - yellow - * - amber - * - orange - * - deep-orange - * - brown - * - grey - * - blue-grey - * - * See https://material.angularjs.org/latest/Theming/01_introduction - * and https://material.io/archive/guidelines/style/color.html#color-color-palette - * - * You can also define your own palettes. See js/Common/Common.app.js. - */ - - // Create new background palette from grey palette var greyMap = $mdThemingProvider.extendPalette('grey', { - // background color of sidebar selected item, - // background color of right panel, - // background color of menus (autocomplete and contextual menus) '200': 'F5F5F5', - // background color of sidebar '300': 'E5E5E5', - // background color of the busy periods of the attendees editor '1000': '4C566A' }); var greenCow = $mdThemingProvider.extendPalette('green', { '600': 'E5E5E5' }); - $mdThemingProvider.definePalette('frost-grey', greyMap); $mdThemingProvider.definePalette('green-cow', greenCow); - - // Apply new palettes to the default theme, remap some of the hues $mdThemingProvider.theme('default') .primaryPalette('green-cow', { - 'default': '400', // background color of top toolbars + 'default': '400', 'hue-1': '400', - 'hue-2': '600', // background color of sidebar toolbar + 'hue-2': '600', 'hue-3': 'A700' }) .accentPalette('green', { - 'default': '600', // background color of fab buttons - 'hue-1': '300', // background color of center list toolbar + 'default': '600', + 'hue-1': '300', 'hue-2': '300', 'hue-3': 'A700' }) .backgroundPalette('frost-grey'); - $mdThemingProvider.generateThemesOnDemand(false); } })(); + */ \ No newline at end of file diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf index 9f6568f5..2513f496 100644 --- a/data/conf/sogo/sogo.conf +++ b/data/conf/sogo/sogo.conf @@ -85,7 +85,7 @@ //LDAPDebugEnabled = YES; //PGDebugEnabled = YES; //MySQL4DebugEnabled = YES; - SOGoUIxDebugEnabled = YES; + //SOGoUIxDebugEnabled = YES; //WODontZipResponse = YES; WOLogFile = "/dev/sogo_log"; } diff --git a/data/web/inc/lib/vendor/robthree/twofactorauth/phpunit.xml.tmppica b/data/hooks/dovecot/.gitkeep similarity index 100% rename from data/web/inc/lib/vendor/robthree/twofactorauth/phpunit.xml.tmppica rename to data/hooks/dovecot/.gitkeep diff --git a/data/hooks/phpfpm/.gitkeep b/data/hooks/phpfpm/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/hooks/postfix/.gitkeep b/data/hooks/postfix/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/hooks/rspamd/.gitkeep b/data/hooks/rspamd/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/hooks/unbound/.gitkeep b/data/hooks/unbound/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/data/web/admin.php b/data/web/admin.php index 990510ba..ab1719eb 100644 --- a/data/web/admin.php +++ b/data/web/admin.php @@ -13,9 +13,18 @@ if (!isset($_SESSION['gal']) && $license_cache = $redis->Get('LICENSE_STATUS_CAC
+
diff --git a/data/web/modals/admin.php b/data/web/modals/admin.php index e796d2e1..2897e23b 100644 --- a/data/web/modals/admin.php +++ b/data/web/modals/admin.php @@ -59,8 +59,8 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
- - ↳ a-z A-Z - _ . + + ↳ a-z - _ .
@@ -171,8 +171,8 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
- - ↳ a-z A-Z - _ . + + ↳ a-z - _ .
@@ -222,6 +222,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
+
+ +
+ +

+
+
diff --git a/docker-compose.yml b/docker-compose.yml index ee014a5d..41f358dd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: - unbound mysql-mailcow: - image: mariadb:10.4 + image: mariadb:10.5 depends_on: - unbound-mailcow stop_grace_period: 45s @@ -56,7 +56,7 @@ services: - redis clamd-mailcow: - image: mailcow/clamd:1.38 + image: mailcow/clamd:1.40 restart: always dns: - ${IPV4_NETWORK:-172.22.1}.254 @@ -101,7 +101,7 @@ services: - rspamd php-fpm-mailcow: - image: mailcow/phpfpm:1.73 + image: mailcow/phpfpm:1.75 command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" depends_on: - redis-mailcow @@ -164,7 +164,7 @@ services: - phpfpm sogo-mailcow: - image: mailcow/sogo:1.95 + image: mailcow/sogo:1.99 environment: - DBNAME=${DBNAME} - DBUSER=${DBUSER} @@ -192,6 +192,16 @@ services: - mysql-socket-vol-1:/var/run/mysqld/:z - sogo-web-vol-1:/sogo_web:z - sogo-userdata-backup-vol-1:/sogo_backup:Z + labels: + ofelia.enabled: "true" + ofelia.job-exec.sogo_sessions.schedule: "@every 1m" + ofelia.job-exec.sogo_sessions.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool expire-sessions $${SOGO_EXPIRE_SESSION} || exit 0\"" + ofelia.job-exec.sogo_ealarms.schedule: "@every 1m" + ofelia.job-exec.sogo_ealarms.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/sieve.creds || exit 0\"" + ofelia.job-exec.sogo_eautoreply.schedule: "@every 1d" + ofelia.job-exec.sogo_eautoreply.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds || exit 0\"" + ofelia.job-exec.sogo_backup.schedule: "@every 1d" + ofelia.job-exec.sogo_backup.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool backup /sogo_backup ALL || exit 0\"" restart: always networks: mailcow-network: @@ -200,7 +210,7 @@ services: - sogo dovecot-mailcow: - image: mailcow/dovecot:1.140 + image: mailcow/dovecot:1.145 depends_on: - mysql-mailcow dns: @@ -249,6 +259,25 @@ services: - "${SIEVE_PORT:-4190}:4190" restart: always tty: true + labels: + ofelia.enabled: "true" + ofelia.job-exec.dovecot_imapsync_runner.schedule: "@every 1m" + ofelia.job-exec.dovecot_imapsync_runner.no-overlap: "true" + ofelia.job-exec.dovecot_imapsync_runner.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu nobody /usr/local/bin/imapsync_runner.pl || exit 0\"" + ofelia.job-exec.dovecot_trim_logs.schedule: "@every 1m" + ofelia.job-exec.dovecot_trim_logs.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/trim_logs.sh || exit 0\"" + ofelia.job-exec.dovecot_quarantine.schedule: "@every 20m" + ofelia.job-exec.dovecot_quarantine.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/quarantine_notify.py || exit 0\"" + ofelia.job-exec.dovecot_clean_q_aged.schedule: "@every 1d" + ofelia.job-exec.dovecot_clean_q_aged.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/clean_q_aged.sh || exit 0\"" + ofelia.job-exec.dovecot_maildir_gc.schedule: "@every 30m" + ofelia.job-exec.dovecot_maildir_gc.command: "/bin/bash -c \"source /source_env.sh ; /usr/local/bin/gosu vmail /usr/local/bin/maildir_gc.sh\"" + ofelia.job-exec.dovecot_sarules.schedule: "@every 1d" + ofelia.job-exec.dovecot_sarules.command: "/bin/bash -c \"/usr/local/bin/sa-rules.sh\"" + ofelia.job-exec.dovecot_fts.schedule: "@every 1d" + ofelia.job-exec.dovecot_fts.command: "/usr/bin/curl http://solr:8983/solr/dovecot-fts/update?optimize=true" + ofelia.job-exec.dovecot_repl_health.schedule: "@every 5m" + ofelia.job-exec.dovecot_repl_health.command: "/bin/bash -c \"/usr/local/bin/gosu vmail /usr/local/bin/repl_health.sh\"" ulimits: nproc: 65535 nofile: @@ -261,7 +290,7 @@ services: - dovecot postfix-mailcow: - image: mailcow/postfix:1.59 + image: mailcow/postfix:1.61 depends_on: - mysql-mailcow volumes: @@ -381,11 +410,12 @@ services: acme-mailcow: depends_on: - nginx-mailcow - image: mailcow/acme:1.78 + image: mailcow/acme:1.79 dns: - ${IPV4_NETWORK:-172.22.1}.254 environment: - LOG_LINES=${LOG_LINES:-9999} + - ACME_CONTACT=${ACME_CONTACT:-} - ADDITIONAL_SAN=${ADDITIONAL_SAN} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - DBNAME=${DBNAME} @@ -416,7 +446,7 @@ services: - acme netfilter-mailcow: - image: mailcow/netfilter:1.39 + image: mailcow/netfilter:1.43 stop_grace_period: 30s depends_on: - dovecot-mailcow @@ -439,7 +469,7 @@ services: - /lib/modules:/lib/modules:ro watchdog-mailcow: - image: mailcow/watchdog:1.90 + image: mailcow/watchdog:1.91 # Debug #command: /watchdog.sh dns: @@ -463,6 +493,7 @@ services: - USE_WATCHDOG=${USE_WATCHDOG:-n} - WATCHDOG_NOTIFY_EMAIL=${WATCHDOG_NOTIFY_EMAIL} - WATCHDOG_NOTIFY_BAN=${WATCHDOG_NOTIFY_BAN:-y} + - WATCHDOG_SUBJECT=${WATCHDOG_SUBJECT:-Watchdog ALERT} - WATCHDOG_EXTERNAL_CHECKS=${WATCHDOG_EXTERNAL_CHECKS:-n} - WATCHDOG_MYSQL_REPLICATION_CHECKS=${WATCHDOG_MYSQL_REPLICATION_CHECKS:-n} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} @@ -536,7 +567,7 @@ services: - solr olefy-mailcow: - image: mailcow/olefy:1.6 + image: mailcow/olefy:1.7 restart: always environment: - TZ=${TZ} @@ -554,7 +585,7 @@ services: - olefy ejabberd-mailcow: - image: mailcow/ejabberd:1.4 + image: mailcow/ejabberd:1.6 volumes: - ./data/conf/ejabberd/ejabberd.yml:/home/ejabberd/conf/ejabberd.yml:z - xmpp-vol-1:/home/ejabberd/database:z @@ -567,6 +598,10 @@ services: dns: - ${IPV4_NETWORK:-172.22.1}.254 hostname: ejabberd.mailcow.local + labels: + ofelia.enabled: "true" + ofelia.job-exec.ejabberd_certs.schedule: "@every 14d" + ofelia.job-exec.ejabberd_certs.command: "/sbin/su-exec ejabberd /home/ejabberd/bin/ejabberdctl --node ejabberd@$${MAILCOW_HOSTNAME} request-certificate all" extra_hosts: - "${MAILCOW_HOSTNAME}:127.0.0.1" environment: @@ -587,6 +622,24 @@ services: aliases: - ejabberd + ofelia-mailcow: + image: mcuadros/ofelia:latest + restart: always + command: daemon --docker + - TZ=${TZ} + depends_on: + - sogo-mailcow + - dovecot-mailcow + - ejabberd-mailcow + labels: + ofelia.enabled: "true" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + mailcow-network: + aliases: + - ofelia + ipv6nat-mailcow: depends_on: - unbound-mailcow diff --git a/generate_config.sh b/generate_config.sh index 6775c6a9..1c6886b8 100755 --- a/generate_config.sh +++ b/generate_config.sh @@ -30,7 +30,7 @@ for bin in openssl curl docker-compose docker git awk sha1sum; do done if [ -f mailcow.conf ]; then - read -r -p "A config file exists and will be overwritten, are you sure you want to contine? [y/N] " response + read -r -p "A config file exists and will be overwritten, are you sure you want to continue? [y/N] " response case $response in [yY][eE][sS]|[yY]) mv mailcow.conf mailcow.conf_backup @@ -279,6 +279,9 @@ USE_WATCHDOG=y # Notify about banned IP (includes whois lookup) WATCHDOG_NOTIFY_BAN=n +# Subject for watchdog mails. Defaults to "Watchdog ALERT" followed by the error message. +#WATCHDOG_SUBJECT= + # Checks if mailcow is an open relay. Requires a SAL. More checks will follow. # https://www.servercow.de/mailcow?lang=en # https://www.servercow.de/mailcow?lang=de @@ -333,6 +336,13 @@ DOVECOT_MASTER_USER= # LEAVE EMPTY IF UNSURE DOVECOT_MASTER_PASS= +# Let's Encrypt registration contact information +# Optional: Leave empty for none +# This value is only used on first order! +# Setting it at a later point will require the following steps: +# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset-tls/ +ACME_CONTACT= + EOF mkdir -p data/assets/ssl diff --git a/helper-scripts/backup_and_restore.sh b/helper-scripts/backup_and_restore.sh index 82002d7b..af15d40a 100755 --- a/helper-scripts/backup_and_restore.sh +++ b/helper-scripts/backup_and_restore.sh @@ -125,7 +125,7 @@ function backup() { docker run --name mailcow-backup --rm \ --network $(docker network ls -qf name=${CMPS_PRJ}_mailcow-network) \ -v $(docker volume ls -qf name=${CMPS_PRJ}_mysql-vol-1):/var/lib/mysql/:ro,z \ - --entrypoint= \ + -t --entrypoint= \ --sysctl net.ipv6.conf.all.disable_ipv6=1 \ -v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \ ${SQLIMAGE} /bin/sh -c "mariabackup --host mysql --user root --password ${DBROOT} --backup --rsync --target-dir=/backup_mariadb ; \ diff --git a/helper-scripts/docker-compose.override.yml.d/EXTERNAL_DNS/docker-compose.override.yml b/helper-scripts/docker-compose.override.yml.d/EXTERNAL_DNS/docker-compose.override.yml index 74763bf2..8b68783e 100644 --- a/helper-scripts/docker-compose.override.yml.d/EXTERNAL_DNS/docker-compose.override.yml +++ b/helper-scripts/docker-compose.override.yml.d/EXTERNAL_DNS/docker-compose.override.yml @@ -42,3 +42,7 @@ services: dockerapi-mailcow: dns: - my.resolvers.ip.addr + + ejabberd-mailcow: + dns: + - my.resolvers.ip.addr diff --git a/helper-scripts/nextcloud.sh b/helper-scripts/nextcloud.sh index 41434b7c..a208d8c4 100755 --- a/helper-scripts/nextcloud.sh +++ b/helper-scripts/nextcloud.sh @@ -72,11 +72,11 @@ elif [[ ${NC_UPDATE} == "y" ]]; then if ! grep -q 'installed: true' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then echo "Nextcloud seems not to be installed." exit 1 - elif ! grep -q 'version: 19\.' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then + elif ! grep -q 'version: 20\.' <<<$(docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings status"); then echo "Cannot upgrade to new major version, please update manually." exit 1 else - curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-19.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ + curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-20.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ && rm nextcloud.tar.bz2 \ && mkdir -p ./data/web/nextcloud/data \ @@ -97,7 +97,7 @@ elif [[ ${NC_INSTALL} == "y" ]]; then ADMIN_NC_PASS=$(> mailcow.conf echo "WATCHDOG_NOTIFY_BAN=y" >> mailcow.conf fi + elif [[ ${option} == "WATCHDOG_SUBJECT" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo "Adding new option \"${option}\" to mailcow.conf" + echo '# Subject for watchdog mails. Defaults to "Watchdog ALERT" followed by the error message.' >> mailcow.conf + echo "#WATCHDOG_SUBJECT=" >> mailcow.conf + fi elif [[ ${option} == "WATCHDOG_EXTERNAL_CHECKS" ]]; then if ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" @@ -426,6 +434,15 @@ for option in ${CONFIG_ARRAY[@]}; do if ! grep -q ${option} mailcow.conf; then echo "XMPP_HTTPS_PORT=5443" >> mailcow.conf fi + elif [[ ${option} == "ACME_CONTACT" ]]; then + if ! grep -q ${option} mailcow.conf; then + echo '# Lets Encrypt registration contact information' >> mailcow.conf + echo '# Optional: Leave empty for none' >> mailcow.conf + echo '# This value is only used on first order!' >> mailcow.conf + echo '# Setting it at a later point will require the following steps:' >> mailcow.conf + echo '# https://mailcow.github.io/mailcow-dockerized-docs/debug-reset-tls/' >> mailcow.conf + echo 'ACME_CONTACT=' >> mailcow.conf + fi elif ! grep -q ${option} mailcow.conf; then echo "Adding new option \"${option}\" to mailcow.conf" echo "${option}=n" >> mailcow.conf