Merge branch 'master' of https://github.com/mailcow/mailcow-dockerized
commit
9f2a6f13a5
|
@ -1,10 +1,9 @@
|
||||||
FROM alpine:3.8
|
FROM alpine:3.9
|
||||||
|
|
||||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||||
|
|
||||||
RUN apk add --update --no-cache \
|
RUN apk add --update --no-cache \
|
||||||
bash \
|
bash \
|
||||||
acme-client \
|
|
||||||
curl \
|
curl \
|
||||||
openssl \
|
openssl \
|
||||||
bind-tools \
|
bind-tools \
|
||||||
|
@ -12,7 +11,10 @@ RUN apk add --update --no-cache \
|
||||||
mariadb-client \
|
mariadb-client \
|
||||||
redis \
|
redis \
|
||||||
tini \
|
tini \
|
||||||
tzdata
|
tzdata \
|
||||||
|
py-pip \
|
||||||
|
&& pip install --upgrade pip \
|
||||||
|
&& pip install acme-tiny
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /srv/docker-entrypoint.sh
|
COPY docker-entrypoint.sh /srv/docker-entrypoint.sh
|
||||||
COPY expand6.sh /srv/expand6.sh
|
COPY expand6.sh /srv/expand6.sh
|
||||||
|
|
|
@ -17,7 +17,7 @@ log_f() {
|
||||||
redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${1}")\"}" > /dev/null
|
redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"base64,$(printf '%s' "${1}")\"}" > /dev/null
|
||||||
else
|
else
|
||||||
redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
|
redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
|
||||||
tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null
|
tr '%&;$"[]{}-\r\n' ' ')\"}" > /dev/null
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,12 @@ log_f "OK" no_date
|
||||||
ACME_BASE=/var/lib/acme
|
ACME_BASE=/var/lib/acme
|
||||||
SSL_EXAMPLE=/var/lib/ssl-example
|
SSL_EXAMPLE=/var/lib/ssl-example
|
||||||
|
|
||||||
mkdir -p ${ACME_BASE}/acme/private
|
mkdir -p ${ACME_BASE}/acme
|
||||||
|
|
||||||
|
# Migrate
|
||||||
|
[[ -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
|
||||||
|
|
||||||
|
|
||||||
reload_configurations(){
|
reload_configurations(){
|
||||||
# Reading container IDs
|
# Reading container IDs
|
||||||
|
@ -112,6 +117,19 @@ get_ipv6(){
|
||||||
echo ${IPV6}
|
echo ${IPV6}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verify_challenge_path(){
|
||||||
|
# verify_challenge_path URL 4|6
|
||||||
|
RAND_FILE=${RANDOM}${RANDOM}${RANDOM}
|
||||||
|
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)" == "200" ]]; then
|
||||||
|
rm /var/www/acme/${RAND_FILE}
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
rm /var/www/acme/${RAND_FILE}
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
[[ ! -f ${ACME_BASE}/dhparams.pem ]] && cp ${SSL_EXAMPLE}/dhparams.pem ${ACME_BASE}/dhparams.pem
|
[[ ! -f ${ACME_BASE}/dhparams.pem ]] && cp ${SSL_EXAMPLE}/dhparams.pem ${ACME_BASE}/dhparams.pem
|
||||||
|
|
||||||
if [[ -f ${ACME_BASE}/cert.pem ]] && [[ -f ${ACME_BASE}/key.pem ]]; then
|
if [[ -f ${ACME_BASE}/cert.pem ]] && [[ -f ${ACME_BASE}/key.pem ]]; then
|
||||||
|
@ -120,20 +138,13 @@ if [[ -f ${ACME_BASE}/cert.pem ]] && [[ -f ${ACME_BASE}/key.pem ]]; then
|
||||||
log_f "Found certificate with issuer other than mailcow snake-oil CA and Let's Encrypt, skipping ACME client..."
|
log_f "Found certificate with issuer other than mailcow snake-oil CA and Let's Encrypt, skipping ACME client..."
|
||||||
sleep 3650d
|
sleep 3650d
|
||||||
exec $(readlink -f "$0")
|
exec $(readlink -f "$0")
|
||||||
else
|
|
||||||
declare -a SAN_ARRAY_NOW
|
|
||||||
SAN_NAMES=$(openssl x509 -noout -text -in ${ACME_BASE}/cert.pem | awk '/X509v3 Subject Alternative Name/ {getline;gsub(/ /, "", $0); print}' | tr -d "DNS:")
|
|
||||||
if [[ ! -z ${SAN_NAMES} ]]; then
|
|
||||||
IFS=',' read -a SAN_ARRAY_NOW <<< ${SAN_NAMES}
|
|
||||||
log_f "Found Let's Encrypt or mailcow snake-oil CA issued certificate with SANs: ${SAN_ARRAY_NOW[*]}"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then
|
if [[ -f ${ACME_BASE}/acme/cert.pem ]] && [[ -f ${ACME_BASE}/acme/key.pem ]]; then
|
||||||
if verify_hash_match ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/acme/private/privkey.pem; then
|
if verify_hash_match ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/key.pem; then
|
||||||
log_f "Restoring previous acme certificate and restarting script..."
|
log_f "Restoring previous acme certificate and restarting script..."
|
||||||
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
|
cp ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/cert.pem
|
||||||
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
|
cp ${ACME_BASE}/acme/key.pem ${ACME_BASE}/key.pem
|
||||||
# Restarting with env var set to trigger a restart,
|
# Restarting with env var set to trigger a restart,
|
||||||
exec env TRIGGER_RESTART=1 $(readlink -f "$0")
|
exec env TRIGGER_RESTART=1 $(readlink -f "$0")
|
||||||
fi
|
fi
|
||||||
|
@ -150,24 +161,59 @@ log_f "Waiting for database... "
|
||||||
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
|
||||||
sleep 2
|
sleep 2
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Waiting for domain table
|
||||||
|
log_f "Waiting for domain table... " no_nl
|
||||||
|
while [[ -z ${DOMAIN_TABLE} ]]; do
|
||||||
|
curl --silent http://nginx/ >/dev/null 2>&1
|
||||||
|
DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
|
||||||
|
[[ -z ${DOMAIN_TABLE} ]] && sleep 10
|
||||||
|
done
|
||||||
|
log_f "OK" no_date
|
||||||
|
|
||||||
log_f "Initializing, please wait... "
|
log_f "Initializing, please wait... "
|
||||||
|
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
|
|
||||||
|
# Re-using previous acme-mailcow account and domain keys
|
||||||
|
if [[ ! -f ${ACME_BASE}/acme/key.pem ]]; then
|
||||||
|
log_f "Generating missing domain private key..."
|
||||||
|
openssl genrsa 4096 > ${ACME_BASE}/acme/key.pem
|
||||||
|
else
|
||||||
|
log_f "Using existing domain key ${ACME_BASE}/acme/key.pem"
|
||||||
|
fi
|
||||||
|
if [[ ! -f ${ACME_BASE}/acme/account.pem ]]; then
|
||||||
|
log_f "Generating missing Lets Encrypt account key..."
|
||||||
|
openssl genrsa 4096 > ${ACME_BASE}/acme/account.pem
|
||||||
|
else
|
||||||
|
log_f "Using existing Lets Encrypt account key ${ACME_BASE}/acme/account.pem"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Skipping IP check when we like to live dangerously
|
||||||
if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
if [[ "${SKIP_IP_CHECK}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
SKIP_IP_CHECK=y
|
SKIP_IP_CHECK=y
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Cleaning up and init validation arrays
|
||||||
unset SQL_DOMAIN_ARR
|
unset SQL_DOMAIN_ARR
|
||||||
unset VALIDATED_CONFIG_DOMAINS
|
unset VALIDATED_CONFIG_DOMAINS
|
||||||
unset ADDITIONAL_VALIDATED_SAN
|
unset ADDITIONAL_VALIDATED_SAN
|
||||||
|
unset ADDITIONAL_WC_ARR
|
||||||
|
unset ADDITIONAL_SAN_ARR
|
||||||
|
unset SAN_CHANGE
|
||||||
|
unset SAN_ARRAY_NOW
|
||||||
|
unset ORPHANED_SAN
|
||||||
|
unset ADDED_SAN
|
||||||
|
SAN_CHANGE=0
|
||||||
|
declare -a SAN_ARRAY_NOW
|
||||||
|
declare -a ORPHANED_SAN
|
||||||
|
declare -a ADDED_SAN
|
||||||
declare -a SQL_DOMAIN_ARR
|
declare -a SQL_DOMAIN_ARR
|
||||||
declare -a VALIDATED_CONFIG_DOMAINS
|
declare -a VALIDATED_CONFIG_DOMAINS
|
||||||
declare -a ADDITIONAL_VALIDATED_SAN
|
declare -a ADDITIONAL_VALIDATED_SAN
|
||||||
|
declare -a ADDITIONAL_WC_ARR
|
||||||
|
declare -a ADDITIONAL_SAN_ARR
|
||||||
IFS=',' read -r -a TMP_ARR <<< "${ADDITIONAL_SAN}"
|
IFS=',' read -r -a TMP_ARR <<< "${ADDITIONAL_SAN}"
|
||||||
log_f "Detecting IP addresses... " no_nl
|
|
||||||
|
|
||||||
unset ADDITIONAL_WC_ARR
|
|
||||||
unset ADDITIONAL_SAN_ARR
|
|
||||||
for i in "${TMP_ARR[@]}" ; do
|
for i in "${TMP_ARR[@]}" ; do
|
||||||
if [[ "$i" =~ \.\*$ ]]; then
|
if [[ "$i" =~ \.\*$ ]]; then
|
||||||
ADDITIONAL_WC_ARR+=(${i::-2})
|
ADDITIONAL_WC_ARR+=(${i::-2})
|
||||||
|
@ -177,6 +223,8 @@ while true; do
|
||||||
done
|
done
|
||||||
ADDITIONAL_WC_ARR+=('autodiscover')
|
ADDITIONAL_WC_ARR+=('autodiscover')
|
||||||
|
|
||||||
|
# Start IP detection
|
||||||
|
log_f "Detecting IP addresses... " no_nl
|
||||||
IPV4=$(get_ipv4)
|
IPV4=$(get_ipv4)
|
||||||
IPV6=$(get_ipv6)
|
IPV6=$(get_ipv6)
|
||||||
log_f "OK" no_date
|
log_f "OK" no_date
|
||||||
|
@ -194,23 +242,15 @@ while true; do
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_f "Waiting for domain table... " no_nl
|
#########################################
|
||||||
while [[ -z ${DOMAIN_TABLE} ]]; do
|
# IP and webroot challenge verification #
|
||||||
curl --silent http://nginx/ >/dev/null 2>&1
|
|
||||||
DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
|
|
||||||
[[ -z ${DOMAIN_TABLE} ]] && sleep 10
|
|
||||||
done
|
|
||||||
log_f "OK" no_date
|
|
||||||
|
|
||||||
while read domains; do
|
while read domains; do
|
||||||
SQL_DOMAIN_ARR+=("${domains}")
|
SQL_DOMAIN_ARR+=("${domains}")
|
||||||
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0" -Bs)
|
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0" -Bs)
|
||||||
|
|
||||||
for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do
|
for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do
|
||||||
for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do
|
for SUBDOMAIN in "${ADDITIONAL_WC_ARR[@]}"; do
|
||||||
if [[ "${SUBDOMAIN}.${SQL_DOMAIN}" == ${MAILCOW_HOSTNAME} ]]; then
|
if [[ "${SUBDOMAIN}.${SQL_DOMAIN}" != "${MAILCOW_HOSTNAME}" ]]; then
|
||||||
log_f "Skipping mailcow hostname (${MAILCOW_HOSTNAME}), will be added anyway"
|
|
||||||
else
|
|
||||||
A_SUBDOMAIN=$(dig A ${SUBDOMAIN}.${SQL_DOMAIN} +short | tail -n 1)
|
A_SUBDOMAIN=$(dig A ${SUBDOMAIN}.${SQL_DOMAIN} +short | tail -n 1)
|
||||||
AAAA_SUBDOMAIN=$(dig AAAA ${SUBDOMAIN}.${SQL_DOMAIN} +short | tail -n 1)
|
AAAA_SUBDOMAIN=$(dig AAAA ${SUBDOMAIN}.${SQL_DOMAIN} +short | tail -n 1)
|
||||||
# Check if CNAME without v6 enabled target
|
# Check if CNAME without v6 enabled target
|
||||||
|
@ -220,16 +260,24 @@ while true; do
|
||||||
if [[ ! -z ${AAAA_SUBDOMAIN} ]]; then
|
if [[ ! -z ${AAAA_SUBDOMAIN} ]]; then
|
||||||
log_f "Found AAAA record for ${SUBDOMAIN}.${SQL_DOMAIN}: ${AAAA_SUBDOMAIN} - skipping A record check"
|
log_f "Found AAAA record for ${SUBDOMAIN}.${SQL_DOMAIN}: ${AAAA_SUBDOMAIN} - skipping A record check"
|
||||||
if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_SUBDOMAIN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_SUBDOMAIN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
||||||
log_f "Confirmed AAAA record ${SUBDOMAIN}.${SQL_DOMAIN}"
|
if verify_challenge_path "${SUBDOMAIN}.${SQL_DOMAIN}" 6; then
|
||||||
|
log_f "Confirmed AAAA record ${AAAA_SUBDOMAIN}"
|
||||||
VALIDATED_CONFIG_DOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}")
|
VALIDATED_CONFIG_DOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}")
|
||||||
|
else
|
||||||
|
log_f "Confirmed AAAA record ${AAAA_SUBDOMAIN}, but HTTP validation failed"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${SUBDOMAIN}.${SQL_DOMAIN} ($(expand ${AAAA_SUBDOMAIN}))"
|
log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${SUBDOMAIN}.${SQL_DOMAIN} ($(expand ${AAAA_SUBDOMAIN}))"
|
||||||
fi
|
fi
|
||||||
elif [[ ! -z ${A_SUBDOMAIN} ]]; then
|
elif [[ ! -z ${A_SUBDOMAIN} ]]; then
|
||||||
log_f "Found A record for ${SUBDOMAIN}.${SQL_DOMAIN}: ${A_SUBDOMAIN}"
|
log_f "Found A record for ${SUBDOMAIN}.${SQL_DOMAIN}: ${A_SUBDOMAIN}"
|
||||||
if [[ ${IPV4:-ERR} == ${A_SUBDOMAIN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
if [[ ${IPV4:-ERR} == ${A_SUBDOMAIN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
||||||
log_f "Confirmed A record ${SUBDOMAIN}.${SQL_DOMAIN}"
|
if verify_challenge_path "${SUBDOMAIN}.${SQL_DOMAIN}" 4; then
|
||||||
|
log_f "Confirmed A record ${A_SUBDOMAIN}"
|
||||||
VALIDATED_CONFIG_DOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}")
|
VALIDATED_CONFIG_DOMAINS+=("${SUBDOMAIN}.${SQL_DOMAIN}")
|
||||||
|
else
|
||||||
|
log_f "Confirmed AAAA record ${A_SUBDOMAIN}, but HTTP validation failed"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log_f "Cannot match your IP ${IPV4} against hostname ${SUBDOMAIN}.${SQL_DOMAIN} (${A_SUBDOMAIN})"
|
log_f "Cannot match your IP ${IPV4} against hostname ${SUBDOMAIN}.${SQL_DOMAIN} (${A_SUBDOMAIN})"
|
||||||
fi
|
fi
|
||||||
|
@ -249,16 +297,24 @@ while true; do
|
||||||
if [[ ! -z ${AAAA_MAILCOW_HOSTNAME} ]]; then
|
if [[ ! -z ${AAAA_MAILCOW_HOSTNAME} ]]; then
|
||||||
log_f "Found AAAA record for ${MAILCOW_HOSTNAME}: ${AAAA_MAILCOW_HOSTNAME} - skipping A record check"
|
log_f "Found AAAA record for ${MAILCOW_HOSTNAME}: ${AAAA_MAILCOW_HOSTNAME} - skipping A record check"
|
||||||
if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_MAILCOW_HOSTNAME}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_MAILCOW_HOSTNAME}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
||||||
log_f "Confirmed AAAA record ${MAILCOW_HOSTNAME}"
|
if verify_challenge_path "${MAILCOW_HOSTNAME}" 6; then
|
||||||
|
log_f "Confirmed AAAA record ${AAAA_MAILCOW_HOSTNAME}"
|
||||||
VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
|
VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
|
||||||
|
else
|
||||||
|
log_f "Confirmed AAAA record ${A_MAILCOW_HOSTNAME}, but HTTP validation failed"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${MAILCOW_HOSTNAME} ($(expand ${AAAA_MAILCOW_HOSTNAME}))"
|
log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${MAILCOW_HOSTNAME} ($(expand ${AAAA_MAILCOW_HOSTNAME}))"
|
||||||
fi
|
fi
|
||||||
elif [[ ! -z ${A_MAILCOW_HOSTNAME} ]]; then
|
elif [[ ! -z ${A_MAILCOW_HOSTNAME} ]]; then
|
||||||
log_f "Found A record for ${MAILCOW_HOSTNAME}: ${A_MAILCOW_HOSTNAME}"
|
log_f "Found A record for ${MAILCOW_HOSTNAME}: ${A_MAILCOW_HOSTNAME}"
|
||||||
if [[ ${IPV4:-ERR} == ${A_MAILCOW_HOSTNAME} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
if [[ ${IPV4:-ERR} == ${A_MAILCOW_HOSTNAME} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
||||||
|
if verify_challenge_path "${MAILCOW_HOSTNAME}" 4; then
|
||||||
log_f "Confirmed A record ${A_MAILCOW_HOSTNAME}"
|
log_f "Confirmed A record ${A_MAILCOW_HOSTNAME}"
|
||||||
VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
|
VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
|
||||||
|
else
|
||||||
|
log_f "Confirmed A record ${A_MAILCOW_HOSTNAME}, but HTTP validation failed"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log_f "Cannot match your IP ${IPV4} against hostname ${MAILCOW_HOSTNAME} (${A_MAILCOW_HOSTNAME})"
|
log_f "Cannot match your IP ${IPV4} against hostname ${MAILCOW_HOSTNAME} (${A_MAILCOW_HOSTNAME})"
|
||||||
fi
|
fi
|
||||||
|
@ -290,16 +346,24 @@ while true; do
|
||||||
if [[ ! -z ${AAAA_SAN} ]]; then
|
if [[ ! -z ${AAAA_SAN} ]]; then
|
||||||
log_f "Found AAAA record for ${SAN}: ${AAAA_SAN} - skipping A record check"
|
log_f "Found AAAA record for ${SAN}: ${AAAA_SAN} - skipping A record check"
|
||||||
if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_SAN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
if [[ $(expand ${IPV6:-"0000:0000:0000:0000:0000:0000:0000:0000"}) == $(expand ${AAAA_SAN}) ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
||||||
log_f "Confirmed AAAA record ${SAN}"
|
if verify_challenge_path "${SAN}" 6; then
|
||||||
|
log_f "Confirmed AAAA record ${AAAA_SAN}"
|
||||||
ADDITIONAL_VALIDATED_SAN+=("${SAN}")
|
ADDITIONAL_VALIDATED_SAN+=("${SAN}")
|
||||||
|
else
|
||||||
|
log_f "Confirmed AAAA record ${AAAA_SAN}, but HTTP validation failed"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${SAN} ($(expand ${AAAA_SAN}))"
|
log_f "Cannot match your IP ${IPV6:-NO_IPV6_LINK} against hostname ${SAN} ($(expand ${AAAA_SAN}))"
|
||||||
fi
|
fi
|
||||||
elif [[ ! -z ${A_SAN} ]]; then
|
elif [[ ! -z ${A_SAN} ]]; then
|
||||||
log_f "Found A record for ${SAN}: ${A_SAN}"
|
log_f "Found A record for ${SAN}: ${A_SAN}"
|
||||||
if [[ ${IPV4:-ERR} == ${A_SAN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
if [[ ${IPV4:-ERR} == ${A_SAN} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
|
||||||
|
if verify_challenge_path "${SAN}" 4; then
|
||||||
log_f "Confirmed A record ${A_SAN}"
|
log_f "Confirmed A record ${A_SAN}"
|
||||||
ADDITIONAL_VALIDATED_SAN+=("${SAN}")
|
ADDITIONAL_VALIDATED_SAN+=("${SAN}")
|
||||||
|
else
|
||||||
|
log_f "Confirmed A record ${A_SAN}, but HTTP validation failed"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
log_f "Cannot match your IP ${IPV4} against hostname ${SAN} (${A_SAN})"
|
log_f "Cannot match your IP ${IPV4} against hostname ${SAN} (${A_SAN})"
|
||||||
fi
|
fi
|
||||||
|
@ -317,123 +381,97 @@ while true; do
|
||||||
exec $(readlink -f "$0")
|
exec $(readlink -f "$0")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
array_diff ORPHANED_SAN SAN_ARRAY_NOW ALL_VALIDATED
|
# Collecting SANs from active certificate
|
||||||
if [[ ! -z ${ORPHANED_SAN[*]} ]] && [[ ${ISSUER} != *"mailcow"* ]]; then
|
SAN_NAMES=$(openssl x509 -noout -text -in ${ACME_BASE}/cert.pem | awk '/X509v3 Subject Alternative Name/ {getline;gsub(/ /, "", $0); print}' | tr -d "DNS:")
|
||||||
DATE=$(date +%Y-%m-%d_%H_%M_%S)
|
if [[ ! -z ${SAN_NAMES} ]]; then
|
||||||
log_f "Found orphaned SAN ${ORPHANED_SAN[*]} in certificate, moving old files to ${ACME_BASE}/acme/private/${DATE}.bak/, keeping key file..."
|
IFS=',' read -a SAN_ARRAY_NOW <<< ${SAN_NAMES}
|
||||||
mkdir -p ${ACME_BASE}/acme/private/${DATE}.bak/
|
|
||||||
[[ -f ${ACME_BASE}/acme/private/account.key ]] && mv ${ACME_BASE}/acme/private/account.key ${ACME_BASE}/acme/private/${DATE}.bak/
|
|
||||||
[[ -f ${ACME_BASE}/acme/fullchain.pem ]] && mv ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/acme/private/${DATE}.bak/
|
|
||||||
[[ -f ${ACME_BASE}/acme/cert.pem ]] && mv ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/acme/private/${DATE}.bak/
|
|
||||||
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/private/${DATE}.bak/ # Keep key for TLSA 3 1 1 records
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Finding difference in SAN array now vs. SAN array by current configuration
|
||||||
|
array_diff ORPHANED_SAN SAN_ARRAY_NOW ALL_VALIDATED
|
||||||
|
if [[ ! -z ${ORPHANED_SAN[*]} ]]; then
|
||||||
|
log_f "Found orphaned SANs ${ORPHANED_SAN[*]}"
|
||||||
|
SAN_CHANGE=1
|
||||||
|
fi
|
||||||
|
array_diff ADDED_SAN ALL_VALIDATED SAN_ARRAY_NOW
|
||||||
|
if [[ ! -z ${ADDED_SAN[*]} ]]; then
|
||||||
|
log_f "Found new SANs ${ADDED_SAN[*]}"
|
||||||
|
SAN_CHANGE=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${SAN_CHANGE} == 0 ]]; then
|
||||||
|
# Certificate did not change but could be due for renewal (4 weeks)
|
||||||
|
if ! openssl x509 -checkend 1209600 -noout -in ${ACME_BASE}/cert.pem; then
|
||||||
|
log_f "Certificate is due for renewal (< 2 weeks)"
|
||||||
|
else
|
||||||
|
log_f "Certificate validation done, neither changed nor due for renewal, sleeping for another day."
|
||||||
|
sleep 1d
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
DATE=$(date +%Y-%m-%d_%H_%M_%S)
|
||||||
|
log_f "Creating backups in ${ACME_BASE}/backups/${DATE}/ ..."
|
||||||
|
mkdir -p ${ACME_BASE}/backups/${DATE}/
|
||||||
|
[[ -f ${ACME_BASE}/acme/acme.csr ]] && cp ${ACME_BASE}/acme/acme.csr ${ACME_BASE}/backups/${DATE}/
|
||||||
|
[[ -f ${ACME_BASE}/acme/cert.pem ]] && cp ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/backups/${DATE}/
|
||||||
|
[[ -f ${ACME_BASE}/acme/key.pem ]] && cp ${ACME_BASE}/acme/key.pem ${ACME_BASE}/backups/${DATE}/
|
||||||
|
[[ -f ${ACME_BASE}/acme/account.pem ]] && cp ${ACME_BASE}/acme/account.pem ${ACME_BASE}/backups/${DATE}/
|
||||||
|
|
||||||
|
# Generating CSR
|
||||||
|
printf "[SAN]\nsubjectAltName=" > /tmp/_SAN
|
||||||
|
printf "DNS:%s," "${ALL_VALIDATED[@]}" >> /tmp/_SAN
|
||||||
|
sed -i '$s/,$//' /tmp/_SAN
|
||||||
|
openssl req -new -sha256 -key ${ACME_BASE}/acme/key.pem -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf /tmp/_SAN) > ${ACME_BASE}/acme/acme.csr
|
||||||
|
|
||||||
if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
if [[ "${LE_STAGING}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||||
log_f "Using Let's Encrypt staging servers"
|
log_f "Using Let's Encrypt staging servers"
|
||||||
STAGING_PARAMETER="-s"
|
STAGING_PARAMETER='--directory-url https://acme-staging-v02.api.letsencrypt.org/directory'
|
||||||
else
|
else
|
||||||
STAGING_PARAMETER=
|
STAGING_PARAMETER=
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ACME_RESPONSE=$(acme-client \
|
# acme-tiny writes info to stderr and ceritifcate to stdout
|
||||||
-v -e -b -N -n ${STAGING_PARAMETER} \
|
# The redirects will do the following:
|
||||||
-a 'https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf' \
|
# - redirect stdout to temp certificate file
|
||||||
-f ${ACME_BASE}/acme/private/account.key \
|
# - redirect acme-tiny stderr to stdout (logs to variable ACME_RESPONSE)
|
||||||
-k ${ACME_BASE}/acme/private/privkey.pem \
|
# - tee stderr to get live output and log to dockerd
|
||||||
-c ${ACME_BASE}/acme \
|
|
||||||
${ALL_VALIDATED[*]} 2>&1 | tee /dev/fd/5)
|
|
||||||
case "$?" in
|
|
||||||
0) # new certs
|
|
||||||
ACME_RESPONSE_B64=$(echo ${ACME_RESPONSE} | openssl enc -e -A -base64)
|
|
||||||
log_f "${ACME_RESPONSE_B64}" redis_only b64
|
|
||||||
# cp the new certificates and keys
|
|
||||||
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
|
|
||||||
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
|
|
||||||
|
|
||||||
# restart docker containers
|
ACME_RESPONSE=$(acme-tiny ${STAGING_PARAMETER} \
|
||||||
if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then
|
--account-key ${ACME_BASE}/acme/account.pem \
|
||||||
log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, restoring mailcow snake-oil and restarting containers..."
|
--disable-check \
|
||||||
cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem
|
--csr ${ACME_BASE}/acme/acme.csr \
|
||||||
cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem
|
--acme-dir /var/www/acme/ 2>&1 > /tmp/_cert.pem | tee /dev/fd/5)
|
||||||
fi
|
|
||||||
reload_configurations
|
case "$?" in
|
||||||
;;
|
0) # cert requested
|
||||||
1) # failure
|
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
|
||||||
if [[ $ACME_RESPONSE =~ "No registration exists" ]]; then
|
log_f "Deploying..."
|
||||||
log_f "Registration keys are invalid, deleting old keys and restarting..."
|
# Deploy the new certificate and key
|
||||||
rm ${ACME_BASE}/acme/private/account.key
|
# Moving temp cert to acme/cert.pem
|
||||||
exec $(readlink -f "$0")
|
if verify_hash_match /tmp/_cert.pem ${ACME_BASE}/acme/key.pem; then
|
||||||
fi
|
mv /tmp/_cert.pem ${ACME_BASE}/acme/cert.pem
|
||||||
if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then
|
cp ${ACME_BASE}/acme/cert.pem ${ACME_BASE}/cert.pem
|
||||||
log_f "Error requesting certificate, restoring previous certificate from backup and restarting containers...."
|
cp ${ACME_BASE}/acme/key.pem ${ACME_BASE}/key.pem
|
||||||
cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem
|
reload_configurations
|
||||||
cp ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ${ACME_BASE}/key.pem
|
rm /var/www/acme/*
|
||||||
TRIGGER_RESTART=1
|
log_f "Certificate successfully deployed, removing backup, sleeping 1d"
|
||||||
elif [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then
|
sleep 1d
|
||||||
log_f "Error requesting certificate, restoring from previous acme request and restarting containers..."
|
else
|
||||||
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
|
log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, ignoring certificate"
|
||||||
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
|
|
||||||
TRIGGER_RESTART=1
|
|
||||||
fi
|
|
||||||
if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then
|
|
||||||
log_f "Error verifying certificates, restoring mailcow snake-oil and restarting containers..."
|
|
||||||
cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem
|
|
||||||
cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem
|
|
||||||
TRIGGER_RESTART=1
|
|
||||||
fi
|
|
||||||
[[ ${TRIGGER_RESTART} == 1 ]] && reload_configurations
|
|
||||||
log_f "Retrying in 30 minutes..."
|
log_f "Retrying in 30 minutes..."
|
||||||
sleep 30m
|
sleep 30m
|
||||||
exec $(readlink -f "$0")
|
exec $(readlink -f "$0")
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
2) # no change
|
*) # non-zero is non-fun
|
||||||
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
|
||||||
if ! diff ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem; then
|
|
||||||
log_f "Certificate was not changed, but active certificate does not match the verified certificate, fixing and restarting containers..."
|
|
||||||
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
|
|
||||||
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
|
|
||||||
TRIGGER_RESTART=1
|
|
||||||
fi
|
|
||||||
if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then
|
|
||||||
log_f "Certificate was not changed, but hashes do not match, restoring from previous acme request and restarting containers..."
|
|
||||||
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
|
|
||||||
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
|
|
||||||
TRIGGER_RESTART=1
|
|
||||||
fi
|
|
||||||
log_f "Certificate was not changed"
|
|
||||||
[[ ${TRIGGER_RESTART} == 1 ]] && reload_configurations
|
|
||||||
;;
|
|
||||||
*) # unspecified
|
|
||||||
ACME_RESPONSE_B64=$(echo ${ACME_RESPONSE} | openssl enc -e -A -base64)
|
|
||||||
log_f "${ACME_RESPONSE_B64}" redis_only b64
|
|
||||||
if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then
|
|
||||||
log_f "Error requesting certificate, restoring previous certificate from backup and restarting containers...."
|
|
||||||
cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem
|
|
||||||
cp ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ${ACME_BASE}/key.pem
|
|
||||||
TRIGGER_RESTART=1
|
|
||||||
elif [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then
|
|
||||||
log_f "Error requesting certificate, restoring from previous acme request and restarting containers..."
|
|
||||||
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
|
|
||||||
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
|
|
||||||
TRIGGER_RESTART=1
|
|
||||||
fi
|
|
||||||
if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then
|
|
||||||
log_f "Error verifying certificates, restoring mailcow snake-oil..."
|
|
||||||
cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem
|
|
||||||
cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem
|
|
||||||
TRIGGER_RESTART=1
|
|
||||||
fi
|
|
||||||
[[ ${TRIGGER_RESTART} == 1 ]] && reload_configurations
|
|
||||||
log_f "Retrying in 30 minutes..."
|
log_f "Retrying in 30 minutes..."
|
||||||
sleep 30m
|
sleep 30m
|
||||||
exec $(readlink -f "$0")
|
exec $(readlink -f "$0")
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
log_f "ACME certificate validation done. Sleeping for another day."
|
|
||||||
sleep 1d
|
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
|
@ -46,7 +46,7 @@ $lang['danger']['malformed_username'] = "Ongeldige gebruikersnaam";
|
||||||
$lang['info']['awaiting_tfa_confirmation'] = "In afwachting van tweefactorauthenticatie...";
|
$lang['info']['awaiting_tfa_confirmation'] = "In afwachting van tweefactorauthenticatie...";
|
||||||
$lang['success']['logged_in_as'] = "Succesvol ingelogd als %s";
|
$lang['success']['logged_in_as'] = "Succesvol ingelogd als %s";
|
||||||
$lang['danger']['login_failed'] = "Aanmelding mislukt";
|
$lang['danger']['login_failed'] = "Aanmelding mislukt";
|
||||||
$lang['danger']['set_acl_failed'] = "ALC kon niet worden ingesteld";
|
$lang['danger']['set_acl_failed'] = "Toegangscontrole kon niet worden ingesteld";
|
||||||
$lang['danger']['no_user_defined'] = "Geen gebruiker gespecificeerd";
|
$lang['danger']['no_user_defined'] = "Geen gebruiker gespecificeerd";
|
||||||
$lang['danger']['script_empty'] = "Script dient ingevuld te worden";
|
$lang['danger']['script_empty'] = "Script dient ingevuld te worden";
|
||||||
$lang['danger']['sieve_error'] = "Sieve-fout: %s";
|
$lang['danger']['sieve_error'] = "Sieve-fout: %s";
|
||||||
|
@ -56,7 +56,7 @@ $lang['danger']['domain_cannot_match_hostname'] = "Het domein dient af te wijken
|
||||||
$lang['warning']['domain_added_sogo_failed'] = "Domein is toegevoegd, maar het hestarten van SOGo mislukte. Controleer de serverlogs.";
|
$lang['warning']['domain_added_sogo_failed'] = "Domein is toegevoegd, maar het hestarten van SOGo mislukte. Controleer de serverlogs.";
|
||||||
$lang['danger']['rl_timeframe'] = "Ratelimit-tijdsbestek is ongeldig";
|
$lang['danger']['rl_timeframe'] = "Ratelimit-tijdsbestek is ongeldig";
|
||||||
$lang['success']['rl_saved'] = "Ratelimit voor object %s is opgeslagen";
|
$lang['success']['rl_saved'] = "Ratelimit voor object %s is opgeslagen";
|
||||||
$lang['success']['acl_saved'] = "ACL voor object %s is opgeslagen";
|
$lang['success']['acl_saved'] = "Toegangscontrole voor object %s is opgeslagen";
|
||||||
$lang['success']['deleted_syncjobs'] = "Synchronisatietaken %s zijn verwijderd";
|
$lang['success']['deleted_syncjobs'] = "Synchronisatietaken %s zijn verwijderd";
|
||||||
$lang['success']['deleted_syncjob'] = "Synchronisatietaak %s is verwijderd";
|
$lang['success']['deleted_syncjob'] = "Synchronisatietaak %s is verwijderd";
|
||||||
$lang['success']['delete_filters'] = "Filters %s zijn verwijderd";
|
$lang['success']['delete_filters'] = "Filters %s zijn verwijderd";
|
||||||
|
@ -154,7 +154,7 @@ $lang['danger']['max_quota_in_use'] = "Postvakquotum moet gelijk zijn aan, of gr
|
||||||
$lang['danger']['domain_quota_m_in_use'] = "Domeinquotum moet gelijk zijn aan, of groter zijn dan %s MiB";
|
$lang['danger']['domain_quota_m_in_use'] = "Domeinquotum moet gelijk zijn aan, of groter zijn dan %s MiB";
|
||||||
$lang['danger']['mailboxes_in_use'] = "Maximaal aantal postvakken moet gelijk zijn aan, of groter zijn dan %d";
|
$lang['danger']['mailboxes_in_use'] = "Maximaal aantal postvakken moet gelijk zijn aan, of groter zijn dan %d";
|
||||||
$lang['danger']['aliases_in_use'] = "Maximaal aantal aliassen moet gelijk zijn aan, of groter zijn dan %d";
|
$lang['danger']['aliases_in_use'] = "Maximaal aantal aliassen moet gelijk zijn aan, of groter zijn dan %d";
|
||||||
$lang['danger']['sender_acl_invalid'] = "ACL-waarde van afzender %s is ongeldig";
|
$lang['danger']['sender_acl_invalid'] = "Toegangscontrole van afzender %s is ongeldig";
|
||||||
$lang['danger']['domain_not_empty'] = "Kan geen domein in gebruik verwijderen";
|
$lang['danger']['domain_not_empty'] = "Kan geen domein in gebruik verwijderen";
|
||||||
$lang['danger']['validity_missing'] = 'Wijs een geldigheidstermijn toe';
|
$lang['danger']['validity_missing'] = 'Wijs een geldigheidstermijn toe';
|
||||||
$lang['user']['loading'] = "Bezig met laden...";
|
$lang['user']['loading'] = "Bezig met laden...";
|
||||||
|
@ -229,12 +229,12 @@ $lang['user']['tag_handling'] = 'Omgaan met e-mailtags';
|
||||||
$lang['user']['tag_in_subfolder'] = 'In submap';
|
$lang['user']['tag_in_subfolder'] = 'In submap';
|
||||||
$lang['user']['tag_in_subject'] = 'In onderwerp';
|
$lang['user']['tag_in_subject'] = 'In onderwerp';
|
||||||
$lang['user']['tag_in_none'] = 'Niets doen';
|
$lang['user']['tag_in_none'] = 'Niets doen';
|
||||||
$lang['user']['tag_help_explain'] = 'In submap: er wordt een nieuwe map aangemaakt, genoemd naar de tag (bijv.: "INBOX/Apple").<br>In onderwerp: de tag wordt vóór het oorspronkelijke onderwerp geplaatst (bijv.: "[Apple] Uw bestelling").';
|
$lang['user']['tag_help_explain'] = 'In submap: er wordt een nieuwe map aangemaakt, genoemd naar de tag (bijv.: "INBOX/Tesla").<br>In onderwerp: de tag wordt vóór het oorspronkelijke onderwerp geplaatst (bijv.: "[Tesla] Uw serviceafspraak").';
|
||||||
$lang['user']['tag_help_example'] = 'Voorbeeld van een e-maildres met tag: ik<b>+Apple</b>@example.org';
|
$lang['user']['tag_help_example'] = 'Voorbeeld van een e-maildres met tag: ik<b>+Tesla</b>@example.org';
|
||||||
|
|
||||||
$lang['user']['eas_reset'] = 'Herstel ActiveSync-apparaatcache';
|
$lang['user']['eas_reset'] = 'Herstel ActiveSync-apparaatcache';
|
||||||
$lang['user']['eas_reset_now'] = 'Herstel nu';
|
$lang['user']['eas_reset_now'] = 'Herstel nu';
|
||||||
$lang['user']['eas_reset_help'] = 'In de meeste gevallen verhelpt dit problemen met ActiveSync op uw apparaten<br><b>Let wel:</b> alle onderdelen zullen opnieuw gedownload moeten worden!';
|
$lang['user']['eas_reset_help'] = 'In de meeste gevallen verhelpt dit problemen met ActiveSync op je apparaten<br><b>Let wel:</b> alle onderdelen zullen opnieuw gedownload moeten worden!';
|
||||||
|
|
||||||
$lang['user']['sogo_profile_reset'] = 'Herstel SOGo-profiel';
|
$lang['user']['sogo_profile_reset'] = 'Herstel SOGo-profiel';
|
||||||
$lang['user']['sogo_profile_reset_now'] = 'Herstel nu';
|
$lang['user']['sogo_profile_reset_now'] = 'Herstel nu';
|
||||||
|
@ -251,9 +251,9 @@ $lang['user']['edit'] = 'Wijzig';
|
||||||
$lang['user']['remove'] = 'Verwijder';
|
$lang['user']['remove'] = 'Verwijder';
|
||||||
$lang['user']['create_syncjob'] = 'Voeg een nieuwe synchronisatietaak toe';
|
$lang['user']['create_syncjob'] = 'Voeg een nieuwe synchronisatietaak toe';
|
||||||
|
|
||||||
$lang['start']['mailcow_apps_detail'] = 'Gebruik een Mailcow-app om uw e-mails, agenda, contacten en meer te bekijken.';
|
$lang['start']['mailcow_apps_detail'] = 'Gebruik een Mailcow-app om je e-mails, agenda, contacten en meer te bekijken.';
|
||||||
$lang['start']['mailcow_panel_detail'] = '<b>Domeinbeheerders</b> kunnen postvakken en aliassen aanmaken, wijzigen en verwijderen. Ook kunnen ze domeinen aanpassen en informatie over deze verkrijgen.<br><b>Gebruikers</b> kunnen tijdelijke aliassen aanmaken, hun wachtwoord aanpassen en de spamfilterinstellingen wijzigen.';
|
$lang['start']['mailcow_panel_detail'] = '<b>Domeinbeheerders</b> kunnen postvakken en aliassen aanmaken, wijzigen en verwijderen. Ook kunnen ze domeinen aanpassen en informatie over deze verkrijgen.<br><b>Gebruikers</b> kunnen tijdelijke aliassen aanmaken, hun wachtwoord aanpassen en de spamfilterinstellingen wijzigen.';
|
||||||
$lang['start']['imap_smtp_server_auth_info'] = 'Gebruik uw volledige e-mailadres en het onversleutelde verificatiemechanisme.<br>De aanmeldgegevens worden versleuteld verstuurd.';
|
$lang['start']['imap_smtp_server_auth_info'] = 'Gebruik je volledige e-mailadres en het bijbehorende (onversleutelde) verificatiemechanisme.<br>De aanmeldgegevens worden versleuteld verstuurd.';
|
||||||
$lang['start']['help'] = 'Toon/verberg hulppaneel';
|
$lang['start']['help'] = 'Toon/verberg hulppaneel';
|
||||||
$lang['header']['mailcow_settings'] = 'Instellingen';
|
$lang['header']['mailcow_settings'] = 'Instellingen';
|
||||||
$lang['header']['administration'] = 'Configuratie & details';
|
$lang['header']['administration'] = 'Configuratie & details';
|
||||||
|
@ -384,7 +384,14 @@ $lang['edit']['multiple_bookings'] = 'Meerdere boekingen';
|
||||||
$lang['edit']['kind'] = 'Soort';
|
$lang['edit']['kind'] = 'Soort';
|
||||||
$lang['edit']['resource'] = 'Hulpbron';
|
$lang['edit']['resource'] = 'Hulpbron';
|
||||||
$lang['edit']['relayhost'] = 'Afzender-afhankelijke transportkaarten';
|
$lang['edit']['relayhost'] = 'Afzender-afhankelijke transportkaarten';
|
||||||
|
$lang['edit']['public_comment'] = 'Publiekelijke opmerking';
|
||||||
|
$lang['mailbox']['public_comment'] = 'Publiekelijke opmerking';
|
||||||
|
$lang['edit']['private_comment'] = 'Persoonlijke opmerking';
|
||||||
|
$lang['mailbox']['private_comment'] = 'Persoonlijke opmerking';
|
||||||
|
$lang['edit']['comment_info'] = 'Een persoonlijke opmerking is niet zichtbaar voor de gebruiker, terwijl een publiekelijke opmerking wel weergegeven zal worden in het overzicht van een gebruiker.';
|
||||||
|
$lang['add']['public_comment'] = 'Publiekelijke opmerking';
|
||||||
|
$lang['add']['private_comment'] = 'Persoonlijke opmerking';
|
||||||
|
$lang['add']['comment_info'] = 'Een persoonlijke opmerking is niet zichtbaar voor de gebruiker, terwijl een publiekelijke opmerking wel weergegeven zal worden in het overzicht van een gebruiker.';
|
||||||
$lang['acl']['spam_alias'] = 'Tijdelijke aliassen';
|
$lang['acl']['spam_alias'] = 'Tijdelijke aliassen';
|
||||||
$lang['acl']['tls_policy'] = 'Versleutelingsbeleid';
|
$lang['acl']['tls_policy'] = 'Versleutelingsbeleid';
|
||||||
$lang['acl']['spam_score'] = 'Spamscore';
|
$lang['acl']['spam_score'] = 'Spamscore';
|
||||||
|
@ -393,14 +400,28 @@ $lang['acl']['delimiter_action'] = 'Delimiter-actie';
|
||||||
$lang['acl']['syncjobs'] = 'Synchronisatietaken';
|
$lang['acl']['syncjobs'] = 'Synchronisatietaken';
|
||||||
$lang['acl']['eas_reset'] = 'Herstel ActiveSync-apparaatcache';
|
$lang['acl']['eas_reset'] = 'Herstel ActiveSync-apparaatcache';
|
||||||
$lang['acl']['sogo_profile_reset'] = 'Herstel SOGo-profiel';
|
$lang['acl']['sogo_profile_reset'] = 'Herstel SOGo-profiel';
|
||||||
$lang['acl']['quarantine'] = 'Quarantaine';
|
$lang['acl']['quarantine'] = 'Quarantaine-acties';
|
||||||
|
$lang['acl']['quarantine_notification'] = 'Quarantaine-meldingen';
|
||||||
|
$lang['acl']['quarantine_attachments'] = 'Quarantaine-bijlagen';
|
||||||
$lang['acl']['alias_domains'] = 'Voeg aliasdomeinen toe';
|
$lang['acl']['alias_domains'] = 'Voeg aliasdomeinen toe';
|
||||||
$lang['acl']['login_as'] = 'Log in als postvakgebruiker';
|
$lang['acl']['login_as'] = 'Log in als postvakgebruiker';
|
||||||
$lang['acl']['bcc_maps'] = 'BCC-kaarten';
|
$lang['acl']['bcc_maps'] = 'BCC-kaarten';
|
||||||
$lang['acl']['filters'] = 'Filters';
|
$lang['acl']['filters'] = 'Filters';
|
||||||
$lang['acl']['ratelimit'] = 'Ratelimit';
|
$lang['acl']['ratelimit'] = 'Ratelimit';
|
||||||
$lang['acl']['recipient_maps'] = 'Ontvanger-kaarten';
|
$lang['acl']['recipient_maps'] = 'Ontvanger-kaarten';
|
||||||
$lang['acl']['prohibited'] = 'Geweigerd door ACL';
|
$lang['acl']['prohibited'] = 'Toegang geweigerd';
|
||||||
|
|
||||||
|
$lang['mailbox']['quarantine_notification'] = 'Quarantaine-meldingen';
|
||||||
|
$lang['mailbox']['never'] = 'Nooit';
|
||||||
|
$lang['mailbox']['hourly'] = 'Ieder uur';
|
||||||
|
$lang['mailbox']['daily'] = 'Dagelijks';
|
||||||
|
$lang['mailbox']['weekly'] = 'Wekelijks';
|
||||||
|
$lang['user']['quarantine_notification'] = 'Quarantaine-meldingen';
|
||||||
|
$lang['user']['never'] = 'Nooit';
|
||||||
|
$lang['user']['hourly'] = 'Ieder uur';
|
||||||
|
$lang['user']['daily'] = 'Dagelijks';
|
||||||
|
$lang['user']['weekly'] = 'Wekelijks';
|
||||||
|
$lang['user']['quarantine_notification_info'] = 'Zodra een melding is verzonden, worden de items als gelezen gemarkeerd en zullen er geen meldingen meer over diezelfde items verstuurd worden.';
|
||||||
|
|
||||||
$lang['add']['generate'] = 'genereer';
|
$lang['add']['generate'] = 'genereer';
|
||||||
$lang['add']['syncjob'] = 'Voeg een nieuwe synchronisatietaak toe';
|
$lang['add']['syncjob'] = 'Voeg een nieuwe synchronisatietaak toe';
|
||||||
|
@ -494,11 +515,11 @@ $lang['tfa']['disable_tfa'] = "Zet TFA uit tot de eerstvolgende succesvolle logi
|
||||||
$lang['tfa']['confirm'] = "Bevestig";
|
$lang['tfa']['confirm'] = "Bevestig";
|
||||||
$lang['tfa']['totp'] = "TOTP (Google Authenticator etc.)";
|
$lang['tfa']['totp'] = "TOTP (Google Authenticator etc.)";
|
||||||
$lang['tfa']['select'] = "Selecteer...";
|
$lang['tfa']['select'] = "Selecteer...";
|
||||||
$lang['tfa']['waiting_usb_auth'] = "<i>In afwachting van USB-apparaat...</i><br><br>Druk nu op de knop van uw U2F-apparaat.";
|
$lang['tfa']['waiting_usb_auth'] = "<i>In afwachting van USB-apparaat...</i><br><br>Druk nu op de knop van je U2F-apparaat.";
|
||||||
$lang['tfa']['waiting_usb_register'] = "<i>In afwachting van USB-apparaat...</i><br><br>Voer uw wachtwoord hierboven in en bevestig de registratie van het U2F-apparaat door op de knop van het apparaat te drukken.";
|
$lang['tfa']['waiting_usb_register'] = "<i>In afwachting van USB-apparaat...</i><br><br>Voer je wachtwoord hierboven in en bevestig de registratie van het U2F-apparaat door op de knop van het apparaat te drukken.";
|
||||||
$lang['tfa']['scan_qr_code'] = "Scan de volgende QR-code met uw authenticatie-app:";
|
$lang['tfa']['scan_qr_code'] = "Scan de volgende QR-code met je authenticatie-app:";
|
||||||
$lang['tfa']['enter_qr_code'] = "Voer deze code in als uw apparaat geen QR-codes kan scannen:";
|
$lang['tfa']['enter_qr_code'] = "Voer deze code in als je apparaat geen QR-codes kan scannen:";
|
||||||
$lang['tfa']['confirm_totp_token'] = "Bevestig de wijzigingen door de, door uw authenticatie-app gegenereerde code, in te voeren.";
|
$lang['tfa']['confirm_totp_token'] = "Bevestig de wijzigingen door de, door je authenticatie-app gegenereerde code, in te voeren.";
|
||||||
|
|
||||||
$lang['admin']['rspamd-com_settings'] = '<a href="https://rspamd.com/doc/configuration/settings.html#settings-structure" target="_blank">Rspamd documentatie</a> - Een beschrijving voor deze instelling zal automatisch worden gegenereerd, bekijk de onderstaande presets voor meer info.';
|
$lang['admin']['rspamd-com_settings'] = '<a href="https://rspamd.com/doc/configuration/settings.html#settings-structure" target="_blank">Rspamd documentatie</a> - Een beschrijving voor deze instelling zal automatisch worden gegenereerd, bekijk de onderstaande presets voor meer info.';
|
||||||
|
|
||||||
|
@ -636,13 +657,15 @@ $lang['admin']['queue_unban'] = "markeer om toe te staan";
|
||||||
$lang['admin']['no_active_bans'] = "Geen actieve verbanningen";
|
$lang['admin']['no_active_bans'] = "Geen actieve verbanningen";
|
||||||
|
|
||||||
$lang['admin']['quarantine'] = "Quarantaine";
|
$lang['admin']['quarantine'] = "Quarantaine";
|
||||||
$lang['admin']['quarantine_retention_size'] = "Maximale retenties per postvak<br />Gebruik 0 om deze functionaliteit <b>uit te zetten</b>.";
|
$lang['admin']['quarantine_retention_size'] = "Maximale retenties per postvak:<br><small>Gebruik 0 om deze functionaliteit <b>uit te zetten</b>.</small>";
|
||||||
$lang['admin']['quarantine_max_size'] = "Maximale grootte in MiB (mail die de limiet overschrijdt zal worden verwijderd)<br />0 betekent <b>niet</b> onbeperkt!";
|
$lang['admin']['quarantine_max_size'] = "Maximale grootte in MiB (mail die de limiet overschrijdt zal worden verwijderd):<br><small>0 betekent <b>niet</b> onbeperkt!</small>";
|
||||||
$lang['admin']['quarantine_exclude_domains'] = "Sluit domeinen en aliasdomeinen uit";
|
$lang['admin']['quarantine_exclude_domains'] = "Sluit de volgende domeinen en aliasdomeinen uit";
|
||||||
$lang['admin']['quarantine_release_format'] = "Vrijgegeven items worden verstuurd als";
|
$lang['admin']['quarantine_release_format'] = "Verstuur vrijgegeven items als";
|
||||||
$lang['admin']['quarantine_release_format_raw'] = "Origineel";
|
$lang['admin']['quarantine_release_format_raw'] = "Origineel";
|
||||||
$lang['admin']['quarantine_release_format_att'] = "Bijlage";
|
$lang['admin']['quarantine_release_format_att'] = "Bijlage";
|
||||||
|
$lang['admin']['quarantine_notification_sender'] = "Afzender van meldingen";
|
||||||
|
$lang['admin']['quarantine_notification_subject'] = "Onderwerp van meldingen";
|
||||||
|
$lang['admin']['quarantine_notification_html'] = "Meldingsjabloon:<br><small>Laat leeg om de standaardsjabloon te herstellen.</small>";
|
||||||
$lang['admin']['ui_texts'] = "UI-labels en teksten";
|
$lang['admin']['ui_texts'] = "UI-labels en teksten";
|
||||||
$lang['admin']['help_text'] = "Pas hulpteksten onder inlogvenster aan (HTML toegestaan)";
|
$lang['admin']['help_text'] = "Pas hulpteksten onder inlogvenster aan (HTML toegestaan)";
|
||||||
$lang['admin']['title_name'] = '"Mailcow UI" website-titel';
|
$lang['admin']['title_name'] = '"Mailcow UI" website-titel';
|
||||||
|
@ -669,6 +692,7 @@ $lang['user']['spam_score_reset'] = "Herstel naar standaardwaarde";
|
||||||
$lang['edit']['spam_policy'] = "Voeg onderdelen toe, of verwijder onderdelen van de witte en zwarte lijst";
|
$lang['edit']['spam_policy'] = "Voeg onderdelen toe, of verwijder onderdelen van de witte en zwarte lijst";
|
||||||
$lang['edit']['spam_alias'] = "Maak een nieuw tijdelijk alias aan, of pas deze aan";
|
$lang['edit']['spam_alias'] = "Maak een nieuw tijdelijk alias aan, of pas deze aan";
|
||||||
|
|
||||||
|
$lang['danger']['comment_too_long'] = "Opmerkingen mogen niet langer dan 160 karakters zijn";
|
||||||
$lang['danger']['img_tmp_missing'] = "Kan afbeelding niet valideren, tijdelijk bestand niet gevonden";
|
$lang['danger']['img_tmp_missing'] = "Kan afbeelding niet valideren, tijdelijk bestand niet gevonden";
|
||||||
$lang['danger']['img_invalid'] = "Kan afbeelding niet valideren";
|
$lang['danger']['img_invalid'] = "Kan afbeelding niet valideren";
|
||||||
$lang['danger']['invalid_mime_type'] = "Ongeldig mime-type";
|
$lang['danger']['invalid_mime_type'] = "Ongeldig mime-type";
|
||||||
|
@ -699,6 +723,11 @@ $lang['quarantine']['subj'] = "Onderwerp";
|
||||||
$lang['quarantine']['text_plain_content'] = "Inhoud (tekst)";
|
$lang['quarantine']['text_plain_content'] = "Inhoud (tekst)";
|
||||||
$lang['quarantine']['text_from_html_content'] = "Inhoud (geconverteerde html)";
|
$lang['quarantine']['text_from_html_content'] = "Inhoud (geconverteerde html)";
|
||||||
$lang['quarantine']['atts'] = "Bijlagen";
|
$lang['quarantine']['atts'] = "Bijlagen";
|
||||||
|
$lang['quarantine']['low_danger'] = "Laag risico";
|
||||||
|
$lang['quarantine']['neutral_danger'] = "Neutraal/geen beoordeling";
|
||||||
|
$lang['quarantine']['medium_danger'] = "Middelmatig risico";
|
||||||
|
$lang['quarantine']['high_danger'] = "Hoog risico";
|
||||||
|
$lang['quarantine']['danger'] = "Risico";
|
||||||
$lang['warning']['fuzzy_learn_error'] = "Fuzzy hash training-fout: %s";
|
$lang['warning']['fuzzy_learn_error'] = "Fuzzy hash training-fout: %s";
|
||||||
$lang['danger']['spam_learn_error'] = "Spamtraining-fout: %s";
|
$lang['danger']['spam_learn_error'] = "Spamtraining-fout: %s";
|
||||||
$lang['success']['qlearn_spam'] = "Bericht %s werd als spam gemarkeerd en is verwijderd";
|
$lang['success']['qlearn_spam'] = "Bericht %s werd als spam gemarkeerd en is verwijderd";
|
||||||
|
@ -715,7 +744,7 @@ $lang['debug']['external_logs'] = 'Externe logs';
|
||||||
$lang['debug']['static_logs'] = 'Statische logs';
|
$lang['debug']['static_logs'] = 'Statische logs';
|
||||||
$lang['debug']['solr_uptime'] = 'Uptime';
|
$lang['debug']['solr_uptime'] = 'Uptime';
|
||||||
$lang['debug']['solr_started_at'] = 'Opgestart op';
|
$lang['debug']['solr_started_at'] = 'Opgestart op';
|
||||||
$lang['debug']['solr_last_modified'] = 'Laatst bewerkt op';
|
$lang['debug']['solr_last_modified'] = 'Voor het laatst bijgewerkt op';
|
||||||
$lang['debug']['solr_size'] = 'Grootte';
|
$lang['debug']['solr_size'] = 'Grootte';
|
||||||
$lang['debug']['solr_docs'] = 'Documenten';
|
$lang['debug']['solr_docs'] = 'Documenten';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue