Merge branch 'master' into patch-4

This commit is contained in:
Joshua Hesketh 2019-04-02 17:08:19 +11:00 committed by GitHub
commit f8ff11a1e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 2220 additions and 1695 deletions

2
.gitignore vendored
View File

@ -1,5 +1,6 @@
rebuild-images.sh rebuild-images.sh
data/conf/sogo/sieve.creds data/conf/sogo/sieve.creds
data/conf/phpfpm/sogo-sso/sogo-sso.pass
data/conf/dovecot/dovecot-master.passwd data/conf/dovecot/dovecot-master.passwd
data/conf/dovecot/dovecot-master.userdb data/conf/dovecot/dovecot-master.userdb
mailcow.conf mailcow.conf
@ -24,6 +25,7 @@ data/conf/nginx/*.custom
data/conf/nginx/*.bak data/conf/nginx/*.bak
data/conf/dovecot/acl_anyone data/conf/dovecot/acl_anyone
data/conf/dovecot/mail_plugins* data/conf/dovecot/mail_plugins*
data/conf/dovecot/sogo-sso.conf
data/conf/dovecot/extra.conf data/conf/dovecot/extra.conf
data/conf/rspamd/custom/* data/conf/rspamd/custom/*
data/conf/portainer/ data/conf/portainer/

View File

@ -5,6 +5,16 @@ exec 5>&1
# Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6 # Thanks to https://github.com/cvmiller -> https://github.com/cvmiller/expand6
source /srv/expand6.sh source /srv/expand6.sh
# Skipping IP check when we like to live dangerously
if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
SKIP_IP_CHECK=y
fi
# Skipping HTTP check when we like to live dangerously
if [[ "${SKIP_HTTP_VERIFICATION}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
SKIP_HTTP_VERIFICATION=y
fi
log_f() { log_f() {
if [[ ${2} == "no_nl" ]]; then if [[ ${2} == "no_nl" ]]; then
echo -n "$(date) - ${1}" echo -n "$(date) - ${1}"
@ -42,7 +52,6 @@ mkdir -p ${ACME_BASE}/acme
[[ -f ${ACME_BASE}/acme/private/privkey.pem ]] && mv ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/key.pem [[ -f ${ACME_BASE}/acme/private/privkey.pem ]] && mv ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/key.pem
[[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/account.pem [[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/account.pem
reload_configurations(){ reload_configurations(){
# Reading container IDs # Reading container IDs
# Wrapping as array to ensure trimmed content when calling $NGINX etc. # Wrapping as array to ensure trimmed content when calling $NGINX etc.
@ -121,7 +130,10 @@ verify_challenge_path(){
# verify_challenge_path URL 4|6 # verify_challenge_path URL 4|6
RAND_FILE=${RANDOM}${RANDOM}${RANDOM} RAND_FILE=${RANDOM}${RANDOM}${RANDOM}
touch /var/www/acme/${RAND_FILE} touch /var/www/acme/${RAND_FILE}
if [[ "$(curl -${2} http://${1}/.well-known/acme-challenge/${RAND_FILE} --write-out %{http_code} --silent --output /dev/null)" =~ ^(2|3) ]]; then if [[ ${SKIP_HTTP_VERIFICATION} == "y" ]]; then
echo '(skipping check, returning 0)'
return 0
elif [[ "$(curl -${2} http://${1}/.well-known/acme-challenge/${RAND_FILE} --write-out %{http_code} --silent --output /dev/null)" =~ ^(2|3) ]]; then
rm /var/www/acme/${RAND_FILE} rm /var/www/acme/${RAND_FILE}
return 0 return 0
else else
@ -156,6 +168,7 @@ else
exec env TRIGGER_RESTART=1 $(readlink -f "$0") exec env TRIGGER_RESTART=1 $(readlink -f "$0")
fi fi
fi fi
chmod 600 ${ACME_BASE}/key.pem
log_f "Waiting for database... " no_nl log_f "Waiting for database... " no_nl
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
@ -196,10 +209,8 @@ while true; do
log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem" log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem"
fi fi
# Skipping IP check when we like to live dangerously chmod 600 ${ACME_BASE}/acme/key.pem
if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then chmod 600 ${ACME_BASE}/acme/account.pem
SKIP_IP_CHECK=y
fi
# Cleaning up and init validation arrays # Cleaning up and init validation arrays
unset SQL_DOMAIN_ARR unset SQL_DOMAIN_ARR
@ -476,6 +487,7 @@ while true; do
ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64) ACME_RESPONSE_B64=$(echo "${ACME_RESPONSE}" | openssl enc -e -A -base64)
log_f "${ACME_RESPONSE_B64}" redis_only b64 log_f "${ACME_RESPONSE_B64}" redis_only b64
log_f "Retrying in 30 minutes..." log_f "Retrying in 30 minutes..."
redis-cli -h redis SET ACME_FAIL_TIME "$(date +%s)"
sleep 30m sleep 30m
exec $(readlink -f "$0") exec $(readlink -f "$0")
;; ;;

View File

@ -7,19 +7,29 @@ if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
fi fi
# Prepare whitelist # Prepare whitelist
mkdir -p /run/clamav /var/lib/clamav
if [[ -s /etc/clamav/whitelist.ign2 ]]; then if [[ -s /etc/clamav/whitelist.ign2 ]]; then
echo "Copying non-empty whitelist.ign2 to /var/lib/clamav/whitelist.ign2"
cp /etc/clamav/whitelist.ign2 /var/lib/clamav/whitelist.ign2 cp /etc/clamav/whitelist.ign2 /var/lib/clamav/whitelist.ign2
fi fi
if [[ ! -f /var/lib/clamav/whitelist.ign2 ]]; then if [[ ! -f /var/lib/clamav/whitelist.ign2 ]]; then
echo "Creating /var/lib/clamav/whitelist.ign2"
echo "Example-Signature.Ignore-1" > /var/lib/clamav/whitelist.ign2 echo "Example-Signature.Ignore-1" > /var/lib/clamav/whitelist.ign2
fi fi
chown clamav:clamav /var/lib/clamav/whitelist.ign2
mkdir -p /run/clamav /var/lib/clamav chown clamav:clamav -R /var/lib/clamav /run/clamav
chown clamav:clamav /run/clamav /var/lib/clamav
chmod 750 /run/clamav
chmod 755 /var/lib/clamav chmod 755 /var/lib/clamav
chmod 644 -R /var/lib/clamav/*
chmod 750 /run/clamav
echo "Stating whitelist.ign2"
stat /var/lib/clamav/whitelist.ign2
dos2unix /var/lib/clamav/whitelist.ign2 dos2unix /var/lib/clamav/whitelist.ign2
sed -i '/^\s*$/d' /var/lib/clamav/whitelist.ign2 sed -i '/^\s*$/d' /var/lib/clamav/whitelist.ign2
BACKGROUND_TASKS=() BACKGROUND_TASKS=()
@ -38,7 +48,7 @@ while true; do
sleep 2m sleep 2m
SANE_MIRRORS="$(dig +ignore +short rsync.sanesecurity.net)" SANE_MIRRORS="$(dig +ignore +short rsync.sanesecurity.net)"
for sane_mirror in ${SANE_MIRRORS}; do for sane_mirror in ${SANE_MIRRORS}; do
rsync -avp --chown=clamav:clamav --timeout=5 rsync://${sane_mirror}/sanesecurity/ \ rsync -avp --chown=clamav:clamav --chmod=Du=rwx,Dgo=rx,Fu=rw,Fog=r --timeout=5 rsync://${sane_mirror}/sanesecurity/ \
--include 'blurl.ndb' \ --include 'blurl.ndb' \
--include 'junk.ndb' \ --include 'junk.ndb' \
--include 'jurlbl.ndb' \ --include 'jurlbl.ndb' \

View File

@ -3,8 +3,8 @@ LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV LC_ALL C ENV LC_ALL C
ENV DOVECOT_VERSION 2.3.4 ENV DOVECOT_VERSION 2.3.5.1
ENV PIGEONHOLE_VERSION 0.5.4 ENV PIGEONHOLE_VERSION 0.5.5
RUN apt-get update && apt-get -y --no-install-recommends install \ RUN apt-get update && apt-get -y --no-install-recommends install \
automake \ automake \

View File

@ -106,7 +106,7 @@ chmod 644 /usr/local/etc/dovecot/mail_plugins /usr/local/etc/dovecot/mail_plugin
cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf cat <<EOF > /usr/local/etc/dovecot/sql/dovecot-dict-sql-userdb.conf
driver = mysql driver = mysql
connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}" connect = "host=/var/run/mysqld/mysqld.sock dbname=${DBNAME} user=${DBUSER} password=${DBPASS}"
user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/:VOLATILEDIR=/var/volatile/%u') AS mail, 5000 AS uid, 5000 AS gid, concat('*:bytes=', quota) AS quota_rule FROM mailbox WHERE username = '%u' AND active = '1' user_query = SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(attributes, '$.mailbox_format')), mailbox_path_prefix, '%d/%n/${MAILDIR_SUB}:VOLATILEDIR=/var/volatile/%u') 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'; iterate_query = SELECT username FROM mailbox WHERE active='1';
EOF EOF
@ -127,6 +127,10 @@ if [[ $(stat -c %U /var/vmail/) != "vmail" ]] ; then chown -R vmail:vmail /var/v
if [[ $(stat -c %U /var/vmail/_garbage) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail/_garbage ; fi if [[ $(stat -c %U /var/vmail/_garbage) != "vmail" ]] ; then chown -R vmail:vmail /var/vmail/_garbage ; fi
if [[ $(stat -c %U /var/attachments) != "vmail" ]] ; then chown -R vmail:vmail /var/attachments ; fi if [[ $(stat -c %U /var/attachments) != "vmail" ]] ; then chown -R vmail:vmail /var/attachments ; fi
# Cleanup random user maildirs
rm -rf /var/vmail/mailcow.local/*
# Create random master for SOGo sieve features # Create random master for SOGo sieve features
RAND_USER=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 16 | head -n 1) 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) RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 24 | head -n 1)
@ -135,6 +139,21 @@ echo ${RAND_USER}@mailcow.local:{SHA1}$(echo -n ${RAND_PASS} | sha1sum | awk '{p
echo ${RAND_USER}@mailcow.local::5000:5000:::: > /usr/local/etc/dovecot/dovecot-master.userdb echo ${RAND_USER}@mailcow.local::5000:5000:::: > /usr/local/etc/dovecot/dovecot-master.userdb
echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds echo ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/sieve.creds
if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
# Create random master Password for SOGo 'login as user' via proxy auth
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
cat <<EOF > /usr/local/etc/dovecot/sogo-sso.conf
passdb {
driver = static
args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
}
EOF
else
rm -f /usr/local/etc/dovecot/sogo-sso.pass
rm -f /usr/local/etc/dovecot/sogo-sso.conf
fi
# 401 is user dovecot # 401 is user dovecot
if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then if [[ ! -s /mail_crypt/ecprivkey.pem || ! -s /mail_crypt/ecpubkey.pem ]]; then
openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem openssl ecparam -name prime256v1 -genkey | openssl pkey -out /mail_crypt/ecprivkey.pem
@ -174,7 +193,7 @@ echo '30 3 * * * vmail /usr/local/bin/doveadm quota recalc -A' > /etc/cron.d/d
echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/console 2>&1' > /etc/cron.d/trim_logs echo '* * * * * vmail /usr/local/bin/trim_logs.sh >> /dev/console 2>&1' > /etc/cron.d/trim_logs
echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/console 2>&1' > /etc/cron.d/maildir_gc echo '25 * * * * vmail /usr/local/bin/maildir_gc.sh >> /dev/console 2>&1' > /etc/cron.d/maildir_gc
echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/console 2>&1' > /etc/cron.d/sa-rules echo '30 1 * * * root /usr/local/bin/sa-rules.sh >> /dev/console 2>&1' > /etc/cron.d/sa-rules
echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize echo '0 2 * * * root /usr/bin/curl http://solr:8983/solr/dovecot-fts/update?optimize=true >> /dev/console 2>&1' > /etc/cron.d/solr-optimize
echo '*/20 * * * * vmail /usr/local/bin/quarantine_notify.py >> /dev/console 2>&1' > /etc/cron.d/quarantine_notify echo '*/20 * * * * vmail /usr/local/bin/quarantine_notify.py >> /dev/console 2>&1' > /etc/cron.d/quarantine_notify
# Fix more than 1 hardlink issue # Fix more than 1 hardlink issue

View File

@ -123,5 +123,3 @@ for record in records:
if last_notification == 0 or (last_notification + 604800) < time_now: if last_notification == 0 or (last_notification + 604800) < time_now:
print "Notifying %s about %d new items in quarantine" % (record['rcpt'], record['counter']) print "Notifying %s about %d new items in quarantine" % (record['rcpt'], record['counter'])
notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl']) notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'])
else:
break

View File

@ -31,7 +31,8 @@ RULES[2] = '-login: Disconnected \(auth failed, .+\): user=.*, method=.+, rip=([
RULES[3] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' RULES[3] = '-login: Aborted login \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
RULES[4] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked' RULES[4] = 'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
RULES[5] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)' RULES[5] = 'mailcow UI: Invalid password for .+ by ([0-9a-f\.:]+)'
#RULES[6] = '-login: Aborted login \(no auth .+\): user=.+, rip=([0-9a-f\.:]+), lip.+' RULES[6] = '([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
#RULES[7] = '-login: Aborted login \(no auth .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
bans = {} bans = {}
log = {} log = {}

View File

