diff --git a/.gitignore b/.gitignore
index e535c710..0653ab8d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,6 @@ data/conf/nginx/*.conf
data/conf/nginx/*.custom
data/conf/nginx/*.bak
data/conf/dovecot/extra.conf
+data/conf/rspamd/custom/*
+data/conf/portainer/
+docker-compose.override.yml
diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh
index c9a91dfa..37e150f6 100755
--- a/data/Dockerfiles/acme/docker-entrypoint.sh
+++ b/data/Dockerfiles/acme/docker-entrypoint.sh
@@ -37,7 +37,7 @@ mkdir -p ${ACME_BASE}/acme/private
restart_containers(){
for container in $*; do
log_f "Restarting ${container}..." no_nl
- C_REST_OUT=$(curl -X POST http://dockerapi:8080/containers/${container}/restart | jq -r '.msg')
+ C_REST_OUT=$(curl -X POST --insecure https://dockerapi/containers/${container}/restart | jq -r '.msg')
log_f "${C_REST_OUT}" no_date
done
}
@@ -125,7 +125,7 @@ else
fi
log_f "Waiting for database... "
-while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do
+while ! mysqladmin ping --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
sleep 2
done
log_f "Initializing, please wait... "
@@ -161,19 +161,19 @@ while true; do
fi
# Container ids may have changed
- CONTAINERS_RESTART=($(curl --silent http://dockerapi:8080/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow") or contains("postfix-mailcow") or contains("dovecot-mailcow")) | .id' | tr "\n" " "))
+ CONTAINERS_RESTART=($(curl --silent --insecure https://dockerapi/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow") or contains("postfix-mailcow") or contains("dovecot-mailcow")) | .id' | tr "\n" " "))
log_f "Waiting for domain table... " no_nl
while [[ -z ${DOMAIN_TABLE} ]]; do
curl --silent http://nginx/ >/dev/null 2>&1
- DOMAIN_TABLE=$(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
+ DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
[[ -z ${DOMAIN_TABLE} ]] && sleep 10
done
log_f "OK" no_date
while read domains; do
SQL_DOMAIN_ARR+=("${domains}")
- done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 UNION SELECT alias_domain FROM alias_domain" -Bs)
+ done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 UNION SELECT alias_domain FROM alias_domain" -Bs)
for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do
A_CONFIG=$(dig A autoconfig.${SQL_DOMAIN} +short | tail -n 1)
diff --git a/data/Dockerfiles/dockerapi/Dockerfile b/data/Dockerfiles/dockerapi/Dockerfile
index 78389c4f..9d0bdaec 100644
--- a/data/Dockerfiles/dockerapi/Dockerfile
+++ b/data/Dockerfiles/dockerapi/Dockerfile
@@ -1,8 +1,10 @@
-FROM python:2-alpine
+FROM alpine:3.8
LABEL maintainer "Andre Peters "
-RUN apk add -U --no-cache iptables ip6tables tzdata
-RUN pip install docker==3.0.1 flask flask-restful
+RUN apk add -U --no-cache python2 python-dev py-pip gcc musl-dev tzdata openssl-dev libffi-dev \
+ && pip2 install --upgrade docker==3.0.1 flask flask-restful pyOpenSSL \
+ && apk del python-dev py2-pip gcc
COPY server.py /
+
CMD ["python2", "-u", "/server.py"]
diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py
index e66cf238..17e5e0fd 100644
--- a/data/Dockerfiles/dockerapi/server.py
+++ b/data/Dockerfiles/dockerapi/server.py
@@ -3,12 +3,16 @@ from flask_restful import Resource, Api
from flask import jsonify
from flask import request
from threading import Thread
+from OpenSSL import crypto
import docker
+import uuid
import signal
import time
import os
import re
import sys
+import ssl
+import socket
docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
app = Flask(__name__)
@@ -93,22 +97,74 @@ class container_post(Resource):
return sieve_return.output
except Exception as e:
return jsonify(type='danger', msg=str(e))
+ # not in use...
+ elif request.json['cmd'] == 'mail_crypt_generate' and request.json['username'] and request.json['old_password'] and request.json['new_password']:
+ try:
+ for container in docker_client.containers.list(filters={"id": container_id}):
+ # create if missing
+ crypto_generate = container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm mailbox cryptokey generate -u '" + request.json['username'].replace("'", "'\\''") + "' -URf"], user='vmail')
+ if crypto_generate.exit_code == 0:
+ # open a shell, bind stdin and return socket
+ cryptokey_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='vmail')
+ # command to be piped to shell
+ cryptokey_cmd = "/usr/local/bin/doveadm mailbox cryptokey password -u '" + request.json['username'].replace("'", "'\\''") + "' -n '" + request.json['new_password'].replace("'", "'\\''") + "' -o '" + request.json['old_password'].replace("'", "'\\''") + "'\n"
+ # socket is .output
+ cryptokey_socket = cryptokey_shell.output;
+ try :
+ # send command utf-8 encoded
+ cryptokey_socket.sendall(cryptokey_cmd.encode('utf-8'))
+ # we won't send more data than this
+ cryptokey_socket.shutdown(socket.SHUT_WR)
+ except socket.error:
+ # exit on socket error
+ return jsonify(type='danger', msg=str('socket error'))
+ # read response
+ cryptokey_response = recv_socket_data(cryptokey_socket)
+ crypto_error = re.search('dcrypt_key_load_private.+failed.+error', cryptokey_response)
+ if crypto_error is not None:
+ return jsonify(type='danger', msg=str("dcrypt_key_load_private error"))
+ return jsonify(type='success', msg=str("key pair generated"))
+ else:
+ return jsonify(type='danger', msg=str(crypto_generate.output))
+ except Exception as e:
+ return jsonify(type='danger', msg=str(e))
+ elif request.json['cmd'] == 'maildir_cleanup' and request.json['maildir']:
+ try:
+ for container in docker_client.containers.list(filters={"id": container_id}):
+ sane_name = re.sub(r'\W+', '', request.json['maildir'])
+ maildir_cleanup = container.exec_run(["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request.json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"], user='vmail')
+ if maildir_cleanup.exit_code == 0:
+ return jsonify(type='success', msg=str("moved to garbage"))
+ else:
+ return jsonify(type='danger', msg=str(maildir_cleanup.output))
+ except Exception as e:
+ return jsonify(type='danger', msg=str(e))
elif request.json['cmd'] == 'worker_password' and request.json['raw']:
try:
for container in docker_client.containers.list(filters={"id": container_id}):
- hash = container.exec_run(["/bin/bash", "-c", "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null"], user='_rspamd')
- if hash.exit_code == 0:
- hash_stdout = str(hash.output)
- for line in hash_stdout.split("\n"):
- if '$2$' in line:
- hash = line.strip()
- f = open("/access.inc", "w")
- f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash.rstrip()) + '";\n')
- f.close()
- container.restart()
+ worker_shell = container.exec_run(["/bin/bash"], stdin=True, socket=True, user='_rspamd')
+ worker_cmd = "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "' 2> /dev/null\n"
+ worker_socket = worker_shell.output;
+ try :
+ worker_socket.sendall(worker_cmd.encode('utf-8'))
+ worker_socket.shutdown(socket.SHUT_WR)
+ except socket.error:
+ return jsonify(type='danger', msg=str('socket error'))
+ worker_response = recv_socket_data(worker_socket)
+ matched = False
+ for line in worker_response.split("\n"):
+ if '$2$' in line:
+ matched = True
+ hash = line.strip()
+ hash_out = re.search('\$2\$.+$', hash).group(0)
+ f = open("/access.inc", "w")
+ f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash_out.rstrip()) + '";\n')
+ f.close()
+ container.restart()
+ if matched:
return jsonify(type='success', msg='command completed successfully')
else:
- return jsonify(type='danger', msg='command did not complete, exit code was ' + int(hash.exit_code))
+ return jsonify(type='danger', msg='command did not complete')
except Exception as e:
return jsonify(type='danger', msg=str(e))
elif request.json['cmd'] == 'mailman_password' and request.json['email'] and request.json['passwd']:
@@ -137,11 +193,62 @@ class GracefulKiller:
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
- def exit_gracefully(self,signum, frame):
+ def exit_gracefully(self, signum, frame):
self.kill_now = True
def startFlaskAPI():
- app.run(debug=False, host='0.0.0.0', port=8080, threaded=True)
+ create_self_signed_cert()
+ try:
+ ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ ctx.check_hostname = False
+ ctx.load_cert_chain(certfile='/cert.pem', keyfile='/key.pem')
+ except:
+ print "Cannot initialize TLS, retrying in 5s..."
+ time.sleep(5)
+ app.run(debug=False, host='0.0.0.0', port=443, threaded=True, ssl_context=ctx)
+
+def recv_socket_data(c_socket, timeout=10):
+ c_socket.setblocking(0)
+ total_data=[];
+ data='';
+ begin=time.time()
+ while True:
+ if total_data and time.time()-begin > timeout:
+ break
+ elif time.time()-begin > timeout*2:
+ break
+ try:
+ data = c_socket.recv(8192)
+ if data:
+ total_data.append(data)
+ #change the beginning time for measurement
+ begin=time.time()
+ else:
+ #sleep for sometime to indicate a gap
+ time.sleep(0.1)
+ break
+ except:
+ pass
+ return ''.join(total_data)
+
+def create_self_signed_cert():
+ pkey = crypto.PKey()
+ pkey.generate_key(crypto.TYPE_RSA, 2048)
+ cert = crypto.X509()
+ cert.get_subject().O = "mailcow"
+ cert.get_subject().CN = "dockerapi"
+ cert.set_serial_number(int(uuid.uuid4()))
+ cert.gmtime_adj_notBefore(0)
+ cert.gmtime_adj_notAfter(10*365*24*60*60)
+ cert.set_issuer(cert.get_subject())
+ cert.set_pubkey(pkey)
+ cert.sign(pkey, 'sha512')
+ cert = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
+ pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
+ with os.fdopen(os.open('/cert.pem', os.O_WRONLY | os.O_CREAT, 0o644), 'w') as handle:
+ handle.write(cert)
+ with os.fdopen(os.open('/key.pem', os.O_WRONLY | os.O_CREAT, 0o600), 'w') as handle:
+ handle.write(pkey)
api.add_resource(containers_get, '/containers/json')
api.add_resource(container_get, '/containers//json')
diff --git a/data/Dockerfiles/dovecot/Dockerfile b/data/Dockerfiles/dovecot/Dockerfile
index 03323914..fdfa84f5 100644
--- a/data/Dockerfiles/dovecot/Dockerfile
+++ b/data/Dockerfiles/dovecot/Dockerfile
@@ -14,6 +14,7 @@ RUN apt-get update && apt-get -y --no-install-recommends install \
cpanminus \
curl \
default-libmysqlclient-dev \
+ dnsutils \
libjson-webtoken-perl \
libcgi-pm-perl \
libcrypt-openssl-rsa-perl \
@@ -88,10 +89,11 @@ RUN curl https://www.dovecot.org/releases/2.3/dovecot-$DOVECOT_VERSION.tar.gz |
&& rm -rf dovecot-2.3-pigeonhole-$PIGEONHOLE_VERSION
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/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync
-RUN echo '* * * * * root /usr/local/bin/trim_logs.sh >> /dev/stdout 2>&1' > /etc/cron.d/trim_logs
-
+RUN echo '* * * * * root /usr/local/bin/imapsync_cron.pl' > /etc/cron.d/imapsync
+RUN echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/dovecot-sync
+RUN echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/stdout 2>&1' > /etc/cron.d/trim_logs
+RUN echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/stdout 2>&1' > /etc/cron.d/maildir_gc
+RUN echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/stdout 2>&1' > /etc/cron.d/sa-rules
COPY trim_logs.sh /usr/local/bin/trim_logs.sh
COPY syslog-ng.conf /etc/syslog-ng/syslog-ng.conf
COPY imapsync /usr/local/bin/imapsync
@@ -101,6 +103,8 @@ COPY report-spam.sieve /usr/local/lib/dovecot/sieve/report-spam.sieve
COPY report-ham.sieve /usr/local/lib/dovecot/sieve/report-ham.sieve
COPY rspamd-pipe-ham /usr/local/lib/dovecot/sieve/rspamd-pipe-ham
COPY rspamd-pipe-spam /usr/local/lib/dovecot/sieve/rspamd-pipe-spam
+COPY sa-rules.sh /usr/local/bin/sa-rules.sh
+COPY maildir_gc.sh /usr/local/bin/maildir_gc.sh
COPY docker-entrypoint.sh /
COPY supervisord.conf /etc/supervisor/supervisord.conf
@@ -109,7 +113,9 @@ RUN chmod +x /usr/local/lib/dovecot/sieve/rspamd-pipe-ham \
/usr/local/bin/imapsync_cron.pl \
/usr/local/bin/postlogin.sh \
/usr/local/bin/imapsync \
- /usr/local/bin/trim_logs.sh
+ /usr/local/bin/trim_logs.sh \
+ /usr/local/bin/sa-rules.sh \
+ /usr/local/bin/maildir_gc.sh
RUN groupadd -g 5000 vmail \
&& groupadd -g 401 dovecot \
diff --git a/data/Dockerfiles/dovecot/docker-entrypoint.sh b/data/Dockerfiles/dovecot/docker-entrypoint.sh
index 70ffb701..db7ecb8b 100755
--- a/data/Dockerfiles/dovecot/docker-entrypoint.sh
+++ b/data/Dockerfiles/dovecot/docker-entrypoint.sh
@@ -2,7 +2,7 @@
set -e
# Wait for MySQL to warm-up
-while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do
+while ! mysqladmin ping --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..."
sleep 2
done
@@ -15,6 +15,7 @@ sed -i "s/LOG_LINES/${LOG_LINES}/g" /usr/local/bin/trim_logs.sh
# Create missing directories
[[ ! -d /usr/local/etc/dovecot/sql/ ]] && mkdir -p /usr/local/etc/dovecot/sql/
+[[ ! -d /var/vmail/_garbage ]] && mkdir -p /var/vmail/_garbage
[[ ! -d /var/vmail/sieve ]] && mkdir -p /var/vmail/sieve
[[ ! -d /etc/sogo ]] && mkdir -p /etc/sogo
@@ -23,7 +24,7 @@ DBPASS=$(echo ${DBPASS} | sed 's/"/\\"/g')
# Create quota dict for Dovecot
cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-quota.conf
-connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
+connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map {
pattern = priv/quota/storage
table = quota2
@@ -40,7 +41,7 @@ EOF
# Create dict used for sieve pre and postfilters
cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_before.conf
-connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
+connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map {
pattern = priv/sieve/name/\$script_name
table = sieve_before
@@ -62,7 +63,7 @@ map {
EOF
cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-sieve_after.conf
-connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
+connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
map {
pattern = priv/sieve/name/\$script_name
table = sieve_after
@@ -87,7 +88,7 @@ EOF
# Create userdb dict for Dovecot
cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf
driver = mysql
-connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
+connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
user_query = SELECT CONCAT('maildir:/var/vmail/',maildir) AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1'
iterate_query = SELECT username FROM mailbox WHERE active='1';
EOF
@@ -95,7 +96,7 @@ EOF
# Create pass dict for Dovecot
cat < /usr/local/etc/dovecot/sql/dovecot-dict-sql-passdb.conf
driver = mysql
-connect = "host=mysql dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
+connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS} ssl_verify_server_cert=no ssl_ca=/etc/ssl/certs/ca-certificates.crt"
default_pass_scheme = SSHA256
password_query = SELECT password FROM mailbox WHERE username = '%u' AND domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') AND JSON_EXTRACT(attributes, '$.force_pw_update') NOT LIKE '%%1%%'
EOF
@@ -106,12 +107,14 @@ cat /usr/local/etc/dovecot/sieve_after > /var/vmail/sieve/global.sieve
# Check permissions of vmail directory.
# Do not do this every start-up, it may take a very long time. So we use a stat check here.
if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail ; fi
+if [[ $(stat -c %U /var/vmail/_garbage) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail/_garbage ; fi
# Create random master for SOGo sieve features
RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1)
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1)
-echo ${RAND_USER}:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /usr/local/etc/dovecot/dovecot-master.passwd
-echo ${RAND_USER}:${RAND_PASS} > /etc/sogo/sieve.creds
+
+echo ${RAND_USER}@mailcow.local:$(doveadm pw -s SHA1 -p ${RAND_PASS}) > /usr/local/etc/dovecot/dovecot-master.passwd
+echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds
# 401 is user dovecot
if [[ ! -f /mail_crypt/ecprivkey.pem || ! -f /mail_crypt/ecpubkey.pem ]]; then
@@ -138,7 +141,10 @@ touch /etc/crontab /etc/cron.*/*
# Clean stopped imapsync jobs
rm -f /tmp/imapsync_busy.lock
-IMAPSYNC_TABLE=$(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
-[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
+IMAPSYNC_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
+[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
+
+# Collect SA rules once now
+/usr/local/bin/sa-rules.sh
exec "$@"
diff --git a/data/Dockerfiles/dovecot/maildir_gc.sh b/data/Dockerfiles/dovecot/maildir_gc.sh
new file mode 100755
index 00000000..24c1e461
--- /dev/null
+++ b/data/Dockerfiles/dovecot/maildir_gc.sh
@@ -0,0 +1,2 @@
+#/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/postlogin.sh b/data/Dockerfiles/dovecot/postlogin.sh
index 343910ff..01a45f31 100755
--- a/data/Dockerfiles/dovecot/postlogin.sh
+++ b/data/Dockerfiles/dovecot/postlogin.sh
@@ -1,4 +1,3 @@
#!/bin/sh
-
export MASTER_USER=$USER
exec "$@"
diff --git a/data/Dockerfiles/dovecot/rspamd-pipe-ham b/data/Dockerfiles/dovecot/rspamd-pipe-ham
index 9d961be0..9b26817c 100755
--- a/data/Dockerfiles/dovecot/rspamd-pipe-ham
+++ b/data/Dockerfiles/dovecot/rspamd-pipe-ham
@@ -3,7 +3,7 @@ FILE=/tmp/mail$$
cat > $FILE
trap "/bin/rm -f $FILE" 0 1 2 3 13 15
-cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/learnham
-cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/fuzzyadd
+cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnham
+cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd
exit 0
diff --git a/data/Dockerfiles/dovecot/rspamd-pipe-spam b/data/Dockerfiles/dovecot/rspamd-pipe-spam
index 3b9e3497..d06aa919 100755
--- a/data/Dockerfiles/dovecot/rspamd-pipe-spam
+++ b/data/Dockerfiles/dovecot/rspamd-pipe-spam
@@ -3,7 +3,7 @@ FILE=/tmp/mail$$
cat > $FILE
trap "/bin/rm -f $FILE" 0 1 2 3 13 15
-cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/learnspam
-cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/fuzzyadd
+cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnspam
+cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd
exit 0
diff --git a/data/Dockerfiles/dovecot/sa-rules.sh b/data/Dockerfiles/dovecot/sa-rules.sh
new file mode 100755
index 00000000..0cea240c
--- /dev/null
+++ b/data/Dockerfiles/dovecot/sa-rules.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+[[ ! -d /tmp/sa-rules-heinlein ]] && mkdir -p /tmp/sa-rules-heinlein
+if [[ ! -f /etc/rspamd/custom/sa-rules-heinlein ]]; then
+ HASH_SA_RULES=0
+else
+ HASH_SA_RULES=$(cat /etc/rspamd/custom/sa-rules-heinlein | md5sum | cut -d' ' -f1)
+fi
+
+curl --connect-timeout 15 --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.tar.gz
+if [[ -f /tmp/sa-rules.tar.gz ]]; then
+ tar xfvz /tmp/sa-rules.tar.gz -C /tmp/sa-rules-heinlein
+ # create complete list of rules in a single file
+ cat /tmp/sa-rules-heinlein/*cf > /etc/rspamd/custom/sa-rules-heinlein
+ # Only restart rspamd-mailcow when rules changed
+ if [[ $(cat /etc/rspamd/custom/sa-rules-heinlein | md5sum | cut -d' ' -f1) != ${HASH_SA_RULES} ]]; then
+ CONTAINER_NAME=rspamd-mailcow
+ CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | \
+ jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | \
+ jq -rc "select( .name | tostring | contains(\"${CONTAINER_NAME}\")) | .id")
+ if [[ ! -z ${CONTAINER_ID} ]]; then
+ curl --silent --insecure -XPOST --connect-timeout 15 --max-time 120 https://dockerapi/containers/${CONTAINER_ID}/restart
+ fi
+ fi
+fi
+rm -rf /tmp/sa-rules-heinlein /tmp/sa-rules.tar.gz
diff --git a/data/Dockerfiles/dovecot/trim_logs.sh b/data/Dockerfiles/dovecot/trim_logs.sh
index b8da9740..8489f27a 100755
--- a/data/Dockerfiles/dovecot/trim_logs.sh
+++ b/data/Dockerfiles/dovecot/trim_logs.sh
@@ -1,8 +1,7 @@
#!/bin/bash
-
-redis-cli -h redis LTRIM ACME_LOG 0 LOG_LINES
-redis-cli -h redis LTRIM POSTFIX_MAILLOG 0 LOG_LINES
-redis-cli -h redis LTRIM DOVECOT_MAILLOG 0 LOG_LINES
-redis-cli -h redis LTRIM SOGO_LOG 0 LOG_LINES
-redis-cli -h redis LTRIM NETFILTER_LOG 0 LOG_LINES
-redis-cli -h redis LTRIM AUTODISCOVER_LOG 0 LOG_LINES
+/usr/bin/redis-cli -h redis LTRIM ACME_LOG 0 LOG_LINES
+/usr/bin/redis-cli -h redis LTRIM POSTFIX_MAILLOG 0 LOG_LINES
+/usr/bin/redis-cli -h redis LTRIM DOVECOT_MAILLOG 0 LOG_LINES
+/usr/bin/redis-cli -h redis LTRIM SOGO_LOG 0 LOG_LINES
+/usr/bin/redis-cli -h redis LTRIM NETFILTER_LOG 0 LOG_LINES
+/usr/bin/redis-cli -h redis LTRIM AUTODISCOVER_LOG 0 LOG_LINES
diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile
index 3acfd09f..0ed81896 100644
--- a/data/Dockerfiles/phpfpm/Dockerfile
+++ b/data/Dockerfiles/phpfpm/Dockerfile
@@ -1,11 +1,11 @@
FROM php:7.2-fpm-alpine3.7
LABEL maintainer "Andre Peters "
-ENV APCU_PECL 5.1.11
+ENV APCU_PECL 5.1.12
ENV IMAGICK_PECL 3.4.3
ENV MAILPARSE_PECL 3.0.2
ENV MEMCACHED_PECL 3.0.4
-ENV REDIS_PECL 4.0.2
+ENV REDIS_PECL 4.1.1
RUN apk add -U --no-cache autoconf \
bash \
diff --git a/data/Dockerfiles/phpfpm/docker-entrypoint.sh b/data/Dockerfiles/phpfpm/docker-entrypoint.sh
index 44d46f74..50c5275d 100755
--- a/data/Dockerfiles/phpfpm/docker-entrypoint.sh
+++ b/data/Dockerfiles/phpfpm/docker-entrypoint.sh
@@ -4,11 +4,13 @@ set -e
function array_by_comma { local IFS=","; echo "$*"; }
# Wait for containers
-while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do
+while ! mysqladmin ping --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
+ echo "Waiting for SQL..."
sleep 2
done
until [[ $(redis-cli -h redis-mailcow PING) == "PONG" ]]; do
+ echo "Waiting for Redis..."
sleep 2
done
@@ -18,11 +20,11 @@ redis-cli -h redis-mailcow DEL DOMAIN_MAP
while read line
do
DOMAIN_ARR+=("$line")
-done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
+done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
while read line
do
DOMAIN_ARR+=("$line")
-done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
+done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
if [[ ! -z ${DOMAIN_ARR} ]]; then
for domain in "${DOMAIN_ARR[@]}"; do
@@ -48,7 +50,7 @@ if [[ ${API_ALLOW_FROM} != "invalid" ]] && \
done
VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]})
if [[ ! -z ${VALIDATED_IPS} ]]; then
- mysql --host mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
+ mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
INSERT INTO api (username, api_key, active, allow_from)
SELECT username, "${API_KEY}", '1', "${VALIDATED_IPS}" FROM admin WHERE superadmin='1' AND active='1'
ON DUPLICATE KEY UPDATE active = '1', allow_from = "${VALIDATED_IPS}", api_key = "${API_KEY}";
diff --git a/data/Dockerfiles/postfix/postfix.sh b/data/Dockerfiles/postfix/postfix.sh
index 28e51ccd..628322d8 100755
--- a/data/Dockerfiles/postfix/postfix.sh
+++ b/data/Dockerfiles/postfix/postfix.sh
@@ -14,7 +14,7 @@ newaliases;
cat < /opt/postfix/conf/sql/mysql_relay_recipient_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT DISTINCT
CASE WHEN '%d' IN (
@@ -29,10 +29,18 @@ query = SELECT DISTINCT
END AS result;
EOF
+cat < /opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf
+user = ${DBUSER}
+password = ${DBPASS}
+hosts = unix:/var/run/mysqld/mysqld.sock
+dbname = ${DBNAME}
+query = SELECT CONCAT(policy, ' ', parameters) AS tls_policy FROM tls_policy_override WHERE active = '1' AND dest = '%s'
+EOF
+
cat < /opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT IF(EXISTS(
SELECT 'TLS_ACTIVE' FROM alias
@@ -49,7 +57,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps
FROM (
@@ -80,7 +88,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_sasl_passwd_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts
WHERE id IN (
@@ -96,7 +104,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT goto FROM alias, alias_domain
WHERE alias_domain.alias_domain = '%d'
@@ -107,7 +115,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT username FROM mailbox, alias_domain
WHERE alias_domain.alias_domain = '%d'
@@ -119,7 +127,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_virtual_alias_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT goto FROM alias
WHERE address='%s'
@@ -129,7 +137,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_recipient_bcc_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT bcc_dest FROM bcc_maps
WHERE local_dest='%s'
@@ -140,7 +148,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_sender_bcc_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT bcc_dest FROM bcc_maps
WHERE local_dest='%s'
@@ -151,7 +159,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_recipient_canonical_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT new_dest FROM recipient_maps
WHERE old_dest='%s'
@@ -161,7 +169,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_virtual_domains_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT alias_domain from alias_domain WHERE alias_domain='%s' AND active='1'
UNION
@@ -174,7 +182,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT maildir FROM mailbox WHERE username='%s' AND active = '1'
EOF
@@ -182,7 +190,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_virtual_relay_domain_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT domain FROM domain WHERE domain='%s' AND backupmx = '1' AND active = '1'
EOF
@@ -190,7 +198,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_virtual_sender_acl.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
# First select queries domain and alias_domain to determine if domains are active.
query = SELECT goto FROM alias
@@ -231,7 +239,7 @@ EOF
cat < /opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf
user = ${DBUSER}
password = ${DBPASS}
-hosts = mysql
+hosts = unix:/var/run/mysqld/mysqld.sock
dbname = ${DBNAME}
query = SELECT goto FROM spamalias
WHERE address='%s'
diff --git a/data/Dockerfiles/postfix/rspamd-pipe-ham b/data/Dockerfiles/postfix/rspamd-pipe-ham
index 9d961be0..9b26817c 100755
--- a/data/Dockerfiles/postfix/rspamd-pipe-ham
+++ b/data/Dockerfiles/postfix/rspamd-pipe-ham
@@ -3,7 +3,7 @@ FILE=/tmp/mail$$
cat > $FILE
trap "/bin/rm -f $FILE" 0 1 2 3 13 15
-cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/learnham
-cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/fuzzyadd
+cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnham
+cat ${FILE} | /usr/bin/curl -H "Flag: 13" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd
exit 0
diff --git a/data/Dockerfiles/postfix/rspamd-pipe-spam b/data/Dockerfiles/postfix/rspamd-pipe-spam
index 3b9e3497..d06aa919 100755
--- a/data/Dockerfiles/postfix/rspamd-pipe-spam
+++ b/data/Dockerfiles/postfix/rspamd-pipe-spam
@@ -3,7 +3,7 @@ FILE=/tmp/mail$$
cat > $FILE
trap "/bin/rm -f $FILE" 0 1 2 3 13 15
-cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/learnspam
-cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/fuzzyadd
+cat ${FILE} | /usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/learnspam
+cat ${FILE} | /usr/bin/curl -H "Flag: 11" -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/fuzzyadd
exit 0
diff --git a/data/Dockerfiles/rspamd/Dockerfile b/data/Dockerfiles/rspamd/Dockerfile
index 67b8aa1c..daf9760b 100644
--- a/data/Dockerfiles/rspamd/Dockerfile
+++ b/data/Dockerfiles/rspamd/Dockerfile
@@ -20,7 +20,7 @@ RUN apt-get update && apt-get install -y \
&& mkdir -p /run/rspamd \
&& chown _rspamd:_rspamd /run/rspamd
-COPY settings.conf /etc/rspamd/modules.d/settings.conf
+COPY settings.conf /etc/rspamd/settings.conf
COPY docker-entrypoint.sh /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
diff --git a/data/Dockerfiles/rspamd/docker-entrypoint.sh b/data/Dockerfiles/rspamd/docker-entrypoint.sh
index afb03bb6..6288550d 100755
--- a/data/Dockerfiles/rspamd/docker-entrypoint.sh
+++ b/data/Dockerfiles/rspamd/docker-entrypoint.sh
@@ -1,6 +1,9 @@
#!/bin/bash
-chown -R _rspamd:_rspamd /var/lib/rspamd
+chown -R _rspamd:_rspamd /var/lib/rspamd /etc/rspamd/local.d /etc/rspamd/override.d /etc/rspamd/custom
+chmod 755 /var/lib/rspamd
[[ ! -f /etc/rspamd/override.d/worker-controller-password.inc ]] && echo '# Placeholder' > /etc/rspamd/override.d/worker-controller-password.inc
+chown _rspamd:_rspamd /etc/rspamd/override.d/worker-controller-password.inc
+[[ ! -f /etc/rspamd/custom/sa-rules-heinlein ]] && echo '# to be auto-filled by dovecot-mailcow' > /etc/rspamd/custom/sa-rules-heinlein
exec "$@"
diff --git a/data/Dockerfiles/rspamd/lua_util.lua b/data/Dockerfiles/rspamd/lua_util.lua
deleted file mode 100644
index a9abd901..00000000
--- a/data/Dockerfiles/rspamd/lua_util.lua
+++ /dev/null
@@ -1,152 +0,0 @@
-local exports = {}
-local lpeg = require 'lpeg'
-
-local split_grammar = {}
-local function rspamd_str_split(s, sep)
- local gr = split_grammar[sep]
-
- if not gr then
- local _sep = lpeg.P(sep)
- local elem = lpeg.C((1 - _sep)^0)
- local p = lpeg.Ct(elem * (_sep * elem)^0)
- gr = p
- split_grammar[sep] = gr
- end
-
- return gr:match(s)
-end
-
-exports.rspamd_str_split = rspamd_str_split
-
-local space = lpeg.S' \t\n\v\f\r'
-local nospace = 1 - space
-local ptrim = space^0 * lpeg.C((space^0 * nospace^1)^0)
-local match = lpeg.match
-exports.rspamd_str_trim = function(s)
- return match(ptrim, s)
-end
-
--- Robert Jay Gould http://lua-users.org/wiki/SimpleRound
-exports.round = function(num, numDecimalPlaces)
- local mult = 10^(numDecimalPlaces or 0)
- return math.floor(num * mult) / mult
-end
-
-exports.template = function(tmpl, keys)
- local var_lit = lpeg.P { lpeg.R("az") + lpeg.R("AZ") + lpeg.R("09") + "_" }
- local var = lpeg.P { (lpeg.P("$") / "") * ((var_lit^1) / keys) }
- local var_braced = lpeg.P { (lpeg.P("${") / "") * ((var_lit^1) / keys) * (lpeg.P("}") / "") }
-
- local template_grammar = lpeg.Cs((var + var_braced + 1)^0)
-
- return lpeg.match(template_grammar, tmpl)
-end
-
-exports.remove_email_aliases = function(email_addr)
- local function check_gmail_user(addr)
- -- Remove all points
- local no_dots_user = string.gsub(addr.user, '%.', '')
- local cap, pluses = string.match(no_dots_user, '^([^%+][^%+]*)(%+.*)$')
- if cap then
- return cap, rspamd_str_split(pluses, '+'), nil
- elseif no_dots_user ~= addr.user then
- return no_dots_user,{},nil
- end
-
- return nil
- end
-
- local function check_address(addr)
- if addr.user then
- local cap, pluses = string.match(addr.user, '^([^%+][^%+]*)(%+.*)$')
- if cap then
- return cap, rspamd_str_split(pluses, '+'), nil
- end
- end
-
- return nil
- end
-
- local function set_addr(addr, new_user, new_domain)
- if new_user then
- addr.user = new_user
- end
- if new_domain then
- addr.domain = new_domain
- end
-
- if addr.domain then
- addr.addr = string.format('%s@%s', addr.user, addr.domain)
- else
- addr.addr = string.format('%s@', addr.user)
- end
-
- if addr.name and #addr.name > 0 then
- addr.raw = string.format('"%s" <%s>', addr.name, addr.addr)
- else
- addr.raw = string.format('<%s>', addr.addr)
- end
- end
-
- local function check_gmail(addr)
- local nu, tags, nd = check_gmail_user(addr)
-
- if nu then
- return nu, tags, nd
- end
-
- return nil
- end
-
- local function check_googlemail(addr)
- local nd = 'gmail.com'
- local nu, tags = check_gmail_user(addr)
-
- if nu then
- return nu, tags, nd
- end
-
- return nil, nil, nd
- end
-
- local specific_domains = {
- ['gmail.com'] = check_gmail,
- ['googlemail.com'] = check_googlemail,
- }
-
- if email_addr then
- if email_addr.domain and specific_domains[email_addr.domain] then
- local nu, tags, nd = specific_domains[email_addr.domain](email_addr)
- if nu or nd then
- set_addr(email_addr, nu, nd)
-
- return nu, tags
- end
- else
- local nu, tags, nd = check_address(email_addr)
- if nu or nd then
- set_addr(email_addr, nu, nd)
-
- return nu, tags
- end
- end
-
- return nil
- end
-end
-
-exports.is_rspamc_or_controller = function(task)
- local ua = task:get_request_header('User-Agent') or ''
- local pwd = task:get_request_header('Password')
- local is_rspamc = false
- if tostring(ua) == 'rspamc' or pwd then is_rspamc = true end
-
- return is_rspamc
-end
-
-local unpack_function = table.unpack or unpack
-exports.unpack = function(t)
- return unpack_function(t)
-end
-
-return exports
diff --git a/data/Dockerfiles/rspamd/ratelimit.lua b/data/Dockerfiles/rspamd/ratelimit.lua
deleted file mode 100644
index 839ec5c6..00000000
--- a/data/Dockerfiles/rspamd/ratelimit.lua
+++ /dev/null
@@ -1,674 +0,0 @@
---[[
-Copyright (c) 2011-2017, Vsevolod Stakhov
-Copyright (c) 2016-2017, Andrew Lewis
-
-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 implements ratelimits using redis
-
-local E = {}
-local N = 'ratelimit'
-local redis_params
--- Senders that are considered as bounce
-local settings = {
- bounce_senders = { 'postmaster', 'mailer-daemon', '', 'null', 'fetchmail-daemon', 'mdaemon' },
--- Do not check ratelimits for these recipients
- whitelisted_rcpts = { 'postmaster', 'mailer-daemon' },
- prefix = 'RL',
- ham_factor_rate = 1.01,
- spam_factor_rate = 0.99,
- ham_factor_burst = 1.02,
- spam_factor_burst = 0.98,
- max_rate_mult = 5,
- max_bucket_mult = 10,
- expire = 60 * 60 * 24 * 2, -- 2 days by default
- limits = {},
- allow_local = false,
-}
-
--- Checks bucket, updating it if needed
--- KEYS[1] - prefix to update, e.g. RL__
--- KEYS[2] - current time in milliseconds
--- KEYS[3] - bucket leak rate (messages per millisecond)
--- KEYS[4] - bucket burst
--- KEYS[5] - expire for a bucket
--- return 1 if message should be ratelimited and 0 if not
--- Redis keys used:
--- l - last hit
--- b - current burst
--- dr - current dynamic rate multiplier (*10000)
--- db - current dynamic burst multiplier (*10000)
-local bucket_check_script = [[
- local last = redis.call('HGET', KEYS[1], 'l')
- local now = tonumber(KEYS[2])
- local dynr, dynb = 0, 0
- if not last then
- -- New bucket
- redis.call('HSET', KEYS[1], 'l', KEYS[2])
- redis.call('HSET', KEYS[1], 'b', '0')
- redis.call('HSET', KEYS[1], 'dr', '10000')
- redis.call('HSET', KEYS[1], 'db', '10000')
- redis.call('EXPIRE', KEYS[1], KEYS[5])
- return {0, 0, 1, 1}
- end
-
- last = tonumber(last)
- local burst = tonumber(redis.call('HGET', KEYS[1], 'b'))
- -- Perform leak
- if burst > 0 then
- if last < tonumber(KEYS[2]) then
- local rate = tonumber(KEYS[3])
- dynr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000.0
- rate = rate * dynr
- local leaked = ((now - last) * rate)
- burst = burst - leaked
- redis.call('HINCRBYFLOAT', KEYS[1], 'b', -(leaked))
- end
- else
- burst = 0
- redis.call('HSET', KEYS[1], 'b', '0')
- end
-
- dynb = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000.0
-
- if (burst + 1) * dynb > tonumber(KEYS[4]) then
- return {1, tostring(burst), tostring(dynr), tostring(dynb)}
- end
-
- return {0, tostring(burst), tostring(dynr), tostring(dynb)}
-]]
-local bucket_check_id
-
-
--- Updates a bucket
--- KEYS[1] - prefix to update, e.g. RL__
--- KEYS[2] - current time in milliseconds
--- KEYS[3] - dynamic rate multiplier
--- KEYS[4] - dynamic burst multiplier
--- KEYS[5] - max dyn rate (min: 1/x)
--- KEYS[6] - max burst rate (min: 1/x)
--- KEYS[7] - expire for a bucket
--- Redis keys used:
--- l - last hit
--- b - current burst
--- dr - current dynamic rate multiplier
--- db - current dynamic burst multiplier
-local bucket_update_script = [[
- local last = redis.call('HGET', KEYS[1], 'l')
- local now = tonumber(KEYS[2])
- if not last then
- -- New bucket
- redis.call('HSET', KEYS[1], 'l', KEYS[2])
- redis.call('HSET', KEYS[1], 'b', '1')
- redis.call('HSET', KEYS[1], 'dr', '10000')
- redis.call('HSET', KEYS[1], 'db', '10000')
- redis.call('EXPIRE', KEYS[1], KEYS[7])
- return {1, 1, 1}
- end
-
- local burst = tonumber(redis.call('HGET', KEYS[1], 'b'))
- local db = tonumber(redis.call('HGET', KEYS[1], 'db')) / 10000
- local dr = tonumber(redis.call('HGET', KEYS[1], 'dr')) / 10000
-
- if dr < tonumber(KEYS[5]) and dr > 1.0 / tonumber(KEYS[5]) then
- dr = dr * tonumber(KEYS[3])
- redis.call('HSET', KEYS[1], 'dr', tostring(math.floor(dr * 10000)))
- end
-
- if db < tonumber(KEYS[6]) and db > 1.0 / tonumber(KEYS[6]) then
- db = db * tonumber(KEYS[4])
- redis.call('HSET', KEYS[1], 'db', tostring(math.floor(db * 10000)))
- end
-
- redis.call('HINCRBYFLOAT', KEYS[1], 'b', 1)
- redis.call('HSET', KEYS[1], 'l', KEYS[2])
- redis.call('EXPIRE', KEYS[1], KEYS[7])
-
- return {tostring(burst), tostring(dr), tostring(db)}
-]]
-local bucket_update_id
-
--- message_func(task, limit_type, prefix, bucket)
-local message_func = function(_, limit_type, _, _)
- return string.format('Ratelimit "%s" exceeded', limit_type)
-end
-
-local rspamd_logger = require "rspamd_logger"
-local rspamd_util = require "rspamd_util"
-local rspamd_lua_utils = require "lua_util"
-local lua_redis = require "lua_redis"
-local fun = require "fun"
-local lua_maps = require "lua_maps"
-local lua_util = require "lua_util"
-local rspamd_hash = require "rspamd_cryptobox_hash"
-
-
-local function load_scripts(cfg, ev_base)
- bucket_check_id = lua_redis.add_redis_script(bucket_check_script, redis_params)
- bucket_update_id = lua_redis.add_redis_script(bucket_update_script, redis_params)
-end
-
-local limit_parser
-local function parse_string_limit(lim, no_error)
- local function parse_time_suffix(s)
- if s == 's' then
- return 1
- elseif s == 'm' then
- return 60
- elseif s == 'h' then
- return 3600
- elseif s == 'd' then
- return 86400
- end
- end
- local function parse_num_suffix(s)
- if s == '' then
- return 1
- elseif s == 'k' then
- return 1000
- elseif s == 'm' then
- return 1000000
- elseif s == 'g' then
- return 1000000000
- end
- end
- local lpeg = require "lpeg"
-
- if not limit_parser then
- local digit = lpeg.R("09")
- limit_parser = {}
- limit_parser.integer =
- (lpeg.S("+-") ^ -1) *
- (digit ^ 1)
- limit_parser.fractional =
- (lpeg.P(".") ) *
- (digit ^ 1)
- limit_parser.number =
- (limit_parser.integer *
- (limit_parser.fractional ^ -1)) +
- (lpeg.S("+-") * limit_parser.fractional)
- limit_parser.time = lpeg.Cf(lpeg.Cc(1) *
- (limit_parser.number / tonumber) *
- ((lpeg.S("smhd") / parse_time_suffix) ^ -1),
- function (acc, val) return acc * val end)
- limit_parser.suffixed_number = lpeg.Cf(lpeg.Cc(1) *
- (limit_parser.number / tonumber) *
- ((lpeg.S("kmg") / parse_num_suffix) ^ -1),
- function (acc, val) return acc * val end)
- limit_parser.limit = lpeg.Ct(limit_parser.suffixed_number *
- (lpeg.S(" ") ^ 0) * lpeg.S("/") * (lpeg.S(" ") ^ 0) *
- limit_parser.time)
- end
- local t = lpeg.match(limit_parser.limit, lim)
-
- if t and t[1] and t[2] and t[2] ~= 0 then
- return t[2], t[1]
- end
-
- if not no_error then
- rspamd_logger.errx(rspamd_config, 'bad limit: %s', lim)
- end
-
- return nil
-end
-
-local function parse_limit(name, data)
- local buckets = {}
- if type(data) == 'table' then
- -- 3 cases here:
- -- * old limit in format [burst, rate]
- -- * vector of strings in Andrew's string format
- -- * proper bucket table
- if #data == 2 and tonumber(data[1]) and tonumber(data[2]) then
- -- Old style ratelimit
- rspamd_logger.warnx(rspamd_config, 'old style ratelimit for %s', name)
- if tonumber(data[1]) > 0 and tonumber(data[2]) > 0 then
- table.insert(buckets, {
- burst = data[1],
- rate = data[2]
- })
- elseif data[1] ~= 0 then
- rspamd_logger.warnx(rspamd_config, 'invalid numbers for %s', name)
- else
- rspamd_logger.infox(rspamd_config, 'disable limit %s, burst is zero', name)
- end
- else
- -- Recursively map parse_limit and flatten the list
- fun.each(function(l)
- -- Flatten list
- for _,b in ipairs(l) do table.insert(buckets, b) end
- end, fun.map(function(d) return parse_limit(d, name) end, data))
- end
- elseif type(data) == 'string' then
- local rep_rate, burst = parse_string_limit(data)
-
- if rep_rate and burst then
- table.insert(buckets, {
- burst = burst,
- rate = 1.0 / rep_rate -- reciprocal
- })
- end
- end
-
- -- Filter valid
- return fun.totable(fun.filter(function(val)
- return type(val.burst) == 'number' and type(val.rate) == 'number'
- end, buckets))
-end
-
---- Check whether this addr is bounce
-local function check_bounce(from)
- return fun.any(function(b) return b == from end, settings.bounce_senders)
-end
-
-local keywords = {
- ['ip'] = {
- ['get_value'] = function(task)
- local ip = task:get_ip()
- if ip and ip:is_valid() then return tostring(ip) end
- return nil
- end,
- },
- ['rip'] = {
- ['get_value'] = function(task)
- local ip = task:get_ip()
- if ip and ip:is_valid() and not ip:is_local() then return tostring(ip) end
- return nil
- end,
- },
- ['from'] = {
- ['get_value'] = function(task)
- local from = task:get_from(0)
- if ((from or E)[1] or E).addr then
- return string.lower(from[1]['addr'])
- end
- return nil
- end,
- },
- ['bounce'] = {
- ['get_value'] = function(task)
- local from = task:get_from(0)
- if not ((from or E)[1] or E).user then
- return '_'
- end
- if check_bounce(from[1]['user']) then return '_' else return nil end
- end,
- },
- ['asn'] = {
- ['get_value'] = function(task)
- local asn = task:get_mempool():get_variable('asn')
- if not asn then
- return nil
- else
- return asn
- end
- end,
- },
- ['user'] = {
- ['get_value'] = function(task)
- local auser = task:get_user()
- if not auser then
- return nil
- else
- return auser
- end
- end,
- },
- ['to'] = {
- ['get_value'] = function(task)
- return task:get_principal_recipient()
- end,
- },
-}
-
-local function gen_rate_key(task, rtype, bucket)
- local key_t = {tostring(lua_util.round(100000.0 / bucket.burst))}
- local key_keywords = lua_util.str_split(rtype, '_')
- local have_user = false
-
- for _, v in ipairs(key_keywords) do
- local ret
-
- if keywords[v] and type(keywords[v]['get_value']) == 'function' then
- ret = keywords[v]['get_value'](task)
- end
- if not ret then return nil end
- if v == 'user' then have_user = true end
- if type(ret) ~= 'string' then ret = tostring(ret) end
- table.insert(key_t, ret)
- end
-
- if have_user and not task:get_user() then
- return nil
- end
-
- return table.concat(key_t, ":")
-end
-
-local function make_prefix(redis_key, name, bucket)
- local hash_len = 24
- if hash_len > #redis_key then hash_len = #redis_key end
- local hash = settings.prefix ..
- string.sub(rspamd_hash.create(redis_key):base32(), 1, hash_len)
- -- Fill defaults
- if not bucket.spam_factor_rate then
- bucket.spam_factor_rate = settings.spam_factor_rate
- end
- if not bucket.ham_factor_rate then
- bucket.ham_factor_rate = settings.ham_factor_rate
- end
- if not bucket.spam_factor_burst then
- bucket.spam_factor_burst = settings.spam_factor_burst
- end
- if not bucket.ham_factor_burst then
- bucket.ham_factor_burst = settings.ham_factor_burst
- end
-
- return {
- bucket = bucket,
- name = name,
- hash = hash
- }
-end
-
-local function limit_to_prefixes(task, k, v, prefixes)
- local n = 0
- for _,bucket in ipairs(v) do
- local prefix = gen_rate_key(task, k, bucket)
-
- if prefix then
- prefixes[prefix] = make_prefix(prefix, k, bucket)
- n = n + 1
- end
- end
-
- return n
-end
-
-local function ratelimit_cb(task)
- if not settings.allow_local and
- rspamd_lua_utils.is_rspamc_or_controller(task) then return end
-
- -- Get initial task data
- local ip = task:get_from_ip()
- if ip and ip:is_valid() and settings.whitelisted_ip then
- if settings.whitelisted_ip:get_key(ip) then
- -- Do not check whitelisted ip
- rspamd_logger.infox(task, 'skip ratelimit for whitelisted IP')
- return
- end
- end
- -- Parse all rcpts
- local rcpts = task:get_recipients()
- local rcpts_user = {}
- if rcpts then
- fun.each(function(r)
- fun.each(function(type) table.insert(rcpts_user, r[type]) end, {'user', 'addr'})
- end, rcpts)
-
- if fun.any(function(r) return settings.whitelisted_rcpts:get_key(r) end, rcpts_user) then
- rspamd_logger.infox(task, 'skip ratelimit for whitelisted recipient')
- return
- end
- end
- -- Get user (authuser)
- if settings.whitelisted_user then
- local auser = task:get_user()
- if settings.whitelisted_user:get_key(auser) then
- rspamd_logger.infox(task, 'skip ratelimit for whitelisted user')
- return
- end
- end
- -- Now create all ratelimit prefixes
- local prefixes = {}
- local nprefixes = 0
-
- for k,v in pairs(settings.limits) do
- nprefixes = nprefixes + limit_to_prefixes(task, k, v, prefixes)
- end
-
- for k, hdl in pairs(settings.custom_keywords or E) do
- local ret, redis_key, bd = pcall(hdl, task)
-
- if ret then
- local bucket = parse_limit(k, bd)
- if bucket[1] then
- prefixes[redis_key] = make_prefix(redis_key, k, bucket[1])
- end
- nprefixes = nprefixes + 1
- else
- rspamd_logger.errx(task, 'cannot call handler for %s: %s',
- k, redis_key)
- end
- end
-
- local function gen_check_cb(prefix, bucket, lim_name)
- return function(err, data)
- if err then
- rspamd_logger.errx('cannot check limit %s: %s %s', prefix, err, data)
- elseif type(data) == 'table' and data[1] and data[1] == 1 then
- -- set symbol only and do NOT soft reject
- if settings.symbol then
- task:insert_result(settings.symbol, 0.0, lim_name .. "(" .. prefix .. ")")
- rspamd_logger.infox(task,
- 'set_symbol_only: ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)',
- lim_name, prefix,
- bucket.burst, bucket.rate,
- data[2], data[3], data[4])
- return
- -- set INFO symbol and soft reject
- elseif settings.info_symbol then
- task:insert_result(settings.info_symbol, 1.0,
- lim_name .. "(" .. prefix .. ")")
- end
- rspamd_logger.infox(task,
- 'ratelimit "%s(%s)" exceeded, (%s / %s): %s (%s:%s dyn)',
- lim_name, prefix,
- bucket.burst, bucket.rate,
- data[2], data[3], data[4])
- task:set_pre_result('soft reject',
- message_func(task, lim_name, prefix, bucket))
- end
- end
- end
-
- -- Don't do anything if pre-result has been already set
- if task:has_pre_result() then return end
-
- if nprefixes > 0 then
- -- Save prefixes to the cache to allow update
- task:cache_set('ratelimit_prefixes', prefixes)
- local now = rspamd_util.get_time()
- now = lua_util.round(now * 1000.0) -- Get milliseconds
- -- Now call check script for all defined prefixes
-
- for pr,value in pairs(prefixes) do
- local bucket = value.bucket
- local rate = (bucket.rate) / 1000.0 -- Leak rate in messages/ms
- rspamd_logger.debugm(N, task, "check limit %s:%s -> %s (%s/%s)",
- value.name, pr, value.hash, bucket.burst, bucket.rate)
- lua_redis.exec_redis_script(bucket_check_id,
- {key = value.hash, task = task, is_write = true},
- gen_check_cb(pr, bucket, value.name),
- {value.hash, tostring(now), tostring(rate), tostring(bucket.burst),
- tostring(settings.expire)})
- end
- end
-end
-
-local function ratelimit_update_cb(task)
- local prefixes = task:cache_get('ratelimit_prefixes')
-
- if prefixes then
- if task:has_pre_result() then
- -- Already rate limited/greylisted, do nothing
- rspamd_logger.debugm(N, task, 'pre-action has been set, do not update')
- return
- end
-
- local is_spam = not (task:get_metric_action() == 'no action')
-
- -- Update each bucket
- for k, v in pairs(prefixes) do
- local bucket = v.bucket
- local function update_bucket_cb(err, data)
- if err then
- rspamd_logger.errx(task, 'cannot update rate bucket %s: %s',
- k, err)
- else
- rspamd_logger.debugm(N, task,
- "updated limit %s:%s -> %s (%s/%s), burst: %s, dyn_rate: %s, dyn_burst: %s",
- v.name, k, v.hash,
- bucket.burst, bucket.rate,
- data[1], data[2], data[3])
- end
- end
- local now = rspamd_util.get_time()
- now = lua_util.round(now * 1000.0) -- Get milliseconds
- local mult_burst = bucket.ham_factor_burst or 1.0
- local mult_rate = bucket.ham_factor_burst or 1.0
-
- if is_spam then
- mult_burst = bucket.spam_factor_burst or 1.0
- mult_rate = bucket.spam_factor_rate or 1.0
- end
-
- lua_redis.exec_redis_script(bucket_update_id,
- {key = v.hash, task = task, is_write = true},
- update_bucket_cb,
- {v.hash, tostring(now), tostring(mult_rate), tostring(mult_burst),
- tostring(settings.max_rate_mult), tostring(settings.max_bucket_mult),
- tostring(settings.expire)})
- end
- end
-end
-
-local opts = rspamd_config:get_all_opt(N)
-if opts then
-
- settings = lua_util.override_defaults(settings, opts)
-
- if opts['limit'] then
- rspamd_logger.errx(rspamd_config, 'Legacy ratelimit config format no longer supported')
- end
-
- if opts['rates'] and type(opts['rates']) == 'table' then
- -- new way of setting limits
- fun.each(function(t, lim)
- local buckets = parse_limit(t, lim)
-
- if buckets and #buckets > 0 then
- settings.limits[t] = buckets
- end
- end, opts['rates'])
- end
-
- local enabled_limits = fun.totable(fun.map(function(t)
- return t
- end, settings.limits))
- rspamd_logger.infox(rspamd_config,
- 'enabled rate buckets: [%1]', table.concat(enabled_limits, ','))
-
- -- Ret, ret, ret: stupid legacy stuff:
- -- If we have a string with commas then load it as as static map
- -- otherwise, apply normal logic of Rspamd maps
-
- local wrcpts = opts['whitelisted_rcpts']
- if type(wrcpts) == 'string' then
- if string.find(wrcpts, ',') then
- settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(
- lua_util.rspamd_str_split(wrcpts, ','), 'set', 'Ratelimit whitelisted rcpts')
- else
- settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set',
- 'Ratelimit whitelisted rcpts')
- end
- elseif type(opts['whitelisted_rcpts']) == 'table' then
- settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(wrcpts, 'set',
- 'Ratelimit whitelisted rcpts')
- else
- -- Stupid default...
- settings.whitelisted_rcpts = lua_maps.rspamd_map_add_from_ucl(
- settings.whitelisted_rcpts, 'set', 'Ratelimit whitelisted rcpts')
- end
-
- if opts['whitelisted_ip'] then
- settings.whitelisted_ip = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_ip', 'radix',
- 'Ratelimit whitelist ip map')
- end
-
- if opts['whitelisted_user'] then
- settings.whitelisted_user = lua_maps.rspamd_map_add('ratelimit', 'whitelisted_user', 'set',
- 'Ratelimit whitelist user map')
- end
-
- settings.custom_keywords = {}
- if opts['custom_keywords'] then
- local ret, res_or_err = pcall(loadfile(opts['custom_keywords']))
-
- if ret then
- opts['custom_keywords'] = {}
- if type(res_or_err) == 'table' then
- for k,hdl in pairs(res_or_err) do
- settings['custom_keywords'][k] = hdl
- end
- elseif type(res_or_err) == 'function' then
- settings['custom_keywords']['custom'] = res_or_err
- end
- else
- rspamd_logger.errx(rspamd_config, 'cannot execute %s: %s',
- opts['custom_keywords'], res_or_err)
- settings['custom_keywords'] = {}
- end
- end
-
- if opts['message_func'] then
- message_func = assert(load(opts['message_func']))()
- end
-
- redis_params = lua_redis.parse_redis_server('ratelimit')
-
- if not redis_params then
- rspamd_logger.infox(rspamd_config, 'no servers are specified, disabling module')
- lua_util.disable_module(N, "redis")
- else
- local s = {
- type = 'prefilter,nostat',
- name = 'RATELIMIT_CHECK',
- priority = 7,
- callback = ratelimit_cb,
- flags = 'empty',
- }
-
- if settings.symbol then
- s.name = settings.symbol
- elseif settings.info_symbol then
- s.name = settings.info_symbol
- end
-
- rspamd_config:register_symbol(s)
- rspamd_config:register_symbol {
- type = 'idempotent',
- name = 'RATELIMIT_UPDATE',
- callback = ratelimit_update_cb,
- }
- end
-end
-
-rspamd_config:add_on_load(function(cfg, ev_base, worker)
- load_scripts(cfg, ev_base)
-end)
diff --git a/data/Dockerfiles/sogo/Dockerfile b/data/Dockerfiles/sogo/Dockerfile
index 30a06d24..9d3965a7 100644
--- a/data/Dockerfiles/sogo/Dockerfile
+++ b/data/Dockerfiles/sogo/Dockerfile
@@ -49,4 +49,6 @@ COPY sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
COPY acl.diff /acl.diff
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
+VOLUME /usr/lib/GNUstep/SOGo/
+
RUN rm -rf /tmp/* /var/tmp/*
diff --git a/data/Dockerfiles/sogo/bootstrap-sogo.sh b/data/Dockerfiles/sogo/bootstrap-sogo.sh
index 46d8ec6c..77f03b63 100755
--- a/data/Dockerfiles/sogo/bootstrap-sogo.sh
+++ b/data/Dockerfiles/sogo/bootstrap-sogo.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# Wait for MySQL to warm-up
-while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do
+while ! mysqladmin ping --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..."
sleep 2
done
@@ -15,10 +15,10 @@ done
# Recreate view
-mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
+mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
while [[ ${VIEW_OK} != 'OK' ]]; do
- mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
+ mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, home, kind, multiple_bookings) AS
SELECT mailbox.username, mailbox.domain, mailbox.username, if(json_extract(attributes, '$.force_pw_update') LIKE '%0%', password, 'invalid'), mailbox.name, mailbox.username, IFNULL(GROUP_CONCAT(ga.aliases SEPARATOR ' '), ''), IFNULL(gda.ad_alias, ''), CONCAT('/var/vmail/', maildir), mailbox.kind, mailbox.multiple_bookings FROM mailbox
LEFT OUTER JOIN grouped_mail_aliases ga ON ga.username REGEXP CONCAT('(^|,)', mailbox.username, '($|,)')
@@ -26,7 +26,7 @@ LEFT OUTER JOIN grouped_domain_alias_address gda ON gda.username = mailbox.usern
WHERE mailbox.active = '1'
GROUP BY mailbox.username;
EOF
- if [[ ! -z $(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
+ if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Will retry to setup SOGo view in 3s"
@@ -37,11 +37,11 @@ done
# Wait for static view table if missing after update and update content
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
- if [[ ! -z $(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
+ if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
echo "Updating _sogo_static_view content..."
- mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view SELECT * from sogo_view"
- mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')"
+ mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view SELECT * from sogo_view"
+ mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')"
else
echo "Waiting for database initialization..."
sleep 3
@@ -50,10 +50,10 @@ done
# Recreate password update trigger
-mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
+mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
while [[ ${TRIGGER_OK} != 'OK' ]]; do
- mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
+ mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELIMITER -
CREATE TRIGGER sogo_update_password AFTER UPDATE ON _sogo_static_view
FOR EACH ROW
@@ -63,7 +63,7 @@ END;
-
DELIMITER ;
EOF
- if [[ ! -z $(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then
+ if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then
TRIGGER_OK=OK
else
echo "Will retry to setup SOGo password update trigger in 3s"
@@ -81,19 +81,19 @@ cat < /var/lib/sogo/GNUstep/Defaults/sogod.plist
OCSAclURL
- mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_acl
+ mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl
OCSCacheFolderURL
- mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_cache_folder
+ mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder
OCSEMailAlarmsFolderURL
- mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_alarms_folder
+ mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_alarms_folder
OCSFolderInfoURL
- mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_folder_info
+ mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_folder_info
OCSSessionsFolderURL
- mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_sessions_folder
+ mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_sessions_folder
OCSStoreURL
- mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_store
+ mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_store
SOGoProfileURL
- mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/sogo_user_profile
+ mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_user_profile
SOGoTimeZone
${TZ}
domains
@@ -138,11 +138,11 @@ while read line
prependPasswordScheme
YES
viewURL
- mysql://${DBUSER}:${DBPASS}@mysql:3306/${DBNAME}/_sogo_static_view
+ mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/_sogo_static_view
" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
-done < <(mysql --host mysql -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain;" -B -N)
+done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain;" -B -N)
# Generate footer
echo '
diff --git a/data/Dockerfiles/sogo/supervisord.conf b/data/Dockerfiles/sogo/supervisord.conf
index 2a889560..1c1422b3 100644
--- a/data/Dockerfiles/sogo/supervisord.conf
+++ b/data/Dockerfiles/sogo/supervisord.conf
@@ -16,13 +16,6 @@ command=/usr/sbin/cron -f
autorestart=true
priority=2
-[program:sogo-webres]
-command=/usr/bin/python -u -m SimpleHTTPServer 9192
-directory=/usr/lib/GNUstep/SOGo/
-user=sogo
-autorestart=true
-priority=4
-
[program:bootstrap-sogo]
command=/bootstrap-sogo.sh
stdout_logfile=/dev/stdout
diff --git a/data/Dockerfiles/unbound/Dockerfile b/data/Dockerfiles/unbound/Dockerfile
index 72e86bc0..4f443b88 100644
--- a/data/Dockerfiles/unbound/Dockerfile
+++ b/data/Dockerfiles/unbound/Dockerfile
@@ -10,6 +10,7 @@ RUN apk add --update --no-cache \
drill \
&& curl -o /etc/unbound/root.hints https://www.internic.net/domain/named.cache \
&& chown root:unbound /etc/unbound \
+ && adduser unbound tty \
&& chmod 775 /etc/unbound
EXPOSE 53/udp 53/tcp
diff --git a/data/Dockerfiles/unbound/docker-entrypoint.sh b/data/Dockerfiles/unbound/docker-entrypoint.sh
index b458cd8a..d179eaca 100755
--- a/data/Dockerfiles/unbound/docker-entrypoint.sh
+++ b/data/Dockerfiles/unbound/docker-entrypoint.sh
@@ -1,8 +1,11 @@
#!/bin/bash
+echo "Setting console permissions..."
+chown root:tty /dev/console
+chmod g+rw /dev/console
echo "Receiving anchor key..."
/usr/sbin/unbound-anchor -a /etc/unbound/trusted-key.key
echo "Receiving root hints..."
curl -#o /etc/unbound/root.hints https://www.internic.net/domain/named.cache
-
+/usr/sbin/unbound-control-setup
exec "$@"
diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh
index c06abbc2..ab528a78 100755
--- a/data/Dockerfiles/watchdog/watchdog.sh
+++ b/data/Dockerfiles/watchdog/watchdog.sh
@@ -59,28 +59,34 @@ function mail_error() {
log_msg "Sent notification email to ${1}"
}
-
get_container_ip() {
# ${1} is container
CONTAINER_ID=()
+ CONTAINER_IPS=()
CONTAINER_IP=
LOOP_C=1
until [[ ${CONTAINER_IP} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || [[ ${LOOP_C} -gt 5 ]]; do
sleep 0.5
# get long container id for exact match
- CONTAINER_ID=($(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | .id"))
+ CONTAINER_ID=($(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring == \"${1}\") | .id"))
# returned id can have multiple elements (if scaled), shuffle for random test
CONTAINER_ID=($(printf "%s\n" "${CONTAINER_ID[@]}" | shuf))
if [[ ! -z ${CONTAINER_ID} ]]; then
for matched_container in "${CONTAINER_ID[@]}"; do
- CONTAINER_IP=$(curl --silent http://dockerapi:8080/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress')
- # grep will do nothing if one of these vars is empty
- [[ -z ${CONTAINER_IP} ]] && continue
- [[ -z ${IPV4_NETWORK} ]] && continue
- # only return ips that are part of our network
- if ! grep -q ${IPV4_NETWORK} <(echo ${CONTAINER_IP}); then
- CONTAINER_IP=
- fi
+ CONTAINER_IPS=($(curl --silent --insecure https://dockerapi/containers/${matched_container}/json | jq -r '.NetworkSettings.Networks[].IPAddress'))
+ for ip_match in "${CONTAINER_IPS[@]}"; do
+ # grep will do nothing if one of these vars is empty
+ [[ -z ${ip_match} ]] && continue
+ [[ -z ${IPV4_NETWORK} ]] && continue
+ # only return ips that are part of our network
+ if ! grep -q ${IPV4_NETWORK} <(echo ${ip_match}); then
+ continue
+ else
+ CONTAINER_IP=${ip_match}
+ break
+ fi
+ done
+ [[ ! -z ${CONTAINER_IP} ]] && break
done
fi
LOOP_C=$((LOOP_C + 1))
@@ -88,7 +94,6 @@ get_container_ip() {
[[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP}
}
-# Check functions
nginx_checks() {
err_count=0
diff_c=0
@@ -118,8 +123,8 @@ mysql_checks() {
while [ ${err_count} -lt ${THRESHOLD} ]; do
host_ip=$(get_container_ip mysql-mailcow)
err_c_cur=${err_count}
- /usr/lib/nagios/plugins/check_mysql -H ${host_ip} -P 3306 -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 1>&2; err_count=$(( ${err_count} + $? ))
- /usr/lib/nagios/plugins/check_mysql_query -H ${host_ip} -P 3306 -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 1>&2; err_count=$(( ${err_count} + $? ))
+ /usr/lib/nagios/plugins/check_mysql -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 1>&2; err_count=$(( ${err_count} + $? ))
+ /usr/lib/nagios/plugins/check_mysql_query -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
@@ -138,7 +143,6 @@ sogo_checks() {
while [ ${err_count} -lt ${THRESHOLD} ]; do
host_ip=$(get_container_ip sogo-mailcow)
err_c_cur=${err_count}
- /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /WebServerResources/css/theme-default.css -p 9192 -R md-default-theme 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 -R "SOGo\.MainUI" 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
@@ -222,7 +226,7 @@ rspamd_checks() {
while [ ${err_count} -lt ${THRESHOLD} ]; do
host_ip=$(get_container_ip rspamd-mailcow)
err_c_cur=${err_count}
- SCORE=$(/usr/bin/curl -s --data-binary @- --unix-socket /rspamd-sock/rspamd.sock http://rspamd/scan -d '
+ SCORE=$(/usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan -d '
To: null@localhost
From: watchdog@localhost
@@ -338,12 +342,12 @@ done
# Monitor dockerapi
(
while true; do
- while nc -z dockerapi 8080; do
+ while nc -z dockerapi 443; do
sleep 3
done
log_msg "Cannot find dockerapi-mailcow, waiting to recover..."
kill -STOP ${BACKGROUND_TASKS[*]}
- until nc -z dockerapi 8080; do
+ until nc -z dockerapi 443; do
sleep 3
done
kill -CONT ${BACKGROUND_TASKS[*]}
@@ -358,10 +362,10 @@ while true; do
if [[ ${com_pipe_answer} =~ .+-mailcow ]]; then
kill -STOP ${BACKGROUND_TASKS[*]}
sleep 3
- CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id")
+ CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id")
if [[ ! -z ${CONTAINER_ID} ]]; then
log_msg "Sending restart command to ${CONTAINER_ID}..."
- curl --silent -XPOST http://dockerapi:8080/containers/${CONTAINER_ID}/restart
+ curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/restart
fi
log_msg "Wait for restarted container to settle and continue watching..."
sleep 30s
diff --git a/data/assets/mysql/docker-entrypoint.sh b/data/assets/mysql/docker-entrypoint.sh
new file mode 100755
index 00000000..94e394ac
--- /dev/null
+++ b/data/assets/mysql/docker-entrypoint.sh
@@ -0,0 +1,192 @@
+#!/bin/bash
+set -eo pipefail
+shopt -s nullglob
+
+openssl req -x509 -sha256 -newkey rsa:2048 -keyout /var/lib/mysql/sql.key -out /var/lib/mysql/sql.crt -days 3650 -nodes -subj '/CN=mysql'
+
+# if command starts with an option, prepend mysqld
+if [ "${1:0:1}" = '-' ]; then
+ set -- mysqld "$@"
+fi
+
+# skip setup if they want an option that stops mysqld
+wantHelp=
+for arg; do
+ case "$arg" in
+ -'?'|--help|--print-defaults|-V|--version)
+ wantHelp=1
+ break
+ ;;
+ esac
+done
+
+# usage: file_env VAR [DEFAULT]
+# ie: file_env 'XYZ_DB_PASSWORD' 'example'
+# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
+# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
+file_env() {
+ local var="$1"
+ local fileVar="${var}_FILE"
+ local def="${2:-}"
+ if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
+ echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
+ exit 1
+ fi
+ local val="$def"
+ if [ "${!var:-}" ]; then
+ val="${!var}"
+ elif [ "${!fileVar:-}" ]; then
+ val="$(< "${!fileVar}")"
+ fi
+ export "$var"="$val"
+ unset "$fileVar"
+}
+
+_check_config() {
+ toRun=( "$@" --verbose --help --log-bin-index="$(mktemp -u)" )
+ if ! errors="$("${toRun[@]}" 2>&1 >/dev/null)"; then
+ cat >&2 <<-EOM
+
+ ERROR: mysqld failed while attempting to check config
+ command was: "${toRun[*]}"
+
+ $errors
+ EOM
+ exit 1
+ fi
+}
+
+# Fetch value from server config
+# We use mysqld --verbose --help instead of my_print_defaults because the
+# latter only show values present in config files, and not server defaults
+_get_config() {
+ local conf="$1"; shift
+ "$@" --verbose --help --log-bin-index="$(mktemp -u)" 2>/dev/null | awk '$1 == "'"$conf"'" { print $2; exit }'
+}
+
+# allow the container to be started with `--user`
+if [ "$1" = 'mysqld' -a -z "$wantHelp" -a "$(id -u)" = '0' ]; then
+ _check_config "$@"
+ DATADIR="$(_get_config 'datadir' "$@")"
+ mkdir -p "$DATADIR"
+ chown -R mysql:mysql "$DATADIR"
+ exec gosu mysql "$BASH_SOURCE" "$@"
+fi
+
+if [ "$1" = 'mysqld' -a -z "$wantHelp" ]; then
+ # still need to check config, container may have started with --user
+ _check_config "$@"
+ # Get config
+ DATADIR="$(_get_config 'datadir' "$@")"
+
+ if [ ! -d "$DATADIR/mysql" ]; then
+ file_env 'MYSQL_ROOT_PASSWORD'
+ if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
+ echo >&2 'error: database is uninitialized and password option is not specified '
+ echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD'
+ exit 1
+ fi
+
+ mkdir -p "$DATADIR"
+
+ echo 'Initializing database'
+ # "Other options are passed to mysqld." (so we pass all "mysqld" arguments directly here)
+ mysql_install_db --datadir="$DATADIR" --rpm "${@:2}"
+ echo 'Database initialized'
+
+ SOCKET="$(_get_config 'socket' "$@")"
+ "$@" --skip-networking --socket="${SOCKET}" &
+ pid="$!"
+
+ mysql=( mysql --protocol=socket -uroot -hlocalhost --socket="${SOCKET}" )
+
+ for i in {30..0}; do
+ if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then
+ break
+ fi
+ echo 'MySQL init process in progress...'
+ sleep 1
+ done
+ if [ "$i" = 0 ]; then
+ echo >&2 'MySQL init process failed.'
+ exit 1
+ fi
+
+ if [ -z "$MYSQL_INITDB_SKIP_TZINFO" ]; then
+ # sed is for https://bugs.mysql.com/bug.php?id=20545
+ mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql
+ fi
+
+ if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
+ export MYSQL_ROOT_PASSWORD="$(pwgen -1 32)"
+ echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"
+ fi
+
+ rootCreate=
+ # default root to listen for connections from anywhere
+ file_env 'MYSQL_ROOT_HOST' '%'
+ if [ ! -z "$MYSQL_ROOT_HOST" -a "$MYSQL_ROOT_HOST" != 'localhost' ]; then
+ # no, we don't care if read finds a terminating character in this heredoc
+ # https://unix.stackexchange.com/questions/265149/why-is-set-o-errexit-breaking-this-read-heredoc-expression/265151#265151
+ read -r -d '' rootCreate <<-EOSQL || true
+ CREATE USER 'root'@'${MYSQL_ROOT_HOST}' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
+ GRANT ALL ON *.* TO 'root'@'${MYSQL_ROOT_HOST}' WITH GRANT OPTION ;
+ EOSQL
+ fi
+
+ "${mysql[@]}" <<-EOSQL
+ -- What's done in this file shouldn't be replicated
+ -- or products like mysql-fabric won't work
+ SET @@SESSION.SQL_LOG_BIN=0;
+
+ DELETE FROM mysql.user WHERE user NOT IN ('mysql.sys', 'mysqlxsys', 'root') OR host NOT IN ('localhost') ;
+ SET PASSWORD FOR 'root'@'localhost'=PASSWORD('${MYSQL_ROOT_PASSWORD}') ;
+ GRANT ALL ON *.* TO 'root'@'localhost' WITH GRANT OPTION ;
+ ${rootCreate}
+ DROP DATABASE IF EXISTS test ;
+ FLUSH PRIVILEGES ;
+ EOSQL
+
+ if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then
+ mysql+=( -p"${MYSQL_ROOT_PASSWORD}" )
+ fi
+
+ file_env 'MYSQL_DATABASE'
+ if [ "$MYSQL_DATABASE" ]; then
+ echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}"
+ mysql+=( "$MYSQL_DATABASE" )
+ fi
+
+ file_env 'MYSQL_USER'
+ file_env 'MYSQL_PASSWORD'
+ if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
+ echo "CREATE USER '$MYSQL_USER'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' ;" | "${mysql[@]}"
+
+ if [ "$MYSQL_DATABASE" ]; then
+ echo "GRANT ALL ON \`$MYSQL_DATABASE\`.* TO '$MYSQL_USER'@'%' ;" | "${mysql[@]}"
+ fi
+ fi
+
+ echo
+ for f in /docker-entrypoint-initdb.d/*; do
+ case "$f" in
+ *.sh) echo "$0: running $f"; . "$f" ;;
+ *.sql) echo "$0: running $f"; "${mysql[@]}" < "$f"; echo ;;
+ *.sql.gz) echo "$0: running $f"; gunzip -c "$f" | "${mysql[@]}"; echo ;;
+ *) echo "$0: ignoring $f" ;;
+ esac
+ echo
+ done
+
+ if ! kill -s TERM "$pid" || ! wait "$pid"; then
+ echo >&2 'MySQL init process failed.'
+ exit 1
+ fi
+
+ echo
+ echo 'MySQL init process done. Ready for start up.'
+ echo
+ fi
+fi
+
+exec "$@"
diff --git a/data/conf/dovecot/dovecot.conf b/data/conf/dovecot/dovecot.conf
index dd8db8a3..2df625e2 100644
--- a/data/conf/dovecot/dovecot.conf
+++ b/data/conf/dovecot/dovecot.conf
@@ -14,7 +14,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 listescape #mail_crypt
+mail_plugins = quota acl zlib listescape mail_crypt mail_crypt_acl
# Dovecot 2.2
#ssl_protocols = !SSLv3
@@ -175,7 +175,7 @@ namespace {
type = shared
separator = /
prefix = Shared/%%u/
- location = maildir:%%h/:CONTROL=~/Shared/%%u:INDEXPVT=~/Shared/%%u
+ location = maildir:%%h/:INDEX=~/Shared/%%u;CONTROL=~/Shared/%%u
subscriptions = no
list = children
}
@@ -223,7 +223,7 @@ service pop3-login {
}
service imap {
executable = imap imap-postlogin
- user = dovenull
+ user = vmail
vsz_limit = 256 M
}
service managesieve {
@@ -244,11 +244,11 @@ userdb {
}
protocol imap {
imap_metadata = yes
- mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape #mail_crypt
+ mail_plugins = quota imap_quota imap_acl acl zlib imap_zlib imap_sieve listescape mail_crypt mail_crypt_acl
}
mail_attribute_dict = file:%h/dovecot-attributes
protocol lmtp {
- mail_plugins = quota sieve acl zlib listescape #mail_crypt
+ mail_plugins = quota sieve acl zlib listescape mail_crypt mail_crypt_acl
auth_socket_path = /usr/local/var/run/dovecot/auth-master
}
protocol sieve {
@@ -288,9 +288,12 @@ plugin {
sieve_before = dict:proxy::sieve_before;name=active;bindir=/var/vmail/sieve_before_bindir
sieve_after = dict:proxy::sieve_after;name=active;bindir=/var/vmail/sieve_after_bindir
sieve_after2 = /var/vmail/sieve/global.sieve
- #mail_crypt_global_private_key = PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
diff --git a/data/conf/rspamd/local.d/multimap.conf b/data/conf/rspamd/local.d/multimap.conf
index da4afaf2..c27a9d63 100644
--- a/data/conf/rspamd/local.d/multimap.conf
+++ b/data/conf/rspamd/local.d/multimap.conf
@@ -25,13 +25,6 @@ WHITELISTED_FWD_HOST {
symbols_set = ["WHITELISTED_FWD_HOST"];
}
-KEEP_SPAM {
- type = "ip";
- map = "redis://KEEP_SPAM";
- action = "accept";
- symbols_set = ["KEEP_SPAM"];
-}
-
LOCAL_BL_ASN {
require_symbols = "!MAILCOW_WHITE";
type = "asn";
@@ -40,11 +33,3 @@ LOCAL_BL_ASN {
description = "Sender's ASN is on the local blacklist";
symbols_set = ["LOCAL_BL_ASN"];
}
-
-#SPOOFED_SENDER {
-# type = "rcpt";
-# filter = "email:domain:tld";
-# map = "redis://DOMAIN_MAP";
-# require_symbols = "AUTH_NA | !RCVD_VIA_SMTP_AUTH";
-# symbols_set = ["SPOOFED_SENDER"];
-#}
diff --git a/data/conf/rspamd/lua/rspamd.local.lua b/data/conf/rspamd/lua/rspamd.local.lua
index 7c9efbb4..c6fc0b98 100644
--- a/data/conf/rspamd/lua/rspamd.local.lua
+++ b/data/conf/rspamd/lua/rspamd.local.lua
@@ -7,6 +7,68 @@ rspamd_config.MAILCOW_AUTH = {
end
}
+rspamd_config:register_symbol({
+ name = 'KEEP_SPAM',
+ type = 'prefilter',
+ callback = function(task)
+ local util = require("rspamd_util")
+ local rspamd_logger = require "rspamd_logger"
+ local rspamd_ip = require 'rspamd_ip'
+ local uname = task:get_user()
+
+ if uname then
+ return false
+ end
+
+ local redis_params = rspamd_parse_redis_server('keep_spam')
+ local ip = task:get_from_ip()
+
+ if not ip:is_valid() then
+ return false
+ end
+
+ local from_ip_string = tostring(ip)
+ ip_check_table = {from_ip_string}
+
+ local maxbits = 128
+ local minbits = 32
+ if ip:get_version() == 4 then
+ maxbits = 32
+ minbits = 8
+ end
+ for i=maxbits,minbits,-1 do
+ local nip = ip:apply_mask(i):to_string() .. "/" .. i
+ table.insert(ip_check_table, nip)
+ end
+ local function keep_spam_cb(err, data)
+ if err then
+ rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
+ return false
+ else
+ for k,v in pairs(data) do
+ if (v and v ~= userdata and v == '1') then
+ rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result", v)
+ task:set_pre_result('accept', 'IP matched with forward hosts')
+ end
+ end
+ end
+ end
+ table.insert(ip_check_table, 1, 'KEEP_SPAM')
+ local redis_ret_user = rspamd_redis_make_request(task,
+ redis_params, -- connect params
+ 'KEEP_SPAM', -- hash key
+ false, -- is write
+ keep_spam_cb, --callback
+ 'HMGET', -- command
+ ip_check_table -- arguments
+ )
+ if not redis_ret_user then
+ rspamd_logger.infox(rspamd_config, "cannot check keep_spam redis map")
+ end
+ end,
+ priority = 19
+})
+
rspamd_config:register_symbol({
name = 'TAG_MOO',
type = 'postfilter',
diff --git a/data/conf/rspamd/meta_exporter/pipe.php b/data/conf/rspamd/meta_exporter/pipe.php
index 7064b7b8..ffa3a9ca 100644
--- a/data/conf/rspamd/meta_exporter/pipe.php
+++ b/data/conf/rspamd/meta_exporter/pipe.php
@@ -6,7 +6,8 @@ require_once "vars.inc.php";
// Do not show errors, we log to using error_log
ini_set('error_reporting', 0);
// Init database
-$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
+//$dsn = $database_type . ':host=' . $database_host . ';dbname=' . $database_name;
+$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
diff --git a/data/conf/rspamd/override.d/worker-controller.inc b/data/conf/rspamd/override.d/worker-controller.inc
index 60338e15..4d4ffe6f 100644
--- a/data/conf/rspamd/override.d/worker-controller.inc
+++ b/data/conf/rspamd/override.d/worker-controller.inc
@@ -1,7 +1,7 @@
bind_socket = "*:11334";
-count = 2;
+count = 1;
secure_ip = "127.0.0.1";
secure_ip = "::1";
-bind_socket = "/rspamd-sock/rspamd.sock mode=0666 owner=nobody";
+bind_socket = "/var/lib/rspamd/rspamd.sock mode=0666 owner=nobody";
.include(try=true; priority=10) "$CONFDIR/override.d/worker-controller-password.inc"
.include(try=true; priority=20) "$CONFDIR/override.d/worker-controller.custom.inc"
diff --git a/data/conf/sogo/sogo.conf b/data/conf/sogo/sogo.conf
index 23fd8240..b5979c82 100644
--- a/data/conf/sogo/sogo.conf
+++ b/data/conf/sogo/sogo.conf
@@ -39,17 +39,17 @@
SxVMemLimit = 384;
- SOGoMaximumPingInterval = 354;
+ SOGoMaximumPingInterval = 3540;
- SOGoInternalSyncInterval = 30;
- SOGoMaximumSyncInterval = 354;
+ SOGoInternalSyncInterval = 45;
+ SOGoMaximumSyncInterval = 3540;
// 100 seems to break some Android clients
- //SOGoMaximumSyncWindowSize = 100;
+ //SOGoMaximumSyncWindowSize = 99;
// This should do the trick for Outlook 2016
- SOGoMaximumSyncResponseSize = 2048;
+ SOGoMaximumSyncResponseSize = 512;
- WOWatchDogRequestTimeout = 10;
+ WOWatchDogRequestTimeout = 20;
WOListenQueueSize = 300;
WONoDetach = YES;
diff --git a/data/conf/unbound/unbound.conf b/data/conf/unbound/unbound.conf
index b3c77671..6d7f1f04 100644
--- a/data/conf/unbound/unbound.conf
+++ b/data/conf/unbound/unbound.conf
@@ -2,7 +2,7 @@ server:
verbosity: 1
interface: 0.0.0.0
interface: ::0
- logfile: /dev/stdout
+ logfile: /dev/console
do-ip4: yes
do-ip6: yes
do-udp: yes
@@ -27,3 +27,12 @@ server:
hide-version: yes
max-udp-size: 4096
msg-buffer-size: 65552
+
+remote-control:
+ control-enable: yes
+ control-interface: 127.0.0.1
+ control-port: 8953
+ server-key-file: "/etc/unbound/unbound_server.key"
+ server-cert-file: "/etc/unbound/unbound_server.pem"
+ control-key-file: "/etc/unbound/unbound_control.key"
+ control-cert-file: "/etc/unbound/unbound_control.pem"
diff --git a/data/web/admin.php b/data/web/admin.php
index d2734431..05ae3174 100644
--- a/data/web/admin.php
+++ b/data/web/admin.php
@@ -22,29 +22,29 @@ $tfa_data = get_tfa();
-
+
=$lang['tfa']['tfa'];?>:
@@ -76,12 +76,10 @@ $tfa_data = get_tfa();
-
-
-
-
-
API
-
+
+
+
@@ -125,12 +124,12 @@ $tfa_data = get_tfa();
=$lang['mailbox']['toggle_all'];?>
=$lang['mailbox']['quick_actions'];?>
=$lang['admin']['add_domain_admin'];?>
@@ -156,13 +155,13 @@ $tfa_data = get_tfa();
@@ -384,7 +383,7 @@ $tfa_data = get_tfa();
-
+
@@ -404,10 +403,10 @@ $tfa_data = get_tfa();
=$lang['mailbox']['quick_actions'];?>
@@ -416,7 +415,7 @@ $tfa_data = get_tfa();
@@ -439,41 +438,41 @@ $tfa_data = get_tfa();
@@ -491,9 +490,9 @@ $tfa_data = get_tfa();
- [=$lang['admin']['queue_unban'];?>]
- [whitelist]
- [blacklist]
+ [=$lang['admin']['queue_unban'];?>]
+ [whitelist]
+ [blacklist]
@@ -528,10 +527,10 @@ $tfa_data = get_tfa();
=$lang['mailbox']['quick_actions'];?>
@@ -540,17 +539,17 @@ $tfa_data = get_tfa();
@@ -565,19 +564,19 @@ $tfa_data = get_tfa();
-
-
+
@@ -649,19 +648,19 @@ $tfa_data = get_tfa();
-
+
-
+
-
-
+
+
-
+
@@ -755,21 +754,21 @@ $tfa_data = get_tfa();
@@ -791,8 +790,8 @@ echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
echo "var log_pagination_size = '". $LOG_PAGINATION_SIZE . "';\n";
?>
-
-
+
+
PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
diff --git a/data/web/css/admin.css b/data/web/css/admin.css
index a53d721c..bc89f1dd 100644
--- a/data/web/css/admin.css
+++ b/data/web/css/admin.css
@@ -65,12 +65,6 @@ body.modal-open {
font-size:9pt;
background:transparent;
}
-.bootstrap-select {
- width: auto!important;
-}
.table-condensed .input-sm {
width: 100%!important;
}
-.full-width-select {
- width: 100%!important;
-}
diff --git a/data/web/css/mailbox.css b/data/web/css/mailbox.css
index da2e96e3..488150d3 100644
--- a/data/web/css/mailbox.css
+++ b/data/web/css/mailbox.css
@@ -5,9 +5,6 @@ table.footable>tbody>tr.footable-empty>td {
.pagination a {
text-decoration: none !important;
}
-.panel panel-default {
- overflow: visible !important;
-}
.btn-group {
width: max-content;
}
diff --git a/data/web/css/mailcow.css b/data/web/css/mailcow.css
index 374688a1..04d1b874 100644
--- a/data/web/css/mailcow.css
+++ b/data/web/css/mailcow.css
@@ -148,3 +148,13 @@ nav .glyphicon {
color: #5a5a5a;
white-space: nowrap;
}
+.haveibeenpwned {
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.full-width-select {
+ width: 100%!important;
+}
\ No newline at end of file
diff --git a/data/web/debug.php b/data/web/debug.php
index 646aaa19..d6616dcb 100644
--- a/data/web/debug.php
+++ b/data/web/debug.php
@@ -288,8 +288,8 @@ echo "var log_pagination_size = '". $LOG_PAGINATION_SIZE . "';\n";
?>
-
-
+
+
-
+
-
+
-
+
@@ -58,7 +58,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
@@ -92,7 +92,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
-
+
@@ -111,13 +111,13 @@ if (isset($_SESSION['mailcow_cc_role'])) {
@@ -136,7 +136,31 @@ if (isset($_SESSION['mailcow_cc_role'])) {
+
+
@@ -165,7 +189,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
-
+
-
+
@@ -588,13 +636,13 @@ if (isset($_SESSION['mailcow_cc_role'])) {
@@ -965,14 +1076,14 @@ else {
-
-
+
+
diff --git a/data/web/img/cow_mailcow.svg b/data/web/img/cow_mailcow.svg
index d4577821..6ba98e46 100644
--- a/data/web/img/cow_mailcow.svg
+++ b/data/web/img/cow_mailcow.svg
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
+ id="white_1_">
\ No newline at end of file
diff --git a/data/web/inc/footer.inc.php b/data/web/inc/footer.inc.php
index cdef8e0b..9e8daf7b 100644
--- a/data/web/inc/footer.inc.php
+++ b/data/web/inc/footer.inc.php
@@ -12,10 +12,20 @@ logger();
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-= (preg_match("/mailbox.php/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
-= (preg_match("/admin.php/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
-= (preg_match("/user.php/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
-= (preg_match("/edit.php/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
-= (preg_match("/quarantine.php/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
-= (preg_match("/debug.php/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
-
-
+
+
+
+
+
+
=$UI_TEXTS['title_name'];?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = (preg_match("/mailbox/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
+ = (preg_match("/admin/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
+ = (preg_match("/user/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
+ = (preg_match("/edit/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
+ = (preg_match("/quarantine/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
+ = (preg_match("/debug/i", $_SERVER['REQUEST_URI'])) ? '
' : null; ?>
+
+
-
-
+
diff --git a/data/web/inc/init_db.inc.php b/data/web/inc/init_db.inc.php
index c318d634..8ece2544 100644
--- a/data/web/inc/init_db.inc.php
+++ b/data/web/inc/init_db.inc.php
@@ -3,7 +3,7 @@ function init_db_schema() {
try {
global $pdo;
- $db_version = "19082018_1004";
+ $db_version = "03102018_1502";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -192,6 +192,26 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
+ "tls_policy_override" => array(
+ "cols" => array(
+ "id" => "INT NOT NULL AUTO_INCREMENT",
+ "dest" => "VARCHAR(255) NOT NULL",
+ "policy" => "ENUM('none', 'may', 'encrypt', 'dane', 'dane-only', 'fingerprint', 'verify', 'secure') NOT NULL",
+ "parameters" => "VARCHAR(255) DEFAULT ''",
+ "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
+ "modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
+ "active" => "TINYINT(1) NOT NULL DEFAULT '1'"
+ ),
+ "keys" => array(
+ "primary" => array(
+ "" => array("id")
+ ),
+ "unique" => array(
+ "dest" => array("dest")
+ ),
+ ),
+ "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+ ),
"quarantine" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
@@ -280,10 +300,7 @@ function init_db_schema() {
"delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'",
"syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
"eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
- "filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
"quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
- "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'",
- "recipient_maps" => "TINYINT(1) NOT NULL DEFAULT '0'",
),
"keys" => array(
"primary" => array(
@@ -417,6 +434,32 @@ function init_db_schema() {
),
"attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
),
+ "da_acl" => array(
+ "cols" => array(
+ "username" => "VARCHAR(255) NOT NULL",
+ "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "quarantine" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "login_as" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "bcc_maps" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'",
+ ),
+ "keys" => array(
+ "primary" => array(
+ "" => array("username")
+ ),
+ "fkey" => array(
+ "fk_domain_admin_acl" => array(
+ "col" => "username",
+ "ref" => "domain_admins.username",
+ "delete" => "CASCADE",
+ "update" => "NO ACTION"
+ )
+ )
+ ),
+ "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
+ ),
"imapsync" => array(
"cols" => array(
"id" => "INT NOT NULL AUTO_INCREMENT",
@@ -950,8 +993,9 @@ DELIMITER ;';
'msg' => 'db_init_complete'
);
- // Fix user_acl
+ // Fix ACL
$stmt = $pdo->query("INSERT INTO `user_acl` (`username`) SELECT `username` FROM `mailbox` WHERE `kind` = '' AND NOT EXISTS (SELECT `username` FROM `user_acl`);");
+ $stmt = $pdo->query("INSERT INTO `da_acl` (`username`) SELECT DISTINCT `username` FROM `domain_admins` WHERE `username` != 'admin' AND NOT EXISTS (SELECT `username` FROM `da_acl`);");
}
catch (PDOException $e) {
$_SESSION['return'][] = array(
diff --git a/data/web/inc/prerequisites.inc.php b/data/web/inc/prerequisites.inc.php
index a5e03672..4cb742af 100644
--- a/data/web/inc/prerequisites.inc.php
+++ b/data/web/inc/prerequisites.inc.php
@@ -35,7 +35,7 @@ $hrs = floor($mins / 60);
$mins -= $hrs * 60;
$offset = sprintf('%+d:%02d', $hrs*$sgn, $mins);
-$dsn = $database_type . ":host=" . $database_host . ";dbname=" . $database_name;
+$dsn = $database_type . ":unix_socket=" . $database_sock . ";dbname=" . $database_name;
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
@@ -46,12 +46,22 @@ try {
$pdo = new PDO($dsn, $database_user, $database_pass, $opt);
}
catch (PDOException $e) {
+// Stop when SQL connection fails
?>
-
Connection to database failed.
The following error was reported:
=$e->getMessage();?>
+
Connection to database failed.
The following error was reported:
=$e->getMessage();?>
+
Connection to dockerapi container failed.
The following error was reported:
=$errno;?> - =$errstr;?>
+ 'danger',
@@ -124,6 +134,7 @@ if (isset($_GET['lang']) && in_array($_GET['lang'], $AVAILABLE_LANGUAGES)) {
require_once $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.php';
include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.acl.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.address_rewriting.inc.php';
@@ -135,12 +146,14 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fwdhost.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.ratelimit.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.relayhost.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.rsettings.inc.php';
+require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.tls_policy_maps.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.fail2ban.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.docker.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/init_db.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/triggers.inc.php';
init_db_schema();
if (isset($_SESSION['mailcow_cc_role'])) {
- set_acl();
+ acl('to_session');
}
$UI_TEXTS = customize('get', 'ui_texts');
+
diff --git a/data/web/inc/triggers.inc.php b/data/web/inc/triggers.inc.php
index 4df1beca..d37c0968 100644
--- a/data/web/inc/triggers.inc.php
+++ b/data/web/inc/triggers.inc.php
@@ -40,7 +40,7 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
}
}
-if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
+if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['acl']['login_as'] == "1") {
if (isset($_GET["duallogin"])) {
$duallogin = html_entity_decode(rawurldecode($_GET["duallogin"]));
if (filter_var($duallogin, FILTER_VALIDATE_EMAIL)) {
diff --git a/data/web/inc/vars.inc.php b/data/web/inc/vars.inc.php
index 4ac0df47..5cf2ea94 100644
--- a/data/web/inc/vars.inc.php
+++ b/data/web/inc/vars.inc.php
@@ -9,6 +9,7 @@ This file will be reset on upgrades.
// SQL database connection variables
$database_type = 'mysql';
+$database_sock = '/var/run/mysqld/mysqld.sock';
$database_host = 'mysql';
$database_user = getenv('DBUSER');
$database_pass = getenv('DBPASS');
@@ -122,3 +123,12 @@ $DOCKER_TIMEOUT = 60;
// Anonymize IPs logged via UI
$ANONYMIZE_IPS = true;
+
+// Force incoming TLS for new mailboxes by default
+$MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_in'] = false;
+
+// Force outgoing TLS for new mailboxes by default
+$MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'] = false;
+
+// Force password change on next login (only allows login to mailcow UI)
+$MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false;
diff --git a/data/web/index.php b/data/web/index.php
index 47339923..c839ea86 100644
--- a/data/web/index.php
+++ b/data/web/index.php
@@ -2,15 +2,15 @@
require_once 'inc/prerequisites.inc.php';
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
- header('Location: /admin.php');
+ header('Location: /admin');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'domainadmin') {
- header('Location: /mailbox.php');
+ header('Location: /mailbox');
exit();
}
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
- header('Location: /user.php');
+ header('Location: /user');
exit();
}
require_once 'inc/header.inc.php';
@@ -107,6 +107,6 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
-
+
' +
' Test' +
- ' ' + lang.edit + '' +
- ' ' + lang.remove + '' +
+ ' ' + lang.edit + '' +
+ ' ' + lang.remove + '' +
'';
item.chkbox = '';
});
} else if (table == 'forwardinghoststable') {
$.each(data, function (i, item) {
item.action = '';
if (item.keep_spam == "yes") {
item.keep_spam = lang.no;
@@ -140,8 +144,8 @@ jQuery(function($){
item.selected_domains = escapeHtml(item.selected_domains.toString().replace(/,/g, " "));
item.chkbox = '';
item.action = '';
});
diff --git a/data/web/js/api.js b/data/web/js/api.js
index 2e770f7b..e8293dbc 100644
--- a/data/web/js/api.js
+++ b/data/web/js/api.js
@@ -5,9 +5,9 @@ $(document).ready(function() {
} else {
var parent_btn_grp = $(elem).parentsUntil(".btn-group").parent();
if (parent_btn_grp.hasClass('btn-group')) {
- parent_btn_grp.replaceWith('