@ -25,23 +25,26 @@ CONTAINER_ID=
# Todo: Better check if upgrade failed # Todo: Better check if upgrade failed
# This can happen due to a broken sogo_view # This can happen due to a broken sogo_view
[ -s /mysql_upgrade_loop ] && SQL_LOOP_C=$(cat /mysql_upgrade_loop) [ -s /mysql_upgrade_loop ] && SQL_LOOP_C=$(cat /mysql_upgrade_loop)
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(\"mysql-mailcow\")) | .id") until [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ ^[[:alnum:]]*$ ]]; do
if [[ ! -z "${CONTAINER_ID}" ]] && [[ "${CONTAINER_ID}" =~ [^a-zA-Z0-9] ]]; then CONTAINER_ID=$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" 2> /dev/null | jq -rc "select( .name | tostring | contains(\"mysql-mailcow\")) | .id" 2> /dev/null)
SQL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json' | jq -r .type) done
if [[ ${SQL_UPGRADE_RETURN} == 'warning' ]]; then echo "MySQL @ ${CONTAINER_ID}"
if [ -z ${SQL_LOOP_C} ]; then SQL_UPGRADE_RETURN=$(curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_upgrade"}' --silent -H 'Content-type: application/json' | jq -r .type)
echo 1 > /mysql_upgrade_loop if [[ ${SQL_UPGRADE_RETURN} == 'warning' ]]; then
echo "MySQL applied an upgrade, restarting PHP-FPM..." if [ -z ${SQL_LOOP_C} ]; then
exit 1 echo 1 > /mysql_upgrade_loop
else echo "MySQL applied an upgrade, restarting PHP-FPM..."
rm /mysql_upgrade_loop exit 1
echo "MySQL was not applied previously, skipping. Restart php-fpm-mailcow to retry or run mysql_upgrade manually." else
while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do rm /mysql_upgrade_loop
echo "Waiting for SQL to return..." echo "MySQL was not applied previously, skipping. Restart php-fpm-mailcow to retry or run mysql_upgrade manually."
sleep 2 while ! mysqladmin status --socket=/var/run/mysqld/mysqld.sock -u${DBUSER} -p${DBPASS} --silent; do
done echo "Waiting for SQL to return..."
fi sleep 2
done
fi fi
else
echo "MySQL is up-to-date"
fi fi
# Trigger db init # Trigger db init

View File

@ -104,8 +104,8 @@ query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts
WHERE id IN ( WHERE id IN (
SELECT relayhost FROM domain SELECT relayhost FROM domain
WHERE CONCAT('@', domain) = '%s' WHERE CONCAT('@', domain) = '%s'
OR '%s' IN ( OR domain IN (
SELECT CONCAT('@', alias_domain) FROM alias_domain SELECT target_domain FROM alias_domain WHERE CONCAT('@', alias_domain) = '%s'
) )
) )
AND active = '1' AND active = '1'

View File

@ -13,7 +13,6 @@ RUN apt-get update && apt-get install -y \
&& echo "deb https://rspamd.com/apt-stable/ bionic main" > /etc/apt/sources.list.d/rspamd.list \ && echo "deb https://rspamd.com/apt-stable/ bionic main" > /etc/apt/sources.list.d/rspamd.list \
&& apt-get update && apt-get install -y rspamd \ && apt-get update && apt-get install -y rspamd \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& echo '.include $LOCAL_CONFDIR/local.d/rspamd.conf.local' > /etc/rspamd/rspamd.conf.local \
&& apt-get autoremove --purge \ && apt-get autoremove --purge \
&& apt-get clean \ && apt-get clean \
&& mkdir -p /run/rspamd \ && mkdir -p /run/rspamd \
@ -21,7 +20,6 @@ RUN apt-get update && apt-get install -y \
COPY settings.conf /etc/rspamd/settings.conf COPY settings.conf /etc/rspamd/settings.conf
COPY docker-entrypoint.sh /docker-entrypoint.sh COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY metadata_exporter.lua /usr/share/rspamd/lua/metadata_exporter.lua
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]

View File

@ -13,6 +13,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
gettext \ gettext \
gnupg \ gnupg \
mysql-client \ mysql-client \
rsync \
supervisor \ supervisor \
syslog-ng \ syslog-ng \
syslog-ng-core \ syslog-ng-core \
@ -52,6 +53,4 @@ RUN chmod +x /bootstrap-sogo.sh \
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
VOLUME /usr/lib/GNUstep/SOGo/
RUN rm -rf /tmp/* /var/tmp/* RUN rm -rf /tmp/* /var/tmp/*

View File

@ -83,9 +83,16 @@ EOF
done done
mkdir -p /var/lib/sogo/GNUstep/Defaults/ if [[ "${ALLOW_ADMIN_EMAIL_LOGIN}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
TRUST_PROXY="YES"
else
TRUST_PROXY="NO"
fi
# cat /dev/urandom seems to hang here occasionally and is not recommended anyway, better use openssl
RAND_PASS=$(openssl rand -base64 16 | tr -dc _A-Z-a-z-0-9)
# Generate plist header with timezone data # Generate plist header with timezone data
mkdir -p /var/lib/sogo/GNUstep/Defaults/
cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//GNUstep//DTD plist 0.9//EN" "http://www.gnustep.org/plist-0_9.xml"> <!DOCTYPE plist PUBLIC "-//GNUstep//DTD plist 0.9//EN" "http://www.gnustep.org/plist-0_9.xml">
@ -93,6 +100,12 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
<dict> <dict>
<key>OCSAclURL</key> <key>OCSAclURL</key>
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl</string> <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl</string>
<key>SOGoIMAPServer</key>
<string>imap://${IPV4_NETWORK}.250:143/?tls=YES</string>
<key>SOGoTrustProxyAuthentication</key>
<string>${TRUST_PROXY}</string>
<key>SOGoEncryptionKey</key>
<string>${RAND_PASS}</string>
<key>OCSCacheFolderURL</key> <key>OCSCacheFolderURL</key>
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder</string> <string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_cache_folder</string>
<key>OCSEMailAlarmsFolderURL</key> <key>OCSEMailAlarmsFolderURL</key>
@ -183,4 +196,8 @@ fi
# Copy logo, if any # Copy logo, if any
[[ -f /etc/sogo/sogo-full.svg ]] && cp /etc/sogo/sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg [[ -f /etc/sogo/sogo-full.svg ]] && cp /etc/sogo/sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
# Rsync web content
echo "Syncing web content with named volume"
rsync -a /usr/lib/GNUstep/SOGo/. /sogo_web/
exec gosu sogo /usr/sbin/sogod exec gosu sogo /usr/sbin/sogod

View File

@ -1,10 +1,13 @@
FROM solr:7-alpine FROM solr:7.7-alpine
USER root USER root
COPY docker-entrypoint.sh / COPY docker-entrypoint.sh /
COPY solr-config-7.7.0.xml /
COPY solr-schema-7.7.0.xml /
RUN apk --no-cache add su-exec curl tzdata \ RUN apk --no-cache add su-exec curl tzdata \
&& chmod +x /docker-entrypoint.sh \ && chmod +x /docker-entrypoint.sh \
&& sync \ && sync \
&& /docker-entrypoint.sh --bootstrap && bash /docker-entrypoint.sh --bootstrap
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]

192
data/Dockerfiles/solr/docker-entrypoint.sh Executable file → Normal file
View File

@ -18,192 +18,44 @@ fi
set -e set -e
# allow easier debugging with `docker run -e VERBOSE=yes`
if [[ "$VERBOSE" = "yes" ]]; then
set -x
fi
# run the optional initdb # run the optional initdb
. /opt/docker-solr/scripts/run-initdb . /opt/docker-solr/scripts/run-initdb
function solr_config() {
curl -XPOST http://localhost:8983/solr/dovecot/schema -H 'Content-type:application/json' -d '{
"add-field-type":{
"name":"long",
"class":"solr.TrieLongField"
},
"add-field-type":{
"name":"text",
"class":"solr.TextField",
"positionIncrementGap":100,
"indexAnalyser":{
"tokenizer":{
"class":"solr.StandardTokenizerFactory"
},
"filter":{
"class":"solr.WordDelimiterFilterFactory",
"generateWordParts":1,
"generateNumberParts":1,
"catenateWorks":1,
"catenateNumbers":1,
"catenateAll":0
},
"filter":{
"class":"solr.LowerCaseFilterFactory"
},
"filter":{
"class":"solr.KeywordMarkerFilterFactory",
"protected":"protwords.txt"
}
},
"queryAnalyzer":{
"tokenizer":{
"class":"solr.StandardTokenizerFactory"
},
"filter":{
"synonyms":"synonyms.txt",
"ignoreCase":true,
"expand":true
},
"filter":{
"class":"solr.LowerCaseFilterFactory"
},
"filter":{
"class":"solr.WordDelimiterFilterFactory",
"generateWordParts":1,
"generateNumberParts":1,
"catenateWords":0,
"catenateNumbers":0,
"catenateAll":0,
"splitOnCaseChange":1
}
}
},
"add-field":{
"name":"uid",
"type":"long",
"indexed":true,
"stored":true,
"required":true
},
"add-field":{
"name":"box",
"type":"string",
"indexed":true,
"stored":true,
"required":true
},
"add-field":{
"name":"user",
"type":"string",
"indexed":true,
"stored":true,
"required":true
},
"add-field":{
"name":"hdr",
"type":"text",
"indexed":true,
"stored":false
},
"add-field":{
"name":"body",
"type":"text",
"indexed":true,
"stored":false
},
"add-field":{
"name":"from",
"type":"text",
"indexed":true,
"stored":false
},
"add-field":{
"name":"to",
"type":"text",
"indexed":true,
"stored":false
},
"add-field":{
"name":"cc",
"type":"text",
"indexed":true,
"stored":false
},
"add-field":{
"name":"bcc",
"type":"text",
"indexed":true,
"stored":false
},
"add-field":{
"name":"subject",
"type":"text",
"indexed":true,
"stored":false
}
}'
curl -XPOST http://localhost:8983/solr/dovecot/config -H 'Content-type:application/json' -d '{
"update-requesthandler":{
"name":"/select",
"class":"solr.SearchHandler",
"defaults":{
"wt":"xml"
}
}
}'
curl -XPOST http://localhost:8983/solr/dovecot/config/updateHandler -d '{
"set-property": {
"updateHandler.autoSoftCommit.maxDocs":500,
"updateHandler.autoSoftCommit.maxTime":120000,
"updateHandler.autoCommit.maxDocs":200,
"updateHandler.autoCommit.maxTime":1800000,
"updateHandler.autoCommit.openSearcher":false
}
}'
}
# fixing volume permission # fixing volume permission
[[ -d /opt/solr/server/solr/dovecot/data ]] && chown -R solr:solr /opt/solr/server/solr/dovecot/data [[ -d /opt/solr/server/solr/dovecot-fts/data ]] && chown -R solr:solr /opt/solr/server/solr/dovecot-fts/data
if [[ "${1}" != "--bootstrap" ]]; then if [[ "${1}" != "--bootstrap" ]]; then
sed -i '/SOLR_HEAP=/c\SOLR_HEAP="'${SOLR_HEAP:-1024}'m"' /opt/solr/bin/solr.in.sh sed -i '/SOLR_HEAP=/c\SOLR_HEAP="'${SOLR_HEAP:-1024}'m"' /opt/solr/bin/solr.in.sh
else else
sed -i '/SOLR_HEAP=/c\SOLR_HEAP="256m"' /opt/solr/bin/solr.in.sh sed -i '/SOLR_HEAP=/c\SOLR_HEAP="256m"' /opt/solr/bin/solr.in.sh
fi fi
# start a Solr so we can use the Schema API, but only on localhost, if [[ "${1}" == "--bootstrap" ]]; then
# so that clients don't see Solr until we have configured it. echo "Creating initial configuration"
echo "Starting local Solr instance to setup configuration" echo "Modifying default config set"
su-exec solr start-local-solr cp /solr-config-7.7.0.xml /opt/solr/server/solr/configsets/_default/conf/solrconfig.xml
cp /solr-schema-7.7.0.xml /opt/solr/server/solr/configsets/_default/conf/schema.xml
rm /opt/solr/server/solr/configsets/_default/conf/managed-schema
# keep a sentinel file so we don't try to create the core a second time echo "Starting local Solr instance to setup configuration"
# for example when we restart a container. su-exec solr start-local-solr
SENTINEL=/opt/docker-solr/core_created
if [[ -f ${SENTINEL} ]]; then echo "Creating core \"dovecot-fts\""
echo "skipping core creation" su-exec solr /opt/solr/bin/solr create -c "dovecot-fts"
else
echo "Creating core \"dovecot\""
su-exec solr /opt/solr/bin/solr create -c "dovecot"
# See https://github.com/docker-solr/docker-solr/issues/27 # See https://github.com/docker-solr/docker-solr/issues/27
echo "Checking core" echo "Checking core"
while ! wget -O - 'http://localhost:8983/solr/admin/cores?action=STATUS' | grep -q instanceDir; do while ! wget -O - 'http://localhost:8983/solr/admin/cores?action=STATUS' | grep -q instanceDir; do
echo "Could not find any cores, waiting..." echo "Could not find any cores, waiting..."
sleep 5 sleep 3
done done
echo "Created core \"dovecot\""
touch ${SENTINEL} echo "Created core \"dovecot-fts\""
echo "Stopping local Solr"
su-exec solr stop-local-solr
exit 0
fi fi
echo "Starting configuration" exec su-exec solr solr-foreground
solr_config
echo "Stopping local Solr"
su-exec solr stop-local-solr
if [[ "${1}" == "--bootstrap" ]]; then
exit 0
else
exec su-exec solr solr-foreground
fi

View File

@ -0,0 +1,289 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- This is the default config with stuff non-essential to Dovecot removed. -->
<config>
<!-- Controls what version of Lucene various components of Solr
adhere to. Generally, you want to use the latest version to
get all bug fixes and improvements. It is highly recommended
that you fully re-index after changing this setting as it can
affect both how text is indexed and queried.
-->
<luceneMatchVersion>7.7.0</luceneMatchVersion>
<!-- A 'dir' option by itself adds any files found in the directory
to the classpath, this is useful for including all jars in a
directory.
When a 'regex' is specified in addition to a 'dir', only the
files in that directory which completely match the regex
(anchored on both ends) will be included.
If a 'dir' option (with or without a regex) is used and nothing
is found that matches, a warning will be logged.
The examples below can be used to load some solr-contribs along
with their external dependencies.
-->
<lib dir="${solr.install.dir:../../../..}/contrib/extraction/lib" regex=".*\.jar" />
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-cell-\d.*\.jar" />
<lib dir="${solr.install.dir:../../../..}/contrib/clustering/lib/" regex=".*\.jar" />
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-clustering-\d.*\.jar" />
<lib dir="${solr.install.dir:../../../..}/contrib/langid/lib/" regex=".*\.jar" />
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-langid-\d.*\.jar" />
<lib dir="${solr.install.dir:../../../..}/contrib/velocity/lib" regex=".*\.jar" />
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-velocity-\d.*\.jar" />
<!-- Data Directory
Used to specify an alternate directory to hold all index data
other than the default ./data under the Solr home. If
replication is in use, this should match the replication
configuration.
-->
<dataDir>${solr.data.dir:}</dataDir>
<!-- The default high-performance update handler -->
<updateHandler class="solr.DirectUpdateHandler2">
<!-- Enables a transaction log, used for real-time get, durability, and
and solr cloud replica recovery. The log can grow as big as
uncommitted changes to the index, so use of a hard autoCommit
is recommended (see below).
"dir" - the target directory for transaction logs, defaults to the
solr data directory.
"numVersionBuckets" - sets the number of buckets used to keep
track of max version values when checking for re-ordered
updates; increase this value to reduce the cost of
synchronizing access to version buckets during high-volume
indexing, this requires 8 bytes (long) * numVersionBuckets
of heap space per Solr core.
-->
<updateLog>
<str name="dir">${solr.ulog.dir:}</str>
<int name="numVersionBuckets">${solr.ulog.numVersionBuckets:65536}</int>
</updateLog>
<!-- AutoCommit
Perform a hard commit automatically under certain conditions.
Instead of enabling autoCommit, consider using "commitWithin"
when adding documents.
http://wiki.apache.org/solr/UpdateXmlMessages
maxDocs - Maximum number of documents to add since the last
commit before automatically triggering a new commit.
maxTime - Maximum amount of time in ms that is allowed to pass
since a document was added before automatically
triggering a new commit.
openSearcher - if false, the commit causes recent index changes
to be flushed to stable storage, but does not cause a new
searcher to be opened to make those changes visible.
If the updateLog is enabled, then it's highly recommended to
have some sort of hard autoCommit to limit the log size.
-->
<autoCommit>
<maxTime>${solr.autoCommit.maxTime:15000}</maxTime>
<openSearcher>false</openSearcher>
</autoCommit>
<!-- softAutoCommit is like autoCommit except it causes a
'soft' commit which only ensures that changes are visible
but does not ensure that data is synced to disk. This is
faster and more near-realtime friendly than a hard commit.
-->
<autoSoftCommit>
<maxTime>${solr.autoSoftCommit.maxTime:-1}</maxTime>
</autoSoftCommit>
<!-- Update Related Event Listeners
Various IndexWriter related events can trigger Listeners to
take actions.
postCommit - fired after every commit or optimize command
postOptimize - fired after every optimize command
-->
</updateHandler>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Query section - these settings control query time things like caches
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<query>
<!-- Solr Internal Query Caches
There are two implementations of cache available for Solr,
LRUCache, based on a synchronized LinkedHashMap, and
FastLRUCache, based on a ConcurrentHashMap.
FastLRUCache has faster gets and slower puts in single
threaded operation and thus is generally faster than LRUCache
when the hit ratio of the cache is high (> 75%), and may be
faster under other scenarios on multi-cpu systems.
-->
<!-- Filter Cache
Cache used by SolrIndexSearcher for filters (DocSets),
unordered sets of *all* documents that match a query. When a
new searcher is opened, its caches may be prepopulated or
"autowarmed" using data from caches in the old searcher.
autowarmCount is the number of items to prepopulate. For
LRUCache, the autowarmed items will be the most recently
accessed items.
Parameters:
class - the SolrCache implementation LRUCache or
(LRUCache or FastLRUCache)
size - the maximum number of entries in the cache
initialSize - the initial capacity (number of entries) of
the cache. (see java.util.HashMap)
autowarmCount - the number of entries to prepopulate from
and old cache.
maxRamMB - the maximum amount of RAM (in MB) that this cache is allowed
to occupy. Note that when this option is specified, the size
and initialSize parameters are ignored.
-->
<filterCache class="solr.FastLRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<!-- Query Result Cache
Caches results of searches - ordered lists of document ids
(DocList) based on a query, a sort, and the range of documents requested.
Additional supported parameter by LRUCache:
maxRamMB - the maximum amount of RAM (in MB) that this cache is allowed
to occupy
-->
<queryResultCache class="solr.LRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<!-- Document Cache
Caches Lucene Document objects (the stored fields for each
document). Since Lucene internal document ids are transient,
this cache will not be autowarmed.
-->
<documentCache class="solr.LRUCache"
size="512"
initialSize="512"
autowarmCount="0"/>
<!-- custom cache currently used by block join -->
<cache name="perSegFilter"
class="solr.search.LRUCache"
size="10"
initialSize="0"
autowarmCount="10"
regenerator="solr.NoOpRegenerator" />
<!-- Lazy Field Loading
If true, stored fields that are not requested will be loaded
lazily. This can result in a significant speed improvement
if the usual case is to not load all stored fields,
especially if the skipped fields are large compressed text
fields.
-->
<enableLazyFieldLoading>true</enableLazyFieldLoading>
<!-- Result Window Size
An optimization for use with the queryResultCache. When a search
is requested, a superset of the requested number of document ids
are collected. For example, if a search for a particular query
requests matching documents 10 through 19, and queryWindowSize is 50,
then documents 0 through 49 will be collected and cached. Any further
requests in that range can be satisfied via the cache.
-->
<queryResultWindowSize>20</queryResultWindowSize>
<!-- Maximum number of documents to cache for any entry in the
queryResultCache.
-->
<queryResultMaxDocsCached>200</queryResultMaxDocsCached>
<!-- Use Cold Searcher
If a search request comes in and there is no current
registered searcher, then immediately register the still
warming searcher and use it. If "false" then all requests
will block until the first searcher is done warming.
-->
<useColdSearcher>false</useColdSearcher>
</query>
<!-- Request Dispatcher
This section contains instructions for how the SolrDispatchFilter
should behave when processing requests for this SolrCore.
-->
<requestDispatcher>
<httpCaching never304="true" />
</requestDispatcher>
<!-- Request Handlers
http://wiki.apache.org/solr/SolrRequestHandler
Incoming queries will be dispatched to a specific handler by name
based on the path specified in the request.
If a Request Handler is declared with startup="lazy", then it will
not be initialized until the first request that uses it.
-->
<!-- SearchHandler
http://wiki.apache.org/solr/SearchHandler
For processing Search Queries, the primary Request Handler
provided with Solr is "SearchHandler" It delegates to a sequent
of SearchComponents (see below) and supports distributed
queries across multiple shards
-->
<requestHandler name="/select" class="solr.SearchHandler">
<!-- default values for query parameters can be specified, these
will be overridden by parameters in the request
-->
<lst name="defaults">
<str name="echoParams">explicit</str>
<int name="rows">10</int>
</lst>
</requestHandler>
<initParams path="/update/**,/select">
<lst name="defaults">
<str name="df">_text_</str>
</lst>
</initParams>
<!-- Response Writers
http://wiki.apache.org/solr/QueryResponseWriter
Request responses will be written using the writer specified by
the 'wt' request parameter matching the name of a registered
writer.
The "default" writer is the default and will be used if 'wt' is
not specified in the request.
-->
<queryResponseWriter name="xml"
default="true"
class="solr.XMLResponseWriter" />
</config>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema name="dovecot-fts" version="2.0">
<fieldType name="string" class="solr.StrField" omitNorms="true" sortMissingLast="true"/>
<fieldType name="long" class="solr.LongPointField" positionIncrementGap="0"/>
<fieldType name="boolean" class="solr.BoolField" sortMissingLast="true"/>
<fieldType name="text" class="solr.TextField" autoGeneratePhraseQueries="true" positionIncrementGap="100">
<analyzer type="index">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.EdgeNGramFilterFactory" minGramSize="3" maxGramSize="20"/>
<filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/>
<filter class="solr.WordDelimiterGraphFilterFactory" catenateNumbers="1" generateNumberParts="1" splitOnCaseChange="1" generateWordParts="1" splitOnNumerics="1" catenateAll="1" catenateWords="1"/>
<filter class="solr.FlattenGraphFilterFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="solr.StandardTokenizerFactory"/>
<filter class="solr.SynonymGraphFilterFactory" expand="true" ignoreCase="true" synonyms="synonyms.txt"/>
<filter class="solr.FlattenGraphFilterFactory"/>
<filter class="solr.StopFilterFactory" words="stopwords.txt" ignoreCase="true"/>
<filter class="solr.WordDelimiterGraphFilterFactory" catenateNumbers="1" generateNumberParts="1" splitOnCaseChange="1" generateWordParts="1" splitOnNumerics="1" catenateAll="1" catenateWords="1"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.KeywordMarkerFilterFactory" protected="protwords.txt"/>
<filter class="solr.PorterStemFilterFactory"/>
</analyzer>
</fieldType>
<field name="id" type="string" indexed="true" required="true" stored="true"/>
<field name="uid" type="long" indexed="true" required="true" stored="true"/>
<field name="box" type="string" indexed="true" required="true" stored="true"/>
<field name="user" type="string" indexed="true" required="true" stored="true"/>
<field name="hdr" type="text" indexed="true" stored="false"/>
<field name="body" type="text" indexed="true" stored="false"/>
<field name="from" type="text" indexed="true" stored="false"/>
<field name="to" type="text" indexed="true" stored="false"/>
<field name="cc" type="text" indexed="true" stored="false"/>
<field name="bcc" type="text" indexed="true" stored="false"/>
<field name="subject" type="text" indexed="true" stored="false"/>
<!-- Used by Solr internally: -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -5,6 +5,8 @@ trap "kill 0" EXIT
# Prepare # Prepare
BACKGROUND_TASKS=() BACKGROUND_TASKS=()
echo "Waiting for containers to settle..."
sleep 10
if [[ "${USE_WATCHDOG}" =~ ^([nN][oO]|[nN])+$ ]]; then if [[ "${USE_WATCHDOG}" =~ ^([nN][oO]|[nN])+$ ]]; then
echo -e "$(date) - USE_WATCHDOG=n, skipping watchdog..." echo -e "$(date) - USE_WATCHDOG=n, skipping watchdog..."
@ -37,7 +39,7 @@ progress() {
log_msg() { log_msg() {
if [[ ${2} != "no_redis" ]]; then if [[ ${2} != "no_redis" ]]; then
redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \ redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null tr '\r\n%&;$"_[]{}-' ' ')\"}" > /dev/null
fi fi
echo $(date) $(printf '%s\n' "${1}") echo $(date) $(printf '%s\n' "${1}")
} }
@ -115,7 +117,7 @@ nginx_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/nginx-mailcow touch /tmp/nginx-mailcow; echo "$(tail -50 /tmp/nginx-mailcow)" > /tmp/nginx-mailcow
host_ip=$(get_container_ip nginx-mailcow) host_ip=$(get_container_ip nginx-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 2>> /tmp/nginx-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 2>> /tmp/nginx-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
@ -140,7 +142,7 @@ unbound_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/unbound-mailcow touch /tmp/unbound-mailcow; echo "$(tail -50 /tmp/unbound-mailcow)" > /tmp/unbound-mailcow
host_ip=$(get_container_ip unbound-mailcow) host_ip=$(get_container_ip unbound-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_dns -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_dns -s ${host_ip} -H stackoverflow.com 2>> /tmp/unbound-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
@ -172,7 +174,7 @@ mysql_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/mysql-mailcow touch /tmp/mysql-mailcow; echo "$(tail -50 /tmp/mysql-mailcow)" > /tmp/mysql-mailcow
host_ip=$(get_container_ip mysql-mailcow) host_ip=$(get_container_ip mysql-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_mysql -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_mysql -s /var/run/mysqld/mysqld.sock -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 2>> /tmp/mysql-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
@ -198,7 +200,7 @@ sogo_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/sogo-mailcow touch /tmp/sogo-mailcow; echo "$(tail -50 /tmp/sogo-mailcow)" > /tmp/sogo-mailcow
host_ip=$(get_container_ip sogo-mailcow) host_ip=$(get_container_ip sogo-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 -R "SOGo\.MainUI" 2>> /tmp/sogo-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 -R "SOGo\.MainUI" 2>> /tmp/sogo-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
@ -223,7 +225,7 @@ postfix_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/postfix-mailcow touch /tmp/postfix-mailcow; echo "$(tail -50 /tmp/postfix-mailcow)" > /tmp/postfix-mailcow
host_ip=$(get_container_ip postfix-mailcow) host_ip=$(get_container_ip postfix-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f "watchdog@invalid" -C "RCPT TO:null@localhost" -C DATA -C . -R 250 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f "watchdog@invalid" -C "RCPT TO:null@localhost" -C DATA -C . -R 250 2>> /tmp/postfix-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
@ -249,7 +251,7 @@ clamd_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/clamd-mailcow touch /tmp/clamd-mailcow; echo "$(tail -50 /tmp/clamd-mailcow)" > /tmp/clamd-mailcow
host_ip=$(get_container_ip clamd-mailcow) host_ip=$(get_container_ip clamd-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_clamd -4 -H ${host_ip} 2>> /tmp/clamd-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_clamd -4 -H ${host_ip} 2>> /tmp/clamd-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
@ -274,7 +276,7 @@ dovecot_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/dovecot-mailcow touch /tmp/dovecot-mailcow; echo "$(tail -50 /tmp/dovecot-mailcow)" > /tmp/dovecot-mailcow
host_ip=$(get_container_ip dovecot-mailcow) host_ip=$(get_container_ip dovecot-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog@invalid" -C "RCPT TO:<watchdog@invalid>" -L -R "User doesn't exist" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog@invalid" -C "RCPT TO:<watchdog@invalid>" -L -R "User doesn't exist" 2>> /tmp/dovecot-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
@ -303,7 +305,7 @@ phpfpm_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/php-fpm-mailcow touch /tmp/php-fpm-mailcow; echo "$(tail -50 /tmp/php-fpm-mailcow)" > /tmp/php-fpm-mailcow
host_ip=$(get_container_ip php-fpm-mailcow) host_ip=$(get_container_ip php-fpm-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9001 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? )) /usr/lib/nagios/plugins/check_tcp -H ${host_ip} -p 9001 2>> /tmp/php-fpm-mailcow 1>&2; err_count=$(( ${err_count} + $? ))
@ -350,6 +352,38 @@ ratelimit_checks() {
return 1 return 1
} }
acme_checks() {
err_count=0
diff_c=0
THRESHOLD=1
ACME_LOG_STATUS=$(redis-cli -h redis GET ACME_FAIL_TIME)
if [[ -z "${ACME_LOG_STATUS}" ]]; then
redis-cli -h redis SET ACME_FAIL_TIME 0
ACME_LOG_STATUS=0
fi
# Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do
err_c_cur=${err_count}
ACME_LOG_STATUS_PREV=${ACME_LOG_STATUS}
ACME_LOG_STATUS=$(redis-cli -h redis GET ACME_FAIL_TIME)
if [[ ${ACME_LOG_STATUS_PREV} != ${ACME_LOG_STATUS} ]]; then
err_count=$(( ${err_count} + 1 ))
fi
[ ${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 "ACME" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
if [[ $? == 10 ]]; then
diff_c=0
sleep 1
else
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
fi
done
return 1
}
ipv6nat_checks() { ipv6nat_checks() {
err_count=0 err_count=0
diff_c=0 diff_c=0
@ -358,10 +392,11 @@ ipv6nat_checks() {
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
err_c_cur=${err_count} err_c_cur=${err_count}
IPV6NAT_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(\"ipv6nat\")) | .id") CONTAINERS=$(curl --silent --insecure https://dockerapi/containers/json)
IPV6NAT_CONTAINER_ID=$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\")) | .id")
if [[ ! -z ${IPV6NAT_CONTAINER_ID} ]]; then if [[ ! -z ${IPV6NAT_CONTAINER_ID} ]]; then
LATEST_STARTED="$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat\") | not)" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)" LATEST_STARTED="$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\") | not)" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)"
LATEST_IPV6NAT="$(curl --silent --insecure https://dockerapi/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat\"))" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)" LATEST_IPV6NAT="$(echo ${CONTAINERS} | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], StartedAt: .State.StartedAt}" | jq -rc "select( .name | tostring | contains(\"ipv6nat-mailcow\"))" | jq -rc .StartedAt | xargs -n1 date +%s -d | sort | tail -n1)"
DIFFERENCE_START_TIME=$(expr ${LATEST_IPV6NAT} - ${LATEST_STARTED} 2>/dev/null) DIFFERENCE_START_TIME=$(expr ${LATEST_IPV6NAT} - ${LATEST_STARTED} 2>/dev/null)
if [[ "${DIFFERENCE_START_TIME}" -lt 30 ]]; then if [[ "${DIFFERENCE_START_TIME}" -lt 30 ]]; then
err_count=$(( ${err_count} + 1 )) err_count=$(( ${err_count} + 1 ))
@ -375,12 +410,13 @@ ipv6nat_checks() {
sleep 1 sleep 1
else else
diff_c=0 diff_c=0
sleep 3600 sleep 300
fi fi
done done
return 1 return 1
} }
rspamd_checks() { rspamd_checks() {
err_count=0 err_count=0
diff_c=0 diff_c=0
@ -388,15 +424,14 @@ rspamd_checks() {
# Reduce error count by 2 after restarting an unhealthy container # Reduce error count by 2 after restarting an unhealthy container
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1 trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do while [ ${err_count} -lt ${THRESHOLD} ]; do
cat /dev/null > /tmp/rspamd-mailcow touch /tmp/rspamd-mailcow; echo "$(tail -50 /tmp/rspamd-mailcow)" > /tmp/rspamd-mailcow
host_ip=$(get_container_ip rspamd-mailcow) host_ip=$(get_container_ip rspamd-mailcow)
err_c_cur=${err_count} err_c_cur=${err_count}
SCORE=$(/usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan -d ' SCORE=$(echo 'To: null@localhost
To: null@localhost
From: watchdog@localhost From: watchdog@localhost
Empty Empty
' | jq -rc .required_score) ' | usr/bin/curl -s --data-binary @- --unix-socket /var/lib/rspamd/rspamd.sock http://rspamd/scan | jq -rc .required_score)
if [[ ${SCORE} != "9999" ]]; then if [[ ${SCORE} != "9999" ]]; then
echo "Rspamd settings check failed" 2>> /tmp/rspamd-mailcow 1>&2 echo "Rspamd settings check failed" 2>> /tmp/rspamd-mailcow 1>&2
err_count=$(( ${err_count} + 1)) err_count=$(( ${err_count} + 1))
@ -517,11 +552,21 @@ done
) & ) &
BACKGROUND_TASKS+=($!) BACKGROUND_TASKS+=($!)
(
while true; do
if ! acme_checks; then
log_msg "ACME client hit error limit"
echo acme-tiny > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
( (
while true; do while true; do
if ! ipv6nat_checks; then if ! ipv6nat_checks; then
log_msg "IPv6 NAT warning: ipv6nat container was not started at least 30s after siblings (not an error)" log_msg "IPv6 NAT warning: ipv6nat-mailcow container was not started at least 30s after siblings (not an error)"
echo ipv6nat > /tmp/com_pipe echo ipv6nat-mailcow > /tmp/com_pipe
fi fi
done done
) & ) &
@ -561,10 +606,16 @@ while true; do
CONTAINER_ID= CONTAINER_ID=
HAS_INITDB= HAS_INITDB=
read com_pipe_answer </tmp/com_pipe read com_pipe_answer </tmp/com_pipe
if [ -s "/tmp/${com_pipe_answer}" ]; then
cat "/tmp/${com_pipe_answer}"
fi
if [[ ${com_pipe_answer} == "ratelimit" ]]; then if [[ ${com_pipe_answer} == "ratelimit" ]]; then
log_msg "At least one ratelimit was applied" log_msg "At least one ratelimit was applied"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "No further information available." [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "Please see mailcow UI logs for further information."
elif [[ ${com_pipe_answer} =~ .+-mailcow ]] || [[ ${com_pipe_answer} == "ipv6nat" ]]; then elif [[ ${com_pipe_answer} == "acme-tiny" ]]; then
log_msg "acme-tiny client returned non-zero exit code"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" "Please check acme-mailcow for ruther information."
elif [[ ${com_pipe_answer} =~ .+-mailcow ]] || [[ ${com_pipe_answer} == "ipv6nat-mailcow" ]]; then
kill -STOP ${BACKGROUND_TASKS[*]} kill -STOP ${BACKGROUND_TASKS[*]}
sleep 3 sleep 3
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") 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")
@ -581,7 +632,7 @@ while true; do
else else
log_msg "Sending restart command to ${CONTAINER_ID}..." log_msg "Sending restart command to ${CONTAINER_ID}..."
curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/restart curl --silent --insecure -XPOST https://dockerapi/containers/${CONTAINER_ID}/restart
if [[ ${com_pipe_answer} != "ipv6nat" ]]; then if [[ ${com_pipe_answer} != "ipv6nat-mailcow" ]]; then
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}" [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${com_pipe_answer}"
fi fi
log_msg "Wait for restarted container to settle and continue watching..." log_msg "Wait for restarted container to settle and continue watching..."

View File

@ -308,7 +308,7 @@ plugin {
acl = vfile acl = vfile
fts = solr fts = solr
fts_autoindex = yes fts_autoindex = yes
fts_solr = url=http://solr:8983/solr/dovecot/ fts_solr = url=http://solr:8983/solr/dovecot-fts/
quota = dict:Userquota::proxy::sqlquota quota = dict:Userquota::proxy::sqlquota
quota_rule2 = Trash:storage=+100%% quota_rule2 = Trash:storage=+100%%
sieve = /var/vmail/sieve/%u.sieve sieve = /var/vmail/sieve/%u.sieve
@ -328,7 +328,8 @@ plugin {
quota_warning = storage=95%% quota-warning 95 %u quota_warning = storage=95%% quota-warning 95 %u
quota_warning2 = storage=80%% quota-warning 80 %u quota_warning2 = storage=80%% quota-warning 80 %u
sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute +vacation-seconds sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.execute
sieve_extensions = +notify +imapflags +vacation-seconds
sieve_max_script_size = 1M sieve_max_script_size = 1M
sieve_max_redirects = 30 sieve_max_redirects = 30
sieve_quota_max_scripts = 0 sieve_quota_max_scripts = 0
@ -383,9 +384,10 @@ service stats {
} }
} }
imap_max_line_length = 2 M imap_max_line_length = 2 M
auth_cache_verify_password_with_worker = yes #auth_cache_verify_password_with_worker = yes
auth_cache_negative_ttl = 0 #auth_cache_negative_ttl = 0
auth_cache_ttl = 30 s #auth_cache_ttl = 30 s
auth_cache_size = 2 M #auth_cache_size = 2 M
!include_try /usr/local/etc/dovecot/extra.conf !include_try /usr/local/etc/dovecot/extra.conf
!include_try /usr/local/etc/dovecot/sogo-sso.conf
default_client_limit = 10400 default_client_limit = 10400

View File

@ -34,6 +34,7 @@ server {
client_max_body_size 0; client_max_body_size 0;
listen 127.0.0.1:65510;
include /etc/nginx/conf.d/listen_plain.active; include /etc/nginx/conf.d/listen_plain.active;
include /etc/nginx/conf.d/listen_ssl.active; include /etc/nginx/conf.d/listen_ssl.active;
include /etc/nginx/conf.d/server_name.active; include /etc/nginx/conf.d/server_name.active;
@ -142,7 +143,19 @@ server {
try_files /autoconfig.php =404; try_files /autoconfig.php =404;
} }
# auth_request endpoint if ALLOW_ADMIN_EMAIL_LOGIN is set
location /sogo-auth-verify {
internal;
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_set_header Content-Length "";
proxy_pass http://127.0.0.1:65510/sogo-auth;
proxy_pass_request_body off;
}
location ^~ /Microsoft-Server-ActiveSync { location ^~ /Microsoft-Server-ActiveSync {
include /etc/nginx/conf.d/sogo_proxy_auth.active;
include /etc/nginx/conf.d/sogo_eas.active; include /etc/nginx/conf.d/sogo_eas.active;
proxy_connect_timeout 4000; proxy_connect_timeout 4000;
proxy_next_upstream timeout error; proxy_next_upstream timeout error;
@ -165,6 +178,7 @@ server {
} }
location ^~ /SOGo { location ^~ /SOGo {
include /etc/nginx/conf.d/sogo_proxy_auth.active;
include /etc/nginx/conf.d/sogo.active; include /etc/nginx/conf.d/sogo.active;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -0,0 +1,10 @@
if printf "%s\n" "${ALLOW_ADMIN_EMAIL_LOGIN}" | grep -E '^([yY][eE][sS]|[yY])+$' >/dev/null; then
echo 'auth_request /sogo-auth-verify;
auth_request_set $user $upstream_http_x_user;
auth_request_set $auth $upstream_http_x_auth;
auth_request_set $auth_type $upstream_http_x_auth_type;
proxy_set_header x-webobjects-remote-user "$user";
proxy_set_header Authorization "$auth";
proxy_set_header x-webobjects-auth-type "$auth_type";
'
fi

View File

View File

@ -0,0 +1 @@
/localhost$/ local:

View File

@ -94,12 +94,16 @@ smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem
smtpd_tls_eecdh_grade = auto smtpd_tls_eecdh_grade = auto
smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA smtpd_tls_exclude_ciphers = ECDHE-RSA-RC4-SHA, RC4, aNULL, DES-CBC3-SHA, ECDHE-RSA-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA
smtpd_tls_loglevel = 1 smtpd_tls_loglevel = 1
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3 smtp_tls_protocols = !SSLv2, !SSLv3
lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3
lmtp_tls_protocols = !SSLv2, !SSLv2, !SSLv3 lmtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3 lmtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3 smtpd_tls_protocols = !SSLv2, !SSLv3
smtpd_tls_security_level = may smtpd_tls_security_level = may
tls_preempt_cipherlist = yes tls_preempt_cipherlist = yes
tls_ssl_options = NO_COMPRESSION tls_ssl_options = NO_COMPRESSION
@ -134,5 +138,5 @@ smtp_sasl_mechanism_filter = plain, login
smtp_tls_policy_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf smtp_tls_policy_maps=proxy:mysql:/opt/postfix/conf/sql/mysql_tls_policy_override_maps.cf
smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre smtp_header_checks = pcre:/opt/postfix/conf/anonymize_headers.pcre
mail_name = Postcow mail_name = Postcow
transport_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf transport_maps = pcre:/opt/postfix/conf/local_transport, proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
smtp_sasl_auth_soft_bounce = no smtp_sasl_auth_soft_bounce = no

View File

@ -2,14 +2,17 @@ smtp inet n - n - 1 postscreen
smtpd pass - - n - - smtpd smtpd pass - - n - - smtpd
-o smtpd_helo_restrictions=permit_mynetworks,reject_non_fqdn_helo_hostname -o smtpd_helo_restrictions=permit_mynetworks,reject_non_fqdn_helo_hostname
-o smtpd_sasl_auth_enable=no -o smtpd_sasl_auth_enable=no
-o smtpd_sender_restrictions=permit_mynetworks,reject_unlisted_sender,reject_unknown_sender_domain
smtps inet n - n - - smtpd smtps inet n - n - - smtpd
-o smtpd_tls_wrappermode=yes -o smtpd_tls_wrappermode=yes
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
-o tls_preempt_cipherlist=yes -o tls_preempt_cipherlist=yes
submission inet n - n - - smtpd submission inet n - n - - smtpd
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject
-o smtpd_enforce_tls=yes -o smtpd_enforce_tls=yes
-o smtpd_tls_security_level=encrypt -o smtpd_tls_security_level=encrypt
-o smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
-o tls_preempt_cipherlist=yes -o tls_preempt_cipherlist=yes
588 inet n - n - - smtpd 588 inet n - n - - smtpd
-o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject -o smtpd_client_restrictions=permit_mynetworks,permit_sasl_authenticated,reject

View File

@ -6,6 +6,8 @@ then any of these will trigger the rule. If a rule is triggered then no more rul
*/ */
header('Content-Type: text/plain'); header('Content-Type: text/plain');
require_once "vars.inc.php"; require_once "vars.inc.php";
// Getting headers sent by the client.
//$headers = apache_request_headers();
ini_set('error_reporting', 0); ini_set('error_reporting', 0);
@ -25,6 +27,23 @@ catch (PDOException $e) {
exit; exit;
} }
// Check if db changed and return header
/*$stmt = $pdo->prepare("SELECT UNIX_TIMESTAMP(UPDATE_TIME) AS `db_update_time` FROM information_schema.tables
WHERE `TABLE_NAME` = 'filterconf'
AND TABLE_SCHEMA = :dbname;");
$stmt->execute(array(
':dbname' => $database_name
));
$db_update_time = $stmt->fetch(PDO::FETCH_ASSOC)['db_update_time'];
if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == $db_update_time)) {
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 304);
exit;
} else {
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $db_update_time).' GMT', true, 200);
}
*/
function parse_email($email) { function parse_email($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false; if (!filter_var($email, FILTER_VALIDATE_EMAIL)) return false;
$a = strrpos($email, '@'); $a = strrpos($email, '@');
@ -107,8 +126,8 @@ function ucl_rcpts($object, $type) {
settings { settings {
watchdog { watchdog {
priority = 10; priority = 10;
rcpt = "/null@localhost/i"; rcpt_mime = "/null@localhost/i";
from = "/watchdog@localhost/i"; from_mime = "/watchdog@localhost/i";
apply "default" { apply "default" {
actions { actions {
reject = 9999.0; reject = 9999.0;
@ -179,7 +198,7 @@ foreach (wl_by_sogo() as $user => $contacts) {
} }
?> ?>
apply "default" { apply "default" {
SOGO_CONTACT = -999.0; SOGO_CONTACT = -99.0;
} }
symbols [ symbols [
"SOGO_CONTACT" "SOGO_CONTACT"
@ -199,12 +218,13 @@ while ($row = array_shift($rows)) {
?> ?>
whitelist_<?=$username_sane;?> { whitelist_<?=$username_sane;?> {
<?php <?php
$list_items = array();
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf` $stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
WHERE `object`= :object WHERE `object`= :object
AND `option` = 'whitelist_from'"); AND `option` = 'whitelist_from'");
$stmt->execute(array(':object' => $row['object'])); $stmt->execute(array(':object' => $row['object']));
$list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($item = array_shift($list_items)) { foreach ($list_items as $item) {
?> ?>
from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i"; from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
<?php <?php
@ -237,24 +257,13 @@ while ($row = array_shift($rows)) {
"MAILCOW_WHITE" "MAILCOW_WHITE"
] ]
} }
whitelist_header_<?=$username_sane;?> { whitelist_mime_<?=$username_sane;?> {
<?php <?php
$header_from = array(); foreach ($list_items as $item) {
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
WHERE `object`= :object
AND `option` = 'whitelist_from'");
$stmt->execute(array(':object' => $row['object']));
$list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
?> ?>
header = { from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
<?php <?php
while ($item = array_shift($list_items)) {
$header_from[] = str_replace('\*', '.*', preg_quote($item['value'], '/'));
} }
?>
"From" = "/(<?=implode('|', $header_from);?>)/i";
}
<?php
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) { if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
?> ?>
priority = 5; priority = 5;
@ -297,13 +306,13 @@ while ($row = array_shift($rows)) {
?> ?>
blacklist_<?=$username_sane;?> { blacklist_<?=$username_sane;?> {
<?php <?php
$items[] = array(); $list_items = array();
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf` $stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
WHERE `object`= :object WHERE `object`= :object
AND `option` = 'blacklist_from'"); AND `option` = 'blacklist_from'");
$stmt->execute(array(':object' => $row['object'])); $stmt->execute(array(':object' => $row['object']));
$list_items = $stmt->fetchAll(PDO::FETCH_ASSOC); $list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($item = array_shift($list_items)) { foreach ($list_items as $item) {
?> ?>
from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i"; from = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
<?php <?php
@ -338,22 +347,11 @@ while ($row = array_shift($rows)) {
} }
blacklist_header_<?=$username_sane;?> { blacklist_header_<?=$username_sane;?> {
<?php <?php
$header_from = array(); foreach ($list_items as $item) {
$stmt = $pdo->prepare("SELECT `value` FROM `filterconf`
WHERE `object`= :object
AND `option` = 'blacklist_from'");
$stmt->execute(array(':object' => $row['object']));
$list_items = $stmt->fetchAll(PDO::FETCH_ASSOC);
?> ?>
header = { from_mime = "/<?='^' . str_replace('\*', '.*', preg_quote($item['value'], '/')) . '$' ;?>/i";
<?php <?php
while ($item = array_shift($list_items)) {
$header_from[] = str_replace('\*', '.*', preg_quote($item['value'], '/'));
} }
?>
"From" = "/(<?=implode('|', $header_from);?>)/i";
}
<?php
if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) { if (!filter_var(trim($row['object']), FILTER_VALIDATE_EMAIL)) {
?> ?>
priority = 5; priority = 5;
@ -425,4 +423,4 @@ while ($row = array_shift($rows)) {
<?php <?php
} }
?> ?>
} }

View File

@ -1,16 +0,0 @@
# rspamd.conf.local
worker "fuzzy" {
# Socket to listen on (UDP and TCP from rspamd 1.3)
bind_socket = "*:11445";
allow_update = ["127.0.0.1", "::1"];
# Number of processes to serve this storage (useful for read scaling)
count = 2;
# Backend ("sqlite" or "redis" - default "sqlite")
backend = "redis";
# Hashes storage time (3 months)
expire = 90d;
# Synchronize updates to the storage each minute
sync = 1min;
}

View File

@ -84,6 +84,9 @@ $rcpt_final_mailboxes = array();
// Loop through all rcpts // Loop through all rcpts
foreach (json_decode($rcpts, true) as $rcpt) { foreach (json_decode($rcpts, true) as $rcpt) {
// Remove tag
$rcpt = preg_replace('/^(.*?)\+.*(@.*)$/', '$1$2', $rcpt);
// Break rcpt into local part and domain part // Break rcpt into local part and domain part
$parsed_rcpt = parse_email($rcpt); $parsed_rcpt = parse_email($rcpt);

View File

@ -0,0 +1,12 @@
# Socket to listen on (UDP and TCP from rspamd 1.3)
bind_socket = "*:11445";
allow_update = ["127.0.0.1", "::1"];
# Number of processes to serve this storage (useful for read scaling)
count = 2;
# Backend ("sqlite" or "redis" - default "sqlite")
backend = "redis";
# Hashes storage time (3 months)
expire = 90d;
# Synchronize updates to the storage each minute
sync = 1min;

View File

@ -1,6 +1,6 @@
bind_socket = "rspamd:9900"; bind_socket = "rspamd:9900";
milter = true; milter = true;
upstream { upstream "local" {
name = "localhost"; name = "localhost";
default = true; default = true;
hosts = "rspamd:11333" hosts = "rspamd:11333"

View File

@ -26,7 +26,6 @@
// (domain3.tld, domain2.tld) // (domain3.tld, domain2.tld)
// ); // );
SOGoIMAPServer = "imap://dovecot:143/?tls=YES";
SOGoSieveServer = "sieve://dovecot:4190/?tls=YES"; SOGoSieveServer = "sieve://dovecot:4190/?tls=YES";
SOGoSMTPServer = "postfix:588"; SOGoSMTPServer = "postfix:588";
WOPort = "0.0.0.0:20000"; WOPort = "0.0.0.0:20000";

View File

@ -746,6 +746,7 @@ $tfa_data = get_tfa();
<div id="active_settings_map" class="collapse" > <div id="active_settings_map" class="collapse" >
<textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea> <textarea autocorrect="off" spellcheck="false" autocapitalize="none" class="form-control textarea-code" rows="20" name="settings_map" readonly><?=file_get_contents('http://nginx:8081/settings.php');?></textarea>
</div> </div>
<br>
<?php $rsettings = rsettings('get'); ?> <?php $rsettings = rsettings('get'); ?>
<form class="form" data-id="rsettings" role="form" method="post"> <form class="form" data-id="rsettings" role="form" method="post">
<div class="row"> <div class="row">

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/*! ======================================================= /*! =======================================================
VERSION 10.6.0 VERSION 10.6.1
========================================================= */ ========================================================= */
/*! ========================================================= /*! =========================================================
* bootstrap-slider.js * bootstrap-slider.js

View File

@ -36,4 +36,7 @@ table.footable>tbody>tr.footable-empty>td {
white-space: -pre-wrap; white-space: -pre-wrap;
white-space: -o-pre-wrap; white-space: -o-pre-wrap;
word-wrap: break-word; word-wrap: break-word;
}
body {
overflow-y:scroll;
} }

View File

@ -521,7 +521,8 @@ if (isset($_SESSION['mailcow_cc_role'])) {
<br /><span id="quotaBadge" class="badge">max. <?=intval($result['max_new_quota'] / 1048576)?> MiB</span> <br /><span id="quotaBadge" class="badge">max. <?=intval($result['max_new_quota'] / 1048576)?> MiB</span>
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" name="quota" style="width:100%" min="1" max="<?=intval($result['max_new_quota'] / 1048576);?>" value="<?=intval($result['quota']) / 1048576;?>" class="form-control"> <input type="number" name="quota" style="width:100%" min="0" max="<?=intval($result['max_new_quota'] / 1048576);?>" value="<?=intval($result['quota']) / 1048576;?>" class="form-control">
<small class="help-block">0 = </small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -0,0 +1,13 @@
<?php
session_start();
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
header('Content-Type: text/plain');
if (!isset($_SESSION['mailcow_cc_role'])) {
exit();
}
if (isset($_GET['token']) && ctype_alnum($_GET['token'])) {
echo $tfa->getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $_GET['token']);
}
?>

View File

@ -58,6 +58,9 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admi
) )
); );
$mail->SMTPDebug = 3; $mail->SMTPDebug = 3;
if ($port == 465) {
$mail->SMTPSecure = "ssl";
}
$mail->Debugoutput = function($str, $level) { $mail->Debugoutput = function($str, $level) {
foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){ foreach(preg_split("/((\r?\n)|(\r\n?)|\n)/", $str) as $line){
if (empty($line)) { continue; } if (empty($line)) { continue; }

View File

@ -26,6 +26,10 @@ $(window).load(function() {
$(".overlay").hide(); $(".overlay").hide();
}); });
$(document).ready(function() { $(document).ready(function() {
$(document).on('shown.bs.modal', function(e) {
modal_id = $(e.relatedTarget).data('target');
$(modal_id).attr("aria-hidden","false");
});
// TFA, CSRF, Alerts in footer.inc.php // TFA, CSRF, Alerts in footer.inc.php
// Other general functions in mailcow.js // Other general functions in mailcow.js
<?php <?php
@ -93,6 +97,15 @@ $(document).ready(function() {
} }
if ($(this).val() == "totp") { if ($(this).val() == "totp") {
$('#TOTPModal').modal('show'); $('#TOTPModal').modal('show');
request_token = $('#tfa-qr-img').data('totp-secret');
$.ajax({
url: '/inc/ajax/qr_gen.php',
data: {
token: request_token,
},
}).done(function (result) {
$("#tfa-qr-img").attr("src", result);
});
$("option:selected").prop("selected", false); $("option:selected").prop("selected", false);
} }
if ($(this).val() == "u2f") { if ($(this).val() == "u2f") {

View File

@ -1,4 +1,12 @@
<?php <?php
function isset_has_content($var) {
if (isset($var) && $var != "") {
return true;
}
else {
return false;
}
}
function hash_password($password) { function hash_password($password) {
$salt_str = bin2hex(openssl_random_pseudo_bytes(8)); $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str); return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
@ -1477,7 +1485,7 @@ function solr_status() {
$endpoint = 'http://solr:8983/solr/admin/cores'; $endpoint = 'http://solr:8983/solr/admin/cores';
$params = array( $params = array(
'action' => 'STATUS', 'action' => 'STATUS',
'core' => 'dovecot', 'core' => 'dovecot-fts',
'indexInfo' => 'true' 'indexInfo' => 'true'
); );
$url = $endpoint . '?' . http_build_query($params); $url = $endpoint . '?' . http_build_query($params);
@ -1494,7 +1502,7 @@ function solr_status() {
else { else {
curl_close($curl); curl_close($curl);
$status = json_decode($response, true); $status = json_decode($response, true);
return (!empty($status['status']['dovecot'])) ? $status['status']['dovecot'] : false; return (!empty($status['status']['dovecot-fts'])) ? $status['status']['dovecot-fts'] : false;
} }
return false; return false;
} }

View File

@ -561,7 +561,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_alias_or_mailbox', htmlspecialchars($address)) 'msg' => array('is_alias_or_mailbox', htmlspecialchars($address))
); );
return false; continue;
} }
$stmt = $pdo->prepare("SELECT `domain` FROM `domain` $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)"); WHERE `domain`= :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2)");
@ -573,7 +573,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('domain_not_found', htmlspecialchars($domain)) 'msg' => array('domain_not_found', htmlspecialchars($domain))
); );
return false; continue;
} }
$stmt = $pdo->prepare("SELECT `address` FROM `spamalias` $stmt = $pdo->prepare("SELECT `address` FROM `spamalias`
WHERE `address`= :address"); WHERE `address`= :address");
@ -585,7 +585,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('is_spam_alias', htmlspecialchars($address)) 'msg' => array('is_spam_alias', htmlspecialchars($address))
); );
return false; continue;
} }
if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) { if ((!filter_var($address, FILTER_VALIDATE_EMAIL) === true) && !empty($local_part)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -593,7 +593,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'alias_invalid' 'msg' => 'alias_invalid'
); );
return false; continue;
} }
if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) { if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $domain)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -601,7 +601,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr), 'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'access_denied' 'msg' => 'access_denied'
); );
return false; continue;
} }
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `active`) $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `public_comment`, `private_comment`, `goto`, `domain`, `active`)
VALUES (:address, :public_comment, :private_comment, :goto, :domain, :active)"); VALUES (:address, :public_comment, :private_comment, :goto, :domain, :active)");
@ -755,8 +755,16 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
$password = $_data['password']; $password = $_data['password'];
$password2 = $_data['password2']; $password2 = $_data['password2'];
$name = $_data['name']; $name = ltrim(rtrim($_data['name'], '>'), '<');
$quota_m = filter_var($_data['quota'], FILTER_SANITIZE_NUMBER_FLOAT); $quota_m = intval($_data['quota']);
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['quarantine_notification'] != "1") && $quota_m === 0) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'unlimited_quota_acl'
);
return false;
}
if (empty($name)) { if (empty($name)) {
$name = $local_part; $name = $local_part;
} }
@ -844,14 +852,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
return false; return false;
} }
if (!is_numeric($quota_m) || $quota_m == "0") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'quota_not_0_not_numeric'
);
return false;
}
if (!empty($password) && !empty($password2)) { if (!empty($password) && !empty($password2)) {
if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) { if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
@ -1993,9 +1993,9 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int']; $active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active_int'];
(int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']); (int)$force_pw_update = (isset($_data['force_pw_update'])) ? intval($_data['force_pw_update']) : intval($is_now['attributes']['force_pw_update']);
(int)$sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']); (int)$sogo_access = (isset($_data['sogo_access'])) ? intval($_data['sogo_access']) : intval($is_now['attributes']['sogo_access']);
$name = (!empty($_data['name'])) ? $_data['name'] : $is_now['name']; (int)$quota_m = (isset_has_content($_data['quota'])) ? intval($_data['quota']) : ($is_now['quota'] / 1048576);
$name = (!empty($_data['name'])) ? ltrim(rtrim($_data['name'], '>'), '<') : $is_now['name'];
$domain = $is_now['domain']; $domain = $is_now['domain'];
$quota_m = (!empty($_data['quota'])) ? $_data['quota'] : ($is_now['quota'] / 1048576);
$quota_b = $quota_m * 1048576; $quota_b = $quota_m * 1048576;
$password = (!empty($_data['password'])) ? $_data['password'] : null; $password = (!empty($_data['password'])) ? $_data['password'] : null;
$password2 = (!empty($_data['password2'])) ? $_data['password2'] : null; $password2 = (!empty($_data['password2'])) ? $_data['password2'] : null;
@ -2008,6 +2008,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
// if already 0 == ok
if ((!isset($_SESSION['acl']['unlimited_quota']) || $_SESSION['acl']['unlimited_quota'] != "1") && ($quota_m == 0 && $is_now['quota'] != 0)) {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => 'unlimited_quota_acl'
);
return false;
}
$stmt = $pdo->prepare("SELECT `quota`, `maxquota` $stmt = $pdo->prepare("SELECT `quota`, `maxquota`
FROM `domain` FROM `domain`
WHERE `domain` = :domain"); WHERE `domain` = :domain");
@ -2021,14 +2030,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
); );
continue; continue;
} }
if (!is_numeric($quota_m) || $quota_m == "0") {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_type, $_data_log, $_attr),
'msg' => array('quota_not_0_not_numeric', htmlspecialchars($quota_m))
);
continue;
}
if ($quota_m > $DomainData['maxquota']) { if ($quota_m > $DomainData['maxquota']) {
$_SESSION['return'][] = array( $_SESSION['return'][] = array(
'type' => 'danger', 'type' => 'danger',
@ -3016,15 +3017,18 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
$mailboxdata['quota'] = $row['quota']; $mailboxdata['quota'] = $row['quota'];
$mailboxdata['attributes'] = json_decode($row['attributes'], true); $mailboxdata['attributes'] = json_decode($row['attributes'], true);
$mailboxdata['quota_used'] = intval($row['bytes']); $mailboxdata['quota_used'] = intval($row['bytes']);
$mailboxdata['percent_in_use'] = round((intval($row['bytes']) / intval($row['quota'])) * 100); $mailboxdata['percent_in_use'] = ($row['quota'] == 0) ? '- ' : round((intval($row['bytes']) / intval($row['quota'])) * 100);
$mailboxdata['messages'] = $row['messages']; $mailboxdata['messages'] = $row['messages'];
$mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count']; $mailboxdata['spam_aliases'] = $SpamaliasUsage['sa_count'];
if ($mailboxdata['percent_in_use'] >= 90) { if ($mailboxdata['percent_in_use'] === '- ') {
$mailboxdata['percent_class'] = "danger"; $mailboxdata['percent_class'] = "info";
} }
elseif ($mailboxdata['percent_in_use'] >= 75) { elseif ($mailboxdata['percent_in_use'] >= 75) {
$mailboxdata['percent_class'] = "warning"; $mailboxdata['percent_class'] = "warning";
} }
elseif ($mailboxdata['percent_in_use'] >= 90) {
$mailboxdata['percent_class'] = "danger";
}
else { else {
$mailboxdata['percent_class'] = "success"; $mailboxdata['percent_class'] = "success";
} }
@ -3525,7 +3529,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
if (strtolower(getenv('SKIP_SOLR')) == 'n') { if (strtolower(getenv('SKIP_SOLR')) == 'n') {
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot/update?commit=true'); curl_setopt($curl, CURLOPT_URL, 'http://solr:8983/solr/dovecot-fts/update?commit=true');
curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml')); curl_setopt($curl, CURLOPT_HTTPHEADER,array('Content-Type: text/xml'));
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_POST, 1);
@ -3714,7 +3718,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
} }
break; break;
} }
if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox'))) { if ($_action != 'get' && in_array($_type, array('domain', 'alias', 'alias_domain', 'mailbox', 'resource'))) {
update_sogo_static_view(); update_sogo_static_view();
} }
} }

View File

@ -3,7 +3,7 @@ function init_db_schema() {
try { try {
global $pdo; global $pdo;
$db_version = "27012019_1217"; $db_version = "30032019_1905";
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'"); $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@ -464,6 +464,7 @@ function init_db_schema() {
"filters" => "TINYINT(1) NOT NULL DEFAULT '1'", "filters" => "TINYINT(1) NOT NULL DEFAULT '1'",
"ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'", "ratelimit" => "TINYINT(1) NOT NULL DEFAULT '1'",
"spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'", "spam_policy" => "TINYINT(1) NOT NULL DEFAULT '1'",
"unlimited_quota" => "TINYINT(1) NOT NULL DEFAULT '0'",
"alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'", "alias_domains" => "TINYINT(1) NOT NULL DEFAULT '0'",
), ),
"keys" => array( "keys" => array(

View File

@ -36,7 +36,8 @@ foreach ($css_dir as $css_file) {
// U2F API + T/HOTP API // U2F API + T/HOTP API
$u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']); $u2f = new u2flib_server\U2F('https://' . $_SERVER['HTTP_HOST']);
$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL); $qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider();
$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider);
// Redis // Redis
$redis = new Redis(); $redis = new Redis();

View File

@ -75,6 +75,24 @@ if (!empty($_SERVER['HTTP_X_API_KEY'])) {
// Update session cookie // Update session cookie
// setcookie(session_name() ,session_id(), time() + $SESSION_LIFETIME); // setcookie(session_name() ,session_id(), time() + $SESSION_LIFETIME);
// Handle logouts
if (isset($_POST["logout"])) {
if (isset($_SESSION["dual-login"])) {
$_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"];
$_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"];
unset($_SESSION["dual-login"]);
header("Location: /mailbox");
exit();
}
else {
session_regenerate_id(true);
session_unset();
session_destroy();
session_write_close();
header("Location: /");
}
}
// Check session // Check session
function session_check() { function session_check() {
if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) { if (isset($_SESSION['mailcow_cc_api']) && $_SESSION['mailcow_cc_api'] === true) {
@ -106,21 +124,3 @@ if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) {
$_POST = array(); $_POST = array();
$_FILES = array(); $_FILES = array();
} }
// Handle logouts
if (isset($_POST["logout"])) {
if (isset($_SESSION["dual-login"])) {
$_SESSION["mailcow_cc_username"] = $_SESSION["dual-login"]["username"];
$_SESSION["mailcow_cc_role"] = $_SESSION["dual-login"]["role"];
unset($_SESSION["dual-login"]);
header("Location: /mailbox");
exit();
}
else {
session_regenerate_id(true);
session_unset();
session_destroy();
session_write_close();
header("Location: /");
}
}

View File

@ -141,7 +141,7 @@ $MAILBOX_DEFAULT_ATTRIBUTES['tls_enforce_out'] = false;
// Force password change on next login (only allows login to mailcow UI) // Force password change on next login (only allows login to mailcow UI)
$MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false; $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false;
// Force password change on next login (only allows login to mailcow UI) // Enable SOGo access (set to false to disable access by default)
$MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true; $MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true;
// Send notification when quarantine is not empty (never, hourly, daily, weekly) // Send notification when quarantine is not empty (never, hourly, daily, weekly)

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,13 @@
// Base64 functions // Base64 functions
var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}}; var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(r){var t,e,o,a,h,n,c,d="",C=0;for(r=Base64._utf8_encode(r);C<r.length;)a=(t=r.charCodeAt(C++))>>2,h=(3&t)<<4|(e=r.charCodeAt(C++))>>4,n=(15&e)<<2|(o=r.charCodeAt(C++))>>6,c=63&o,isNaN(e)?n=c=64:isNaN(o)&&(c=64),d=d+this._keyStr.charAt(a)+this._keyStr.charAt(h)+this._keyStr.charAt(n)+this._keyStr.charAt(c);return d},decode:function(r){var t,e,o,a,h,n,c="",d=0;for(r=r.replace(/[^A-Za-z0-9\+\/\=]/g,"");d<r.length;)t=this._keyStr.indexOf(r.charAt(d++))<<2|(a=this._keyStr.indexOf(r.charAt(d++)))>>4,e=(15&a)<<4|(h=this._keyStr.indexOf(r.charAt(d++)))>>2,o=(3&h)<<6|(n=this._keyStr.indexOf(r.charAt(d++))),c+=String.fromCharCode(t),64!=h&&(c+=String.fromCharCode(e)),64!=n&&(c+=String.fromCharCode(o));return c=Base64._utf8_decode(c)},_utf8_encode:function(r){r=r.replace(/\r\n/g,"\n");for(var t="",e=0;e<r.length;e++){var o=r.charCodeAt(e);o<128?t+=String.fromCharCode(o):o>127&&o<2048?(t+=String.fromCharCode(o>>6|192),t+=String.fromCharCode(63&o|128)):(t+=String.fromCharCode(o>>12|224),t+=String.fromCharCode(o>>6&63|128),t+=String.fromCharCode(63&o|128))}return t},_utf8_decode:function(r){for(var t="",e=0,o=c1=c2=0;e<r.length;)(o=r.charCodeAt(e))<128?(t+=String.fromCharCode(o),e++):o>191&&o<224?(c2=r.charCodeAt(e+1),t+=String.fromCharCode((31&o)<<6|63&c2),e+=2):(c2=r.charCodeAt(e+1),c3=r.charCodeAt(e+2),t+=String.fromCharCode((15&o)<<12|(63&c2)<<6|63&c3),e+=3);return t}};
jQuery(function($){ jQuery(function($){
acl_data = JSON.parse(acl); acl_data = JSON.parse(acl);
// http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery // http://stackoverflow.com/questions/24816/escaping-html-strings-with-jquery
var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"}; var entityMap={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","/":"&#x2F;","`":"&#x60;","=":"&#x3D;"};
function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})} function escapeHtml(n){return String(n).replace(/[&<>"'`=\/]/g,function(n){return entityMap[n]})}
function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]} function humanFileSize(i){if(Math.abs(i)<1024)return i+" B";var B=["KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],e=-1;do{i/=1024,++e}while(Math.abs(i)>=1024&&e<B.length-1);return i.toFixed(1)+" "+B[e]}
function draw_quarantine_table() { function draw_quarantine_table() {
ft_quarantinetable = FooTable.init('#quarantinetable', { ft_quarantinetable = FooTable.init('#quarantinetable', {
"columns": [ "columns": [
@ -56,54 +58,52 @@ jQuery(function($){
"empty": lang.empty, "empty": lang.empty,
"paging": {"enabled": true,"limit": 5,"size": pagination_size}, "paging": {"enabled": true,"limit": 5,"size": pagination_size},
"sorting": {"enabled": true}, "sorting": {"enabled": true},
"on": {
"ready.ft.table": btn_group_quarantine,
"after.ft.paging": btn_group_quarantine
},
"filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table}, "filtering": {"enabled": true,"position": "left","connectors": false,"placeholder": lang.filter_table},
}); });
} }
btn_group_quarantine = function(ev, ft){ $('body').on('click', '.show_qid_info', function (e) {
$('.show_qid_info').on('click', function (e) { e.preventDefault();
e.preventDefault(); var qitem = $(this).data('item');
var qitem = $(this).data('item'); var qError = $("#qid_error");
$('#qidDetailModal').modal('show');
$( "#qid_error" ).hide(); $('#qidDetailModal').modal('show');
$.ajax({ qError.hide();
url: '/inc/ajax/qitem_details.php',
data: { id: qitem }, $.ajax({
dataType: 'json', url: '/inc/ajax/qitem_details.php',
success: function(data){ data: { id: qitem },
if (typeof data.error !== 'undefined') { dataType: 'json',
$( "#qid_error" ).text(data.error); success: function(data){
$( "#qid_error" ).show(); if (typeof data.error !== 'undefined') {
} qError.text(data.error);
$( "li" ).each(function( index ) { qError.show();
console.log( index + ": " + $( this ).text() );
});
$('[data-id="qitems_single"]').each(function( index ) {
$(this).attr("data-item", qitem);
});
$('#qid_detail_subj').text(data.subject);
$('#qid_detail_text').text(data.text_plain);
$('#qid_detail_text_from_html').text(data.text_html);
if (typeof data.attachments !== 'undefined') {
$( "#qid_detail_atts" ).text('');
$.each(data.attachments, function( index, value ) {
$( "#qid_detail_atts" ).append(
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
);
});
}
else {
$( "#qid_detail_atts" ).text('-');
}
} }
}); $('[data-id="qitems_single"]').each(function(index) {
}) $(this).attr("data-item", qitem);
} });
$('#qid_detail_subj').text(data.subject);
$('#qid_detail_text').text(data.text_plain);
$('#qid_detail_text_from_html').text(data.text_html);
if (typeof data.attachments !== 'undefined') {
qAtts = $("#qid_detail_atts");
qAtts.text('');
$.each(data.attachments, function(index, value) {
qAtts.append(
'<p><a href="/inc/ajax/qitem_details.php?id=' + qitem + '&att=' + index + '" target="_blank">' + value[0] + '</a> (' + value[1] + ')' +
' - <small><a href="' + value[3] + '" target="_blank">' + lang.check_hash + '</a></small></p>'
);
});
}
else {
qAtts.text('-');
}
}
});
});
// Initial table drawings // Initial table drawings
draw_quarantine_table(); draw_quarantine_table();
}); });

View File

@ -19,6 +19,7 @@ $lang['footer']['cancel'] = 'Abbrechen';
$lang['footer']['hibp_nok'] = 'Übereinstimmung gefunden! Dieses Passwort ist potentiell gefährlich!'; $lang['footer']['hibp_nok'] = 'Übereinstimmung gefunden! Dieses Passwort ist potentiell gefährlich!';
$lang['footer']['hibp_ok'] = 'Keine Übereinstimmung gefunden.'; $lang['footer']['hibp_ok'] = 'Keine Übereinstimmung gefunden.';
$lang['danger']['unlimited_quota_acl'] = "Unendliche Quota untersagt durch ACL";
$lang['danger']['mysql_error'] = "MySQL Fehler: %s"; $lang['danger']['mysql_error'] = "MySQL Fehler: %s";
$lang['danger']['redis_error'] = "Redis Fehler: %s"; $lang['danger']['redis_error'] = "Redis Fehler: %s";
$lang['danger']['unknown_tfa_method'] = "Unbekannte TFA Methode"; $lang['danger']['unknown_tfa_method'] = "Unbekannte TFA Methode";
@ -38,7 +39,7 @@ $lang['danger']['rspamd_ui_pw_length'] = "Rspamd UI Passwort muss mindestens 6 Z
$lang['success']['rspamd_ui_pw_set'] = "Rspamd UI Passwort wurde gesetzt"; $lang['success']['rspamd_ui_pw_set'] = "Rspamd UI Passwort wurde gesetzt";
$lang['success']['queue_command_success'] = "Queue-Aufgabe erfolgreich ausgeführt"; $lang['success']['queue_command_success'] = "Queue-Aufgabe erfolgreich ausgeführt";
$lang['danger']['unknown'] = "Ein unbekannter Fehler trat auf"; $lang['danger']['unknown'] = "Ein unbekannter Fehler trat auf";
$lang['danger']['malformed_username'] = "Benutzername hat falsches Format"; $lang['danger']['malformed_username'] = "Benutzername hat ein falsches Format";
$lang['info']['awaiting_tfa_confirmation'] = "Warte auf TFA Verifizierung"; $lang['info']['awaiting_tfa_confirmation'] = "Warte auf TFA Verifizierung";
$lang['success']['logged_in_as'] = "Eingeloggt als %s"; $lang['success']['logged_in_as'] = "Eingeloggt als %s";
$lang['danger']['login_failed'] = "Anmeldung fehlgeschlagen"; $lang['danger']['login_failed'] = "Anmeldung fehlgeschlagen";
@ -49,7 +50,7 @@ $lang['danger']['sieve_error'] = "Sieve Parser: %s";
$lang['danger']['value_missing'] = "Bitte alle Felder ausfüllen"; $lang['danger']['value_missing'] = "Bitte alle Felder ausfüllen";
$lang['danger']['filter_type'] = "Falscher Filtertyp"; $lang['danger']['filter_type'] = "Falscher Filtertyp";
$lang['danger']['domain_cannot_match_hostname'] = "Domain darf nicht dem Hostnamen entsprechen"; $lang['danger']['domain_cannot_match_hostname'] = "Domain darf nicht dem Hostnamen entsprechen";
$lang['warning']['domain_added_sogo_failed'] = "Domain wurde hinzugefügt aber SOGo konnte nicht neugestartet werden"; $lang['warning']['domain_added_sogo_failed'] = "Domain wurde hinzugefügt, aber SOGo konnte nicht neugestartet werden";
$lang['danger']['rl_timeframe'] = "Ratelimit Zeitraum ist inkorrekt"; $lang['danger']['rl_timeframe'] = "Ratelimit Zeitraum ist inkorrekt";
$lang['success']['rl_saved'] = "Ratelimit für Objekt %s wurde gesetzt"; $lang['success']['rl_saved'] = "Ratelimit für Objekt %s wurde gesetzt";
$lang['success']['acl_saved'] = "ACL für Objekt %s wurde gesetzt"; $lang['success']['acl_saved'] = "ACL für Objekt %s wurde gesetzt";
@ -87,7 +88,7 @@ $lang['success']['item_deleted'] = "Objekt %s wurde entfernt";
$lang['danger']['alias_empty'] = 'Alias-Adresse darf nicht leer sein'; $lang['danger']['alias_empty'] = 'Alias-Adresse darf nicht leer sein';
$lang['danger']['goto_empty'] = 'Ziel-Adresse darf nicht leer sein'; $lang['danger']['goto_empty'] = 'Ziel-Adresse darf nicht leer sein';
$lang['danger']['policy_list_from_exists'] = 'Ein Eintrag mit diesem Wert existiert bereits'; $lang['danger']['policy_list_from_exists'] = 'Ein Eintrag mit diesem Wert existiert bereits';
$lang['danger']['policy_list_from_invalid'] = 'Eintrag hat ungültiges Format'; $lang['danger']['policy_list_from_invalid'] = 'Eintrag hat ein ungültiges Format';
$lang['danger']['alias_invalid'] = 'Alias-Adresse %s ist ungültig'; $lang['danger']['alias_invalid'] = 'Alias-Adresse %s ist ungültig';
$lang['danger']['goto_invalid'] = 'Ziel-Adresse %s ist ungültig'; $lang['danger']['goto_invalid'] = 'Ziel-Adresse %s ist ungültig';
$lang['danger']['last_key'] = 'Letzter Key kann nicht gelöscht werden'; $lang['danger']['last_key'] = 'Letzter Key kann nicht gelöscht werden';
@ -104,7 +105,7 @@ $lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden ges
$lang['success']['mailbox_modified'] = 'Änderungen an Mailbox %s wurden gespeichert'; $lang['success']['mailbox_modified'] = 'Änderungen an Mailbox %s wurden gespeichert';
$lang['success']['resource_modified'] = "Änderungen an Ressource %s wurden gespeichert"; $lang['success']['resource_modified'] = "Änderungen an Ressource %s wurden gespeichert";
$lang['success']['object_modified'] = "Änderungen an Objekt %s wurden gespeichert"; $lang['success']['object_modified'] = "Änderungen an Objekt %s wurden gespeichert";
$lang['success']['f2b_modified'] = "Änderungen an Fail2ban Parametern wurden gespeichert"; $lang['success']['f2b_modified'] = "Änderungen an Fail2ban-Parametern wurden gespeichert";
$lang['danger']['targetd_not_found'] = 'Ziel-Domain %s nicht gefunden'; $lang['danger']['targetd_not_found'] = 'Ziel-Domain %s nicht gefunden';
$lang['success']['aliasd_added'] = 'Alias-Domain %s wurde angelegt'; $lang['success']['aliasd_added'] = 'Alias-Domain %s wurde angelegt';
$lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden gespeichert'; $lang['success']['aliasd_modified'] = 'Änderungen an Alias-Domain %s wurden gespeichert';
@ -139,8 +140,8 @@ $lang['success']['alias_domain_removed'] = 'Alias-Domain %s wurde entfernt';
$lang['success']['domain_admin_removed'] = 'Domain-Administrator %s wurde entfernt'; $lang['success']['domain_admin_removed'] = 'Domain-Administrator %s wurde entfernt';
$lang['success']['admin_removed'] = 'Administrator %s wurde entfernt'; $lang['success']['admin_removed'] = 'Administrator %s wurde entfernt';
$lang['success']['mailbox_removed'] = 'Mailbox %s wurde entfernt'; $lang['success']['mailbox_removed'] = 'Mailbox %s wurde entfernt';
$lang['success']['eas_reset'] = "ActiveSync Gerät des Benutzers %s wurden zurückgesetzt"; $lang['success']['eas_reset'] = "ActiveSync Gerät des Benutzers %s wurde zurückgesetzt";
$lang['success']['sogo_profile_reset'] = "ActiveSync Gerät des Benutzers %s wurden zurückgesetzt"; $lang['success']['sogo_profile_reset'] = "ActiveSync Gerät des Benutzers %s wurde zurückgesetzt";
$lang['success']['resource_removed'] = 'Ressource %s wurde entfernt'; $lang['success']['resource_removed'] = 'Ressource %s wurde entfernt';
$lang['warning']['cannot_delete_self'] = 'Kann derzeit eingeloggten Benutzer nicht entfernen'; $lang['warning']['cannot_delete_self'] = 'Kann derzeit eingeloggten Benutzer nicht entfernen';
$lang['warning']['no_active_admin'] = 'Kann letzten aktiven Administrator nicht deaktivieren'; $lang['warning']['no_active_admin'] = 'Kann letzten aktiven Administrator nicht deaktivieren';
@ -171,7 +172,7 @@ $lang['user']['new_password_description'] = 'Mindestanforderung: 6 Zeichen lang,
$lang['user']['spam_aliases'] = 'Temporäre E-Mail Aliasse'; $lang['user']['spam_aliases'] = 'Temporäre E-Mail Aliasse';
$lang['user']['alias'] = 'Alias'; $lang['user']['alias'] = 'Alias';
$lang['user']['shared_aliases'] = 'Geteilte Alias-Adressen'; $lang['user']['shared_aliases'] = 'Geteilte Alias-Adressen';
$lang['user']['shared_aliases_desc'] = 'Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen wie die des Spam-Filters oder der Verschlüsselungsrichtlinie berücksichtigt. Entsprechende Spam-Filter können lediglich von einem Administrator vorgenommen werden.'; $lang['user']['shared_aliases_desc'] = 'Geteilte Alias-Adressen werden nicht bei benutzerdefinierten Einstellungen, wie die des Spam-Filters oder der Verschlüsselungsrichtlinie, berücksichtigt. Entsprechende Spam-Filter können lediglich von einem Administrator vorgenommen werden.';
$lang['user']['direct_aliases'] = 'Direkte Alias-Adressen'; $lang['user']['direct_aliases'] = 'Direkte Alias-Adressen';
$lang['user']['direct_aliases_desc'] = 'Nur direkte Alias-Adressen werden für benutzerdefinierte Einstellungen berücksichtigt.'; $lang['user']['direct_aliases_desc'] = 'Nur direkte Alias-Adressen werden für benutzerdefinierte Einstellungen berücksichtigt.';
$lang['user']['is_catch_all'] = 'Ist Catch-All Adresse für Domain(s)'; $lang['user']['is_catch_all'] = 'Ist Catch-All Adresse für Domain(s)';
@ -200,7 +201,7 @@ $lang['user']['spamfilter_bl_desc'] = 'Für E-Mail-Adressen, die vom Spamfilter
$lang['user']['spamfilter_table_rule'] = 'Regel'; $lang['user']['spamfilter_table_rule'] = 'Regel';
$lang['user']['spamfilter_table_action'] = 'Aktion'; $lang['user']['spamfilter_table_action'] = 'Aktion';
$lang['user']['spamfilter_table_empty'] = 'Keine Einträge vorhanden'; $lang['user']['spamfilter_table_empty'] = 'Keine Einträge vorhanden';
$lang['user']['spamfilter_table_remove'] = 'entfernen'; $lang['user']['spamfilter_table_remove'] = 'Entfernen';
$lang['user']['spamfilter_table_add'] = 'Eintrag hinzufügen'; $lang['user']['spamfilter_table_add'] = 'Eintrag hinzufügen';
$lang['user']['spamfilter_behavior'] = 'Bewertung'; $lang['user']['spamfilter_behavior'] = 'Bewertung';
$lang['user']['spamfilter_green'] = 'Grün: Die Nachricht ist kein Spam'; $lang['user']['spamfilter_green'] = 'Grün: Die Nachricht ist kein Spam';
@ -247,7 +248,7 @@ $lang['user']['edit'] = 'Bearbeiten';
$lang['user']['remove'] = 'Entfernen'; $lang['user']['remove'] = 'Entfernen';
$lang['user']['create_syncjob'] = 'Neuen Sync-Job erstellen'; $lang['user']['create_syncjob'] = 'Neuen Sync-Job erstellen';
$lang['start']['mailcow_apps_detail'] = 'Verwenden Sie mailcow Apps, um E-Mails abzurufen, Kalender- und Kontakte zu verwalten und vieles mehr.'; $lang['start']['mailcow_apps_detail'] = 'Verwenden Sie mailcow Apps, um E-Mails abzurufen, Kalender und Kontakte zu verwalten und vieles mehr.';
$lang['start']['mailcow_panel_detail'] = '<b>Domain-Administratoren</b> erstellen, verändern oder löschen Mailboxen, verwalten die Domäne und sehen sonstige Einstellungen ein.<br> $lang['start']['mailcow_panel_detail'] = '<b>Domain-Administratoren</b> erstellen, verändern oder löschen Mailboxen, verwalten die Domäne und sehen sonstige Einstellungen ein.<br>
Als <b>Mailbox-Benutzer</b> erstellen Sie hier zeitlich limitierte Aliasse, ändern das Verhalten des Spamfilters, setzen ein neues Passwort und vieles mehr.'; Als <b>Mailbox-Benutzer</b> erstellen Sie hier zeitlich limitierte Aliasse, ändern das Verhalten des Spamfilters, setzen ein neues Passwort und vieles mehr.';
$lang['start']['imap_smtp_server_auth_info'] = 'Bitte verwenden Sie Ihre vollständige E-Mail-Adresse sowie das PLAIN-Authentifizierungsverfahren.<br> $lang['start']['imap_smtp_server_auth_info'] = 'Bitte verwenden Sie Ihre vollständige E-Mail-Adresse sowie das PLAIN-Authentifizierungsverfahren.<br>
@ -390,7 +391,7 @@ $lang['add']['comment_info'] = 'Ein privater Kommentar ist für den Benutzer nic
$lang['acl']['spam_alias'] = 'Temporäre E-Mail Aliasse'; $lang['acl']['spam_alias'] = 'Temporäre E-Mail Aliasse';
$lang['acl']['tls_policy'] = 'Verschlüsselungsrichtlinie'; $lang['acl']['tls_policy'] = 'Verschlüsselungsrichtlinie';
$lang['acl']['spam_score'] = 'Spam Bewertung'; $lang['acl']['spam_score'] = 'Spam-Bewertung';
$lang['acl']['spam_policy'] = 'Blacklist/Whitelist'; $lang['acl']['spam_policy'] = 'Blacklist/Whitelist';
$lang['acl']['delimiter_action'] = 'Delimiter Aktionen (tags)'; $lang['acl']['delimiter_action'] = 'Delimiter Aktionen (tags)';
$lang['acl']['syncjobs'] = 'Sync Jobs'; $lang['acl']['syncjobs'] = 'Sync Jobs';
@ -405,6 +406,7 @@ $lang['acl']['bcc_maps'] = 'BCC Maps';
$lang['acl']['filters'] = 'Filter'; $lang['acl']['filters'] = 'Filter';
$lang['acl']['ratelimit'] = 'Rate limit'; $lang['acl']['ratelimit'] = 'Rate limit';
$lang['acl']['recipient_maps'] = 'Empfängerumschreibungen'; $lang['acl']['recipient_maps'] = 'Empfängerumschreibungen';
$lang['acl']['unlimited_quota'] = 'Unendliche Quota für Mailboxen';
$lang['acl']['prohibited'] = 'Untersagt durch Richtlinie'; $lang['acl']['prohibited'] = 'Untersagt durch Richtlinie';
$lang['mailbox']['quarantine_notification'] = 'Quarantäne-Benachrichtigung'; $lang['mailbox']['quarantine_notification'] = 'Quarantäne-Benachrichtigung';
@ -466,7 +468,7 @@ $lang['add']['select'] = 'Bitte auswählen';
$lang['add']['target_domain'] = 'Ziel-Domain'; $lang['add']['target_domain'] = 'Ziel-Domain';
$lang['add']['kind'] = 'Art'; $lang['add']['kind'] = 'Art';
$lang['add']['mailbox_username'] = 'Benutzername (linker Teil der E-Mail-Adresse)'; $lang['add']['mailbox_username'] = 'Benutzername (linker Teil der E-Mail-Adresse)';
$lang['add']['full_name'] = 'Vor- und Zuname'; $lang['add']['full_name'] = 'Vor- und Nachname';
$lang['add']['quota_mb'] = 'Speicherplatz (MiB)'; $lang['add']['quota_mb'] = 'Speicherplatz (MiB)';
$lang['add']['select_domain'] = 'Bitte zuerst eine Domain auswählen'; $lang['add']['select_domain'] = 'Bitte zuerst eine Domain auswählen';
$lang['add']['password'] = 'Passwort'; $lang['add']['password'] = 'Passwort';
@ -526,12 +528,12 @@ $lang['admin']['import'] = 'Importieren';
$lang['admin']['duplicate'] = 'Duplizieren'; $lang['admin']['duplicate'] = 'Duplizieren';
$lang['admin']['import_private_key'] = 'Private Key importieren'; $lang['admin']['import_private_key'] = 'Private Key importieren';
$lang['admin']['duplicate_dkim'] = 'DKIM duplizieren'; $lang['admin']['duplicate_dkim'] = 'DKIM duplizieren';
$lang['admin']['f2b_parameters'] = 'Fail2ban Parameter'; $lang['admin']['f2b_parameters'] = 'Fail2ban-Parameter';
$lang['admin']['f2b_ban_time'] = 'Banzeit (s)'; $lang['admin']['f2b_ban_time'] = 'Bannzeit (s)';
$lang['admin']['f2b_max_attempts'] = 'Max. Versuche'; $lang['admin']['f2b_max_attempts'] = 'Max. Versuche';
$lang['admin']['f2b_retry_window'] = 'Wiederholungen im Zeitraum von (s)'; $lang['admin']['f2b_retry_window'] = 'Wiederholungen im Zeitraum von (s)';
$lang['admin']['f2b_netban_ipv4'] = 'Netzbereich für IPv4 Bans (8-32)'; $lang['admin']['f2b_netban_ipv4'] = 'Netzbereich für IPv4-Bans (8-32)';
$lang['admin']['f2b_netban_ipv6'] = 'Netzbereich für IPv6 Bans (8-128)'; $lang['admin']['f2b_netban_ipv6'] = 'Netzbereich für IPv6-Bans (8-128)';
$lang['admin']['f2b_whitelist'] = 'Whitelist für Netzwerke und Hosts'; $lang['admin']['f2b_whitelist'] = 'Whitelist für Netzwerke und Hosts';
$lang['admin']['r_inactive'] = 'Inaktive Restriktionen'; $lang['admin']['r_inactive'] = 'Inaktive Restriktionen';
$lang['admin']['r_active'] = 'Aktive Restriktionen'; $lang['admin']['r_active'] = 'Aktive Restriktionen';
@ -607,11 +609,11 @@ $lang['admin']['forwarding_hosts_hint'] = 'Eingehende Nachrichten werden von den
$lang['admin']['forwarding_hosts_add_hint'] = 'Sie können entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.'; $lang['admin']['forwarding_hosts_add_hint'] = 'Sie können entweder IPv4/IPv6-Adressen, Netzwerke in CIDR-Notation, Hostnamen (die zu IP-Adressen aufgelöst werden), oder Domainnamen (die zu IP-Adressen aufgelöst werden, indem ihr SPF-Record abgefragt wird oder, in dessen Abwesenheit, ihre MX-Records) angeben.';
$lang['admin']['relayhosts_hint'] = 'Erstellen Sie senderabhängige Transporte, um diese im Einstellungsdialog einer Domain auszuwählen.<br> $lang['admin']['relayhosts_hint'] = 'Erstellen Sie senderabhängige Transporte, um diese im Einstellungsdialog einer Domain auszuwählen.<br>
Der Transporttyp lautet immer "smtp:". Benutzereinstellungen bezüglich Verschlüsselungsrichtlinie werden beim Transport berücksichtigt.'; Der Transporttyp lautet immer "smtp:". Benutzereinstellungen bezüglich Verschlüsselungsrichtlinie werden beim Transport berücksichtigt.';
$lang['admin']['transports_hint'] = 'Transport Maps <b>überwiegen</b> senderabhängige Transport Maps und ignorieren die individuellen Einstellungen eines Benutzers bezüglich Verschlüsselungsrichtlinie, da der Absender bei Ermittlung der Transportregel nicht berücksichtigt wird.<br> $lang['admin']['transports_hint'] = 'Transport Maps <b>überwiegen</b> senderabhängige Transport Maps.
Der Transport erfolgt immer via "smtp:".<br> Transport Maps ignorieren Mailbox-Einstellungen für ausgehende Verschlüsselung. Eine serverweite TLS-Richtlinie wird jedoch angewendet.<br>
Ein Eintrag in der TLS Policy Map kann eine Verschlüsselung erzwingen.<br> Der Transport erfolgt immer via "smtp:".<br>
Die Authentifizierung wird anhand des Host Parameters ermittelt, hierbei würde bei einem beispielhaften Next Hop "[host]:25" immer zuerst "host" abfragt und <b>erst im Anschluss</b> "[host]:25".<br> Adressen, die mit "/localhost$/" übereinstimmen, werden immer via "local:" transportiert, daher sind sie von einer Zieldefinition "*" ausgeschlossen.<br>
Dieses Verhalten schließt die <b>gleichzeitige Verwendung</b> von Einträgen der Art "host" sowie "[host]:25" aus.'; Die Authentifizierung wird anhand des "Next hop" Parameters ermittelt. Hierbei würde bei einem beispielhaften Wert "[host]:25" immer zuerst "host" abfragt und <b>erst im Anschluss</b> "[host]:25". Dieses Verhalten schließt die <b>gleichzeitige Verwendung</b> von Einträgen der Art "host" sowie "[host]:25" aus.';
$lang['admin']['add_relayhost_hint'] = 'Bitte beachten Sie, dass Anmeldedaten unverschlüsselt gespeichert werden.<br> $lang['admin']['add_relayhost_hint'] = 'Bitte beachten Sie, dass Anmeldedaten unverschlüsselt gespeichert werden.<br>
Angelegte Transporte dieser Art sind <b>senderabhängig</b> und müssen erst einer Domain zugewiesen werden, bevor sie als Transport verwendet werden.<br> Angelegte Transporte dieser Art sind <b>senderabhängig</b> und müssen erst einer Domain zugewiesen werden, bevor sie als Transport verwendet werden.<br>
Diese Einstellungen entsprechen demach <i>nicht</i> dem "relayhost" Parameter in Postfix.'; Diese Einstellungen entsprechen demach <i>nicht</i> dem "relayhost" Parameter in Postfix.';
@ -769,8 +771,8 @@ $lang['mailbox']['bcc_type'] = "BCC Typ";
$lang['mailbox']['bcc_sender_map'] = "Senderabhängig"; $lang['mailbox']['bcc_sender_map'] = "Senderabhängig";
$lang['mailbox']['bcc_rcpt_map'] = "Empfängerabhängig"; $lang['mailbox']['bcc_rcpt_map'] = "Empfängerabhängig";
$lang['mailbox']['bcc_local_dest'] = "Lokales Ziel"; $lang['mailbox']['bcc_local_dest'] = "Lokales Ziel";
$lang['mailbox']['bcc_destinations'] = "BCC Ziel"; $lang['mailbox']['bcc_destinations'] = "BCC-Ziel";
$lang['mailbox']['bcc_destination'] = "BCC Ziel"; $lang['mailbox']['bcc_destination'] = "BCC-Ziel";
$lang['edit']['bcc_dest_format'] = 'BCC-Ziel muss eine gültige E-Mail-Adresse sein.'; $lang['edit']['bcc_dest_format'] = 'BCC-Ziel muss eine gültige E-Mail-Adresse sein.';
$lang['mailbox']['bcc'] = "BCC"; $lang['mailbox']['bcc'] = "BCC";
@ -809,7 +811,7 @@ $lang['oauth2']['authorize_app'] = 'Anwendung authorisieren';
$lang['oauth2']['deny'] = 'Ablehnen'; $lang['oauth2']['deny'] = 'Ablehnen';
$lang['oauth2']['access_denied'] = 'Bitte als Mailbox-Nutzer einloggen, um den Zugriff via OAuth2 zu erlauben.'; $lang['oauth2']['access_denied'] = 'Bitte als Mailbox-Nutzer einloggen, um den Zugriff via OAuth2 zu erlauben.';
$lang['admin']['sys_mails'] = 'System E-Mails'; $lang['admin']['sys_mails'] = 'System-E-Mails';
$lang['admin']['subject'] = 'Betreff'; $lang['admin']['subject'] = 'Betreff';
$lang['admin']['from'] = 'Absender'; $lang['admin']['from'] = 'Absender';
$lang['admin']['include_exclude'] = 'Ein- und Ausschlüsse'; $lang['admin']['include_exclude'] = 'Ein- und Ausschlüsse';

View File

@ -20,6 +20,7 @@ $lang['footer']['cancel'] = 'Cancel';
$lang['footer']['hibp_nok'] = 'Matched! This is a potentially dangerous password!'; $lang['footer']['hibp_nok'] = 'Matched! This is a potentially dangerous password!';
$lang['footer']['hibp_ok'] = 'No match found.'; $lang['footer']['hibp_ok'] = 'No match found.';
$lang['danger']['unlimited_quota_acl'] = "Unlimited quota prohibited by ACL";
$lang['danger']['mysql_error'] = "MySQL error: %s"; $lang['danger']['mysql_error'] = "MySQL error: %s";
$lang['danger']['redis_error'] = "Redis error: %s"; $lang['danger']['redis_error'] = "Redis error: %s";
$lang['danger']['unknown_tfa_method'] = "Unknown TFA method"; $lang['danger']['unknown_tfa_method'] = "Unknown TFA method";
@ -418,6 +419,7 @@ $lang['acl']['bcc_maps'] = 'BCC maps';
$lang['acl']['filters'] = 'Filters'; $lang['acl']['filters'] = 'Filters';
$lang['acl']['ratelimit'] = 'Rate limit'; $lang['acl']['ratelimit'] = 'Rate limit';
$lang['acl']['recipient_maps'] = 'Recipient maps'; $lang['acl']['recipient_maps'] = 'Recipient maps';
$lang['acl']['unlimited_quota'] = 'Unlimited quota for mailboxes';
$lang['acl']['prohibited'] = 'Prohibited by ACL'; $lang['acl']['prohibited'] = 'Prohibited by ACL';
$lang['mailbox']['quarantine_notification'] = 'Quarantine notifications'; $lang['mailbox']['quarantine_notification'] = 'Quarantine notifications';
@ -631,9 +633,11 @@ $lang['admin']['forwarding_hosts_hint'] = 'Incoming messages are unconditionally
$lang['admin']['forwarding_hosts_add_hint'] = 'You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).'; $lang['admin']['forwarding_hosts_add_hint'] = 'You can either specify IPv4/IPv6 addresses, networks in CIDR notation, host names (which will be resolved to IP addresses), or domain names (which will be resolved to IP addresses by querying SPF records or, in their absence, MX records).';
$lang['admin']['relayhosts_hint'] = 'Define sender-dependent transports to be able to select them in a domains configuration dialog.<br> $lang['admin']['relayhosts_hint'] = 'Define sender-dependent transports to be able to select them in a domains configuration dialog.<br>
The transport service is always "smtp:". A users individual outbound TLS policy setting is taken into account.'; The transport service is always "smtp:". A users individual outbound TLS policy setting is taken into account.';
$lang['admin']['transports_hint'] = 'A transport map entry <b>overrules</b> a sender-dependent transport map</b>.<br> $lang['admin']['transports_hint'] = ' A transport map entry <b>overrules</b> a sender-dependent transport map</b>.<br>
Outbound TLS policy settings per-user are ignored and can only be enfored by TLS policy map entries. The transport service is always "smtp:".<br> Outbound TLS policy settings per-user are ignored and can only be enfored by TLS policy map entries.<br>
To determine credentials for an exemplary next hop "[host]:25", Postfix <b>always</b> queries for "nexthop" before searching for "[nexthop]:25". This behavior makes it impossible to use "nexthop" and "[nexthop]:25" at the same time.'; The transport service for defined transports is always "smtp:".<br>
Adresses matching "/localhost$/" will always be transported via "local:", therefore a "*" destination will not apply to those addresses.<br>
To determine credentials for an exemplary next hop "[host]:25", Postfix <b>always</b> queries for "host" before searching for "[host]:25". This behavior makes it impossible to use "host" and "[host]:25" at the same time.';
$lang['admin']['add_relayhost_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.'; $lang['admin']['add_relayhost_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.';
$lang['admin']['add_transports_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.'; $lang['admin']['add_transports_hint'] = 'Please be aware that authentication data, if any, will be stored as plain text.';
$lang['admin']['host'] = 'Host'; $lang['admin']['host'] = 'Host';

View File

@ -348,6 +348,11 @@ $is_dual = (!empty($_SESSION["dual-login"]["username"])) ? 'true' : 'false';
echo "var role = '". $role . "';\n"; echo "var role = '". $role . "';\n";
echo "var is_dual = " . $is_dual . ";\n"; echo "var is_dual = " . $is_dual . ";\n";
echo "var pagination_size = '". $PAGINATION_SIZE . "';\n"; echo "var pagination_size = '". $PAGINATION_SIZE . "';\n";
$ALLOW_ADMIN_EMAIL_LOGIN = (preg_match(
"/^([yY][eE][sS]|[yY])+$/",
$_ENV["ALLOW_ADMIN_EMAIL_LOGIN"]
)) ? "true" : "false";
echo "var ALLOW_ADMIN_EMAIL_LOGIN = " . $ALLOW_ADMIN_EMAIL_LOGIN . ";\n";
?> ?>
</script> </script>
<?php <?php

View File

@ -81,7 +81,7 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
<ol> <ol>
<li> <li>
<p><?=$lang['tfa']['scan_qr_code'];?></p> <p><?=$lang['tfa']['scan_qr_code'];?></p>
<img src="<?=$tfa->getQRCodeImageAsDataUri($_SESSION['mailcow_cc_username'], $totp_secret);?>"> <img id="tfa-qr-img" data-totp-secret="<?=$totp_secret;?>" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">
<p class="help-block"><?=$lang['tfa']['enter_qr_code'];?>:<br /> <p class="help-block"><?=$lang['tfa']['enter_qr_code'];?>:<br />
<code><?=$totp_secret;?></code> <code><?=$totp_secret;?></code>
</p> </p>

View File

@ -43,8 +43,8 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
<br /><span id="quotaBadge" class="badge">max. - MiB</span> <br /><span id="quotaBadge" class="badge">max. - MiB</span>
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" class="form-control" name="quota" min="1" max="" id="addInputQuota" disabled value="<?=$lang['add']['select_domain'];?>" required> <input type="text" class="form-control" name="quota" min="0" max="" id="addInputQuota" disabled value="<?=$lang['add']['select_domain'];?>" required>
<small class="help-block">min. 1</small> <small class="help-block">0 = </small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -785,24 +785,3 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div> </div>
</div> </div>
</div><!-- DNS info modal --> </div><!-- DNS info modal -->
<script>
$('#addResourceModal').on('shown.bs.modal', function() {
$("#multiple_bookings").val($("#multiple_bookings_select").val());
if ($("#multiple_bookings").val() == "custom") {
$("#multiple_bookings_custom_div").show();
$("#multiple_bookings").val($("#multiple_bookings_custom").val());
}
})
$("#multiple_bookings_select").change(function() {
$("#multiple_bookings").val($("#multiple_bookings_select").val());
if ($("#multiple_bookings").val() == "custom") {
$("#multiple_bookings_custom_div").show();
}
else {
$("#multiple_bookings_custom_div").hide();
}
});
$("#multiple_bookings_custom").bind ("change keypress keyup blur", function () {
$("#multiple_bookings").val($("#multiple_bookings_custom").val());
});
</script>

86
data/web/sogo-auth.php Normal file
View File

@ -0,0 +1,86 @@
<?php
$ALLOW_ADMIN_EMAIL_LOGIN = (preg_match(
"/^([yY][eE][sS]|[yY])+$/",
$_ENV["ALLOW_ADMIN_EMAIL_LOGIN"]
));
$session_var_user_allowed = 'sogo-sso-user-allowed';
$session_var_pass = 'sogo-sso-pass';
// prevent if feature is disabled
if (!$ALLOW_ADMIN_EMAIL_LOGIN) {
header('HTTP/1.0 403 Forbidden');
echo "this feature is disabled";
exit;
}
// validate credentials for basic auth requests
elseif (isset($_SERVER['PHP_AUTH_USER'])) {
// load prerequisites only when required
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
$username = $_SERVER['PHP_AUTH_USER'];
$password = $_SERVER['PHP_AUTH_PW'];
$login_check = check_login($username, $password);
if ($login_check === 'user') {
header("X-User: $username");
header("X-Auth: Basic ".base64_encode("$username:$password"));
header("X-Auth-Type: Basic");
exit;
} else {
header('HTTP/1.0 401 Unauthorized');
echo 'Invalid login';
exit;
}
}
// check permissions and redirect for direct GET ?login=xy requests
elseif (isset($_GET['login'])) {
// load prerequisites only when required
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/prerequisites.inc.php';
// check permissions
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['acl']['login_as'] == "1") {
$login = html_entity_decode(rawurldecode($_GET["login"]));
if (filter_var($login, FILTER_VALIDATE_EMAIL)) {
if (!empty(mailbox('get', 'mailbox_details', $login))) {
// load master password
$sogo_sso_pass = file_get_contents("/etc/sogo-sso/sogo-sso.pass");
// register username and password in session
$_SESSION[$session_var_user_allowed][] = $login;
$_SESSION[$session_var_pass] = $sogo_sso_pass;
// redirect to sogo (sogo will get the correct credentials via nginx auth_request
header("Location: /SOGo/so/${login}");
exit;
}
}
}
header('HTTP/1.0 403 Forbidden');
exit;
}
// only check for admin-login on sogo GUI requests
elseif (
strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0
) {
// this is an nginx auth_request call, we check for existing sogo-sso session variables
session_start();
// extract email address from "/SOGo/so/user@domain/xy"
$url_parts = explode("/", $_SERVER['HTTP_X_ORIGINAL_URI']);
$email = $url_parts[3];
// check if this email is in session allowed list
if (
!empty($email) &&
filter_var($email, FILTER_VALIDATE_EMAIL) &&
is_array($_SESSION[$session_var_user_allowed]) &&
in_array($email, $_SESSION[$session_var_user_allowed])
) {
$username = $email;
$password = $_SESSION[$session_var_pass];
header("X-User: $username");
header("X-Auth: Basic ".base64_encode("$username:$password"));
header("X-Auth-Type: Basic");
exit;
}
}
// if username is empty, SOGo will use the normal login methods / login form
header("X-User: ");
header("X-Auth: ");
header("X-Auth-Type: ");

View File

@ -63,6 +63,7 @@ if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'doma
</div> </div>
</div> </div>
</div> </div>
</div>
<?php <?php
} }
elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') { elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'user') {
@ -92,227 +93,227 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
} }
?> ?>
<div class="container"> <div class="container">
<h3><?=$lang['user']['user_settings'];?></h3>
<div class="panel panel-default"> <!-- Nav tabs -->
<div class="panel-heading"><?=$lang['user']['mailbox_details'];?></div> <ul class="nav nav-tabs" role="tablist">
<div class="panel-body"> <li role="presentation" class="active"><a href="#userSettings" aria-controls="userSettings" role="tab" data-toggle="tab"><?=$lang['user']['mailbox_details'];?></a></li>
<div class="row"> <li role="presentation"><a href="#SpamAliases" aria-controls="SpamAliases" role="tab" data-toggle="tab"><?=$lang['user']['spam_aliases'];?></a></li>
<div class="col-sm-offset-3 col-sm-9"> <li role="presentation"><a href="#Spamfilter" aria-controls="Spamfilter" role="tab" data-toggle="tab"><?=$lang['user']['spamfilter'];?></a></li>
<?php if ($mailboxdata['attributes']['force_pw_update'] == "1"): ?> <li role="presentation"><a href="#Syncjobs" aria-controls="Syncjobs" role="tab" data-toggle="tab"><?=$lang['user']['sync_jobs'];?></a></li>
<div class="alert alert-danger"><?=$lang['user']['force_pw_update'];?></div> </ul>
<?php endif; ?>
<p><a href="#pwChangeModal" data-toggle="modal">[<?=$lang['user']['change_password'];?>]</a></p>
<p><a target="_blank" href="https://mailcow.github.io/mailcow-dockerized-docs/client/#<?=$clientconfigstr;?>">[<?=$lang['user']['client_configuration'];?>]</a></p>
<p><a href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p>
<p><small>
<?php
if ($_SESSION['mailcow_cc_last_login']['remote']):
?>
<span style="margin-right:10px" class="glyphicon glyphicon-log-in"></span> <span data-time="<?=$_SESSION['mailcow_cc_last_login']['time'];?>" class="last_login_date"></span> (<?=$_SESSION['mailcow_cc_last_login']['remote'];?>)
<?php
else: echo "Last login: -"; endif;
?>
</small></p>
</div>
</div>
<hr> <hr>
<?php // Get user information about aliases
$user_get_alias_details = user_get_alias_details($username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>:
<p class="small"><?=$lang['user']['direct_aliases_desc'];?></p>
</div>
<div class="col-md-9 col-xs-7">
<?php
if ($user_get_alias_details['direct_aliases'] === false) {
echo '&#10008;';
}
else {
foreach (array_filter($user_get_alias_details['direct_aliases']) as $direct_alias => $direct_alias_meta) {
(!empty($direct_alias_meta['public_comment'])) ?
printf('%s &mdash; <span class="bg-info">%s</span><br>', $direct_alias, $direct_alias_meta['public_comment']) :
printf('%s<br>', $direct_alias);
}
}
?>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>:
<p class="small"><?=$lang['user']['shared_aliases_desc'];?></p>
</div>
<div class="col-md-9 col-xs-7">
<?php
if ($user_get_alias_details['shared_aliases'] === false) {
echo '&#10008;';
}
else {
foreach (array_filter($user_get_alias_details['shared_aliases']) as $shared_alias => $shared_alias_meta) {
(!empty($shared_alias_meta['public_comment'])) ?
printf('%s &mdash; <span class="bg-info">%s</span><br>', $shared_alias, $shared_alias_meta['public_comment']) :
printf('%s<br>', $shared_alias); <div class="tab-content">
}
} <div role="tabpanel" class="tab-pane active" id="userSettings">
?> <div class="panel panel-default">
</div> <div class="panel-heading"><?=$lang['user']['mailbox_details'];?></div>
</div> <div class="panel-body">
<hr> <div class="row">
<div class="row"> <div class="col-sm-offset-3 col-sm-9">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div> <?php if ($mailboxdata['attributes']['force_pw_update'] == "1"): ?>
<div class="col-md-9 col-xs-7"> <div class="alert alert-danger"><?=$lang['user']['force_pw_update'];?></div>
<p><?=($user_get_alias_details['aliases_also_send_as'] == '*') ? $lang['user']['sender_acl_disabled'] : $user_get_alias_details['aliases_also_send_as'];?></p> <?php endif; ?>
</div> <p><a href="#pwChangeModal" data-toggle="modal">[<?=$lang['user']['change_password'];?>]</a></p>
</div> <p><a target="_blank" href="https://mailcow.github.io/mailcow-dockerized-docs/client/#<?=$clientconfigstr;?>">[<?=$lang['user']['client_configuration'];?>]</a></p>
<div class="row"> <p><a href="#userFilterModal" data-toggle="modal">[<?=$lang['user']['show_sieve_filters'];?>]</a></p>
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_send_as_all'];?>:</div> <p><small>
<div class="col-md-9 col-xs-7"> <?php
<p><?=$user_get_alias_details['aliases_send_as_all'];?></p> if ($_SESSION['mailcow_cc_last_login']['remote']):
</div> ?>
</div> <span style="margin-right:10px" class="glyphicon glyphicon-log-in"></span> <span data-time="<?=$_SESSION['mailcow_cc_last_login']['time'];?>" class="last_login_date"></span> (<?=$_SESSION['mailcow_cc_last_login']['remote'];?>)
<div class="row"> <?php
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['is_catch_all'];?>:</div> else: echo "Last login: -"; endif;
<div class="col-md-9 col-xs-7"> ?>
<p><?=$user_get_alias_details['is_catch_all'];?></p> </small></p>
</div> </div>
</div> </div>
<hr> <hr>
<div class="row"> <?php // Get user information about aliases
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['in_use'];?>:</div> $user_get_alias_details = user_get_alias_details($username);
<div class="col-md-5 col-xs-7"> ?>
<div class="progress"> <div class="row">
<div class="progress-bar progress-bar-<?=$mailboxdata['percent_class'];?>" role="progressbar" aria-valuenow="<?=$mailboxdata['percent_in_use'];?>" aria-valuemin="0" aria-valuemax="100" style="min-width:2em;width: <?=$mailboxdata['percent_in_use'];?>%;"> <div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['direct_aliases'];?>:
<?=$mailboxdata['percent_in_use'];?>% <p class="small"><?=$lang['user']['direct_aliases_desc'];?></p>
</div>
<div class="col-md-9 col-xs-7">
<?php
if ($user_get_alias_details['direct_aliases'] === false) {
echo '&#10008;';
}
else {
foreach (array_filter($user_get_alias_details['direct_aliases']) as $direct_alias => $direct_alias_meta) {
(!empty($direct_alias_meta['public_comment'])) ?
printf('%s &mdash; <span class="bg-info">%s</span><br>', $direct_alias, $direct_alias_meta['public_comment']) :
printf('%s<br>', $direct_alias);
}
}
?>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['shared_aliases'];?>:
<p class="small"><?=$lang['user']['shared_aliases_desc'];?></p>
</div>
<div class="col-md-9 col-xs-7">
<?php
if ($user_get_alias_details['shared_aliases'] === false) {
echo '&#10008;';
}
else {
foreach (array_filter($user_get_alias_details['shared_aliases']) as $shared_alias => $shared_alias_meta) {
(!empty($shared_alias_meta['public_comment'])) ?
printf('%s &mdash; <span class="bg-info">%s</span><br>', $shared_alias, $shared_alias_meta['public_comment']) :
printf('%s<br>', $shared_alias);
}
}
?>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_also_send_as'];?>:</div>
<div class="col-md-9 col-xs-7">
<p><?=($user_get_alias_details['aliases_also_send_as'] == '*') ? $lang['user']['sender_acl_disabled'] : $user_get_alias_details['aliases_also_send_as'];?></p>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['aliases_send_as_all'];?>:</div>
<div class="col-md-9 col-xs-7">
<p><?=$user_get_alias_details['aliases_send_as_all'];?></p>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['is_catch_all'];?>:</div>
<div class="col-md-9 col-xs-7">
<p><?=$user_get_alias_details['is_catch_all'];?></p>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['in_use'];?>:</div>
<div class="col-md-5 col-xs-7">
<div class="progress">
<div class="progress-bar progress-bar-<?=$mailboxdata['percent_class'];?>" role="progressbar" aria-valuenow="<?=$mailboxdata['percent_in_use'];?>" aria-valuemin="0" aria-valuemax="100" style="min-width:2em;width: <?=$mailboxdata['percent_in_use'];?>%;">
<?=$mailboxdata['percent_in_use'];?>%
</div>
</div>
<p><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=($mailboxdata['quota'] == 0) ? '∞' : formatBytes($mailboxdata['quota'], 2);?><br><?=$mailboxdata['messages'];?> <?=$lang['user']['messages'];?></p>
</div>
</div>
<hr>
<?php
// Show tagging options
$get_tagging_options = mailbox('get', 'delimiter_action', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tag_handling'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['delimiter_action'];?>">
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subfolder") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"subfolder"}'><?=$lang['user']['tag_in_subfolder'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subject") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"subject"}'><?=$lang['user']['tag_in_subject'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "none") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"none"}'><?=$lang['user']['tag_in_none'];?></button>
</div>
<p class="help-block"><?=$lang['user']['tag_help_explain'];?></p>
<p class="help-block"><?=$lang['user']['tag_help_example'];?></p>
</div>
</div>
<?php
// Show TLS policy options
$get_tls_policy = mailbox('get', 'tls_policy', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tls_policy'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['tls_policy'];?>">
<button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_in'] == "1") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="tls_policy"
data-api-url='edit/tls_policy'
data-api-attr='{"tls_enforce_in":<?=($get_tls_policy['tls_enforce_in'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_in'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_out'] == "1") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="tls_policy"
data-api-url='edit/tls_policy'
data-api-attr='{"tls_enforce_out":<?=($get_tls_policy['tls_enforce_out'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_out'];?></button>
</div>
<p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p>
</div>
</div>
<?php
// Show quarantine_notification options
$quarantine_notification = mailbox('get', 'quarantine_notification', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['quarantine_notification'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_notification'];?>">
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "never") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"never"}'><?=$lang['user']['never'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "hourly") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"hourly"}'><?=$lang['user']['hourly'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "daily") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"daily"}'><?=$lang['user']['daily'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "weekly") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"weekly"}'><?=$lang['user']['weekly'];?></button>
</div>
<p class="help-block"><?=$lang['user']['quarantine_notification_info'];?></p>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['eas_reset'];?>:</div>
<div class="col-md-9 col-xs-7">
<button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['eas_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="eas_cache" data-api-url='delete/eas_cache' href="#"><?=$lang['user']['eas_reset_now'];?></button>
<p class="help-block"><?=$lang['user']['eas_reset_help'];?></p>
</div>
</div>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['sogo_profile_reset'];?>:</div>
<div class="col-md-9 col-xs-7">
<button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['sogo_profile_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['sogo_profile_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="sogo_profile" data-api-url='delete/sogo_profile' href="#"><?=$lang['user']['sogo_profile_reset_now'];?></button>
<p class="help-block"><?=$lang['user']['sogo_profile_reset_help'];?></p>
</div>
</div> </div>
</div> </div>
<p><?=formatBytes($mailboxdata['quota_used'], 2);?> / <?=formatBytes($mailboxdata['quota'], 2);?>, <?=$mailboxdata['messages'];?> <?=$lang['user']['messages'];?></p>
</div>
</div>
<hr>
<?php
// Show tagging options
$get_tagging_options = mailbox('get', 'delimiter_action', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tag_handling'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['delimiter_action'];?>">
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subfolder") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"subfolder"}'><?=$lang['user']['tag_in_subfolder'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "subject") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"subject"}'><?=$lang['user']['tag_in_subject'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tagging_options == "none") ? 'active' : null; ?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="delimiter_action"
data-api-url='edit/delimiter_action'
data-api-attr='{"tagged_mail_handler":"none"}'><?=$lang['user']['tag_in_none'];?></button>
</div>
<p class="help-block"><?=$lang['user']['tag_help_explain'];?></p>
<p class="help-block"><?=$lang['user']['tag_help_example'];?></p>
</div>
</div>
<?php
// Show TLS policy options
$get_tls_policy = mailbox('get', 'tls_policy', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['tls_policy'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['tls_policy'];?>">
<button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_in'] == "1") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="tls_policy"
data-api-url='edit/tls_policy'
data-api-attr='{"tls_enforce_in":<?=($get_tls_policy['tls_enforce_in'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_in'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($get_tls_policy['tls_enforce_out'] == "1") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="tls_policy"
data-api-url='edit/tls_policy'
data-api-attr='{"tls_enforce_out":<?=($get_tls_policy['tls_enforce_out'] == "1") ? "0" : "1";?>}'><?=$lang['user']['tls_enforce_out'];?></button>
</div>
<p class="help-block"><?=$lang['user']['tls_policy_warning'];?></p>
</div>
</div>
<?php
// Show quarantine_notification options
$quarantine_notification = mailbox('get', 'quarantine_notification', $username);
?>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['quarantine_notification'];?>:</div>
<div class="col-md-9 col-xs-7">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['quarantine_notification'];?>">
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "never") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"never"}'><?=$lang['user']['never'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "hourly") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"hourly"}'><?=$lang['user']['hourly'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "daily") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"daily"}'><?=$lang['user']['daily'];?></button>
<button type="button" class="btn btn-sm btn-default <?=($quarantine_notification == "weekly") ? "active" : null;?>"
data-action="edit_selected"
data-item="<?= htmlentities($username); ?>"
data-id="quarantine_notification"
data-api-url='edit/quarantine_notification'
data-api-attr='{"quarantine_notification":"weekly"}'><?=$lang['user']['weekly'];?></button>
</div>
<p class="help-block"><?=$lang['user']['quarantine_notification_info'];?></p>
</div>
</div>
<hr>
<div class="row">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['eas_reset'];?>:</div>
<div class="col-md-9 col-xs-7">
<button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['eas_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['eas_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="eas_cache" data-api-url='delete/eas_cache' href="#"><?=$lang['user']['eas_reset_now'];?></button>
<p class="help-block"><?=$lang['user']['eas_reset_help'];?></p>
</div> </div>
</div> </div>
<div class="row"> <div role="tabpanel" class="tab-pane" id="SpamAliases">
<div class="col-md-3 col-xs-5 text-right"><?=$lang['user']['sogo_profile_reset'];?>:</div>
<div class="col-md-9 col-xs-7">
<button class="btn btn-xs btn-default" data-acl="<?=$_SESSION['acl']['sogo_profile_reset'];?>" data-action="delete_selected" data-text="<?=$lang['user']['sogo_profile_reset'];?>?" data-item="<?= htmlentities($username); ?>" data-id="sogo_profile" data-api-url='delete/sogo_profile' href="#"><?=$lang['user']['sogo_profile_reset_now'];?></button>
<p class="help-block"><?=$lang['user']['sogo_profile_reset_help'];?></p>
</div>
</div>
</div>
</div>
<!-- Nav tabs -->
<ul class="nav nav-pills nav-justified" role="tablist">
<li role="presentation" class="active"><a href="#SpamAliases" aria-controls="SpamAliases" role="tab" data-toggle="tab"><?=$lang['user']['spam_aliases'];?></a></li>
<li role="presentation"><a href="#Spamfilter" aria-controls="Spamfilter" role="tab" data-toggle="tab"><?=$lang['user']['spamfilter'];?></a></li>
<li role="presentation"><a href="#Syncjobs" aria-controls="Syncjobs" role="tab" data-toggle="tab"><?=$lang['user']['sync_jobs'];?></a></li>
</ul>
<hr>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="SpamAliases">
<div class="row"> <div class="row">
<div class="col-md-12 col-sm-12 col-xs-12"> <div class="col-md-12 col-sm-12 col-xs-12">
<div class="table-responsive"> <div class="table-responsive">
@ -320,7 +321,6 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
</div> </div>
</div> </div>
</div> </div>
<div class="mass-actions-user"> <div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_alias'];?>"> <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_alias'];?>">
<div class="btn-group"> <div class="btn-group">
@ -348,7 +348,6 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="Spamfilter"> <div role="tabpanel" class="tab-pane" id="Spamfilter">
@ -376,10 +375,8 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<p><?=$lang['user']['spamfilter_hint'];?></p> <p><?=$lang['user']['spamfilter_hint'];?></p>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-10"> <div class="col-sm-10">
</div> </div>
<div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>"> <div class="btn-group" data-acl="<?=$_SESSION['acl']['spam_policy'];?>">
<a data-acl="<?=$_SESSION['acl']['spam_score'];?>" type="button" class="btn btn-sm btn-success" data-action="edit_selected" <a data-acl="<?=$_SESSION['acl']['spam_score'];?>" type="button" class="btn btn-sm btn-success" data-action="edit_selected"
@ -394,7 +391,6 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
data-api-attr='{"spam_score":"default"}'><?=$lang['user']['spam_score_reset'];?></a> data-api-attr='{"spam_score":"default"}'><?=$lang['user']['spam_score_reset'];?></a>
</div> </div>
</div> </div>
</form> </form>
<hr> <hr>
<div class="row"> <div class="row">
@ -419,7 +415,6 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
</span> </span>
</div> </div>
</form> </form>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<h4><?=$lang['user']['spamfilter_bl'];?></h4> <h4><?=$lang['user']['spamfilter_bl'];?></h4>
@ -451,7 +446,6 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped" id="sync_job_table"></table> <table class="table table-striped" id="sync_job_table"></table>
</div> </div>
<div class="mass-actions-user"> <div class="mass-actions-user">
<div class="btn-group" data-acl="<?=$_SESSION['acl']['syncjobs'];?>"> <div class="btn-group" data-acl="<?=$_SESSION['acl']['syncjobs'];?>">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a> <a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="syncjob" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
@ -465,10 +459,9 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a> <a class="btn btn-sm btn-success" href="#" data-toggle="modal" data-target="#addSyncJobModal"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['create_syncjob'];?></a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div><!-- /container --> </div><!-- /container -->
<div style="margin-bottom:200px;"></div> <div style="margin-bottom:200px;"></div>
<?php <?php

View File

@ -55,7 +55,7 @@ services:
- redis - redis
clamd-mailcow: clamd-mailcow:
image: mailcow/clamd:1.21 image: mailcow/clamd:1.22
build: ./data/Dockerfiles/clamd build: ./data/Dockerfiles/clamd
restart: always restart: always
environment: environment:
@ -71,7 +71,7 @@ services:
- clamd - clamd
rspamd-mailcow: rspamd-mailcow:
image: mailcow/rspamd:1.34 image: mailcow/rspamd:1.38
build: ./data/Dockerfiles/rspamd build: ./data/Dockerfiles/rspamd
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
@ -94,7 +94,7 @@ services:
- rspamd - rspamd
php-fpm-mailcow: php-fpm-mailcow:
image: mailcow/phpfpm:1.34 image: mailcow/phpfpm:1.36
build: ./data/Dockerfiles/phpfpm build: ./data/Dockerfiles/phpfpm
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0" command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on: depends_on:
@ -106,6 +106,7 @@ services:
- mysql-socket-vol-1:/var/run/mysqld/ - mysql-socket-vol-1:/var/run/mysqld/
- ./data/conf/sogo/:/etc/sogo/ - ./data/conf/sogo/:/etc/sogo/
- ./data/conf/rspamd/meta_exporter:/meta_exporter:ro - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro
- ./data/conf/phpfpm/sogo-sso/:/etc/sogo-sso/
- ./data/conf/phpfpm/php-fpm.d/pools.conf:/usr/local/etc/php-fpm.d/z-pools.conf - ./data/conf/phpfpm/php-fpm.d/pools.conf:/usr/local/etc/php-fpm.d/z-pools.conf
- ./data/conf/phpfpm/php-conf.d/opcache-recommended.ini:/usr/local/etc/php/conf.d/opcache-recommended.ini - ./data/conf/phpfpm/php-conf.d/opcache-recommended.ini:/usr/local/etc/php/conf.d/opcache-recommended.ini
- ./data/conf/phpfpm/php-conf.d/upload.ini:/usr/local/etc/php/conf.d/upload.ini - ./data/conf/phpfpm/php-conf.d/upload.ini:/usr/local/etc/php/conf.d/upload.ini
@ -130,6 +131,7 @@ services:
- API_ALLOW_FROM=${API_ALLOW_FROM:-invalid} - API_ALLOW_FROM=${API_ALLOW_FROM:-invalid}
- COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-mailcow-dockerized} - COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-mailcow-dockerized}
- SKIP_SOLR=${SKIP_SOLR:-y} - SKIP_SOLR=${SKIP_SOLR:-y}
- ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
restart: always restart: always
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
@ -139,7 +141,7 @@ services:
- phpfpm - phpfpm
sogo-mailcow: sogo-mailcow:
image: mailcow/sogo:1.51 image: mailcow/sogo:1.54
build: ./data/Dockerfiles/sogo build: ./data/Dockerfiles/sogo
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
@ -149,11 +151,14 @@ services:
- LOG_LINES=${LOG_LINES:-9999} - LOG_LINES=${LOG_LINES:-9999}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- ACL_ANYONE=${ACL_ANYONE:-disallow} - ACL_ANYONE=${ACL_ANYONE:-disallow}
- ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
- IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
volumes: volumes:
- ./data/conf/sogo/:/etc/sogo/ - ./data/conf/sogo/:/etc/sogo/
- ./data/web/inc/init_db.inc.php:/init_db.inc.php - ./data/web/inc/init_db.inc.php:/init_db.inc.php
- ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js - ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js
- mysql-socket-vol-1:/var/run/mysqld/ - mysql-socket-vol-1:/var/run/mysqld/
- sogo-web-vol-1:/sogo_web
restart: always restart: always
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
@ -164,7 +169,7 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: mailcow/dovecot:1.62 image: mailcow/dovecot:1.67
build: ./data/Dockerfiles/dovecot build: ./data/Dockerfiles/dovecot
cap_add: cap_add:
- NET_BIND_SERVICE - NET_BIND_SERVICE
@ -172,6 +177,7 @@ services:
- ./data/conf/dovecot:/usr/local/etc/dovecot - ./data/conf/dovecot:/usr/local/etc/dovecot
- ./data/assets/ssl:/etc/ssl/mail/:ro - ./data/assets/ssl:/etc/ssl/mail/:ro
- ./data/conf/sogo/:/etc/sogo/ - ./data/conf/sogo/:/etc/sogo/
- ./data/conf/phpfpm/sogo-sso/:/etc/phpfpm/
- vmail-vol-1:/var/vmail - vmail-vol-1:/var/vmail
- vmail-attachments-vol-1:/var/attachments - vmail-attachments-vol-1:/var/attachments
- crypt-vol-1:/mail_crypt/ - crypt-vol-1:/mail_crypt/
@ -185,9 +191,12 @@ services:
- DBUSER=${DBUSER} - DBUSER=${DBUSER}
- DBPASS=${DBPASS} - DBPASS=${DBPASS}
- TZ=${TZ} - TZ=${TZ}
- IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
- ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
- MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-1440} - MAILDIR_GC_TIME=${MAILDIR_GC_TIME:-1440}
- ACL_ANYONE=${ACL_ANYONE:-disallow} - ACL_ANYONE=${ACL_ANYONE:-disallow}
- SKIP_SOLR=${SKIP_SOLR:-y} - SKIP_SOLR=${SKIP_SOLR:-y}
- MAILDIR_SUB=${MAILDIR_SUB:-}
ports: ports:
- "${DOVEADM_PORT:-127.0.0.1:19991}:12345" - "${DOVEADM_PORT:-127.0.0.1:19991}:12345"
- "${IMAP_PORT:-143}:143" - "${IMAP_PORT:-143}:143"
@ -207,11 +216,12 @@ services:
hostname: ${MAILCOW_HOSTNAME} hostname: ${MAILCOW_HOSTNAME}
networks: networks:
mailcow-network: mailcow-network:
ipv4_address: ${IPV4_NETWORK:-172.22.1}.250
aliases: aliases:
- dovecot - dovecot
postfix-mailcow: postfix-mailcow:
image: mailcow/postfix:1.29 image: mailcow/postfix:1.31
build: ./data/Dockerfiles/postfix build: ./data/Dockerfiles/postfix
volumes: volumes:
- ./data/conf/postfix:/opt/postfix/conf - ./data/conf/postfix:/opt/postfix/conf
@ -262,6 +272,7 @@ services:
envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active && envsubst < /etc/nginx/conf.d/templates/server_name.template > /etc/nginx/conf.d/server_name.active &&
envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active && envsubst < /etc/nginx/conf.d/templates/sogo.template > /etc/nginx/conf.d/sogo.active &&
envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /etc/nginx/conf.d/sogo_eas.active && envsubst < /etc/nginx/conf.d/templates/sogo_eas.template > /etc/nginx/conf.d/sogo_eas.active &&
. /etc/nginx/conf.d/templates/sogo.auth_request.template.sh > /etc/nginx/conf.d/sogo_proxy_auth.active &&
nginx -qt && nginx -qt &&
until ping phpfpm -c1 > /dev/null; do sleep 1; done && until ping phpfpm -c1 > /dev/null; do sleep 1; done &&
until ping sogo -c1 > /dev/null; do sleep 1; done && until ping sogo -c1 > /dev/null; do sleep 1; done &&
@ -274,14 +285,14 @@ services:
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- IPV4_NETWORK=${IPV4_NETWORK:-172.22.1} - IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
- TZ=${TZ} - TZ=${TZ}
- ALLOW_ADMIN_EMAIL_LOGIN=${ALLOW_ADMIN_EMAIL_LOGIN:-n}
volumes: volumes:
- ./data/web:/web:ro - ./data/web:/web:ro
- ./data/conf/rspamd/dynmaps:/dynmaps:ro - ./data/conf/rspamd/dynmaps:/dynmaps:ro
- ./data/assets/ssl/:/etc/ssl/mail/:ro - ./data/assets/ssl/:/etc/ssl/mail/:ro
- ./data/conf/nginx/:/etc/nginx/conf.d/:rw - ./data/conf/nginx/:/etc/nginx/conf.d/:rw
- ./data/conf/rspamd/meta_exporter:/meta_exporter:ro - ./data/conf/rspamd/meta_exporter:/meta_exporter:ro
volumes_from: - sogo-web-vol-1:/usr/lib/GNUstep/SOGo/
- sogo-mailcow
ports: ports:
- "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}" - "${HTTPS_BIND:-0.0.0.0}:${HTTPS_PORT:-443}:${HTTPS_PORT:-443}"
- "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}" - "${HTTP_BIND:-0.0.0.0}:${HTTP_PORT:-80}:${HTTP_PORT:-80}"
@ -296,7 +307,7 @@ services:
acme-mailcow: acme-mailcow:
depends_on: depends_on:
- nginx-mailcow - nginx-mailcow
image: mailcow/acme:1.48 image: mailcow/acme:1.51
build: ./data/Dockerfiles/acme build: ./data/Dockerfiles/acme
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
@ -309,6 +320,7 @@ services:
- DBPASS=${DBPASS} - DBPASS=${DBPASS}
- SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n} - SKIP_LETS_ENCRYPT=${SKIP_LETS_ENCRYPT:-n}
- SKIP_IP_CHECK=${SKIP_IP_CHECK:-n} - SKIP_IP_CHECK=${SKIP_IP_CHECK:-n}
- SKIP_HTTP_VERIFICATION=${SKIP_HTTP_VERIFICATION:-n}
- LE_STAGING=${LE_STAGING:-n} - LE_STAGING=${LE_STAGING:-n}
- TZ=${TZ} - TZ=${TZ}
volumes: volumes:
@ -323,7 +335,7 @@ services:
- acme - acme
netfilter-mailcow: netfilter-mailcow:
image: mailcow/netfilter:1.22 image: mailcow/netfilter:1.23
build: ./data/Dockerfiles/netfilter build: ./data/Dockerfiles/netfilter
stop_grace_period: 30s stop_grace_period: 30s
depends_on: depends_on:
@ -347,7 +359,7 @@ services:
- /lib/modules:/lib/modules:ro - /lib/modules:/lib/modules:ro
watchdog-mailcow: watchdog-mailcow:
image: mailcow/watchdog:1.34 image: mailcow/watchdog:1.39
# Debug # Debug
#command: /watchdog.sh #command: /watchdog.sh
build: ./data/Dockerfiles/watchdog build: ./data/Dockerfiles/watchdog
@ -395,11 +407,11 @@ services:
- dockerapi - dockerapi
solr-mailcow: solr-mailcow:
image: mailcow/solr:1.1 image: mailcow/solr:1.4
build: ./data/Dockerfiles/solr build: ./data/Dockerfiles/solr
restart: always restart: always
volumes: volumes:
- solr-vol-1:/opt/solr/server/solr/dovecot/data - solr-vol-1:/opt/solr/server/solr/dovecot-fts/data
dns: dns:
- ${IPV4_NETWORK:-172.22.1}.254 - ${IPV4_NETWORK:-172.22.1}.254
environment: environment:
@ -411,7 +423,7 @@ services:
aliases: aliases:
- solr - solr
ipv6nat: ipv6nat-mailcow:
depends_on: depends_on:
- unbound-mailcow - unbound-mailcow
- mysql-mailcow - mysql-mailcow
@ -440,6 +452,8 @@ services:
networks: networks:
mailcow-network: mailcow-network:
driver: bridge driver: bridge
driver_opts:
com.docker.network.bridge.name: br-mailcow
enable_ipv6: true enable_ipv6: true
ipam: ipam:
driver: default driver: default
@ -459,3 +473,4 @@ volumes:
solr-vol-1: solr-vol-1:
postfix-vol-1: postfix-vol-1:
crypt-vol-1: crypt-vol-1:
sogo-web-vol-1:

View File

@ -16,6 +16,7 @@ if [ -f mailcow.conf ]; then
case $response in case $response in
[yY][eE][sS]|[yY]) [yY][eE][sS]|[yY])
mv mailcow.conf mailcow.conf_backup mv mailcow.conf mailcow.conf_backup
chmod 600 mailcow.conf_backup
;; ;;
*) *)
exit 1 exit 1
@ -185,6 +186,10 @@ SKIP_LETS_ENCRYPT=n
SKIP_IP_CHECK=n SKIP_IP_CHECK=n
# Skip HTTP verification in ACME container - y/n
SKIP_HTTP_VERIFICATION=n
# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n # Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n
SKIP_CLAMD=${SKIP_CLAMD} SKIP_CLAMD=${SKIP_CLAMD}
@ -200,6 +205,10 @@ SOLR_HEAP=1024
USE_WATCHDOG=n USE_WATCHDOG=n
# Allow admins to log into SOGo as email user (without any password)
ALLOW_ADMIN_EMAIL_LOGIN=n
# Send notifications by mail (no DKIM signature, sent from watchdog@MAILCOW_HOSTNAME) # Send notifications by mail (no DKIM signature, sent from watchdog@MAILCOW_HOSTNAME)
# Can by multiple rcpts, NO quotation marks # Can by multiple rcpts, NO quotation marks
@ -233,9 +242,14 @@ IPV6_NETWORK=fd4d:6169:6c63:6f77::/64
#API_KEY= #API_KEY=
#API_ALLOW_FROM=127.0.0.1,1.2.3.4 #API_ALLOW_FROM=127.0.0.1,1.2.3.4
# mail_home is ~/Maildir
MAILDIR_SUB=Maildir
EOF EOF
mkdir -p data/assets/ssl mkdir -p data/assets/ssl
chmod 600 mailcow.conf
# copy but don't overwrite existing certificate # copy but don't overwrite existing certificate
cp -n data/assets/ssl-example/*.pem data/assets/ssl/ cp -n data/assets/ssl-example/*.pem data/assets/ssl/

View File

@ -76,12 +76,10 @@ elif [[ ${NC_UPDATE} == "y" ]]; then
curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \
&& tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \
&& rm nextcloud.tar.bz2 \ && rm nextcloud.tar.bz2 \
&& rm -rf ./data/web/nextcloud/updater \
&& mkdir -p ./data/web/nextcloud/data \ && mkdir -p ./data/web/nextcloud/data \
&& mkdir -p ./data/web/nextcloud/custom_apps \ && chmod +x ./data/web/nextcloud/occ \
&& chmod +x ./data/web/nextcloud/occ docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" \
docker exec -it $(docker ps -f name=php-fpm-mailcow -q) bash -c "chown www-data:www-data -R /web/nextcloud" docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings upgrade"
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ --no-warnings upgrade"
fi fi
elif [[ ${NC_INSTALL} == "y" ]]; then elif [[ ${NC_INSTALL} == "y" ]]; then
@ -106,12 +104,10 @@ elif [[ ${NC_INSTALL} == "y" ]]; then
curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \ curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-15.tar.bz2" || { echo "Failed to download Nextcloud archive."; exit 1; } \
&& tar -xjf nextcloud.tar.bz2 -C ./data/web/ \ && tar -xjf nextcloud.tar.bz2 -C ./data/web/ \
&& rm nextcloud.tar.bz2 \ && rm nextcloud.tar.bz2 \
&& rm -rf ./data/web/nextcloud/updater \
&& mkdir -p ./data/web/nextcloud/data \ && mkdir -p ./data/web/nextcloud/data \
&& mkdir -p ./data/web/nextcloud/custom_apps \
&& chmod +x ./data/web/nextcloud/occ && chmod +x ./data/web/nextcloud/occ
docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud/data /web/nextcloud/config /web/nextcloud/apps /web/nextcloud/custom_apps" docker exec -it $(docker ps -f name=php-fpm-mailcow -q) /bin/bash -c "chown -R www-data:www-data /web/nextcloud/data /web/nextcloud/config /web/nextcloud/apps"
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings maintenance:install \ docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ --no-warnings maintenance:install \
--database mysql \ --database mysql \
--database-host mysql \ --database-host mysql \

View File

@ -6,9 +6,12 @@ if [ "$(id -u)" -ne "0" ]; then
exit 1 exit 1
fi fi
#exit on error and pipefail # Exit on error and pipefail
set -o pipefail set -o pipefail
# Add /opt/bin to PATH
PATH=$PATH:/opt/bin
umask 0022 umask 0022
for bin in curl docker-compose docker git awk sha1sum; do for bin in curl docker-compose docker git awk sha1sum; do
@ -68,8 +71,12 @@ while (($#)); do
case "${1}" in case "${1}" in
--check|-c) --check|-c)
echo "Checking remote code for updates..." echo "Checking remote code for updates..."
git fetch origin #${BRANCH} LATEST_REV=$(git ls-remote --exit-code --refs --quiet https://github.com/mailcow/mailcow-dockerized ${BRANCH} | cut -f1)
if [[ -z $(git log HEAD --pretty=format:"%H" | grep $(git rev-parse origin/${BRANCH})) ]]; then if [ $? -ne 0 ]; then
echo "A problem occurred while trying to fetch the latest revision from github."
exit 99
fi
if [[ -z $(git log HEAD --pretty=format:"%H" | grep "${LATEST_REV}") ]]; then
echo "Updated code is available." echo "Updated code is available."
exit 0 exit 0
else else
@ -98,6 +105,7 @@ while (($#)); do
done done
[[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing"; exit 1;} [[ ! -f mailcow.conf ]] && { echo "mailcow.conf is missing"; exit 1;}
chmod 600 mailcow.conf
source mailcow.conf source mailcow.conf
DOTS=${MAILCOW_HOSTNAME//[^.]}; DOTS=${MAILCOW_HOSTNAME//[^.]};
if [ ${#DOTS} -lt 2 ]; then if [ ${#DOTS} -lt 2 ]; then
@ -127,9 +135,12 @@ CONFIG_ARRAY=(
"API_KEY" "API_KEY"
"API_ALLOW_FROM" "API_ALLOW_FROM"
"MAILDIR_GC_TIME" "MAILDIR_GC_TIME"
"MAILDIR_SUB"
"ACL_ANYONE" "ACL_ANYONE"
"SOLR_HEAP" "SOLR_HEAP"
"SKIP_SOLR" "SKIP_SOLR"
"ALLOW_ADMIN_EMAIL_LOGIN"
"SKIP_HTTP_VERIFICATION"
) )
sed -i '$a\' mailcow.conf sed -i '$a\' mailcow.conf
@ -235,6 +246,13 @@ for option in ${CONFIG_ARRAY[@]}; do
echo '# Disable Solr or if you do not want to store a readable index of your mails in solr-vol-1.' >> mailcow.conf echo '# Disable Solr or if you do not want to store a readable index of your mails in solr-vol-1.' >> mailcow.conf
echo "SKIP_SOLR=y" >> mailcow.conf echo "SKIP_SOLR=y" >> mailcow.conf
fi fi
elif [[ ${option} == "MAILDIR_SUB" ]]; then
if ! grep -q ${option} mailcow.conf; then
echo "Adding new option \"${option}\" to mailcow.conf"
echo '# MAILDIR_SUB defines a path in a users virtual home to keep the maildir in. Leave empty for updated setups.' >> mailcow.conf
echo "#MAILDIR_SUB=Maildir" >> mailcow.conf
echo "MAILDIR_SUB=" >> mailcow.conf
fi
elif ! grep -q ${option} mailcow.conf; then elif ! grep -q ${option} mailcow.conf; then
echo "Adding new option \"${option}\" to mailcow.conf" echo "Adding new option \"${option}\" to mailcow.conf"
echo "${option}=n" >> mailcow.conf echo "${option}=n" >> mailcow.conf
@ -351,9 +369,8 @@ if grep -q 'SYSCTL_IPV6_DISABLED=1' mailcow.conf; then
read -p "Press any key to continue..." < /dev/tty read -p "Press any key to continue..." < /dev/tty
fi fi
echo -e "Fixing project name... " # Checking for old project name bug
sed -i 's#COMPOSEPROJECT_NAME#COMPOSE_PROJECT_NAME#g' mailcow.conf sed -i 's#COMPOSEPROJECT_NAME#COMPOSE_PROJECT_NAME#g' mailcow.conf
sed -i '/COMPOSE_PROJECT_NAME=/s/-//g' mailcow.conf
echo -e "Fixing PHP-FPM worker ports for Nginx sites..." echo -e "Fixing PHP-FPM worker ports for Nginx sites..."
sed -i 's#phpfpm:9000#phpfpm:9002#g' data/conf/nginx/*.conf sed -i 's#phpfpm:9000#phpfpm:9002#g' data/conf/nginx/*.conf