Fix metrics

master
André 2017-10-26 10:22:16 +02:00
commit 25ee53289b
96 changed files with 2854 additions and 823 deletions

4
.gitignore vendored
View File

@ -7,14 +7,18 @@ data/conf/nginx/listen*active
data/conf/nginx/server_name.active data/conf/nginx/server_name.active
data/conf/postfix/sql data/conf/postfix/sql
data/conf/dovecot/sql data/conf/dovecot/sql
data/conf/nextcloud-*.bak
data/web/inc/vars.local.inc.php data/web/inc/vars.local.inc.php
data/assets/ssl/* data/assets/ssl/*
.vscode/* .vscode/*
data/web/.well-known/acme-challenge data/web/.well-known/acme-challenge
data/web/nextcloud/
data/conf/rspamd/local.d/* data/conf/rspamd/local.d/*
data/conf/rspamd/override.d/* data/conf/rspamd/override.d/*
!data/conf/nginx/dynmaps.conf !data/conf/nginx/dynmaps.conf
!data/conf/nginx/site.conf !data/conf/nginx/site.conf
data/conf/nginx/*.conf data/conf/nginx/*.conf
data/conf/nginx/*.custom
data/conf/nginx/*.bak
data/conf/dovecot/extra.conf data/conf/dovecot/extra.conf
data/conf/rspamd/custom/* data/conf/rspamd/custom/*

View File

@ -9,8 +9,9 @@ RUN apk add --update --no-cache \
openssl \ openssl \
bind-tools \ bind-tools \
jq \ jq \
mariadb-client mariadb-client \
tini
COPY docker-entrypoint.sh /srv/docker-entrypoint.sh COPY docker-entrypoint.sh /srv/docker-entrypoint.sh
ENTRYPOINT ["/srv/docker-entrypoint.sh"] CMD ["/sbin/tini", "-g", "--", "/srv/docker-entrypoint.sh"]

View File

@ -1,4 +1,6 @@
#!/bin/bash #!/bin/bash
set -o pipefail
exec 5>&1
ACME_BASE=/var/lib/acme ACME_BASE=/var/lib/acme
SSL_EXAMPLE=/var/lib/ssl-example SSL_EXAMPLE=/var/lib/ssl-example
@ -6,24 +8,40 @@ SSL_EXAMPLE=/var/lib/ssl-example
mkdir -p ${ACME_BASE}/acme/private mkdir -p ${ACME_BASE}/acme/private
restart_containers(){ restart_containers(){
for container in $*; do for container in $*; do
echo "Restarting ${container}..." echo "Restarting ${container}..."
curl -X POST \ curl -X POST http://dockerapi:8080/containers/${container}/restart
--unix-socket /var/run/docker.sock \ done
"http/containers/${container}/restart" }
done
log_f() {
if [[ ${2} == "no_nl" ]]; then
echo -n "$(date) - ${1}"
elif [[ ${2} == "no_date" ]]; then
echo "${1}"
else
echo "$(date) - ${1}"
fi
}
array_diff() {
# https://stackoverflow.com/questions/2312762, Alex Offshore
eval local ARR1=\(\"\${$2[@]}\"\)
eval local ARR2=\(\"\${$3[@]}\"\)
local IFS=$'\n'
mapfile -t $1 < <(comm -23 <(echo "${ARR1[*]}" | sort) <(echo "${ARR2[*]}" | sort))
} }
verify_hash_match(){ verify_hash_match(){
CERT_HASH=$(openssl x509 -noout -modulus -in "${1}" | openssl md5) CERT_HASH=$(openssl x509 -noout -modulus -in "${1}" | openssl md5)
KEY_HASH=$(openssl rsa -noout -modulus -in "${2}" | openssl md5) KEY_HASH=$(openssl rsa -noout -modulus -in "${2}" | openssl md5)
if [[ ${CERT_HASH} != ${KEY_HASH} ]]; then if [[ ${CERT_HASH} != ${KEY_HASH} ]]; then
echo "Certificate and key hashes do not match!" log_f "Certificate and key hashes do not match!"
return 1 return 1
else else
echo "Verified hashes." log_f "Verified hashes."
return 0 return 0
fi fi
} }
get_ipv4(){ get_ipv4(){
@ -31,7 +49,7 @@ get_ipv4(){
local IPV4_SRCS= local IPV4_SRCS=
local TRY= local TRY=
IPV4_SRCS[0]="api.ipify.org" IPV4_SRCS[0]="api.ipify.org"
IPV4_SRCS[1]="ifconfig.co" IPV4_SRCS[1]="ifconfig.co"-
IPV4_SRCS[2]="icanhazip.com" IPV4_SRCS[2]="icanhazip.com"
IPV4_SRCS[3]="v4.ident.me" IPV4_SRCS[3]="v4.ident.me"
IPV4_SRCS[4]="ipecho.net/plain" IPV4_SRCS[4]="ipecho.net/plain"
@ -47,224 +65,243 @@ get_ipv4(){
[[ ! -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
ISSUER=$(openssl x509 -in ${ACME_BASE}/cert.pem -noout -issuer) ISSUER=$(openssl x509 -in ${ACME_BASE}/cert.pem -noout -issuer)
if [[ ${ISSUER} != *"Let's Encrypt"* && ${ISSUER} != *"mailcow"* ]]; then if [[ ${ISSUER} != *"Let's Encrypt"* && ${ISSUER} != *"mailcow"* ]]; then
echo "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 else
declare -a SAN_ARRAY_NOW 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:") 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 if [[ ! -z ${SAN_NAMES} ]]; then
IFS=',' read -a SAN_ARRAY_NOW <<< ${SAN_NAMES} IFS=',' read -a SAN_ARRAY_NOW <<< ${SAN_NAMES}
echo "Found Let's Encrypt or mailcow snake-oil CA issued certificate with SANs: ${SAN_ARRAY_NOW[*]}" log_f "Found Let's Encrypt or mailcow snake-oil CA issued certificate with SANs: ${SAN_ARRAY_NOW[*]}"
fi 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/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.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/fullchain.pem ${ACME_BASE}/acme/private/privkey.pem; then
echo "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/fullchain.pem ${ACME_BASE}/cert.pem
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
exec env TRIGGER_RESTART=1 $(readlink -f "$0") # Restarting with env var set to trigger a restart,
fi exec env TRIGGER_RESTART=1 $(readlink -f "$0")
ISSUER="mailcow" fi
else ISSUER="mailcow"
echo "Restoring mailcow snake-oil certificates and restarting script..." else
cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem log_f "Restoring mailcow snake-oil certificates and restarting script..."
cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem
exec env TRIGGER_RESTART=1 $(readlink -f "$0") cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem
fi exec env TRIGGER_RESTART=1 $(readlink -f "$0")
fi
fi fi
while ! mysqladmin ping --host mysql -u${DBUSER} -p${DBPASS} --silent; do
echo "Waiting for database to come up..."
sleep 2
done
while true; do while true; do
if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..." log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
sleep 3650d sleep 365d
exec $(readlink -f "$0") exec $(readlink -f "$0")
fi fi
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
unset SQL_DOMAIN_ARR unset SQL_DOMAIN_ARR
unset VALIDATED_CONFIG_DOMAINS unset VALIDATED_CONFIG_DOMAINS
unset ADDITIONAL_VALIDATED_SAN unset ADDITIONAL_VALIDATED_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
IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}" IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}"
IPV4=$(get_ipv4) IPV4=$(get_ipv4)
# Container ids may have changed # Container ids may have changed
CONTAINERS_RESTART=($(curl --silent --unix-socket /var/run/docker.sock http/containers/json | jq -rc 'map(select(.Names[] | contains ("nginx-mailcow") or contains ("postfix-mailcow") or contains ("dovecot-mailcow"))) | .[] .Id' | tr "\n" " ")) CONTAINERS_RESTART=($(curl --silent http://dockerapi:8080/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow") or contains("postfix-mailcow") or contains("dovecot-mailcow")) | .id' | tr "\n" " "))
while read domain; do log_f "Waiting for domain tables... " no_nl
SQL_DOMAIN_ARR+=("${domain}") while [[ -z ${DOMAIN_TABLE} ]]; do
done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0" -Bs) DOMAIN_TABLE=$(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
while read alias_domain; do [[ -z ${DOMAIN_TABLE} ]] && sleep 10
SQL_DOMAIN_ARR+=("${alias_domain}") done
done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs) log_f "OK" no_date
for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do while read domains; do
A_CONFIG=$(dig A autoconfig.${SQL_DOMAIN} +short | tail -n 1) SQL_DOMAIN_ARR+=("${domains}")
if [[ ! -z ${A_CONFIG} ]]; then done < <(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 UNION SELECT alias_domain FROM alias_domain" -Bs)
echo "Found A record for autoconfig.${SQL_DOMAIN}: ${A_CONFIG}"
if [[ ${IPV4:-ERR} == ${A_CONFIG} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then for SQL_DOMAIN in "${SQL_DOMAIN_ARR[@]}"; do
echo "Confirmed A record autoconfig.${SQL_DOMAIN}" A_CONFIG=$(dig A autoconfig.${SQL_DOMAIN} +short | tail -n 1)
VALIDATED_CONFIG_DOMAINS+=("autoconfig.${SQL_DOMAIN}") if [[ ! -z ${A_CONFIG} ]]; then
else log_f "Found A record for autoconfig.${SQL_DOMAIN}: ${A_CONFIG}"
echo "Cannot match your IP ${IPV4} against hostname autoconfig.${SQL_DOMAIN} (${A_CONFIG})" if [[ ${IPV4:-ERR} == ${A_CONFIG} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
fi log_f "Confirmed A record autoconfig.${SQL_DOMAIN}"
else VALIDATED_CONFIG_DOMAINS+=("autoconfig.${SQL_DOMAIN}")
echo "No A record for autoconfig.${SQL_DOMAIN} found" else
fi log_f "Cannot match your IP ${IPV4} against hostname autoconfig.${SQL_DOMAIN} (${A_CONFIG})"
fi
else
log_f "No A record for autoconfig.${SQL_DOMAIN} found"
fi
A_DISCOVER=$(dig A autodiscover.${SQL_DOMAIN} +short | tail -n 1) A_DISCOVER=$(dig A autodiscover.${SQL_DOMAIN} +short | tail -n 1)
if [[ ! -z ${A_DISCOVER} ]]; then if [[ ! -z ${A_DISCOVER} ]]; then
echo "Found A record for autodiscover.${SQL_DOMAIN}: ${A_DISCOVER}" log_f "Found A record for autodiscover.${SQL_DOMAIN}: ${A_DISCOVER}"
if [[ ${IPV4:-ERR} == ${A_DISCOVER} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then if [[ ${IPV4:-ERR} == ${A_DISCOVER} ]] || [[ ${SKIP_IP_CHECK} == "y" ]]; then
echo "Confirmed A record autodiscover.${SQL_DOMAIN}" log_f "Confirmed A record autodiscover.${SQL_DOMAIN}"
VALIDATED_CONFIG_DOMAINS+=("autodiscover.${SQL_DOMAIN}") VALIDATED_CONFIG_DOMAINS+=("autodiscover.${SQL_DOMAIN}")
else else
echo "Cannot match your IP ${IPV4} against hostname autodiscover.${SQL_DOMAIN} (${A_DISCOVER})" log_f "Cannot match your IP ${IPV4} against hostname autodiscover.${SQL_DOMAIN} (${A_DISCOVER})"
fi fi
else else
echo "No A record for autodiscover.${SQL_DOMAIN} found" log_f "No A record for autodiscover.${SQL_DOMAIN} found"
fi fi
done done
A_MAILCOW_HOSTNAME=$(dig A ${MAILCOW_HOSTNAME} +short | tail -n 1) A_MAILCOW_HOSTNAME=$(dig A ${MAILCOW_HOSTNAME} +short | tail -n 1)
if [[ ! -z ${A_MAILCOW_HOSTNAME} ]]; then if [[ ! -z ${A_MAILCOW_HOSTNAME} ]]; then
echo "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
echo "Confirmed A record ${MAILCOW_HOSTNAME}" log_f "Confirmed A record ${MAILCOW_HOSTNAME}"
VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} VALIDATED_MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
else else
echo "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
else else
echo "No A record for ${MAILCOW_HOSTNAME} found" log_f "No A record for ${MAILCOW_HOSTNAME} found"
fi fi
for SAN in "${ADDITIONAL_SAN_ARR[@]}"; do for SAN in "${ADDITIONAL_SAN_ARR[@]}"; do
if [[ ${SAN} == ${MAILCOW_HOSTNAME} ]]; then if [[ ${SAN} == ${MAILCOW_HOSTNAME} ]]; then
continue continue
fi fi
A_SAN=$(dig A ${SAN} +short | tail -n 1) A_SAN=$(dig A ${SAN} +short | tail -n 1)
if [[ ! -z ${A_SAN} ]]; then if [[ ! -z ${A_SAN} ]]; then
echo "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
echo "Confirmed A record ${SAN}" log_f "Confirmed A record ${SAN}"
ADDITIONAL_VALIDATED_SAN+=("${SAN}") ADDITIONAL_VALIDATED_SAN+=("${SAN}")
else else
echo "Cannot match your IP against hostname ${SAN}" log_f "Cannot match your IP against hostname ${SAN}"
fi fi
else else
echo "No A record for ${SAN} found" log_f "No A record for ${SAN} found"
fi fi
done done
# Unique elements # Unique elements
ALL_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs)) ALL_VALIDATED=(${VALIDATED_MAILCOW_HOSTNAME} $(echo ${VALIDATED_CONFIG_DOMAINS[*]} ${ADDITIONAL_VALIDATED_SAN[*]} | xargs -n1 | sort -u | xargs))
if [[ -z ${ALL_VALIDATED[*]} ]]; then if [[ -z ${ALL_VALIDATED[*]} ]]; then
echo "Cannot validate hostnames, skipping Let's Encrypt for 1 hour." log_f "Cannot validate hostnames, skipping Let's Encrypt for 1 hour."
echo "Use SKIP_LETS_ENCRYPT=y in mailcow.conf to skip it permanently." log_f "Use SKIP_LETS_ENCRYPT=y in mailcow.conf to skip it permanently."
sleep 1h sleep 1h
exec $(readlink -f "$0") exec $(readlink -f "$0")
fi fi
ORPHANED_SAN=($(echo ${SAN_ARRAY_NOW[*]} ${ALL_VALIDATED[*]} | tr ' ' '\n' | sort | uniq -u )) array_diff ORPHANED_SAN SAN_ARRAY_NOW ALL_VALIDATED
if [[ ! -z ${ORPHANED_SAN[*]} ]] && [[ ${ISSUER} != *"mailcow"* ]]; then if [[ ! -z ${ORPHANED_SAN[*]} ]] && [[ ${ISSUER} != *"mailcow"* ]]; then
DATE=$(date +%Y-%m-%d_%H_%M_%S) DATE=$(date +%Y-%m-%d_%H_%M_%S)
echo "Found orphaned SAN ${ORPHANED_SAN[*]} in certificate, moving old files to ${ACME_BASE}/acme/private/${DATE}.bak/, keeping key file..." log_f "Found orphaned SAN ${ORPHANED_SAN[*]} in certificate, moving old files to ${ACME_BASE}/acme/private/${DATE}.bak/, keeping key file..."
mkdir -p ${ACME_BASE}/acme/private/${DATE}.bak/ 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/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/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/ [[ -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 cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/acme/private/${DATE}.bak/ # Keep key for TLSA 3 1 1 records
fi fi
acme-client \ ACME_RESPONSE=$(acme-client \
-v -e -b -N -n \ -v -e -b -N -n \
-f ${ACME_BASE}/acme/private/account.key \ -f ${ACME_BASE}/acme/private/account.key \
-k ${ACME_BASE}/acme/private/privkey.pem \ -k ${ACME_BASE}/acme/private/privkey.pem \
-c ${ACME_BASE}/acme \ -c ${ACME_BASE}/acme \
${ALL_VALIDATED[*]} ${ALL_VALIDATED[*]} 2>&1 | tee /dev/fd/5)
case "$?" in case "$?" in
0) # new certs 0) # new certs
# cp the new certificates and keys # cp the new certificates and keys
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
# restart docker containers # restart docker containers
if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then
echo "Certificate was successfully requested, but key and certificate have non-matching hashes, restoring mailcow snake-oil and restarting containers..." log_f "Certificate was successfully requested, but key and certificate have non-matching hashes, restoring mailcow snake-oil and restarting containers..."
cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem
cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem
fi fi
restart_containers ${CONTAINERS_RESTART[*]} restart_containers ${CONTAINERS_RESTART[*]}
;; ;;
1) # failure 1) # failure
if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then if [[ $ACME_RESPONSE =~ "No registration exists" ]]; then
echo "Error requesting certificate, restoring previous certificate from backup and restarting containers...." log_f "Registration keys are invalid, deleting old keys and restarting..."
cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem rm ${ACME_BASE}/acme/private/account.key
cp ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ${ACME_BASE}/key.pem rm ${ACME_BASE}/acme/private/privkey.pem
TRIGGER_RESTART=1 exec $(readlink -f "$0")
fi
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 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 ]] && restart_containers ${CONTAINERS_RESTART[*]}
log_f "Retrying in 30 minutes..."
sleep 30m
exec $(readlink -f "$0")
;;
2) # no change
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
[[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]}
;;
*) # unspecified
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 elif [[ -f ${ACME_BASE}/acme/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/privkey.pem ]]; then
echo "Error requesting certificate, restoring from previous acme request and restarting containers..." 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/fullchain.pem ${ACME_BASE}/cert.pem
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
TRIGGER_RESTART=1 TRIGGER_RESTART=1
fi fi
if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then
echo "Error verifying certificates, restoring mailcow snake-oil and restarting containers..." log_f "Error verifying certificates, restoring mailcow snake-oil..."
cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem cp ${SSL_EXAMPLE}/cert.pem ${ACME_BASE}/cert.pem
cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem cp ${SSL_EXAMPLE}/key.pem ${ACME_BASE}/key.pem
TRIGGER_RESTART=1 TRIGGER_RESTART=1
fi fi
[[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]} [[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]}
echo "Retrying in 30 minutes..." log_f "Retrying in 30 minutes..."
sleep 30m sleep 30m
exec $(readlink -f "$0") exec $(readlink -f "$0")
;; ;;
2) # no change esac
if ! diff ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem; then
echo "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
restart_containers ${CONTAINERS_RESTART[*]}
fi
if ! verify_hash_match ${ACME_BASE}/cert.pem ${ACME_BASE}/key.pem; then
echo "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
restart_containers ${CONTAINERS_RESTART[*]}
fi
;;
*) # unspecified
if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then
echo "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
echo "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
echo "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 ]] && restart_containers ${CONTAINERS_RESTART[*]}
sleep 3650d
;;
esac
echo "ACME certificate validation done. Sleeping for another day." log_f "ACME certificate validation done. Sleeping for another day."
sleep 1d sleep 1d
done done

Binary file not shown.

View File

@ -7,7 +7,7 @@ COPY dl_files.sh bootstrap.sh ./
# Installation # Installation
RUN apk add --update \ RUN apk add --update \
&& apk add --no-cache clamav clamav-libunrar curl bash \ && apk add --no-cache clamav clamav-libunrar curl bash tini \
&& chmod +x /dl_files.sh \ && chmod +x /dl_files.sh \
&& set -ex; /bin/bash /dl_files.sh \ && set -ex; /bin/bash /dl_files.sh \
&& mkdir /run/clamav \ && mkdir /run/clamav \
@ -15,12 +15,14 @@ RUN apk add --update \
&& chmod 750 /run/clamav \ && chmod 750 /run/clamav \
&& sed -i '/Foreground yes/s/^#//g' /etc/clamav/clamd.conf \ && sed -i '/Foreground yes/s/^#//g' /etc/clamav/clamd.conf \
&& sed -i '/TCPSocket 3310/s/^#//g' /etc/clamav/clamd.conf \ && sed -i '/TCPSocket 3310/s/^#//g' /etc/clamav/clamd.conf \
&& sed -i 's#LogFile /var/log/clamav/clamd.log#LogFile /tmp/logpipe_clamd#g' /etc/clamav/clamd.conf \
&& sed -i 's/#PhishingSignatures yes/PhishingSignatures no/g' /etc/clamav/clamd.conf \ && sed -i 's/#PhishingSignatures yes/PhishingSignatures no/g' /etc/clamav/clamd.conf \
&& sed -i 's/#PhishingScanURLs yes/PhishingScanURLs no/g' /etc/clamav/clamd.conf \ && sed -i 's/#PhishingScanURLs yes/PhishingScanURLs no/g' /etc/clamav/clamd.conf \
&& sed -i 's#UpdateLogFile /var/log/clamav/freshclam.log#UpdateLogFile /tmp/logpipe_freshclam#g' /etc/clamav/freshclam.conf \
&& sed -i '/Foreground yes/s/^#//g' /etc/clamav/freshclam.conf && sed -i '/Foreground yes/s/^#//g' /etc/clamav/freshclam.conf
# Port provision # Port provision
EXPOSE 3310 EXPOSE 3310
# AV daemon bootstrapping # AV daemon bootstrapping
CMD ["/bootstrap.sh"] CMD ["/sbin/tini", "-g", "--", "/bootstrap.sh"]

View File

@ -1,13 +1,34 @@
#!/bin/bash #!/bin/bash
touch /var/log/clamav/clamd.log /var/log/clamav/freshclam.log
chown -R clamav:clamav /var/log/clamav/
if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "SKIP_CLAMD=y, skipping ClamAV..." echo "SKIP_CLAMD=y, skipping ClamAV..."
exit 0 sleep 365d
exit 0
fi fi
freshclam -d & # Create log pipes
clamd & touch /var/log/clamav/clamd.log /var/log/clamav/freshclam.log
mkfifo -m 600 /tmp/logpipe_clamd
mkfifo -m 600 /tmp/logpipe_freshclam
chown -R clamav:clamav /var/log/clamav/ /tmp/logpipe_*
cat <> /tmp/logpipe_clamd 1>&2 &
cat <> /tmp/logpipe_freshclam 1>&2 &
tail -f /var/log/clamav/clamd.log /var/log/clamav/freshclam.log # Prepare
BACKGROUND_TASKS=()
freshclam -d &
BACKGROUND_TASKS+=($!)
clamd &
BACKGROUND_TASKS+=($!)
while true; do
for bg_task in ${BACKGROUND_TASKS[*]}; do
if ! kill -0 ${bg_task} 1>&2; then
echo "Worker ${bg_task} died, stopping container waiting for respawn..."
kill -TERM 1
fi
sleep 10
done
done

View File

@ -0,0 +1,8 @@
FROM python:2-alpine
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
RUN apk add -U --no-cache iptables ip6tables
RUN pip install docker flask flask-restful
COPY server.py /
CMD ["python2", "-u", "/server.py"]

View File

@ -0,0 +1,62 @@
from flask import Flask
from flask_restful import Resource, Api
from flask import jsonify
import docker
docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock')
app = Flask(__name__)
api = Api(app)
class containers_get(Resource):
def get(self):
containers = {}
for container in docker_client.containers.list(all=True):
containers.update({container.attrs['Id']: container.attrs})
return containers
class container_get(Resource):
def get(self, container_id):
if container_id and container_id.isalnum():
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
return container.attrs
else:
return jsonify(message='No or invalid id defined')
class container_post(Resource):
def post(self, container_id, post_action):
if container_id and container_id.isalnum() and post_action:
if post_action == 'stop':
try:
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.stop()
except:
return 'Error'
else:
return 'OK'
elif post_action == 'start':
try:
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.start()
except:
return 'Error'
else:
return 'OK'
elif post_action == 'restart':
try:
for container in docker_client.containers.list(all=True, filters={"id": container_id}):
container.restart()
except:
return 'Error'
else:
return 'OK'
else:
return jsonify(message='Invalid action')
else:
return jsonify(message='Invalid container id or missing action')
api.add_resource(containers_get, '/containers/json')
api.add_resource(container_get, '/containers/<string:container_id>/json')
api.add_resource(container_post, '/containers/<string:container_id>/<string:post_action>')
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port='8080')

View File

@ -39,12 +39,13 @@ destination d_redis_cleanup {
); );
}; };
filter f_mail { facility(mail); }; filter f_mail { facility(mail); };
filter f_not_watchdog { not message("172\.22\.1\.248"); };
log { log {
source(s_src); source(s_src);
filter(f_not_watchdog);
destination(d_stdout); destination(d_stdout);
filter(f_mail); filter(f_mail);
destination(d_redis_ui_log); destination(d_redis_ui_log);
destination(d_redis_f2b_channel); destination(d_redis_f2b_channel);
destination(d_redis_cleanup); destination(d_redis_cleanup);
}; };

View File

@ -14,7 +14,8 @@ import json
yes_regex = re.compile(r'([yY][eE][sS]|[yY])+$') yes_regex = re.compile(r'([yY][eE][sS]|[yY])+$')
if re.search(yes_regex, os.getenv('SKIP_FAIL2BAN', 0)): if re.search(yes_regex, os.getenv('SKIP_FAIL2BAN', 0)):
print "Skipping Fail2ban container..." print "SKIP_FAIL2BAN=y, Skipping Fail2ban container..."
time.sleep(31536000)
raise SystemExit raise SystemExit
r = redis.StrictRedis(host='172.22.1.249', decode_responses=True, port=6379, db=0) r = redis.StrictRedis(host='172.22.1.249', decode_responses=True, port=6379, db=0)

View File

@ -1,8 +1,17 @@
FROM php:7.1-fpm-alpine FROM php:7.1-fpm-alpine
LABEL maintainer "Andre Peters <andre.peters@servercow.de>" LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
ENV REDIS_PECL 3.1.4
ENV MEMCACHED_PECL 3.0.3
ENV APCU_PECL 5.1.8
ENV IMAGICK_PECL 3.4.3
RUN apk add -U --no-cache libxml2-dev \ RUN apk add -U --no-cache libxml2-dev \
icu-dev \ icu-dev \
imap-dev \
libmemcached-dev \
cyrus-sasl-dev \
pcre-dev \
icu-libs \ icu-libs \
redis \ redis \
mysql-client \ mysql-client \
@ -11,13 +20,42 @@ RUN apk add -U --no-cache libxml2-dev \
g++ \ g++ \
make \ make \
openssl \ openssl \
&& pecl install redis \ openssl-dev \
samba-client \
libpng \
libpng-dev \
libjpeg-turbo-dev \
libwebp-dev \
zlib-dev \
libxpm-dev \
c-client \
imagemagick-dev \
imagemagick \
libtool \
librsvg \
&& pear install channel://pear.php.net/Net_IDNA2-0.2.0 \
channel://pear.php.net/Auth_SASL-1.1.0 \
Net_IMAP \
NET_SMTP \
Mail_mime \
&& pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \
&& docker-php-ext-enable redis apcu memcached imagick \
&& pecl clear-cache \ && pecl clear-cache \
&& docker-php-ext-configure intl \ && docker-php-ext-configure intl \
&& docker-php-ext-install intl pdo pdo_mysql xmlrpc \ && docker-php-ext-install -j 4 intl pdo pdo_mysql xmlrpc gd zip pcntl opcache \
&& docker-php-ext-enable redis \ && docker-php-ext-configure imap --with-imap --with-imap-ssl \
&& pear install channel://pear.php.net/Net_IDNA2-0.1.1 Auth_SASL Net_IMAP NET_SMTP Net_IDNA2 Mail_mime \ && docker-php-ext-install -j 4 imap \
&& apk del autoconf g++ make libxml2-dev icu-dev && apk del --purge autoconf g++ make libxml2-dev icu-dev imap-dev openssl-dev cyrus-sasl-dev pcre-dev libpng-dev libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev imagemagick-dev \
&& { \
echo 'opcache.enable=1'; \
echo 'opcache.enable_cli=1'; \
echo 'opcache.interned_strings_buffer=8'; \
echo 'opcache.max_accelerated_files=10000'; \
echo 'opcache.memory_consumption=128'; \
echo 'opcache.save_comments=1'; \
echo 'opcache.revalidate_freq=1'; \
} > /usr/local/etc/php/conf.d/opcache-recommended.ini
COPY ./docker-entrypoint.sh / COPY ./docker-entrypoint.sh /

View File

@ -4,8 +4,8 @@ trap "postfix stop" EXIT
[[ ! -d /opt/postfix/conf/sql/ ]] && mkdir -p /opt/postfix/conf/sql/ [[ ! -d /opt/postfix/conf/sql/ ]] && mkdir -p /opt/postfix/conf/sql/
if [[ -z $(grep null /etc/aliases) ]]; then if [[ -z $(grep null /etc/aliases) ]]; then
echo null: /dev/null >> /etc/aliases; echo null: /dev/null >> /etc/aliases;
newaliases; newaliases;
fi fi
cat <<EOF > /opt/postfix/conf/sql/mysql_relay_recipient_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_relay_recipient_maps.cf
@ -13,7 +13,17 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT DISTINCT CASE WHEN '%d' IN (SELECT domain FROM domain WHERE relay_all_recipients=1 AND domain='%d' AND backupmx=1) THEN '%s' ELSE (SELECT goto FROM alias WHERE address='%s' AND active='1') END AS result; query = SELECT DISTINCT
CASE WHEN '%d' IN (
SELECT domain FROM domain
WHERE relay_all_recipients=1
AND domain='%d'
AND backupmx=1
)
THEN '%s' ELSE (
SELECT goto FROM alias WHERE address='%s' AND active='1'
)
END AS result;
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf cat <<EOF > /opt/postfix/conf/sql/mysql_tls_enforce_in_policy.cf
@ -21,7 +31,16 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT IF( EXISTS( SELECT 'TLS_ACTIVE' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address='%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain='%d')) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1'), 'reject_plaintext_session', NULL) AS 'tls_enforce_in'; query = SELECT IF(EXISTS(
SELECT 'TLS_ACTIVE' FROM alias
LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto
WHERE (address='%s'
OR address IN (
SELECT CONCAT('%u', '@', target_domain) FROM alias_domain
WHERE alias_domain='%d'
)
) AND mailbox.tls_enforce_in = '1' AND mailbox.active = '1'
), 'reject_plaintext_session', NULL) AS 'tls_enforce_in';
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_sender_dependent_default_transport_maps.cf
@ -31,9 +50,26 @@ hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps query = SELECT GROUP_CONCAT(transport SEPARATOR '') AS transport_maps
FROM ( FROM (
SELECT IF(EXISTS(SELECT 'smtp_type' FROM alias LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto WHERE (address = '%s' OR address IN (SELECT CONCAT('%u', '@', target_domain) FROM alias_domain WHERE alias_domain = '%d')) AND mailbox.tls_enforce_out = '1' AND mailbox.active = '1'), 'smtp_enforced_tls:', 'smtp:') AS 'transport' SELECT IF(EXISTS(SELECT 'smtp_type' FROM alias
LEFT OUTER JOIN mailbox ON mailbox.username = alias.goto
WHERE (address = '%s'
OR address IN (
SELECT CONCAT('%u', '@', target_domain) FROM alias_domain
WHERE alias_domain = '%d'
)
)
AND mailbox.tls_enforce_out = '1'
AND mailbox.active = '1'
), 'smtp_enforced_tls:', 'smtp:') AS 'transport'
UNION ALL UNION ALL
SELECT hostname AS transport FROM relayhosts LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id WHERE relayhosts.active = '1' AND domain = '%d' OR domain IN (SELECT target_domain FROM alias_domain WHERE alias_domain = '%d') SELECT hostname AS transport FROM relayhosts
LEFT OUTER JOIN domain ON domain.relayhost = relayhosts.id
WHERE relayhosts.active = '1'
AND domain = '%d'
OR domain IN (
SELECT target_domain FROM alias_domain
WHERE alias_domain = '%d'
)
) )
AS transport_view; AS transport_view;
EOF EOF
@ -43,7 +79,11 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts WHERE id IN (SELECT relayhost FROM domain WHERE CONCAT('@', domain) = '%s'); query = SELECT CONCAT_WS(':', username, password) AS auth_data FROM relayhosts
WHERE id IN (
SELECT relayhost FROM domain
WHERE CONCAT('@', domain) = '%s'
);
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_catchall_maps.cf
@ -51,7 +91,10 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT goto FROM alias,alias_domain WHERE alias_domain.alias_domain = '%d' and alias.address = CONCAT('@', alias_domain.target_domain) AND alias.active = 1 AND alias_domain.active='1' query = SELECT goto FROM alias, alias_domain
WHERE alias_domain.alias_domain = '%d'
AND alias.address = CONCAT('@', alias_domain.target_domain)
AND alias.active = 1 AND alias_domain.active='1'
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_domain_maps.cf
@ -59,7 +102,11 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT username FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' and mailbox.username = CONCAT('%u', '@', alias_domain.target_domain) AND mailbox.active = 1 AND alias_domain.active='1' query = SELECT username FROM mailbox, alias_domain
WHERE alias_domain.alias_domain = '%d'
AND mailbox.username = CONCAT('%u', '@', alias_domain.target_domain)
AND mailbox.active = '1'
AND alias_domain.active='1'
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_alias_maps.cf
@ -67,7 +114,9 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT goto FROM alias WHERE address='%s' AND active='1'; query = SELECT goto FROM alias
WHERE address='%s'
AND active='1';
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_domains_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_domains_maps.cf
@ -75,7 +124,12 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT alias_domain from alias_domain WHERE alias_domain='%s' AND active='1' UNION SELECT domain FROM domain WHERE domain='%s' AND active = '1' AND backupmx = '0' query = SELECT alias_domain from alias_domain WHERE alias_domain='%s' AND active='1'
UNION
SELECT domain FROM domain
WHERE domain='%s'
AND active = '1'
AND backupmx = '0'
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_mailbox_maps.cf
@ -99,7 +153,39 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT goto FROM alias WHERE address='%s' AND active='1' AND (domain IN (SELECT domain FROM domain WHERE domain='%d' AND active='1') OR domain in (SELECT target_domain FROM alias_domain WHERE alias_domain='%d' AND active='1')) UNION SELECT logged_in_as FROM sender_acl WHERE send_as='@%d' OR send_as='%s' OR send_as IN ( SELECT CONCAT ('@',target_domain) FROM alias_domain WHERE alias_domain = '%d') OR send_as IN ( SELECT CONCAT ('%u','@',target_domain) FROM alias_domain WHERE alias_domain = '%d' ) AND logged_in_as NOT IN (SELECT goto FROM alias WHERE address='%s') UNION SELECT username FROM mailbox,alias_domain WHERE alias_domain.alias_domain = '%d' AND mailbox.username = CONCAT('%u','@',alias_domain.target_domain) AND mailbox.active ='1' AND alias_domain.active='1' # First select queries domain and alias_domain to determine if domains are active.
query = SELECT goto FROM alias
WHERE address='%s'
AND active='1'
AND (domain IN
(SELECT domain FROM domain
WHERE domain='%d'
AND active='1')
OR domain in (
SELECT alias_domain FROM alias_domain
WHERE alias_domain='%d'
AND active='1'
)
)
UNION
SELECT logged_in_as FROM sender_acl
WHERE send_as='@%d'
OR send_as='%s'
OR send_as IN (
SELECT CONCAT('@',target_domain) FROM alias_domain
WHERE alias_domain = '%d')
OR send_as IN (
SELECT CONCAT('%u','@',target_domain) FROM alias_domain
WHERE alias_domain = '%d')
AND logged_in_as NOT IN (
SELECT goto FROM alias
WHERE address='%s')
UNION
SELECT username FROM mailbox, alias_domain
WHERE alias_domain.alias_domain = '%d'
AND mailbox.username = CONCAT('%u','@',alias_domain.target_domain)
AND mailbox.active ='1'
AND alias_domain.active='1'
EOF EOF
cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf cat <<EOF > /opt/postfix/conf/sql/mysql_virtual_spamalias_maps.cf
@ -107,7 +193,9 @@ user = ${DBUSER}
password = ${DBPASS} password = ${DBPASS}
hosts = mysql hosts = mysql
dbname = ${DBNAME} dbname = ${DBNAME}
query = SELECT goto FROM spamalias WHERE address='%s' AND validity >= UNIX_TIMESTAMP() query = SELECT goto FROM spamalias
WHERE address='%s'
AND validity >= UNIX_TIMESTAMP()
EOF EOF
# Reset GPG key permissions # Reset GPG key permissions
@ -124,9 +212,9 @@ postfix set-permissions
postconf -c /opt/postfix/conf postconf -c /opt/postfix/conf
if [[ $? != 0 ]]; then if [[ $? != 0 ]]; then
echo "Postfix configuration error, refusing to start." echo "Postfix configuration error, refusing to start."
exit 1 exit 1
else else
postfix -c /opt/postfix/conf start postfix -c /opt/postfix/conf start
sleep 126144000 sleep 126144000
fi fi

View File

@ -1,5 +1,6 @@
[supervisord] [supervisord]
nodaemon=true nodaemon=true
user=root
[program:syslog-ng] [program:syslog-ng]
command=/usr/sbin/syslog-ng --foreground --no-caps command=/usr/sbin/syslog-ng --foreground --no-caps
@ -12,14 +13,3 @@ autostart=true
[program:postfix] [program:postfix]
command=/opt/postfix.sh command=/opt/postfix.sh
autorestart=true autorestart=true
[unix_http_server]
file=/var/tmp/supervisord.sock
chmod=0770
chown=nobody:nogroup
[supervisorctl]
serverurl=unix:///var/tmp/supervisord.sock
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

View File

@ -39,8 +39,10 @@ destination d_redis_cleanup {
); );
}; };
filter f_mail { facility(mail); }; filter f_mail { facility(mail); };
filter f_skip_local { not facility (local0, local1, local2, local3, local4, local5, local6, local7); };
log { log {
source(s_src); source(s_src);
filter(f_skip_local);
destination(d_stdout); destination(d_stdout);
filter(f_mail); filter(f_mail);
destination(d_redis_ui_log); destination(d_redis_ui_log);

View File

@ -22,7 +22,8 @@ COPY settings.conf /etc/rspamd/modules.d/settings.conf
#COPY ratelimit.lua /usr/share/rspamd/lua/ratelimit.lua #COPY ratelimit.lua /usr/share/rspamd/lua/ratelimit.lua
#COPY lua_util.lua /usr/share/rspamd/lib/lua_util.lua #COPY lua_util.lua /usr/share/rspamd/lib/lua_util.lua
COPY docker-entrypoint.sh /docker-entrypoint.sh COPY docker-entrypoint.sh /docker-entrypoint.sh
COPY tini /sbin/tini
ENTRYPOINT ["/docker-entrypoint.sh"] ENTRYPOINT ["/docker-entrypoint.sh"]
CMD /usr/bin/rspamd -f -u _rspamd -g _rspamd CMD ["/usr/bin/rspamd", "-f", "-u", "_rspamd", "-g", "_rspamd"]

View File

@ -2,4 +2,4 @@
chown -R _rspamd:_rspamd /var/lib/rspamd chown -R _rspamd:_rspamd /var/lib/rspamd
exec "$@" exec /sbin/tini -- "$@"

Binary file not shown.

View File

@ -28,8 +28,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
RUN mkdir /usr/share/doc/sogo \ RUN mkdir /usr/share/doc/sogo \
&& touch /usr/share/doc/sogo/empty.sh \ && touch /usr/share/doc/sogo/empty.sh \
&& apt-key adv --keyserver sks.labs.nic.cz --recv-key A04BE668 \ && apt-key adv --keyserver keys.gnupg.net --recv-key 0x810273C4 \
&& echo "deb http://www.axis.cz/linux/debian stretch sogo-v3" > /etc/apt/sources.list.d/sogo.list \ && echo "deb http://packages.inverse.ca/SOGo/nightly/3/debian/ stretch stretch" > /etc/apt/sources.list.d/sogo.list \
&& apt-get update && apt-get install -y --force-yes \ && apt-get update && apt-get install -y --force-yes \
sogo \ sogo \
sogo-activesync \ sogo-activesync \

View File

@ -1,5 +1,6 @@
[supervisord] [supervisord]
nodaemon=true nodaemon=true
user=root
[program:syslog-ng] [program:syslog-ng]
command=/usr/sbin/syslog-ng --foreground --no-caps command=/usr/sbin/syslog-ng --foreground --no-caps
@ -32,12 +33,3 @@ priority=3
startretries=10 startretries=10
autorestart=true autorestart=true
stopwaitsecs=120 stopwaitsecs=120
[inet_http_server]
port=9191
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=http://localhost:9191

View File

@ -0,0 +1,33 @@
FROM alpine:3.6
LABEL maintainer "André Peters <andre.peters@servercow.de>"
# Installation
RUN apk add --update \
&& apk add --no-cache nagios-plugins-smtp \
nagios-plugins-tcp \
nagios-plugins-http \
nagios-plugins-ping \
curl \
bash \
jq \
fcgi \
nagios-plugins-mysql \
nagios-plugins-dns \
nagios-plugins-disk \
bind-tools \
redis \
perl \
perl-io-socket-ssl \
perl-socket \
perl-socket6 \
perl-mime-lite \
perl-term-readkey \
tini \
&& curl https://raw.githubusercontent.com/mludvig/smtp-cli/v3.8/smtp-cli -o /smtp-cli \
&& chmod +x smtp-cli
COPY watchdog.sh /watchdog.sh
ENTRYPOINT ["/sbin/tini", "-g", "--"]
# Less verbose
CMD /watchdog.sh 2> /dev/null

Binary file not shown.

View File

@ -0,0 +1,381 @@
#!/bin/bash
trap "exit" INT TERM
trap "kill 0" EXIT
# Prepare
BACKGROUND_TASKS=()
if [[ "${USE_WATCHDOG}" =~ ^([nN][oO]|[nN])+$ ]]; then
echo -e "$(date) - USE_WATCHDOG=n, skipping watchdog..."
sleep 365d
exec $(readlink -f "$0")
fi
# Checks pipe their corresponding container name in this pipe
if [[ ! -p /tmp/com_pipe ]]; then
mkfifo /tmp/com_pipe
fi
# Common functions
progress() {
SERVICE=${1}
TOTAL=${2}
CURRENT=${3}
DIFF=${4}
[[ -z ${DIFF} ]] && DIFF=0
[[ -z ${TOTAL} || -z ${CURRENT} ]] && return
[[ ${CURRENT} -gt ${TOTAL} ]] && return
[[ ${CURRENT} -lt 0 ]] && CURRENT=0
PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} ))
echo -ne "$(date) - ${SERVICE} health level: \e[7m${PERCENT}%\e[0m (${CURRENT}/${TOTAL}), health trend: "
[[ ${DIFF} =~ ^-[1-9] ]] && echo -en '[\e[41m \e[0m] ' || echo -en '[\e[42m \e[0m] '
echo "(${DIFF})"
}
log_to_redis() {
redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}")\"}"
}
function mail_error() {
[[ -z ${1} ]] && return 1
[[ -z ${2} ]] && return 2
RCPT_DOMAIN=$(echo ${1} | awk -F @ {'print $NF'})
RCPT_MX=$(dig +short ${RCPT_DOMAIN} mx | sort -n | awk '{print $2; exit}')
if [[ -z ${RCPT_MX} ]]; then
log_to_redis "Cannot determine MX for ${1}, skipping email notification..."
echo "Cannot determine MX for ${1}"
return 1
fi
./smtp-cli --missing-modules-ok \
--subject="Watchdog: ${2} service hit the error rate limit" \
--body-plain="Service was restarted, please check your mailcow installation." \
--to=${1} \
--from="watchdog@${MAILCOW_HOSTNAME}" \
--server="${RCPT_MX}" \
--hello-host=${MAILCOW_HOSTNAME}
}
get_container_ip() {
# ${1} is container
CONTAINER_ID=
CONTAINER_IP=
LOOP_C=1
until [[ ${CONTAINER_IP} =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] || [[ ${LOOP_C} -gt 5 ]]; do
sleep 1
CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${1}\")) | .id")
if [[ ! -z ${CONTAINER_ID} ]]; then
CONTAINER_IP=$(curl --silent http://dockerapi:8080/containers/${CONTAINER_ID}/json | jq -r '.NetworkSettings.Networks[].IPAddress')
fi
LOOP_C=$((LOOP_C + 1))
done
[[ ${LOOP_C} -gt 5 ]] && echo 240.0.0.0 || echo ${CONTAINER_IP}
}
# Check functions
nginx_checks() {
err_count=0
diff_c=0
THRESHOLD=16
# 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
host_ip=$(get_container_ip nginx-mailcow)
err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u / -p 8081 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "Nginx" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
done
return 1
}
mysql_checks() {
err_count=0
diff_c=0
THRESHOLD=12
# 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
host_ip=$(get_container_ip mysql-mailcow)
err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_mysql -H ${host_ip} -P 3306 -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_mysql_query -H ${host_ip} -P 3306 -u ${DBUSER} -p ${DBPASS} -d ${DBNAME} -q "SELECT COUNT(*) FROM information_schema.tables" 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "MySQL/MariaDB" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
done
return 1
}
sogo_checks() {
err_count=0
diff_c=0
THRESHOLD=20
# 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
host_ip=$(get_container_ip sogo-mailcow)
err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /WebServerResources/css/theme-default.css -p 9192 -R md-default-theme 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_http -4 -H ${host_ip} -u /SOGo.index/ -p 20000 -R "SOGo\sGroupware" 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "SOGo" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
done
return 1
}
postfix_checks() {
err_count=0
diff_c=0
THRESHOLD=16
# 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
host_ip=$(get_container_ip postfix-mailcow)
err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -f watchdog -C "RCPT TO:null@localhost" -C DATA -C . -R 250 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 589 -S 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "Postfix" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
done
return 1
}
dovecot_checks() {
err_count=0
diff_c=0
THRESHOLD=24
# 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
host_ip=$(get_container_ip dovecot-mailcow)
err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_smtp -4 -H ${host_ip} -p 24 -f "watchdog" -C "RCPT TO:<watchdog@invalid>" -L -R "User doesn't exist" 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 993 -S -e "OK " 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_imap -4 -H ${host_ip} -p 143 -e "OK " 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 10001 -e "VERSION" 1>&2; err_count=$(( ${err_count} + $? ))
/usr/lib/nagios/plugins/check_tcp -4 -H ${host_ip} -p 4190 -e "Dovecot ready" 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "Dovecot" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
done
return 1
}
phpfpm_checks() {
err_count=0
diff_c=0
THRESHOLD=10
# 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
host_ip=$(get_container_ip php-fpm-mailcow)
err_c_cur=${err_count}
cgi-fcgi -bind -connect ${host_ip}:9000 | grep "Content-type" 1>&2; err_count=$(( ${err_count} + ($? * 2)))
/usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "PHP-FPM" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
done
return 1
}
rspamd_checks() {
err_count=0
diff_c=0
THRESHOLD=10
# 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
host_ip=$(get_container_ip rspamd-mailcow)
err_c_cur=${err_count}
SCORE=$(curl --silent ${host_ip}:11333/scan -d '
To: null@localhost
From: watchdog@localhost
Empty
' | jq -rc .required_score)
if [[ ${SCORE} != "9999" ]]; then
echo "Rspamd settings check failed" 1>&2
err_count=$(( ${err_count} + 1))
else
echo "Rspamd settings check succeeded" 1>&2
fi
/usr/lib/nagios/plugins/check_ping -4 -H ${host_ip} -w 2000,10% -c 4000,100% -p2 1>&2; err_count=$(( ${err_count} + $? ))
[ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
[ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
progress "Rspamd" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
done
return 1
}
dns_checks() {
err_count=0
diff_c=0
THRESHOLD=28
# 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
host_ip=$(get_container_ip unbound-mailcow)
err_c_cur=${err_count}
/usr/lib/nagios/plugins/check_dns -H google.com 1>&2; err_count=$(( ${err_count} + ($? * 2)))
/usr/lib/nagios/plugins/check_dns -s ${host_ip} -H google.com 1>&2; err_count=$(( ${err_count} + ($? * 2)))
dig +dnssec org. @${host_ip} | grep -E 'flags:.+ad' 1>&2; err_count=$(( ${err_count} + ($? * 2)))
[ ${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 "Unbound" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
diff_c=0
sleep $(( ( RANDOM % 30 ) + 10 ))
done
return 1
}
# Create watchdog agents
(
while true; do
if ! nginx_checks; then
log_to_redis "Nginx hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "nginx-mailcow"
echo -e "\e[31m$(date) - Nginx hit error limit\e[0m"
echo nginx-mailcow > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! mysql_checks; then
log_to_redis "MySQL hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "mysql-mailcow"
echo -e "\e[31m$(date) - MySQL hit error limit\e[0m"
echo mysql-mailcow > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! phpfpm_checks; then
log_to_redis "PHP-FPM hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "php-fpm-mailcow"
echo -e "\e[31m$(date) - PHP-FPM hit error limit\e[0m"
echo php-fpm-mailcow > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! sogo_checks; then
log_to_redis "SOGo hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "sogo-mailcow"
echo -e "\e[31m$(date) - SOGo hit error limit\e[0m"
echo sogo-mailcow > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! postfix_checks; then
log_to_redis "Postfix hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "postfix-mailcow"
echo -e "\e[31m$(date) - Postfix hit error limit\e[0m"
echo postfix-mailcow > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! dovecot_checks; then
log_to_redis "Dovecot hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "dovecot-mailcow"
echo -e "\e[31m$(date) - Dovecot hit error limit\e[0m"
echo dovecot-mailcow > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! dns_checks; then
log_to_redis "Unbound hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "unbound-mailcow"
echo -e "\e[31m$(date) - Unbound hit error limit\e[0m"
#echo unbound-mailcow > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
(
while true; do
if ! rspamd_checks; then
log_to_redis "Rspamd hit error limit"
[[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "rspamd-mailcow"
echo -e "\e[31m$(date) - Rspamd hit error limit\e[0m"
echo rspamd-mailcow > /tmp/com_pipe
fi
done
) &
BACKGROUND_TASKS+=($!)
# Monitor watchdog agents, stop script when agents fails and wait for respawn by Docker (restart:always:n)
(
while true; do
for bg_task in ${BACKGROUND_TASKS[*]}; do
if ! kill -0 ${bg_task} 1>&2; then
echo "Worker ${bg_task} died, stopping watchdog and waiting for respawn..."
log_to_redis "Worker ${bg_task} died, stopping watchdog and waiting for respawn..."
kill -TERM 1
fi
sleep 10
done
done
) &
# Restart container when threshold limit reached
while true; do
CONTAINER_ID=
read com_pipe_answer </tmp/com_pipe
if [[ ${com_pipe_answer} =~ .+-mailcow ]]; then
kill -STOP ${BACKGROUND_TASKS[*]}
sleep 3
CONTAINER_ID=$(curl --silent http://dockerapi:8080/containers/json | jq -r ".[] | {name: .Config.Labels[\"com.docker.compose.service\"], id: .Id}" | jq -rc "select( .name | tostring | contains(\"${com_pipe_answer}\")) | .id")
if [[ ! -z ${CONTAINER_ID} ]]; then
log_to_redis "Sending restart command to ${CONTAINER_ID}..."
echo "Sending restart command to ${CONTAINER_ID}..."
curl --silent -XPOST http://dockerapi:8080/containers/${CONTAINER_ID}/restart
fi
echo "Wait for restarted container to settle and continue watching..."
sleep 30s
kill -CONT ${BACKGROUND_TASKS[*]}
kill -USR1 ${BACKGROUND_TASKS[*]}
fi
done

View File

@ -0,0 +1,186 @@
map $http_x_forwarded_proto $client_req_scheme_nc {
default $scheme;
https https;
}
server {
include /etc/nginx/conf.d/listen_ssl.active;
include /etc/nginx/mime.types;
charset utf-8;
override_charset on;
ssl on;
ssl_certificate /etc/ssl/mail/cert.pem;
ssl_certificate_key /etc/ssl/mail/key.pem;
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
server_name NC_SERVER_SUB;
root /web/nextcloud/;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location = /.well-known/carddav {
return 301 $client_req_scheme_nc://$host/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $client_req_scheme_nc://$host/remote.php/dav;
}
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
set_real_ip_from 172.22.1.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location / {
rewrite ^ /index.php$uri;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all;
}
location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
#Avoid sending the security headers twice
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass phpfpm:9000;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
}
location ~ ^/(?:updater|ocs-provider)(?:$|/) {
try_files $uri/ =404;
index index.php;
}
location ~ \.(?:css|js|woff|svg|gif)$ {
try_files $uri /index.php$uri$is_args$args;
add_header Cache-Control "public, max-age=15778463";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
access_log off;
}
location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ {
try_files $uri /index.php$uri$is_args$args;
access_log off;
}
}
server {
include /etc/nginx/conf.d/listen_ssl.active;
include /etc/nginx/mime.types;
charset utf-8;
override_charset on;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
server_name NC_SERVER_SUB;
root /web/nextcloud/;
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
location = /.well-known/carddav {
return 301 $client_req_scheme_nc://$host/remote.php/dav;
}
location = /.well-known/caldav {
return 301 $client_req_scheme_nc://$host/remote.php/dav;
}
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
set_real_ip_from 172.22.1.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
location / {
rewrite ^ /index.php$uri;
}
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all;
}
location ~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass phpfpm:9000;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
}
location ~ ^/(?:updater|ocs-provider)(?:$|/) {
try_files $uri/ =404;
index index.php;
}
location ~ \.(?:css|js|woff|svg|gif)$ {
try_files $uri /index.php$uri$is_args$args;
add_header Cache-Control "public, max-age=15778463";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
access_log off;
}
location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ {
try_files $uri /index.php$uri$is_args$args;
access_log off;
}
}

View File

@ -0,0 +1,2 @@
#!/bin/bash
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ ${@}

View File

@ -0,0 +1,41 @@
location ^~ /nextcloud {
location /nextcloud {
rewrite ^ /nextcloud/index.php$uri;
}
location ~ ^/nextcloud/(?:build|tests|config|lib|3rdparty|templates|data)/ {
deny all;
}
location ~ ^/nextcloud/(?:\.|autotest|occ|issue|indie|db_|console) {
deny all;
}
location ~ ^/nextcloud/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\.php(?:$|/) {
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTPS on;
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass phpfpm:9000;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
}
location ~ ^/nextcloud/(?:updater|ocs-provider)(?:$|/) {
try_files $uri/ =404;
index index.php;
}
location ~ \.(?:css|js|woff|svg|gif)$ {
try_files $uri /nextcloud/index.php$uri$is_args$args;
add_header Cache-Control "public, max-age=15778463";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
access_log off;
}
location ~ \.(?:png|html|ttf|ico|jpg|jpeg)$ {
try_files $uri /nextcloud/index.php$uri$is_args$args;
access_log off;
}
}

View File

@ -1,9 +1,6 @@
server_tokens off; server_tokens off;
# includes to http {
proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g; proxy_cache_path /tmp levels=1:2 keys_zone=sogo:10m inactive=24h max_size=1g;
server_names_hash_bucket_size 64; server_names_hash_bucket_size 64;
# }
map $http_x_forwarded_proto $client_req_scheme { map $http_x_forwarded_proto $client_req_scheme {
default $scheme; default $scheme;
@ -11,7 +8,174 @@ map $http_x_forwarded_proto $client_req_scheme {
} }
server { server {
include /etc/nginx/conf.d/listen_ssl.active; include /etc/nginx/mime.types;
charset utf-8;
override_charset on;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
index index.php index.html;
include /etc/nginx/conf.d/listen_plain.active;
include /etc/nginx/conf.d/server_name.active;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
absolute_redirect off;
root /web;
location ~ ^/api/v1/(.*)$ {
try_files $uri $uri/ /json_api.php?query=$1;
}
location ^~ /.well-known/acme-challenge/ {
allow all;
default_type "text/plain";
}
# If behind reverse proxy, forwards the correct IP
set_real_ip_from 172.22.1.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;
location ^~ /principals {
return 301 /SOGo/dav;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9000;
fastcgi_index index.php;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PHP_VALUE "max_execution_time = 1200
max_input_time = 1200
memory_limit = 64M";
fastcgi_read_timeout 1200;
}
location /rspamd/ {
proxy_pass http://172.22.1.253:11334/;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
}
location ~* ^/Autodiscover/Autodiscover.xml {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9000;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files /autodiscover.php =404;
}
location ~* ^/Autodiscover/Autodiscover.json {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9000;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files /autodiscover-json.php =404;
}
location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9000;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files /autoconfig.php =404;
}
location ^~ /Microsoft-Server-ActiveSync {
proxy_pass http://172.22.1.252:20000/SOGo/Microsoft-Server-ActiveSync;
proxy_connect_timeout 1000;
proxy_next_upstream timeout error;
proxy_send_timeout 1000;
proxy_read_timeout 1000;
proxy_buffer_size 8k;
proxy_buffers 4 32k;
proxy_temp_file_write_size 64k;
proxy_busy_buffers_size 64k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header x-webobjects-server-protocol HTTP/1.0;
proxy_set_header x-webobjects-remote-host $remote_addr;
proxy_set_header x-webobjects-server-name $server_name;
proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host;
proxy_set_header x-webobjects-server-port $server_port;
client_body_buffer_size 128k;
client_max_body_size 0;
}
location ^~ /SOGo {
proxy_pass http://172.22.1.252:20000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header x-webobjects-server-protocol HTTP/1.0;
proxy_set_header x-webobjects-remote-host $remote_addr;
proxy_set_header x-webobjects-server-name $server_name;
proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host;
proxy_set_header x-webobjects-server-port $server_port;
client_body_buffer_size 128k;
client_max_body_size 0;
break;
}
location /SOGo.woa/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $http_host;
proxy_cache sogo;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
#alias /usr/lib/GNUstep/SOGo/WebServerResources/;
allow all;
}
location /.woa/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $http_host;
proxy_cache sogo;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
#alias /usr/lib/GNUstep/SOGo/WebServerResources/;
allow all;
}
location /SOGo/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $http_host;
proxy_cache sogo;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
#alias /usr/lib/GNUstep/SOGo/WebServerResources/;
allow all;
}
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ {
proxy_pass http://172.22.1.252:9192/$1.SOGo/Resources/$2;
proxy_set_header Host $http_host;
proxy_cache sogo;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
#alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
}
include /etc/nginx/conf.d/site.*.custom;
}
server {
include /etc/nginx/mime.types; include /etc/nginx/mime.types;
charset utf-8; charset utf-8;
override_charset on; override_charset on;
@ -22,14 +186,22 @@ server {
ssl_protocols TLSv1.2; ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains";
ssl_session_cache shared:SSL:50m; ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d; ssl_session_timeout 1d;
ssl_session_tickets off; ssl_session_tickets off;
add_header Strict-Transport-Security max-age=15768000;
add_header Strict-Transport-Security "max-age=15768000; includeSubDomains";
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header X-Robots-Tag none;
add_header X-Download-Options noopen;
add_header X-Permitted-Cross-Domain-Policies none;
index index.php index.html; index index.php index.html;
include /etc/nginx/conf.d/listen_ssl.active;
include /etc/nginx/conf.d/server_name.active; include /etc/nginx/conf.d/server_name.active;
error_log /var/log/nginx/error.log; error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log; access_log /var/log/nginx/access.log;
absolute_redirect off; absolute_redirect off;
@ -86,151 +258,12 @@ server {
try_files /autodiscover.php =404; try_files /autodiscover.php =404;
} }
location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml { location ~* ^/Autodiscover/Autodiscover.json {
fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9000; fastcgi_pass phpfpm:9000;
include /etc/nginx/fastcgi_params; include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files /autoconfig.php =404; try_files /autodiscover-json.php =404;
}
location ^~ /Microsoft-Server-ActiveSync {
proxy_pass http://172.22.1.252:20000/SOGo/Microsoft-Server-ActiveSync;
proxy_connect_timeout 1000;
proxy_next_upstream timeout error;
proxy_send_timeout 1000;
proxy_read_timeout 1000;
proxy_buffer_size 8k;
proxy_buffers 4 32k;
proxy_temp_file_write_size 64k;
proxy_busy_buffers_size 64k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header x-webobjects-server-protocol HTTP/1.0;
proxy_set_header x-webobjects-remote-host $remote_addr;
proxy_set_header x-webobjects-server-name $server_name;
proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host;
proxy_set_header x-webobjects-server-port $server_port;
client_body_buffer_size 128k;
client_max_body_size 0;
}
location ^~ /SOGo {
proxy_pass http://172.22.1.252:20000;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header x-webobjects-server-protocol HTTP/1.0;
proxy_set_header x-webobjects-remote-host $remote_addr;
proxy_set_header x-webobjects-server-name $server_name;
proxy_set_header x-webobjects-server-url $client_req_scheme://$http_host;
proxy_set_header x-webobjects-server-port $server_port;
client_body_buffer_size 128k;
client_max_body_size 0;
break;
}
location /SOGo.woa/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $http_host;
proxy_cache sogo;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
#alias /usr/lib/GNUstep/SOGo/WebServerResources/;
allow all;
}
location /.woa/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $http_host;
proxy_cache sogo;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
#alias /usr/lib/GNUstep/SOGo/WebServerResources/;
allow all;
}
location /SOGo/WebServerResources/ {
proxy_pass http://172.22.1.252:9192/WebServerResources/;
proxy_set_header Host $http_host;
proxy_cache sogo;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
#alias /usr/lib/GNUstep/SOGo/WebServerResources/;
allow all;
}
location (^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\.(jpg|png|gif|css|js)$ {
proxy_pass http://172.22.1.252:9192/$1.SOGo/Resources/$2;
proxy_set_header Host $http_host;
proxy_cache sogo;
proxy_cache_valid 200 1d;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
#alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
}
}
server {
include /etc/nginx/conf.d/listen_plain.active;
include /etc/nginx/mime.types;
charset utf-8;
override_charset on;
index index.php index.html;
include /etc/nginx/conf.d/server_name.active;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
absolute_redirect off;
root /web;
location ~ ^/api/v1/(.*)$ {
try_files $uri $uri/ /json_api.php?query=$1;
}
location ^~ /.well-known/acme-challenge/ {
allow all;
default_type "text/plain";
}
# If behind reverse proxy, forwards the correct IP
set_real_ip_from 172.22.1.1;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
rewrite ^/.well-known/caldav$ /SOGo/dav/ permanent;
rewrite ^/.well-known/carddav$ /SOGo/dav/ permanent;
location ^~ /principals {
return 301 /SOGo/dav;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9000;
fastcgi_index index.php;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PHP_VALUE "max_execution_time = 1200
max_input_time = 1200
memory_limit = 64M";
fastcgi_read_timeout 1200;
}
location /rspamd/ {
proxy_pass http://172.22.1.253:11334/;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
}
location ~* ^/Autodiscover/Autodiscover.xml {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass phpfpm:9000;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
try_files /autodiscover.php =404;
} }
location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml { location ~ /(?:m|M)ail/(?:c|C)onfig-v1.1.xml {
@ -317,4 +350,5 @@ server {
#alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2; #alias /usr/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
} }
include /etc/nginx/conf.d/site.*.custom;
} }

View File

@ -62,7 +62,7 @@ smtpd_sasl_authenticated_header = yes
smtpd_sasl_path = inet:dovecot:10001 smtpd_sasl_path = inet:dovecot:10001
smtpd_sasl_type = dovecot smtpd_sasl_type = dovecot
smtpd_sender_login_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf smtpd_sender_login_maps = proxy:mysql:/opt/postfix/conf/sql/mysql_virtual_sender_acl.cf
smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, permit_mynetworks, reject_sender_login_mismatch, permit_sasl_authenticated, reject_unlisted_sender, reject_unknown_sender_domain smtpd_sender_restrictions = reject_authenticated_sender_login_mismatch, permit_mynetworks, permit_sasl_authenticated, reject_unlisted_sender, reject_unknown_sender_domain
smtpd_soft_error_limit = 3 smtpd_soft_error_limit = 3
smtpd_tls_auth_only = yes smtpd_tls_auth_only = yes
smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem smtpd_tls_dh1024_param_file = /etc/ssl/mail/dhparams.pem

View File

@ -44,6 +44,8 @@ anvil unix - - n - 1 anvil
scache unix - - n - 1 scache scache unix - - n - 1 scache
maildrop unix - n n - - pipe flags=DRhu maildrop unix - n n - - pipe flags=DRhu
user=vmail argv=/usr/bin/maildrop -d ${recipient} user=vmail argv=/usr/bin/maildrop -d ${recipient}
# start zeyple
zeyple unix - n n - - pipe zeyple unix - n n - - pipe
user=zeyple argv=/usr/local/bin/zeyple.py ${recipient} user=zeyple argv=/usr/local/bin/zeyple.py ${recipient}
127.0.0.1:10026 inet n - n - 10 smtpd 127.0.0.1:10026 inet n - n - 10 smtpd
@ -55,5 +57,34 @@ zeyple unix - n n - - pipe
-o smtpd_recipient_restrictions=permit_mynetworks,reject -o smtpd_recipient_restrictions=permit_mynetworks,reject
-o mynetworks=127.0.0.0/8 -o mynetworks=127.0.0.0/8
-o smtpd_authorized_xforward_hosts=127.0.0.0/8 -o smtpd_authorized_xforward_hosts=127.0.0.0/8
# end zeyple
# start whitelist_fwd
127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh 127.0.0.1:10027 inet n n n - 0 spawn user=nobody argv=/usr/local/bin/whitelist_forwardinghosts.sh
# end whitelist_fwd
# start watchdog-specific
589 inet n - n - - smtpd
-o smtpd_client_restrictions=permit_mynetworks,reject
-o syslog_name=watchdog
-o syslog_facility=local7
-o smtpd_milters=
-o cleanup_service_name=watchdog_cleanup
-o non_smtpd_milters=
watchdog_cleanup unix n - n - 0 cleanup
-o syslog_name=watchdog
-o syslog_facility=local7
-o queue_service_name=watchdog_qmgr
watchdog_qmgr fifo n - n 300 1 qmgr
-o syslog_facility=local7
-o syslog_name=watchdog
-o rewrite_service_name=watchdog_rewrite
watchdog_rewrite unix - - n - - trivial-rewrite
-o syslog_facility=local7
-o syslog_name=watchdog
-o local_transport=watchdog_discard
watchdog_discard unix - - n - - discard
-o syslog_facility=local7
-o syslog_name=watchdog
# end watchdog-specific

View File

@ -28,17 +28,28 @@ function in_net($addr, $net) {
return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask); return substr($addr_bin, 0, $mask) == substr($net_bin, 0, $mask);
} }
try { if (isset($_GET['host'])) {
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) { try {
if (in_net($_GET['host'], $host)) { foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
echo '200 PERMIT'; if (in_net($_GET['host'], $host)) {
exit; echo '200 PERMIT';
exit;
}
}
echo '200 DUNNO';
}
catch (RedisException $e) {
echo '200 DUNNO';
exit;
}
} else {
try {
foreach ($redis->hGetAll('WHITELISTED_FWD_HOST') as $host => $source) {
echo $host . "\n";
} }
} }
echo '200 DUNNO'; catch (RedisException $e) {
} exit;
catch (RedisException $e) { }
echo '200 DUNNO';
exit;
} }
?> ?>

View File

@ -0,0 +1,2 @@
<html>
</html>

View File

@ -97,6 +97,18 @@ function ucl_rcpts($object, $type) {
} }
?> ?>
settings { settings {
watchdog {
priority = 10;
rcpt = "/null@localhost/i";
from = "/watchdog@localhost/i";
apply "default" {
actions {
reject = 9999.0;
greylist = 9998.0;
"add header" = 9997.0;
}
}
}
<?php <?php
/* /*

View File

@ -2,7 +2,7 @@ rules {
DKIM_FAIL { DKIM_FAIL {
action = "add header"; action = "add header";
expression = "R_DKIM_REJECT & !MAILLIST & !MAILCOW_WHITE & !MAILCOW_BLACK"; expression = "R_DKIM_REJECT & !MAILLIST & !MAILCOW_WHITE & !MAILCOW_BLACK";
require_action = ["no action", "greylist"]; require_action = ["no action", "greylist", "soft reject"];
} }
VIRUS_FOUND { VIRUS_FOUND {
action = "reject"; action = "reject";
@ -19,4 +19,9 @@ rules {
expression = "WHITELISTED_FWD_HOST"; expression = "WHITELISTED_FWD_HOST";
require_action = ["greylist", "soft reject"]; require_action = ["greylist", "soft reject"];
} }
ADD_UNAUTH_SUBJ {
action = "rewrite subject";
subject = "[Unauth] %s";
expression = "SPOOFED_SENDER";
}
} }

View File

@ -0,0 +1 @@
whitelisted_ip = "http://172.22.1.251:8081/forwardinghosts.php";

View File

@ -34,3 +34,7 @@ group "MX" {
one_shot = "true"; one_shot = "true";
} }
} }
symbol "SPOOFED_SENDER" {
description = "Sender is not authenticated but part of mailcow managed domains";
score = 1.0;
}

View File

@ -1,22 +1,29 @@
RCPT_MAILCOW_DOMAIN { RCPT_MAILCOW_DOMAIN {
type = "rcpt"; type = "rcpt";
filter = "email:domain" filter = "email:domain";
map = "redis://DOMAIN_MAP" map = "redis://DOMAIN_MAP";
} }
RCPT_WANTS_SUBJECT_TAG { RCPT_WANTS_SUBJECT_TAG {
type = "rcpt"; type = "rcpt";
filter = "email:addr" filter = "email:addr"
map = "redis://RCPT_WANTS_SUBJECT_TAG" map = "redis://RCPT_WANTS_SUBJECT_TAG";
} }
WHITELISTED_FWD_HOST { WHITELISTED_FWD_HOST {
type = "ip"; type = "ip";
map = "redis://WHITELISTED_FWD_HOST" map = "redis://WHITELISTED_FWD_HOST";
} }
KEEP_SPAM { KEEP_SPAM {
type = "ip"; type = "ip";
map = "redis://KEEP_SPAM" map = "redis://KEEP_SPAM";
action = "accept"; action = "accept";
} }
SPOOFED_SENDER {
type = "rcpt";
filter = "email:domain:tld";
map = "redis://DOMAIN_MAP";
require_symbols = "AUTH_NA | !RCVD_VIA_SMTP_AUTH";
}

View File

@ -1,5 +1 @@
# rspamd.conf.local # rspamd.conf.local
history_redis {}
worker "log_helper" {
count = 1;
}

View File

@ -58,6 +58,9 @@ rspamd_config:register_symbol({
local redis_params = rspamd_parse_redis_server('dyn_rl') local redis_params = rspamd_parse_redis_server('dyn_rl')
local rspamd_logger = require "rspamd_logger" local rspamd_logger = require "rspamd_logger"
local envfrom = task:get_from(1) local envfrom = task:get_from(1)
if not envfrom then
return false
end
local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case local env_from_domain = envfrom[1].domain:lower() -- get smtp from domain in lower case
local env_from_addr = envfrom[1].addr:lower() -- get smtp from addr in lower case local env_from_addr = envfrom[1].addr:lower() -- get smtp from addr in lower case
@ -108,3 +111,15 @@ rspamd_config:register_symbol({
end, end,
priority = 20 priority = 20
}) })
rspamd_config:register_symbol({
name = 'NO_LOG_STAT',
type = 'postfilter',
callback = function(task)
local from = task:get_header('From')
if from and (from == 'monitoring-system@everycloudtech.us' or from == 'watchdog@localhost') then
task:set_flag('no_log')
task:set_flag('no_stat')
end
end
})

View File

@ -0,0 +1,14 @@
rates {
# Format: "1 / 1h" or "20 / 1m" etc. - global ratelimits are disabled by default
to = "100 / 1s";
to_ip = "100 / 1s";
to_ip_from = "100 / 1s";
bounce_to = "100 / 1s";
bounce_to_ip = "100 / 1s";
user = "100 / 1s";
}
whitelisted_rcpts = "postmaster,mailer-daemon";
max_rcpt = 5;
custom_keywords = "/etc/rspamd/custom/ratelimit.lua";
user_keywords = ["user", "customrl"];
dynamic_rates = { customrl = "customrl"}

View File

@ -43,8 +43,8 @@
SOGoInternalSyncInterval = 30; SOGoInternalSyncInterval = 30;
SOGoMaximumSyncInterval = 354; SOGoMaximumSyncInterval = 354;
SOGoMaximumSyncWindowSize = 0; SOGoMaximumSyncWindowSize = 100;
SOGoMaximumSyncResponseSize = 1024; SOGoMaximumSyncResponseSize = 5172;
WOWatchDogRequestTimeout = 10; WOWatchDogRequestTimeout = 10;
WOListenQueueSize = 300; WOListenQueueSize = 300;

View File

@ -19,6 +19,7 @@ server:
private-address: 169.254.0.0/16 private-address: 169.254.0.0/16
private-address: fd00::/8 private-address: fd00::/8
private-address: fe80::/10 private-address: fe80::/10
private-address: fd4d:6169:6c63:6f77::/64
root-hints: "/etc/unbound/root.hints" root-hints: "/etc/unbound/root.hints"
hide-identity: yes hide-identity: yes
hide-version: yes hide-version: yes

View File

@ -24,6 +24,7 @@ $tfa_data = get_tfa();
<li role="presentation"><a href="#tab-sogo-logs" aria-controls="tab-sogo-logs" role="tab" data-toggle="tab">SOGo</a></li> <li role="presentation"><a href="#tab-sogo-logs" aria-controls="tab-sogo-logs" role="tab" data-toggle="tab">SOGo</a></li>
<li role="presentation"><a href="#tab-fail2ban-logs" aria-controls="tab-fail2ban-logs" role="tab" data-toggle="tab">Fail2ban</a></li> <li role="presentation"><a href="#tab-fail2ban-logs" aria-controls="tab-fail2ban-logs" role="tab" data-toggle="tab">Fail2ban</a></li>
<li role="presentation"><a href="#tab-rspamd-history" aria-controls="tab-rspamd-history" role="tab" data-toggle="tab">Rspamd</a></li> <li role="presentation"><a href="#tab-rspamd-history" aria-controls="tab-rspamd-history" role="tab" data-toggle="tab">Rspamd</a></li>
<li role="presentation"><a href="#tab-autodiscover-logs" aria-controls="tab-autodiscover-logs" role="tab" data-toggle="tab">Autodiscover</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -128,6 +129,7 @@ $tfa_data = get_tfa();
<a href="#fwdhosts" class="list-group-item"><?=$lang['admin']['forwarding_hosts'];?></a> <a href="#fwdhosts" class="list-group-item"><?=$lang['admin']['forwarding_hosts'];?></a>
<a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a> <a href="#f2bparams" class="list-group-item"><?=$lang['admin']['f2b_parameters'];?></a>
<a href="#relayhosts" class="list-group-item">Relayhosts</a> <a href="#relayhosts" class="list-group-item">Relayhosts</a>
<a href="#customize" class="list-group-item"><?=$lang['admin']['customize'];?></a>
<a href="#top" class="list-group-item" style="border-top:1px dashed #dadada"> <?=$lang['admin']['to_top'];?></a> <a href="#top" class="list-group-item" style="border-top:1px dashed #dadada"> <?=$lang['admin']['to_top'];?></a>
</div> </div>
</div> </div>
@ -149,10 +151,10 @@ $tfa_data = get_tfa();
<div class="row"> <div class="row">
<div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" /></div> <div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$domain;?>" /></div>
<div class="col-xs-2"> <div class="col-xs-2">
<p>Domain: <strong><?=htmlspecialchars($domain);?></strong><br /> <p>Domain: <strong><?=htmlspecialchars($domain);?></strong>
<span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span> <p><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
<span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span> <p><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
<span class="label label-info"><?=$dkim['length'];?> bit</span> <p><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
</p> </p>
</div> </div>
<div class="col-xs-9"> <div class="col-xs-9">
@ -179,10 +181,10 @@ $tfa_data = get_tfa();
<div class="row"> <div class="row">
<div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" /></div> <div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$alias_domain;?>" /></div>
<div class="col-xs-1 col-xs-offset-1"> <div class="col-xs-1 col-xs-offset-1">
<p><small> Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong><br /></small> <p><small> Alias-Domain: <strong><?=htmlspecialchars($alias_domain);?></strong></small>
<span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span> <p><span class="label label-success"><?=$lang['admin']['dkim_key_valid'];?></span></p>
<span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span> <p><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
<span class="label label-info"><?=$dkim['length'];?> bit</span> <p><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
</p> </p>
</div> </div>
<div class="col-xs-9"> <div class="col-xs-9">
@ -211,10 +213,10 @@ $tfa_data = get_tfa();
<div class="row"> <div class="row">
<div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$blind;?>" /></div> <div class="col-xs-1"><input type="checkbox" data-id="dkim" name="multi_select" value="<?=$blind;?>" /></div>
<div class="col-xs-2"> <div class="col-xs-2">
<p>Domain: <strong><?=htmlspecialchars($blind);?></strong><br /> <p>Domain: <strong><?=htmlspecialchars($blind);?></strong>
<span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span> <p><span class="label label-warning"><?=$lang['admin']['dkim_key_unused'];?></span></p>
<span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span> <p><span class="label label-primary">Selector '<?=$dkim['dkim_selector'];?>'</span></p>
<span class="label label-info"><?=$dkim['length'];?> bit</span> <p><span class="label label-info"><?=$dkim['length'];?> bit</span></p>
</p> </p>
</div> </div>
<div class="col-xs-9"> <div class="col-xs-9">
@ -259,9 +261,7 @@ $tfa_data = get_tfa();
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="private_key_file"><?=$lang['admin']['private_key'];?>:</label> <label for="private_key_file"><?=$lang['admin']['private_key'];?>:</label>
<textarea class="form-control" rows="5" name="private_key_file" id="private_key_file" required placeholder="-----BEGIN RSA PRIVATE KEY----- <textarea class="form-control" rows="5" name="private_key_file" id="private_key_file" required placeholder="-----BEGIN RSA KEY-----"></textarea>
XYZ
-----END RSA PRIVATE KEY-----"></textarea>
</div> </div>
<button class="btn btn-default" id="add_item" data-id="dkim_import" data-api-url='add/dkim_import' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['import'];?></button> <button class="btn btn-default" id="add_item" data-id="dkim_import" data-api-url='add/dkim_import' data-api-attr='{}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['admin']['import'];?></button>
</form> </form>
@ -375,6 +375,82 @@ XYZ
</form> </form>
</div> </div>
</div> </div>
<span class="anchor" id="customize"></span>
<div class="panel panel-default">
<div class="panel-heading"><?=$lang['admin']['customize'];?></div>
<div class="panel-body">
<legend><?=$lang['admin']['change_logo'];?></legend>
<p class="help-block"><?=$lang['admin']['logo_info'];?></p>
<form class="form-inline" role="form" method="post" enctype="multipart/form-data">
<p>
<input type="file" name="main_logo" class="filestyle" data-buttonName="btn-default" data-buttonText="Select" accept="image/gif, image/jpeg, image/pjpeg, image/x-png, image/png, image/svg+xml">
<button name="submit_main_logo" type="submit" class="btn btn-success"><span class="glyphicon glyphicon-cloud-upload"></span> <?=$lang['admin']['upload'];?></button>
</p>
</form>
<?php
if ($main_logo = customize('get', 'main_logo')):
$specs = customize('get', 'main_logo_specs');
?>
<div class="row">
<div class="col-sm-3">
<div class="thumbnail">
<img class="img-thumbnail" src="<?=$main_logo;?>" alt="mailcow logo">
<div class="caption">
<span class="label label-info"><?=$specs['geometry']['width'];?>x<?=$specs['geometry']['height'];?> px</span>
<span class="label label-info"><?=$specs['mimetype'];?></span>
<span class="label label-info"><?=$specs['fileSize'];?></span>
</div>
</div>
<hr>
<form class="form-inline" role="form" method="post">
<p><button name="reset_main_logo" type="submit" class="btn btn-xs btn-default"><?=$lang['admin']['reset_default'];?></button></p>
</form>
</div>
</div>
<?php
endif;
?>
<legend><?=$lang['admin']['app_links'];?></legend>
<p class="help-block"><?=$lang['admin']['merged_vars_hint'];?></p>
<form class="form-inline" data-id="app_links" role="form" method="post">
<table class="table table-condensed" style="width:1%;white-space: nowrap;" id="app_link_table">
<tr>
<th><?=$lang['admin']['app_name'];?></th>
<th><?=$lang['admin']['link'];?></th>
<th>&nbsp;</th>
</tr>
<?php
$app_links = customize('get', 'app_links');
foreach ($app_links as $row) {
foreach ($row as $key => $val):
?>
<tr>
<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required value="<?=$key;?>"></td>
<td><input class="input-sm form-control" data-id="app_links" type="text" name="href" required value="<?=$val;?>"></td>
<td><a href="#" role="button" class="btn btn-xs btn-default" type="button"><?=$lang['admin']['remove_row'];?></a></td>
</tr>
<?php
endforeach;
}
foreach ($MAILCOW_APPS as $app):
?>
<tr>
<td><input class="input-sm form-control" value="<?=htmlspecialchars($app['name']);?>" disabled></td>
<td><input class="input-sm form-control" value="<?=htmlspecialchars($app['link']);?>" disabled></td>
<td>&nbsp;</td>
</tr>
<?php
endforeach;
?>
</table>
<div class="btn-group">
<button class="btn btn-success" id="edit_selected" data-item="admin" data-id="app_links" data-reload="no" data-api-url='edit/app_links' data-api-attr='{}' href="#"><?=$lang['admin']['save'];?></button>
<button class="btn btn-default" type="button" id="add_app_link_row"><?=$lang['admin']['add_row'];?></button>
</div>
</form>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -469,6 +545,24 @@ XYZ
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="tab-autodiscover-logs">
<div class="panel panel-default">
<div class="panel-heading">Autodiscover
<div class="btn-group pull-right">
<a class="btn btn-xs btn-default dropdown-toggle" data-toggle="dropdown" href="#"><?=$lang['admin']['action'];?> <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#" id="refresh_autodiscover_log"><?=$lang['admin']['refresh'];?></a></li>
</ul>
</div>
</div>
<div class="panel-body">
<div class="table-responsive">
<table class="table table-striped table-condensed" id="autodiscover_log"></table>
</div>
</div>
</div>
</div>
</div> </div>
</div> <!-- /container --> </div> <!-- /container -->
<?php <?php

32
data/web/api.php 100644
View File

@ -0,0 +1,32 @@
<?php
set_time_limit (0);
$address = '0.0.0.0';
$port = 7777;
$con = 1;
$word = "";
$sock = socket_create(AF_INET, SOCK_STREAM, 0);
$bind = socket_bind($sock, $address, $port);
socket_listen($sock);
while ($con == 1)
{
$client = socket_accept($sock);
$input = socket_read($client, 2024);
if ($input == 'exit')
{
$close = socket_close($sock);
$con = 0;
}
if($con == 1)
{
$word .= $input;
}
}
echo $word;

View File

@ -0,0 +1,21 @@
<?php
require_once 'inc/vars.inc.php';
require_once 'inc/functions.inc.php';
$default_autodiscover_config = $autodiscover_config;
if(file_exists('inc/vars.local.inc.php')) {
include_once 'inc/vars.local.inc.php';
}
$autodiscover_config = array_merge($default_autodiscover_config, $autodiscover_config);
header('Content-type: application/json');
if ($_GET['Protocol'] == 'ActiveSync') {
echo '{"Protocol":"ActiveSync","Url":"' . $autodiscover_config['activesync']['url'] . '"}';
}
elseif ($_GET['Protocol'] == 'AutodiscoverV1') {
echo '{"Protocol":"AutodiscoverV1","Url":"https://' . $_SERVER['HTTP_HOST'] . '/autodiscover/autodiscover.xml"}';
}
else {
http_response_code(400);
echo '{"ErrorCode":"InvalidProtocol","ErrorMessage":"The given protocol value \u0027' . $_GET['Protocol'] . '\u0027 is invalid. Supported values are \u0027ActiveSync,AutodiscoverV1\u0027"}';
}
?>

View File

@ -15,14 +15,13 @@ error_reporting(0);
$data = trim(file_get_contents("php://input")); $data = trim(file_get_contents("php://input"));
// Desktop client needs IMAP, unless it's Outlook 2013 or higher on Windows if (strpos($data, 'autodiscover/outlook/responseschema') !== false) {
if (strpos($data, 'autodiscover/outlook/responseschema') !== false) { // desktop client
$autodiscover_config['autodiscoverType'] = 'imap'; $autodiscover_config['autodiscoverType'] = 'imap';
if ($autodiscover_config['useEASforOutlook'] == 'yes' && if ($autodiscover_config['useEASforOutlook'] == 'yes' &&
// Office for macOS does not support EAS // Office for macOS does not support EAS
strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') === false && strpos($_SERVER['HTTP_USER_AGENT'], 'Mac') === false &&
// Outlook 2013 (version 15) or higher // Outlook 2013 (version 15) or higher
preg_match('/(Outlook|Office).+1[5-9]\./', $_SERVER['HTTP_USER_AGENT']) preg_match('/(Outlook|Office).+15\./', $_SERVER['HTTP_USER_AGENT'])
) { ) {
$autodiscover_config['autodiscoverType'] = 'activesync'; $autodiscover_config['autodiscoverType'] = 'activesync';
} }
@ -39,7 +38,26 @@ $login_user = strtolower(trim($_SERVER['PHP_AUTH_USER']));
$login_role = check_login($login_user, $_SERVER['PHP_AUTH_PW']); $login_role = check_login($login_user, $_SERVER['PHP_AUTH_PW']);
if (!isset($_SERVER['PHP_AUTH_USER']) OR $login_role !== "user") { if (!isset($_SERVER['PHP_AUTH_USER']) OR $login_role !== "user") {
header('WWW-Authenticate: Basic realm=""'); try {
$json = json_encode(
array(
"time" => time(),
"ua" => $_SERVER['HTTP_USER_AGENT'],
"user" => "none",
"service" => "Error: must be authenticated"
)
);
$redis->lPush('AUTODISCOVER_LOG', $json);
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
header('WWW-Authenticate: Basic realm="' . $_SERVER['HTTP_HOST'] . '"');
header('HTTP/1.0 401 Unauthorized'); header('HTTP/1.0 401 Unauthorized');
exit(0); exit(0);
} }
@ -52,6 +70,25 @@ else {
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006"> <Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/responseschema/2006">
<?php <?php
if(!$data) { if(!$data) {
try {
$json = json_encode(
array(
"time" => time(),
"ua" => $_SERVER['HTTP_USER_AGENT'],
"user" => $_SERVER['PHP_AUTH_USER'],
"service" => "Error: invalid or missing request data"
)
);
$redis->lPush('AUTODISCOVER_LOG', $json);
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
list($usec, $sec) = explode(' ', microtime()); list($usec, $sec) = explode(' ', microtime());
?> ?>
<Response> <Response>
@ -82,12 +119,30 @@ else {
die("Failed to determine name from SQL"); die("Failed to determine name from SQL");
} }
if (!empty($MailboxData['name'])) { if (!empty($MailboxData['name'])) {
$displayname = utf8_encode($MailboxData['name']); $displayname = $MailboxData['name'];
} }
else { else {
$displayname = $email; $displayname = $email;
} }
try {
$json = json_encode(
array(
"time" => time(),
"ua" => $_SERVER['HTTP_USER_AGENT'],
"user" => $_SERVER['PHP_AUTH_USER'],
"service" => $autodiscover_config['autodiscoverType']
)
);
$redis->lPush('AUTODISCOVER_LOG', $json);
$redis->lTrim('AUTODISCOVER_LOG', 0, 100);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
if ($autodiscover_config['autodiscoverType'] == 'imap') { if ($autodiscover_config['autodiscoverType'] == 'imap') {
?> ?>
<Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"> <Response xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
@ -121,13 +176,13 @@ else {
</Protocol> </Protocol>
<Protocol> <Protocol>
<Type>CalDAV</Type> <Type>CalDAV</Type>
<Server>https://<?=$autodiscover_config['caldav']['server'];?><?php if ($autodiscover_config['caldav']['port'] != 443) echo ':'.$autodiscover_config['caldav']['port']; ?>/SOGo/dav/<?=$email;?>/Calendar</Server> <Server>https://<?=$autodiscover_config['caldav']['server'];?><?php if ($autodiscover_config['caldav']['port'] != 443) echo ':'.$autodiscover_config['caldav']['port']; ?>/SOGo/dav/<?=$email;?>/</Server>
<DomainRequired>off</DomainRequired> <DomainRequired>off</DomainRequired>
<LoginName><?=$email;?></LoginName> <LoginName><?=$email;?></LoginName>
</Protocol> </Protocol>
<Protocol> <Protocol>
<Type>CardDAV</Type> <Type>CardDAV</Type>
<Server>https://<?=$autodiscover_config['carddav']['server'];?><?php if ($autodiscover_config['caldav']['port'] != 443) echo ':'.$autodiscover_config['carddav']['port']; ?>/SOGo/dav/<?=$email;?>/Contacts</Server> <Server>https://<?=$autodiscover_config['carddav']['server'];?><?php if ($autodiscover_config['caldav']['port'] != 443) echo ':'.$autodiscover_config['carddav']['port']; ?>/SOGo/dav/<?=$email;?>/</Server>
<DomainRequired>off</DomainRequired> <DomainRequired>off</DomainRequired>
<LoginName><?=$email;?></LoginName> <LoginName><?=$email;?></LoginName>
</Protocol> </Protocol>

View File

@ -53,3 +53,7 @@ body.modal-open {
top: 65px; top: 65px;
z-index: 1; z-index: 1;
} }
.thumbnail img {
min-height:100px;
height:100px;
}

View File

@ -83,6 +83,9 @@ body.modal-open {
overflow: inherit; overflow: inherit;
padding-right: inherit !important; padding-right: inherit !important;
} }
body {
font-size:11pt;
}
#mailcow-alert { #mailcow-alert {
position: fixed; position: fixed;
bottom: 8px; bottom: 8px;
@ -99,3 +102,12 @@ legend {
-o-user-select: none; -o-user-select: none;
user-select: none; user-select: none;
} }
.navbar .navbar-brand {
padding-top: 5px;
}
.navbar .navbar-brand img {
height: 40px;
}
.mailcow-logo img {
max-width: 250px;
}

View File

@ -457,6 +457,23 @@ if (isset($_SESSION['mailcow_cc_role'])) {
</select> </select>
</div> </div>
</div> </div>
<?php
$mailbox_acl = get_acl($mailbox);
?>
<div class="form-group">
<label class="control-label col-sm-2" for="sender_acl">ACL:</label>
<div class="col-sm-10">
<select multiple data-width="100%" style="width:100%" >
<?php
foreach ($mailbox_acl as $key => $val) {
?>
<option value="<?=$key;?>" <?=($val == 1) ? 'selected' : null;?>><?=$lang['edit'][$key];?></option>
<?php
}
?>
</select>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label> <label class="control-label col-sm-2" for="password"><?=$lang['edit']['password'];?></label>
<div class="col-sm-10"> <div class="col-sm-10">

View File

@ -5,36 +5,117 @@ if (!isset($_SESSION['mailcow_cc_role']) OR !in_array($_SESSION['mailcow_cc_role
echo "Not allowed." . PHP_EOL; echo "Not allowed." . PHP_EOL;
exit(); exit();
} }
if ($_GET['ACTION'] == "start") {
$request = xmlrpc_encode_request("supervisor.startProcess", 'bootstrap-sogo', array('encoding'=>'utf-8')); function docker($service_name, $action, $post_action = null, $post_fields = null) {
$context = stream_context_create(array('http' => array( $curl = curl_init();
'method' => "POST", curl_setopt($curl, CURLOPT_HTTPHEADER,array( 'Content-Type: application/json' ));
'header' => "Content-Length: " . strlen($request), switch($action) {
'content' => $request case 'get_id':
))); curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/json');
$file = @file_get_contents("http://sogo:9191/RPC2", false, $context) or die("Cannot connect to $remote_server:$listener_port"); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$response = xmlrpc_decode($file); curl_setopt($curl, CURLOPT_POST, 0);
if (isset($response['faultString'])) { $response = curl_exec($curl);
echo '<b><span class="pull-right text-warning">' . $response['faultString'] . '</span></b>'; if ($response === false) {
} $err = curl_error($curl);
else { curl_close($curl);
echo '<b><span class="pull-right text-success">OK</span></b>'; return $err;
}
else {
curl_close($curl);
$containers = json_decode($response, true);
if (!empty($containers)) {
foreach ($containers as $container) {
if ($container['Config']['Labels']['com.docker.compose.service'] == $service_name) {
return trim($container['Id']);
}
}
}
}
return false;
break;
case 'info':
$container_id = docker($service_name, 'get_id');
if (ctype_xdigit($container_id)) {
curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/json');
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 0);
$response = curl_exec($curl);
if ($response === false) {
$err = curl_error($curl);
curl_close($curl);
return $err;
}
else {
curl_close($curl);
if (empty($response)) {
return true;
}
else {
return json_decode($response, true);
}
}
}
else {
return false;
}
break;
case 'post':
if (!empty($post_action)) {
$container_id = docker($service_name, 'get_id');
if (ctype_xdigit($container_id) && ctype_alnum($post_action)) {
curl_setopt($curl, CURLOPT_URL, 'http://dockerapi:8080/containers/' . $container_id . '/' . $post_action);
curl_setopt($curl, CURLOPT_POST, 1);
if (!empty($post_fields)) {
curl_setopt( $curl, CURLOPT_POSTFIELDS, json_encode($post_fields));
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec($curl);
if ($response === false) {
$err = curl_error($curl);
curl_close($curl);
return $err;
}
else {
curl_close($curl);
if (empty($response)) {
return true;
}
else {
return $response;
}
}
}
}
break;
} }
} }
elseif ($_GET['ACTION'] == "stop") {
$request = xmlrpc_encode_request("supervisor.stopProcess", 'bootstrap-sogo', array('encoding'=>'utf-8')); if ($_GET['ACTION'] == "start") {
$context = stream_context_create(array('http' => array( $retry = 0;
'method' => "POST", while (docker('sogo-mailcow', 'info')['State']['Running'] != 1 && $retry <= 3) {
'header' => "Content-Length: " . strlen($request), $response = docker('sogo-mailcow', 'post', 'start');
'content' => $request $last_response = (trim($response) == "\"OK\"") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response . '</span></b>';
))); if (trim($response) == "\"OK\"") {
$file = @file_get_contents("http://sogo:9191/RPC2", false, $context) or die("Cannot connect to $remote_server:$listener_port"); break;
$response = xmlrpc_decode($file); }
if (isset($response['faultString'])) { usleep(1500000);
echo '<b><span class="pull-right text-warning">' . $response['faultString'] . '</span></b>'; $retry++;
} }
else { echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Already running</span></b>' : $last_response;
echo '<b><span class="pull-right text-success">OK</span></b>';
}
} }
if ($_GET['ACTION'] == "stop") {
$retry = 0;
while (docker('sogo-mailcow', 'info')['State']['Running'] == 1 && $retry <= 3) {
$response = docker('sogo-mailcow', 'post', 'stop');
$last_response = (trim($response) == "\"OK\"") ? '<b><span class="pull-right text-success">OK</span></b>' : '<b><span class="pull-right text-danger">Error: ' . $response . '</span></b>';
if (trim($response) == "\"OK\"") {
break;
}
usleep(1500000);
$retry++;
}
echo (!isset($last_response)) ? '<b><span class="pull-right text-warning">Not running</span></b>' : $last_response;
}
?> ?>

View File

@ -6,10 +6,14 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/modals/footer.php';
<script src="/js/bootstrap-switch.min.js"></script> <script src="/js/bootstrap-switch.min.js"></script>
<script src="/js/bootstrap-slider.min.js"></script> <script src="/js/bootstrap-slider.min.js"></script>
<script src="/js/bootstrap-select.min.js"></script> <script src="/js/bootstrap-select.min.js"></script>
<script src="/js/bootstrap-filestyle.min.js"></script>
<script src="/js/notifications.min.js"></script> <script src="/js/notifications.min.js"></script>
<script src="/js/u2f-api.js"></script> <script src="/js/u2f-api.js"></script>
<script src="/js/api.js"></script> <script src="/js/api.js"></script>
<script> <script>
$(window).scroll(function() {
sessionStorage.scrollTop = $(this).scrollTop();
});
// Select language and reopen active URL without POST // Select language and reopen active URL without POST
function setLang(sel) { function setLang(sel) {
$.post( "<?= $_SERVER['REQUEST_URI']; ?>", {lang: sel} ); $.post( "<?= $_SERVER['REQUEST_URI']; ?>", {lang: sel} );
@ -192,6 +196,9 @@ $(document).ready(function() {
// CSRF // CSRF
$('<input type="hidden" value="<?= $_SESSION['CSRF']['TOKEN']; ?>">').attr('id', 'csrf_token').attr('name', 'csrf_token').appendTo('form'); $('<input type="hidden" value="<?= $_SESSION['CSRF']['TOKEN']; ?>">').attr('id', 'csrf_token').attr('name', 'csrf_token').appendTo('form');
if (sessionStorage.scrollTop != "undefined") {
$(window).scrollTop(sessionStorage.scrollTop);
}
}); });
</script> </script>

View File

@ -0,0 +1,119 @@
<?php
function autoconfiguration($_action, $_type, $_data = null) {
global $pdo;
global $lang;
switch ($_action) {
case 'edit':
if (!isset($_SESSION['acl']['eas_autoconfig']) || $_SESSION['acl']['eas_autoconfig'] != "1" ) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
switch ($_type) {
case 'autodiscover':
$objects = (array)$_data['object'];
foreach ($objects as $object) {
if (is_valid_domain_name($object) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) {
$exclude_regex = (isset($_data['exclude_regex'])) ? $_data['exclude_regex'] : null;
$exclude_regex = (isset($_data['exclude_regex'])) ? $_data['exclude_regex'] : null;
try {
$stmt = $pdo->prepare("SELECT COUNT(`domain`) AS `domain_c` FROM `autodiscover`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $object));
$num_results = $stmt->fetchColumn();
if ($num_results > 0) {
$stmt = $pdo->prepare("SELECT COUNT(`domain`) AS `domain_c` FROM `autodiscover`
WHERE `domain` = :domain");
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
elseif (filter_var($object, FILTER_VALIDATE_EMAIL) === true && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $object)) {
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => sprintf($lang['success']['domain_modified'], htmlspecialchars(implode(', ', $objects)))
);
break;
}
break;
case 'get':
switch ($_type) {
case 'autodiscover':
$autodiscover = array();
if (is_valid_domain_name($_data) && hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
try {
$stmt = $pdo->prepare("SELECT * FROM `autodiscover`
WHERE `domain` = :domain");
$stmt->execute(array(':domain' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$autodiscover['mailbox'] = $row['mailbox'];
$autodiscover['domain'] = $row['domain'];
$autodiscover['service'] = $row['service'];
$autodiscover['exclude_regex'] = $row['exclude_regex'];
$autodiscover['created'] = $row['created'];
$autodiscover['modified'] = $row['modified'];
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
elseif (filter_var($_data, FILTER_VALIDATE_EMAIL) === true && hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
try {
$stmt = $pdo->prepare("SELECT * FROM `autodiscover`
WHERE `mailbox` = :mailbox");
$stmt->execute(array(':mailbox' => $_data));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while($row = array_shift($rows)) {
$autodiscover['mailbox'] = $row['mailbox'];
$autodiscover['domain'] = $row['domain'];
$autodiscover['service'] = $row['service'];
$autodiscover['exclude_regex'] = $row['exclude_regex'];
$autodiscover['created'] = $row['created'];
$autodiscover['modified'] = $row['modified'];
}
}
catch(PDOException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'MySQL: '.$e
);
return false;
}
}
return $autodiscover;
break;
}
break;
case 'reset':
switch ($_type) {
case 'autodiscover':
if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
return false;
}
break;
}
break;
}
}
$miau = "Microsoft Office/15.0 (Windows NT 5.1; macOS Outlook 16.0.4734; Pro)";
preg_match("/^((?!.*Mac|.*emClient).)*(Outlook|Office).+1[5-9].*/i", $miau, $output_array);
if (empty($output_array)) {
echo "imap";
}

View File

@ -0,0 +1,180 @@
<?php
function customize($_action, $_item, $_data = null) {
global $redis;
global $lang;
switch ($_action) {
case 'add':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
switch ($_item) {
case 'main_logo':
if (in_array($_data['main_logo']['type'], array('image/gif', 'image/jpeg', 'image/pjpeg', 'image/x-png', 'image/png', 'image/svg+xml'))) {
try {
if (file_exists($_data['main_logo']['tmp_name']) !== true) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Cannot validate image file: Temporary file not found'
);
return false;
}
$image = new Imagick($_data['main_logo']['tmp_name']);
if ($image->valid() !== true) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Cannot validate image file'
);
return false;
}
$image->destroy();
}
catch (ImagickException $e) {
$image->destroy();
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Cannot validate image file'
);
return false;
}
}
else {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Invalid mime type'
);
return false;
}
try {
$redis->Set('MAIN_LOGO', 'data:' . $_data['main_logo']['type'] . ';base64,' . base64_encode(file_get_contents($_data['main_logo']['tmp_name'])));
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => 'File uploaded successfully'
);
break;
}
break;
case 'edit':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
switch ($_item) {
case 'app_links':
$apps = (array)$_data['app'];
$links = (array)$_data['href'];
$out = array();
if (count($apps) == count($links)) {;
for ($i = 0; $i < count($apps); $i++) {
$out[] = array($apps[$i] => $links[$i]);
}
try {
$redis->set('APP_LINKS', json_encode($out));
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
}
$_SESSION['return'] = array(
'type' => 'success',
'msg' => 'Saved changes to app links'
);
break;
}
break;
case 'delete':
if ($_SESSION['mailcow_cc_role'] != "admin") {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => sprintf($lang['danger']['access_denied'])
);
return false;
}
switch ($_item) {
case 'main_logo':
try {
if ($redis->del('MAIN_LOGO')) {
$_SESSION['return'] = array(
'type' => 'success',
'msg' => 'Reset default logo'
);
return true;
}
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
break;
}
break;
case 'get':
switch ($_item) {
case 'app_links':
try {
$app_links = json_decode($redis->get('APP_LINKS'), true);
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
return ($app_links) ? $app_links : false;
break;
case 'main_logo':
try {
return $redis->get('MAIN_LOGO');
}
catch (RedisException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Redis: '.$e
);
return false;
}
break;
case 'main_logo_specs':
try {
$image = new Imagick();
$img_data = explode('base64,', customize('get', 'main_logo'));
if ($img_data[1]) {
$image->readImageBlob(base64_decode($img_data[1]));
}
return $image->identifyImage();
}
catch (ImagickException $e) {
$_SESSION['return'] = array(
'type' => 'danger',
'msg' => 'Error: Imagick exception while reading image'
);
return false;
}
break;
}
break;
}
}

View File

@ -244,6 +244,23 @@ function set_acl() {
return false; return false;
} }
} }
function get_acl($username) {
global $pdo;
if ($_SESSION['mailcow_cc_role'] != "admin") {
return false;
}
$username = strtolower(trim($username));
$stmt = $pdo->prepare("SELECT * FROM `user_acl` WHERE `username` = :username");
$stmt->execute(array(':username' => $username));
$acl = $stmt->fetch(PDO::FETCH_ASSOC);
unset($acl['username']);
if (!empty($acl)) {
return $acl;
}
else {
return false;
}
}
function formatBytes($size, $precision = 2) { function formatBytes($size, $precision = 2) {
if(!is_numeric($size)) { if(!is_numeric($size)) {
return "0"; return "0";
@ -900,6 +917,14 @@ function get_logs($container, $lines = 100) {
return $data_array; return $data_array;
} }
} }
if ($container == "autodiscover-mailcow") {
if ($data = $redis->lRange('AUTODISCOVER_LOG', 0, $lines)) {
foreach ($data as $json_line) {
$data_array[] = json_decode($json_line, true);
}
return $data_array;
}
}
if ($container == "rspamd-history") { if ($container == "rspamd-history") {
$curl = curl_init(); $curl = curl_init();
curl_setopt($curl, CURLOPT_URL,"http://rspamd-mailcow:11334/history"); curl_setopt($curl, CURLOPT_URL,"http://rspamd-mailcow:11334/history");

View File

@ -755,7 +755,7 @@ function mailbox($_action, $_type, $_data = null) {
':active' => $active ':active' => $active
)); ));
$stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`) $stmt = $pdo->prepare("INSERT INTO `quota2` (`username`, `bytes`, `messages`)
VALUES (:username, '0', '0')"); VALUES (:username, '0', '0') ON DUPLICATE KEY UPDATE `bytes` = '0', `messages` = '0';");
$stmt->execute(array(':username' => $username)); $stmt->execute(array(':username' => $username));
$stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`) $stmt = $pdo->prepare("INSERT INTO `alias` (`address`, `goto`, `domain`, `active`)
VALUES (:username1, :username2, :domain, :active)"); VALUES (:username1, :username2, :domain, :active)");
@ -1291,11 +1291,11 @@ function mailbox($_action, $_type, $_data = null) {
$port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1']; $port1 = (!empty($_data['port1'])) ? $_data['port1'] : $is_now['port1'];
$password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1']; $password1 = (!empty($_data['password1'])) ? $_data['password1'] : $is_now['password1'];
$host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1']; $host1 = (!empty($_data['host1'])) ? $_data['host1'] : $is_now['host1'];
$subfolder2 = (!empty($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2']; $subfolder2 = (isset($_data['subfolder2'])) ? $_data['subfolder2'] : $is_now['subfolder2'];
$enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1']; $enc1 = (!empty($_data['enc1'])) ? $_data['enc1'] : $is_now['enc1'];
$mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval']; $mins_interval = (!empty($_data['mins_interval'])) ? $_data['mins_interval'] : $is_now['mins_interval'];
$exclude = (!empty($_data['exclude'])) ? $_data['exclude'] : $is_now['exclude']; $exclude = (!empty($_data['exclude'])) ? $_data['exclude'] : '';
$maxage = (!empty($_data['maxage'])) ? $_data['maxage'] : $is_now['maxage']; $maxage = (isset($_data['maxage']) && $_data['maxage'] != "") ? intval($_data['maxage']) : $is_now['maxage'];
} }
else { else {
$_SESSION['return'] = array( $_SESSION['return'] = array(

View File

@ -32,7 +32,7 @@ function policy($_action, $_scope, $_data = null) {
$object_list = "whitelist_from"; $object_list = "whitelist_from";
} }
$object_from = preg_replace('/\.+/', '.', rtrim(preg_replace("/\.\*/", "*", trim(strtolower($_data['object_from']))), '.')); $object_from = preg_replace('/\.+/', '.', rtrim(preg_replace("/\.\*/", "*", trim(strtolower($_data['object_from']))), '.'));
if (!ctype_alnum(str_replace(array('@', '.', '-', '*'), '', $object_from))) { if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) {
$_SESSION['return'] = array( $_SESSION['return'] = array(
'type' => 'danger', 'type' => 'danger',
'msg' => sprintf($lang['danger']['policy_list_from_invalid']) 'msg' => sprintf($lang['danger']['policy_list_from_invalid'])
@ -112,7 +112,7 @@ function policy($_action, $_scope, $_data = null) {
$object_list = "whitelist_from"; $object_list = "whitelist_from";
} }
$object_from = preg_replace('/\.+/', '.', rtrim(preg_replace("/\.\*/", "*", trim(strtolower($_data['object_from']))), '.')); $object_from = preg_replace('/\.+/', '.', rtrim(preg_replace("/\.\*/", "*", trim(strtolower($_data['object_from']))), '.'));
if (!ctype_alnum(str_replace(array('@', '.', '-', '*'), '', $object_from))) { if (!ctype_alnum(str_replace(array('@', '_', '.', '-', '*'), '', $object_from))) {
$_SESSION['return'] = array( $_SESSION['return'] = array(
'type' => 'danger', 'type' => 'danger',
'msg' => sprintf($lang['danger']['policy_list_from_invalid']) 'msg' => sprintf($lang['danger']['policy_list_from_invalid'])

View File

@ -27,7 +27,7 @@ function relayhost($_action, $_data = null) {
$stmt->execute(array( $stmt->execute(array(
':hostname' => $hostname, ':hostname' => $hostname,
':username' => $username, ':username' => $username,
':password' => $password, ':password' => str_replace(':', '\:', $password),
':active' => '1' ':active' => '1'
)); ));
} }

View File

@ -39,7 +39,7 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="/"><img height="32" alt="mailcow-logo" style="margin-top: -5px;" src="/img/cow_mailcow.svg"></a> <a class="navbar-brand" href="/"><img alt="mailcow-logo" src="<?=($main_logo = customize('get', 'main_logo')) ? $main_logo : '/img/cow_mailcow.svg';?>"></a>
</div> </div>
<div id="navbar" class="navbar-collapse collapse"> <div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
@ -102,6 +102,14 @@
<li title="<?= htmlspecialchars($app['description']); ?>"><a href="<?= htmlspecialchars($app['link']); ?>"><?= htmlspecialchars($app['name']); ?></a></li> <li title="<?= htmlspecialchars($app['description']); ?>"><a href="<?= htmlspecialchars($app['link']); ?>"><?= htmlspecialchars($app['name']); ?></a></li>
<?php <?php
endforeach; endforeach;
$app_links = customize('get', 'app_links');
foreach ($app_links as $row) {
foreach ($row as $key => $val):
?>
<li><a href="<?= htmlspecialchars($val); ?>"><?= htmlspecialchars($key); ?></a></li>
<?php
endforeach;
}
?> ?>
</ul> </ul>
</li> </li>

View File

@ -3,7 +3,7 @@ function init_db_schema() {
try { try {
global $pdo; global $pdo;
$db_version = "15092017_0754"; $db_version = "25102017_0748";
$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));
@ -165,7 +165,6 @@ function init_db_schema() {
"delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'", "delimiter_action" => "TINYINT(1) NOT NULL DEFAULT '1'",
"syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'", "syncjobs" => "TINYINT(1) NOT NULL DEFAULT '1'",
"eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'", "eas_reset" => "TINYINT(1) NOT NULL DEFAULT '1'",
"eas_autoconfig" => "TINYINT(1) NOT NULL DEFAULT '1'"
), ),
"keys" => array( "keys" => array(
"fkey" => array( "fkey" => array(
@ -498,6 +497,13 @@ function init_db_schema() {
$stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'"); $stmt = $pdo->query("SHOW TABLES LIKE '" . $table . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) { if ($num_results != 0) {
$stmt = $pdo->prepare("SELECT CONCAT('ALTER TABLE ', `table_schema`, '.', `table_name`, ' DROP FOREIGN KEY ', `constraint_name`, ';') AS `FKEY_DROP` FROM `information_schema`.`table_constraints`
WHERE `constraint_type` = 'FOREIGN KEY' AND `table_name` = :table;");
$stmt->execute(array(':table' => $table));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
while ($row = array_shift($rows)) {
$pdo->query($row['FKEY_DROP']);
}
foreach($properties['cols'] as $column => $type) { foreach($properties['cols'] as $column => $type) {
$stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "'"); $stmt = $pdo->query("SHOW COLUMNS FROM `" . $table . "` LIKE '" . $column . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@ -542,7 +548,7 @@ function init_db_schema() {
$stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'"); $stmt = $pdo->query("SHOW KEYS FROM `" . $table . "` WHERE Key_name = '" . $key_name . "'");
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC)); $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
if ($num_results != 0) { if ($num_results != 0) {
$pdo->query("ALTER TABLE `" . $table . "` DROP FOREIGN KEY `" . $key_name . "`"); $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $key_name . "`");
} }
@list($table_ref, $field_ref) = explode('.', $key_values['ref']); @list($table_ref, $field_ref) = explode('.', $key_values['ref']);
$pdo->query("ALTER TABLE `" . $table . "` ADD FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`) $pdo->query("ALTER TABLE `" . $table . "` ADD FOREIGN KEY `" . $key_name . "` (" . $key_values['col'] . ") REFERENCES `" . $table_ref . "` (`" . $field_ref . "`)
@ -582,12 +588,7 @@ function init_db_schema() {
// Step 2: Drop all vanished indexes // Step 2: Drop all vanished indexes
while ($row = array_shift($keys_in_table)) { while ($row = array_shift($keys_in_table)) {
if (!in_array($row['Key_name'], $keys_to_exist)) { if (!in_array($row['Key_name'], $keys_to_exist)) {
try { $pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $row['Key_name'] . "`");
$pdo->query("ALTER TABLE `" . $table . "` DROP FOREIGN KEY `" . $row['Key_name'] . "`");
}
finally {
$pdo->query("ALTER TABLE `" . $table . "` DROP INDEX `" . $row['Key_name'] . "`");
}
} }
} }
// Step 3: Drop all vanished primary keys // Step 3: Drop all vanished primary keys

View File

@ -8,19 +8,20 @@
"packages": [ "packages": [
{ {
"name": "phpmailer/phpmailer", "name": "phpmailer/phpmailer",
"version": "v5.2.23", "version": "v5.2.25",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git", "url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "7115df4a6f76281109ebe352900c42403b728bb4" "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/7115df4a6f76281109ebe352900c42403b728bb4", "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2baf20b01690fba8cf720c1ebcf9b988eda50915",
"reference": "7115df4a6f76281109ebe352900c42403b728bb4", "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-ctype": "*",
"php": ">=5.0.0" "php": ">=5.0.0"
}, },
"require-dev": { "require-dev": {
@ -80,7 +81,7 @@
} }
], ],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP", "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"time": "2017-03-15T19:32:56+00:00" "time": "2017-08-28T11:12:07+00:00"
}, },
{ {
"name": "robthree/twofactorauth", "name": "robthree/twofactorauth",

View File

@ -91,20 +91,21 @@
}, },
{ {
"name": "phpmailer/phpmailer", "name": "phpmailer/phpmailer",
"version": "v5.2.23", "version": "v5.2.25",
"version_normalized": "5.2.23.0", "version_normalized": "5.2.25.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git", "url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "7115df4a6f76281109ebe352900c42403b728bb4" "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/7115df4a6f76281109ebe352900c42403b728bb4", "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/2baf20b01690fba8cf720c1ebcf9b988eda50915",
"reference": "7115df4a6f76281109ebe352900c42403b728bb4", "reference": "2baf20b01690fba8cf720c1ebcf9b988eda50915",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-ctype": "*",
"php": ">=5.0.0" "php": ">=5.0.0"
}, },
"require-dev": { "require-dev": {
@ -130,7 +131,7 @@
"suggest": { "suggest": {
"league/oauth2-google": "Needed for Google XOAUTH2 authentication" "league/oauth2-google": "Needed for Google XOAUTH2 authentication"
}, },
"time": "2017-03-15T19:32:56+00:00", "time": "2017-08-28T11:12:07+00:00",
"type": "library", "type": "library",
"installation-source": "dist", "installation-source": "dist",
"autoload": { "autoload": {

View File

@ -0,0 +1 @@
Non-security issues and pull requests are no longer being accepted for the legacy PHPMailer 5.2 branch. Migrate to PHPMailer 6.0 (or later) and report your issue there.

View File

@ -0,0 +1 @@
Non-security issues and pull requests are no longer being accepted for the legacy PHPMailer 5.2 branch. Migrate to PHPMailer 6.0 (or later) and report your issue there.

View File

@ -1 +1 @@
5.2.23 5.2.25

View File

@ -31,7 +31,7 @@ class PHPMailer
* The PHPMailer Version number. * The PHPMailer Version number.
* @var string * @var string
*/ */
public $Version = '5.2.23'; public $Version = '5.2.25';
/** /**
* Email priority. * Email priority.
@ -440,9 +440,9 @@ class PHPMailer
* *
* Parameters: * Parameters:
* boolean $result result of the send action * boolean $result result of the send action
* string $to email address of the recipient * array $to email addresses of the recipients
* string $cc cc email addresses * array $cc cc email addresses
* string $bcc bcc email addresses * array $bcc bcc email addresses
* string $subject the subject * string $subject the subject
* string $body the email body * string $body the email body
* string $from email address of sender * string $from email address of sender
@ -1622,8 +1622,13 @@ class PHPMailer
foreach ($hosts as $hostentry) { foreach ($hosts as $hostentry) {
$hostinfo = array(); $hostinfo = array();
if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { if (!preg_match(
'/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
trim($hostentry),
$hostinfo
)) {
// Not a valid host entry // Not a valid host entry
$this->edebug('Ignoring invalid host: ' . $hostentry);
continue; continue;
} }
// $hostinfo[2]: optional ssl or tls prefix // $hostinfo[2]: optional ssl or tls prefix
@ -1742,6 +1747,7 @@ class PHPMailer
'dk' => 'da', 'dk' => 'da',
'no' => 'nb', 'no' => 'nb',
'se' => 'sv', 'se' => 'sv',
'sr' => 'rs'
); );
if (isset($renamed_langcodes[$langcode])) { if (isset($renamed_langcodes[$langcode])) {
@ -2024,10 +2030,7 @@ class PHPMailer
{ {
$result = ''; $result = '';
if ($this->MessageDate == '') { $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate);
$this->MessageDate = self::rfcDate();
}
$result .= $this->headerLine('Date', $this->MessageDate);
// To be created automatically by mail() // To be created automatically by mail()
if ($this->SingleTo) { if ($this->SingleTo) {
@ -4033,7 +4036,7 @@ class phpmailerException extends Exception
*/ */
public function errorMessage() public function errorMessage()
{ {
$errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n"; $errorMsg = '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
return $errorMsg; return $errorMsg;
} }
} }

View File

@ -34,7 +34,7 @@ class POP3
* @var string * @var string
* @access public * @access public
*/ */
public $Version = '5.2.23'; public $Version = '5.2.25';
/** /**
* Default POP3 port number. * Default POP3 port number.

View File

@ -30,7 +30,7 @@ class SMTP
* The PHPMailer SMTP version number. * The PHPMailer SMTP version number.
* @var string * @var string
*/ */
const VERSION = '5.2.23'; const VERSION = '5.2.25';
/** /**
* SMTP line break constant. * SMTP line break constant.
@ -81,7 +81,7 @@ class SMTP
* @deprecated Use the `VERSION` constant instead * @deprecated Use the `VERSION` constant instead
* @see SMTP::VERSION * @see SMTP::VERSION
*/ */
public $Version = '5.2.23'; public $Version = '5.2.25';
/** /**
* SMTP server port number. * SMTP server port number.
@ -151,9 +151,8 @@ class SMTP
public $Timelimit = 300; public $Timelimit = 300;
/** /**
* @var array patterns to extract smtp transaction id from smtp reply * @var array Patterns to extract an SMTP transaction id from reply to a DATA command.
* Only first capture group will be use, use non-capturing group to deal with it * The first capture group in each regex will be used as the ID.
* Extend this class to override this property to fulfil your needs.
*/ */
protected $smtp_transaction_id_patterns = array( protected $smtp_transaction_id_patterns = array(
'exim' => '/[0-9]{3} OK id=(.*)/', 'exim' => '/[0-9]{3} OK id=(.*)/',
@ -161,6 +160,12 @@ class SMTP
'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/' 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
); );
/**
* @var string The last transaction ID issued in response to a DATA command,
* if one was detected
*/
protected $last_smtp_transaction_id;
/** /**
* The socket for the server connection. * The socket for the server connection.
* @var resource * @var resource
@ -227,7 +232,7 @@ class SMTP
break; break;
case 'html': case 'html':
//Cleans up output a bit for a better looking, HTML-safe output //Cleans up output a bit for a better looking, HTML-safe output
echo htmlentities( echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities(
preg_replace('/[\r\n]+/', '', $str), preg_replace('/[\r\n]+/', '', $str),
ENT_QUOTES, ENT_QUOTES,
'UTF-8' 'UTF-8'
@ -709,6 +714,7 @@ class SMTP
$savetimelimit = $this->Timelimit; $savetimelimit = $this->Timelimit;
$this->Timelimit = $this->Timelimit * 2; $this->Timelimit = $this->Timelimit * 2;
$result = $this->sendCommand('DATA END', '.', 250); $result = $this->sendCommand('DATA END', '.', 250);
$this->recordLastTransactionID();
//Restore timelimit //Restore timelimit
$this->Timelimit = $savetimelimit; $this->Timelimit = $savetimelimit;
return $result; return $result;
@ -989,7 +995,10 @@ class SMTP
public function client_send($data) public function client_send($data)
{ {
$this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
return fwrite($this->smtp_conn, $data); set_error_handler(array($this, 'errorHandler'));
$result = fwrite($this->smtp_conn, $data);
restore_error_handler();
return $result;
} }
/** /**
@ -1089,8 +1098,10 @@ class SMTP
$this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
$this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
$data .= $str; $data .= $str;
// If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
if ((isset($str[3]) and $str[3] == ' ')) { // or 4th character is a space, we are done reading, break the loop,
// string array access is a micro-optimisation over strlen
if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
break; break;
} }
// Timed-out? Log and break // Timed-out? Log and break
@ -1226,26 +1237,40 @@ class SMTP
} }
/** /**
* Will return the ID of the last smtp transaction based on a list of patterns provided * Extract and return the ID of the last SMTP transaction based on
* in SMTP::$smtp_transaction_id_patterns. * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
* Relies on the host providing the ID in response to a DATA command.
* If no reply has been received yet, it will return null. * If no reply has been received yet, it will return null.
* If no pattern has been matched, it will return false. * If no pattern was matched, it will return false.
* @return bool|null|string * @return bool|null|string
*/ */
public function getLastTransactionID() protected function recordLastTransactionID()
{ {
$reply = $this->getLastReply(); $reply = $this->getLastReply();
if (empty($reply)) { if (empty($reply)) {
return null; $this->last_smtp_transaction_id = null;
} } else {
$this->last_smtp_transaction_id = false;
foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
return $matches[1]; $this->last_smtp_transaction_id = $matches[1];
}
} }
} }
return false; return $this->last_smtp_transaction_id;
}
/**
* Get the queue/transaction ID of the last SMTP transaction
* If no reply has been received yet, it will return null.
* If no pattern was matched, it will return false.
* @return bool|null|string
* @see recordLastTransactionID()
*/
public function getLastTransactionID()
{
return $this->last_smtp_transaction_id;
} }
} }

View File

@ -20,6 +20,7 @@
} }
], ],
"require": { "require": {
"ext-ctype": "*",
"php": ">=5.0.0" "php": ">=5.0.0"
}, },
"require-dev": { "require-dev": {

View File

@ -58,46 +58,53 @@ class phpmailerAppException extends phpmailerException
$example_code .= "\n\nclass phpmailerAppException extends phpmailerException {}"; $example_code .= "\n\nclass phpmailerAppException extends phpmailerException {}";
$example_code .= "\n\ntry {"; $example_code .= "\n\ntry {";
// Convert a string to its JavaScript representation.
function JSString($s) {
static $from = array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"');
static $to = array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\\"');
return is_null($s)? 'null': '"' . str_replace($from, $to, "$s") . '"';
}
try { try {
if (isset($_POST["submit"]) && $_POST['submit'] == "Submit") { if (isset($_POST["submit"]) && $_POST['submit'] == "Submit") {
$to = $_POST['To_Email']; $to = $to_email;
if (!PHPMailer::validateAddress($to)) { if (!PHPMailer::validateAddress($to)) {
throw new phpmailerAppException("Email address " . $to . " is invalid -- aborting!"); throw new phpmailerAppException("Email address " . $to . " is invalid -- aborting!");
} }
$example_code .= "\n\$to = '{$_POST['To_Email']}';"; $example_code .= "\n\$to = '" . addslashes($to_email) . "';";
$example_code .= "\nif(!PHPMailer::validateAddress(\$to)) {"; $example_code .= "\nif(!PHPMailer::validateAddress(\$to)) {";
$example_code .= "\n throw new phpmailerAppException(\"Email address \" . " . $example_code .= "\n throw new phpmailerAppException(\"Email address \" . " .
"\$to . \" is invalid -- aborting!\");"; "\$to . \" is invalid -- aborting!\");";
$example_code .= "\n}"; $example_code .= "\n}";
switch ($_POST['test_type']) { switch ($test_type) {
case 'smtp': case 'smtp':
$mail->isSMTP(); // telling the class to use SMTP $mail->isSMTP(); // telling the class to use SMTP
$mail->SMTPDebug = (integer)$_POST['smtp_debug']; $mail->SMTPDebug = (integer)$smtp_debug;
$mail->Host = $_POST['smtp_server']; // SMTP server $mail->Host = $smtp_server; // SMTP server
$mail->Port = (integer)$_POST['smtp_port']; // set the SMTP port $mail->Port = (integer)$smtp_port; // set the SMTP port
if ($_POST['smtp_secure']) { if ($smtp_secure) {
$mail->SMTPSecure = strtolower($_POST['smtp_secure']); $mail->SMTPSecure = strtolower($smtp_secure);
} }
$mail->SMTPAuth = array_key_exists('smtp_authenticate', $_POST); // enable SMTP authentication? $mail->SMTPAuth = array_key_exists('smtp_authenticate', $_POST); // enable SMTP authentication?
if (array_key_exists('smtp_authenticate', $_POST)) { if (array_key_exists('smtp_authenticate', $_POST)) {
$mail->Username = $_POST['authenticate_username']; // SMTP account username $mail->Username = $authenticate_username; // SMTP account username
$mail->Password = $_POST['authenticate_password']; // SMTP account password $mail->Password = $authenticate_password; // SMTP account password
} }
$example_code .= "\n\$mail->isSMTP();"; $example_code .= "\n\$mail->isSMTP();";
$example_code .= "\n\$mail->SMTPDebug = " . $_POST['smtp_debug'] . ";"; $example_code .= "\n\$mail->SMTPDebug = " . (integer) $smtp_debug . ";";
$example_code .= "\n\$mail->Host = \"" . $_POST['smtp_server'] . "\";"; $example_code .= "\n\$mail->Host = \"" . addslashes($smtp_server) . "\";";
$example_code .= "\n\$mail->Port = \"" . $_POST['smtp_port'] . "\";"; $example_code .= "\n\$mail->Port = \"" . addslashes($smtp_port) . "\";";
$example_code .= "\n\$mail->SMTPSecure = \"" . strtolower($_POST['smtp_secure']) . "\";"; $example_code .= "\n\$mail->SMTPSecure = \"" . addslashes(strtolower($smtp_secure)) . "\";";
$example_code .= "\n\$mail->SMTPAuth = " . (array_key_exists( $example_code .= "\n\$mail->SMTPAuth = " . (array_key_exists(
'smtp_authenticate', 'smtp_authenticate',
$_POST $_POST
) ? 'true' : 'false') . ";"; ) ? 'true' : 'false') . ";";
if (array_key_exists('smtp_authenticate', $_POST)) { if (array_key_exists('smtp_authenticate', $_POST)) {
$example_code .= "\n\$mail->Username = \"" . $_POST['authenticate_username'] . "\";"; $example_code .= "\n\$mail->Username = \"" . addslashes($authenticate_username) . "\";";
$example_code .= "\n\$mail->Password = \"" . $_POST['authenticate_password'] . "\";"; $example_code .= "\n\$mail->Password = \"" . addslashes($authenticate_password) . "\";";
} }
break; break;
case 'mail': case 'mail':
@ -118,59 +125,59 @@ try {
try { try {
if ($_POST['From_Name'] != '') { if ($_POST['From_Name'] != '') {
$mail->addReplyTo($_POST['From_Email'], $_POST['From_Name']); $mail->addReplyTo($from_email, $from_name);
$mail->setFrom($_POST['From_Email'], $_POST['From_Name']); $mail->setFrom($from_email, $from_name);
$example_code .= "\n\$mail->addReplyTo(\"" . $example_code .= "\n\$mail->addReplyTo(\"" .
$_POST['From_Email'] . "\", \"" . $_POST['From_Name'] . "\");"; addslashes($from_email) . "\", \"" . addslashes($from_name) . "\");";
$example_code .= "\n\$mail->setFrom(\"" . $example_code .= "\n\$mail->setFrom(\"" .
$_POST['From_Email'] . "\", \"" . $_POST['From_Name'] . "\");"; addslashes($from_email) . "\", \"" . addslashes($from_name) . "\");";
} else { } else {
$mail->addReplyTo($_POST['From_Email']); $mail->addReplyTo($from_email);
$mail->setFrom($_POST['From_Email'], $_POST['From_Email']); $mail->setFrom($from_email, $from_email);
$example_code .= "\n\$mail->addReplyTo(\"" . $_POST['From_Email'] . "\");"; $example_code .= "\n\$mail->addReplyTo(\"" . addslashes($from_email) . "\");";
$example_code .= "\n\$mail->setFrom(\"" . $example_code .= "\n\$mail->setFrom(\"" .
$_POST['From_Email'] . "\", \"" . $_POST['From_Email'] . "\");"; addslashes($from_email) . "\", \"" . addslashes($from_email) . "\");";
} }
if ($_POST['To_Name'] != '') { if ($_POST['To_Name'] != '') {
$mail->addAddress($to, $_POST['To_Name']); $mail->addAddress($to, $to_name);
$example_code .= "\n\$mail->addAddress(\"$to\", \"" . $_POST['To_Name'] . "\");"; $example_code .= "\n\$mail->addAddress(\"$to\", \"" . addslashes($to_name) . "\");";
} else { } else {
$mail->addAddress($to); $mail->addAddress($to);
$example_code .= "\n\$mail->addAddress(\"$to\");"; $example_code .= "\n\$mail->addAddress(\"$to\");";
} }
if ($_POST['bcc_Email'] != '') { if ($_POST['bcc_Email'] != '') {
$indiBCC = explode(" ", $_POST['bcc_Email']); $indiBCC = explode(" ", $bcc_email);
foreach ($indiBCC as $key => $value) { foreach ($indiBCC as $key => $value) {
$mail->addBCC($value); $mail->addBCC($value);
$example_code .= "\n\$mail->addBCC(\"$value\");"; $example_code .= "\n\$mail->addBCC(\"" . addslashes($value) . "\");";
} }
} }
if ($_POST['cc_Email'] != '') { if ($_POST['cc_Email'] != '') {
$indiCC = explode(" ", $_POST['cc_Email']); $indiCC = explode(" ", $cc_Email);
foreach ($indiCC as $key => $value) { foreach ($indiCC as $key => $value) {
$mail->addCC($value); $mail->addCC($value);
$example_code .= "\n\$mail->addCC(\"$value\");"; $example_code .= "\n\$mail->addCC(\"" . addslashes($value) . "\");";
} }
} }
} catch (phpmailerException $e) { //Catch all kinds of bad addressing } catch (phpmailerException $e) { //Catch all kinds of bad addressing
throw new phpmailerAppException($e->getMessage()); throw new phpmailerAppException($e->getMessage());
} }
$mail->Subject = $_POST['Subject'] . ' (PHPMailer test using ' . strtoupper($_POST['test_type']) . ')'; $mail->Subject = $subject . ' (PHPMailer test using ' . strtoupper($test_type) . ')';
$example_code .= "\n\$mail->Subject = \"" . $_POST['Subject'] . $example_code .= "\n\$mail->Subject = \"" . addslashes($subject) .
' (PHPMailer test using ' . strtoupper($_POST['test_type']) . ')";'; ' (PHPMailer test using ' . addslashes(strtoupper($test_type)) . ')";';
if ($_POST['Message'] == '') { if ($_POST['Message'] == '') {
$body = file_get_contents('contents.html'); $body = file_get_contents('contents.html');
} else { } else {
$body = $_POST['Message']; $body = $message;
} }
$example_code .= "\n\$body = <<<'EOT'\n" . htmlentities($body) . "\nEOT;"; $example_code .= "\n\$body = <<<'EOT'\n$body\nEOT;";
$mail->WordWrap = 78; // set word wrap to the RFC2822 limit $mail->WordWrap = 78; // set word wrap to the RFC2822 limit
$mail->msgHTML($body, dirname(__FILE__), true); //Create message bodies and embed images $mail->msgHTML($body, dirname(__FILE__), true); //Create message bodies and embed images
@ -187,7 +194,7 @@ try {
$example_code .= "\n\ntry {"; $example_code .= "\n\ntry {";
$example_code .= "\n \$mail->send();"; $example_code .= "\n \$mail->send();";
$example_code .= "\n \$results_messages[] = \"Message has been sent using " . $example_code .= "\n \$results_messages[] = \"Message has been sent using " .
strtoupper($_POST['test_type']) . "\";"; addslashes(strtoupper($test_type)) . "\";";
$example_code .= "\n}"; $example_code .= "\n}";
$example_code .= "\ncatch (phpmailerException \$e) {"; $example_code .= "\ncatch (phpmailerException \$e) {";
$example_code .= "\n throw new phpmailerAppException('Unable to send to: ' . \$to. ': '.\$e->getMessage());"; $example_code .= "\n throw new phpmailerAppException('Unable to send to: ' . \$to. ': '.\$e->getMessage());";
@ -195,7 +202,7 @@ try {
try { try {
$mail->send(); $mail->send();
$results_messages[] = "Message has been sent using " . strtoupper($_POST["test_type"]); $results_messages[] = "Message has been sent using " . strtoupper($test_type);
} catch (phpmailerException $e) { } catch (phpmailerException $e) {
throw new phpmailerAppException("Unable to send to: " . $to . ': ' . $e->getMessage()); throw new phpmailerAppException("Unable to send to: " . $to . ': ' . $e->getMessage());
} }
@ -309,22 +316,22 @@ $example_code .= "\n}";
function startAgain() { function startAgain() {
var post_params = { var post_params = {
"From_Name": "<?php echo $from_name; ?>", "From_Name": <?php echo JSString($from_name); ?>,
"From_Email": "<?php echo $from_email; ?>", "From_Email": <?php echo JSString($from_email); ?>,
"To_Name": "<?php echo $to_name; ?>", "To_Name": <?php echo JSString($to_name); ?>,
"To_Email": "<?php echo $to_email; ?>", "To_Email": <?php echo JSString($to_email); ?>,
"cc_Email": "<?php echo $cc_email; ?>", "cc_Email": <?php echo JSString($cc_email); ?>,
"bcc_Email": "<?php echo $bcc_email; ?>", "bcc_Email": <?php echo JSString($bcc_email); ?>,
"Subject": "<?php echo $subject; ?>", "Subject": <?php echo JSString($subject); ?>,
"Message": "<?php echo $message; ?>", "Message": <?php echo JSString($message); ?>,
"test_type": "<?php echo $test_type; ?>", "test_type": <?php echo JSString($test_type); ?>,
"smtp_debug": "<?php echo $smtp_debug; ?>", "smtp_debug": <?php echo JSString($smtp_debug); ?>,
"smtp_server": "<?php echo $smtp_server; ?>", "smtp_server": <?php echo JSString($smtp_server); ?>,
"smtp_port": "<?php echo $smtp_port; ?>", "smtp_port": <?php echo JSString($smtp_port); ?>,
"smtp_secure": "<?php echo $smtp_secure; ?>", "smtp_secure": <?php echo JSString($smtp_secure); ?>,
"smtp_authenticate": "<?php echo $smtp_authenticate; ?>", "smtp_authenticate": <?php echo JSString($smtp_authenticate); ?>,
"authenticate_username": "<?php echo $authenticate_username; ?>", "authenticate_username": <?php echo JSString($authenticate_username); ?>,
"authenticate_password": "<?php echo $authenticate_password; ?>" "authenticate_password": <?php echo JSString($authenticate_password); ?>
}; };
var resetForm = document.createElement("form"); var resetForm = document.createElement("form");
@ -374,7 +381,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
echo "<button type=\"submit\" onclick=\"startAgain();\">Start Over</button><br>\n"; echo "<button type=\"submit\" onclick=\"startAgain();\">Start Over</button><br>\n";
echo "<br><span>Script:</span>\n"; echo "<br><span>Script:</span>\n";
echo "<pre class=\"brush: php;\">\n"; echo "<pre class=\"brush: php;\">\n";
echo $example_code; echo htmlentities($example_code);
echo "\n</pre>\n"; echo "\n</pre>\n";
echo "\n<hr style=\"margin: 3em;\">\n"; echo "\n<hr style=\"margin: 3em;\">\n";
} }
@ -390,7 +397,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<label for="From_Name"><strong>From</strong> Name</label> <label for="From_Name"><strong>From</strong> Name</label>
</td> </td>
<td class="colrite"> <td class="colrite">
<input type="text" id="From_Name" name="From_Name" value="<?php echo $from_name; ?>" <input type="text" id="From_Name" name="From_Name" value="<?php echo htmlentities($from_name); ?>"
style="width:95%;" autofocus placeholder="Your Name"> style="width:95%;" autofocus placeholder="Your Name">
</td> </td>
</tr> </tr>
@ -399,7 +406,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<label for="From_Email"><strong>From</strong> Email Address</label> <label for="From_Email"><strong>From</strong> Email Address</label>
</td> </td>
<td class="colrite"> <td class="colrite">
<input type="text" id="From_Email" name="From_Email" value="<?php echo $from_email; ?>" <input type="text" id="From_Email" name="From_Email" value="<?php echo htmlentities($from_email); ?>"
style="width:95%;" required placeholder="Your.Email@example.com"> style="width:95%;" required placeholder="Your.Email@example.com">
</td> </td>
</tr> </tr>
@ -408,7 +415,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<label for="To_Name"><strong>To</strong> Name</label> <label for="To_Name"><strong>To</strong> Name</label>
</td> </td>
<td class="colrite"> <td class="colrite">
<input type="text" id="To_Name" name="To_Name" value="<?php echo $to_name; ?>" <input type="text" id="To_Name" name="To_Name" value="<?php echo htmlentities($to_name); ?>"
style="width:95%;" placeholder="Recipient's Name"> style="width:95%;" placeholder="Recipient's Name">
</td> </td>
</tr> </tr>
@ -417,7 +424,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<label for="To_Email"><strong>To</strong> Email Address</label> <label for="To_Email"><strong>To</strong> Email Address</label>
</td> </td>
<td class="colrite"> <td class="colrite">
<input type="text" id="To_Email" name="To_Email" value="<?php echo $to_email; ?>" <input type="text" id="To_Email" name="To_Email" value="<?php echo htmlentities($to_email); ?>"
style="width:95%;" required placeholder="Recipients.Email@example.com"> style="width:95%;" required placeholder="Recipients.Email@example.com">
</td> </td>
</tr> </tr>
@ -428,7 +435,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
</label> </label>
</td> </td>
<td class="colrite"> <td class="colrite">
<input type="text" id="cc_Email" name="cc_Email" value="<?php echo $cc_email; ?>" <input type="text" id="cc_Email" name="cc_Email" value="<?php echo htmlentities($cc_email); ?>"
style="width:95%;" placeholder="cc1@example.com, cc2@example.com"> style="width:95%;" placeholder="cc1@example.com, cc2@example.com">
</td> </td>
</tr> </tr>
@ -439,7 +446,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
</label> </label>
</td> </td>
<td class="colrite"> <td class="colrite">
<input type="text" id="bcc_Email" name="bcc_Email" value="<?php echo $bcc_email; ?>" <input type="text" id="bcc_Email" name="bcc_Email" value="<?php echo htmlentities($bcc_email); ?>"
style="width:95%;" placeholder="bcc1@example.com, bcc2@example.com"> style="width:95%;" placeholder="bcc1@example.com, bcc2@example.com">
</td> </td>
</tr> </tr>
@ -448,7 +455,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<label for="Subject"><strong>Subject</strong></label> <label for="Subject"><strong>Subject</strong></label>
</td> </td>
<td class="colrite"> <td class="colrite">
<input type="text" name="Subject" id="Subject" value="<?php echo $subject; ?>" <input type="text" name="Subject" id="Subject" value="<?php echo htmlentities($subject); ?>"
style="width:95%;" placeholder="Email Subject"> style="width:95%;" placeholder="Email Subject">
</td> </td>
</tr> </tr>
@ -460,7 +467,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
</td> </td>
<td class="colrite"> <td class="colrite">
<textarea name="Message" id="Message" style="width:95%;height:5em;" <textarea name="Message" id="Message" style="width:95%;height:5em;"
placeholder="Body of your email"><?php echo $message; ?></textarea> placeholder="Body of your email"><?php echo htmlentities($message); ?></textarea>
</td> </td>
</tr> </tr>
</table> </table>
@ -531,7 +538,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<td class="colleft"><label for="smtp_server">SMTP Server</label></td> <td class="colleft"><label for="smtp_server">SMTP Server</label></td>
<td class="colrite"> <td class="colrite">
<input type="text" id="smtp_server" name="smtp_server" <input type="text" id="smtp_server" name="smtp_server"
value="<?php echo $smtp_server; ?>" style="width:95%;" value="<?php echo htmlentities($smtp_server); ?>" style="width:95%;"
placeholder="smtp.server.com"> placeholder="smtp.server.com">
</td> </td>
</tr> </tr>
@ -539,7 +546,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<td class="colleft" style="width: 5em;"><label for="smtp_port">SMTP Port</label></td> <td class="colleft" style="width: 5em;"><label for="smtp_port">SMTP Port</label></td>
<td class="colrite"> <td class="colrite">
<input type="text" name="smtp_port" id="smtp_port" size="3" <input type="text" name="smtp_port" id="smtp_port" size="3"
value="<?php echo $smtp_port; ?>" placeholder="Port"> value="<?php echo htmlentities($smtp_port); ?>" placeholder="Port">
</td> </td>
</tr> </tr>
<tr> <tr>
@ -560,14 +567,14 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<?php if ($smtp_authenticate != '') { <?php if ($smtp_authenticate != '') {
echo "checked"; echo "checked";
} ?> } ?>
value="<?php echo $smtp_authenticate; ?>"> value="true">
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="colleft"><label for="authenticate_username">Authenticate Username</label></td> <td class="colleft"><label for="authenticate_username">Authenticate Username</label></td>
<td class="colrite"> <td class="colrite">
<input type="text" id="authenticate_username" name="authenticate_username" <input type="text" id="authenticate_username" name="authenticate_username"
value="<?php echo $authenticate_username; ?>" style="width:95%;" value="<?php echo htmlentities($authenticate_username); ?>" style="width:95%;"
placeholder="SMTP Server Username"> placeholder="SMTP Server Username">
</td> </td>
</tr> </tr>
@ -575,7 +582,7 @@ if (isset($_POST["submit"]) && $_POST["submit"] == "Submit") {
<td class="colleft"><label for="authenticate_password">Authenticate Password</label></td> <td class="colleft"><label for="authenticate_password">Authenticate Password</label></td>
<td class="colrite"> <td class="colrite">
<input type="password" name="authenticate_password" id="authenticate_password" <input type="password" name="authenticate_password" id="authenticate_password"
value="<?php echo $authenticate_password; ?>" style="width:95%;" value="<?php echo htmlentities($authenticate_password); ?>" style="width:95%;"
placeholder="SMTP Server Password"> placeholder="SMTP Server Password">
</td> </td>
</tr> </tr>

View File

@ -1,6 +1,7 @@
<?php <?php
/** /**
* This example shows settings to use when sending via Google's Gmail servers. * This example shows settings to use when sending via Google's Gmail servers.
* The IMAP section shows how to save this message to the 'Sent Mail' folder using IMAP commands.
*/ */
//SMTP needs accurate times, and the PHP time zone MUST be set //SMTP needs accurate times, and the PHP time zone MUST be set
@ -72,4 +73,27 @@ if (!$mail->send()) {
echo "Mailer Error: " . $mail->ErrorInfo; echo "Mailer Error: " . $mail->ErrorInfo;
} else { } else {
echo "Message sent!"; echo "Message sent!";
//Section 2: IMAP
//Uncomment these to save your message in the 'Sent Mail' folder.
#if (save_mail($mail)) {
# echo "Message saved!";
#}
}
//Section 2: IMAP
//IMAP commands requires the PHP IMAP Extension, found at: https://php.net/manual/en/imap.setup.php
//Function to call which uses the PHP imap_*() functions to save messages: https://php.net/manual/en/book.imap.php
//You can use imap_getmailboxes($imapStream, '/imap/ssl') to get a list of available folders or labels, this can
//be useful if you are trying to get this working on a non-Gmail IMAP server.
function save_mail($mail) {
//You can change 'Sent Mail' to any other folder or tag
$path = "{imap.gmail.com:993/imap/ssl}[Gmail]/Sent Mail";
//Tell your server to open an IMAP connection using the same username and password as you used for SMTP
$imapStream = imap_open($path, $mail->Username, $mail->Password);
$result = imap_append($imapStream, $path, $mail->getSentMIMEMessage());
imap_close($imapStream);
return $result;
} }

View File

@ -43,8 +43,8 @@ $mail->SMTPAuth = true;
//Set AuthType //Set AuthType
$mail->AuthType = 'XOAUTH2'; $mail->AuthType = 'XOAUTH2';
//User Email to use for SMTP authentication - Use the same Email used in Google Developer Console //User Email to use for SMTP authentication - user who gave consent to our app
$mail->oauthUserEmail = "someone@gmail.com"; $mail->oauthUserEmail = "from@gmail.com";
//Obtained From Google Developer Console //Obtained From Google Developer Console
$mail->oauthClientId = "RANDOMCHARS-----duv1n2.apps.googleusercontent.com"; $mail->oauthClientId = "RANDOMCHARS-----duv1n2.apps.googleusercontent.com";

View File

@ -1,17 +1,17 @@
#PHPMailer Extras # PHPMailer Extras
These classes provide optional additional functions to PHPMailer. These classes provide optional additional functions to PHPMailer.
These are not loaded by the PHPMailer autoloader, so in some cases you may need to `require` them yourself before using them. These are not loaded by the PHPMailer autoloader, so in some cases you may need to `require` them yourself before using them.
##EasyPeasyICS ## EasyPeasyICS
This class was originally written by Manuel Reinhard and provides a simple means of generating ICS/vCal files that are used in sending calendar events. PHPMailer does not use it directly, but you can use it to generate content appropriate for placing in the `Ical` property of PHPMailer. The PHPMailer project is now its official home as Manuel has given permission for that and is no longer maintaining it himself. This class was originally written by Manuel Reinhard and provides a simple means of generating ICS/vCal files that are used in sending calendar events. PHPMailer does not use it directly, but you can use it to generate content appropriate for placing in the `Ical` property of PHPMailer. The PHPMailer project is now its official home as Manuel has given permission for that and is no longer maintaining it himself.
##htmlfilter ## htmlfilter
This class by Konstantin Riabitsev and Jim Jagielski implements HTML filtering to remove potentially malicious tags, such as `<script>` or `onclick=` attributes that can result in XSS attacks. This is a simple filter and is not as comprehensive as [HTMLawed](http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/) or [HTMLPurifier](http://htmlpurifier.org), but it's easier to use and considerably better than nothing! PHPMailer does not use it directly, but you may want to apply it to user-supplied HTML before using it as a message body. This class by Konstantin Riabitsev and Jim Jagielski implements HTML filtering to remove potentially malicious tags, such as `<script>` or `onclick=` attributes that can result in XSS attacks. This is a simple filter and is not as comprehensive as [HTMLawed](http://www.bioinformatics.org/phplabware/internal_utilities/htmLawed/) or [HTMLPurifier](http://htmlpurifier.org), but it's easier to use and considerably better than nothing! PHPMailer does not use it directly, but you may want to apply it to user-supplied HTML before using it as a message body.
##NTLM_SASL_client ## NTLM_SASL_client
This class by Manuel Lemos (bundled with permission) adds the ability to authenticate with Microsoft Windows mail servers that use NTLM-based authentication. It is used by PHPMailer if you send via SMTP and set the `AuthType` property to `NTLM`; you will also need to use the `Realm` and `Workstation` properties. The original source is [here](http://www.phpclasses.org/browse/file/7495.html). This class by Manuel Lemos (bundled with permission) adds the ability to authenticate with Microsoft Windows mail servers that use NTLM-based authentication. It is used by PHPMailer if you send via SMTP and set the `AuthType` property to `NTLM`; you will also need to use the `Realm` and `Workstation` properties. The original source is [here](http://www.phpclasses.org/browse/file/7495.html).

View File

@ -0,0 +1,26 @@
<?php
/**
* Bosnian PHPMailer language file: refer to English translation for definitive list
* @package PHPMailer
* @author Ermin Islamagić <ermin@islamagic.com>
*/
$PHPMAILER_LANG['authenticate'] = 'SMTP Greška: Neuspjela prijava.';
$PHPMAILER_LANG['connect_host'] = 'SMTP Greška: Ne moguće se spojiti sa SMTP serverom.';
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Greška: Podatci nisu prihvaćeni.';
$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.';
$PHPMAILER_LANG['encoding'] = 'Nepoznata kriptografija: ';
$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: ';
$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: ';
$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: ';
$PHPMAILER_LANG['from_failed'] = 'SMTP Greška: Slanje sa navedenih e-mail adresa nije uspjelo: ';
$PHPMAILER_LANG['recipients_failed'] = 'SMTP Greška: Slanje na navedene e-mail adrese nije uspjelo: ';
$PHPMAILER_LANG['instantiate'] = 'Ne mogu pokrenuti mail funkcionalnost.';
$PHPMAILER_LANG['invalid_address'] = 'E-mail nije poslan. Neispravna e-mail adresa: ';
$PHPMAILER_LANG['mailer_not_supported'] = ' mailer nije podržan.';
$PHPMAILER_LANG['provide_address'] = 'Definišite barem jednu adresu primaoca.';
$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: ';
$PHPMAILER_LANG['smtp_connect_failed'] = 'Spajanje na SMTP server nije uspjelo.';
$PHPMAILER_LANG['smtp_error'] = 'SMTP greška: ';
$PHPMAILER_LANG['variable_set'] = 'Nije moguće postaviti varijablu ili je vratiti nazad: ';
$PHPMAILER_LANG['extension_missing'] = 'Nedostaje ekstenzija: ';

View File

@ -1,25 +1,25 @@
<?php <?php
/** /**
* Norwegian PHPMailer language file: refer to English translation for definitive list * Norwegian Bokmål PHPMailer language file: refer to English translation for definitive list
* @package PHPMailer * @package PHPMailer
*/ */
$PHPMAILER_LANG['authenticate'] = 'SMTP Feil: Kunne ikke autentisere.'; $PHPMAILER_LANG['authenticate'] = 'SMTP Feil: Kunne ikke autentisere.';
$PHPMAILER_LANG['connect_host'] = 'SMTP Feil: Kunne ikke koble til SMTP tjener.'; $PHPMAILER_LANG['connect_host'] = 'SMTP Feil: Kunne ikke koble til SMTP tjener.';
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP Feil: Data ble ikke akseptert.'; $PHPMAILER_LANG['data_not_accepted'] = 'SMTP Feil: Datainnhold ikke akseptert.';
$PHPMAILER_LANG['empty_message'] = 'Meldingsinnholdet er tomt'; $PHPMAILER_LANG['empty_message'] = 'Melding kropp tomt';
$PHPMAILER_LANG['encoding'] = 'Ukjent tegnkoding: '; $PHPMAILER_LANG['encoding'] = 'Ukjent koding: ';
$PHPMAILER_LANG['execute'] = 'Kunne ikke utføre: '; $PHPMAILER_LANG['execute'] = 'Kunne ikke utføre: ';
$PHPMAILER_LANG['file_access'] = 'Får ikke tilgang til filen: '; $PHPMAILER_LANG['file_access'] = 'Får ikke tilgang til filen: ';
$PHPMAILER_LANG['file_open'] = 'Fil feil: Kunne ikke åpne filen: '; $PHPMAILER_LANG['file_open'] = 'Fil Feil: Kunne ikke åpne filen: ';
$PHPMAILER_LANG['from_failed'] = 'Følgende avsenderadresse feilet: '; $PHPMAILER_LANG['from_failed'] = 'Følgende Frå adresse feilet: ';
$PHPMAILER_LANG['instantiate'] = 'Kunne ikke initialisere mailfunksjonen.'; $PHPMAILER_LANG['instantiate'] = 'Kunne ikke initialisere post funksjon.';
$PHPMAILER_LANG['invalid_address'] = 'Meldingen ble ikke sendt, følgende adresse er ugyldig: '; $PHPMAILER_LANG['invalid_address'] = 'Ugyldig adresse: ';
$PHPMAILER_LANG['provide_address'] = 'Du må angi minst en mottakeradresse.'; $PHPMAILER_LANG['mailer_not_supported'] = ' sender er ikke støttet.';
$PHPMAILER_LANG['mailer_not_supported'] = ' mailer er ikke supportert.'; $PHPMAILER_LANG['provide_address'] = 'Du må opppgi minst en mottakeradresse.';
$PHPMAILER_LANG['recipients_failed'] = 'SMTP Feil: Følgende mottagere feilet: '; $PHPMAILER_LANG['recipients_failed'] = 'SMTP Feil: Følgende mottakeradresse feilet: ';
$PHPMAILER_LANG['signing'] = 'Signeringsfeil: '; $PHPMAILER_LANG['signing'] = 'Signering Feil: ';
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() feilet.'; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP connect() feilet.';
$PHPMAILER_LANG['smtp_error'] = 'SMTP-serverfeil: '; $PHPMAILER_LANG['smtp_error'] = 'SMTP server feil: ';
$PHPMAILER_LANG['variable_set'] = 'Kan ikke sette eller resette variabelen: '; $PHPMAILER_LANG['variable_set'] = 'Kan ikke skrive eller omskrive variabel: ';
//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; $PHPMAILER_LANG['extension_missing'] = 'Utvidelse mangler: ';

View File

@ -5,6 +5,7 @@
* @author Paulo Henrique Garcia <paulo@controllerweb.com.br> * @author Paulo Henrique Garcia <paulo@controllerweb.com.br>
* @author Lucas Guimarães <lucas@lucasguimaraes.com> * @author Lucas Guimarães <lucas@lucasguimaraes.com>
* @author Phelipe Alves <phelipealvesdesouza@gmail.com> * @author Phelipe Alves <phelipealvesdesouza@gmail.com>
* @author Fabio Beneditto <fabiobeneditto@gmail.com>
*/ */
$PHPMAILER_LANG['authenticate'] = 'Erro de SMTP: Não foi possível autenticar.'; $PHPMAILER_LANG['authenticate'] = 'Erro de SMTP: Não foi possível autenticar.';
@ -15,7 +16,7 @@ $PHPMAILER_LANG['encoding'] = 'Codificação desconhecida: ';
$PHPMAILER_LANG['execute'] = 'Não foi possível executar: '; $PHPMAILER_LANG['execute'] = 'Não foi possível executar: ';
$PHPMAILER_LANG['file_access'] = 'Não foi possível acessar o arquivo: '; $PHPMAILER_LANG['file_access'] = 'Não foi possível acessar o arquivo: ';
$PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: Não foi possível abrir o arquivo: '; $PHPMAILER_LANG['file_open'] = 'Erro de Arquivo: Não foi possível abrir o arquivo: ';
$PHPMAILER_LANG['from_failed'] = 'Os seguintes remententes falharam: '; $PHPMAILER_LANG['from_failed'] = 'Os seguintes remetentes falharam: ';
$PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.'; $PHPMAILER_LANG['instantiate'] = 'Não foi possível instanciar a função mail.';
$PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: '; $PHPMAILER_LANG['invalid_address'] = 'Endereço de e-mail inválido: ';
$PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.'; $PHPMAILER_LANG['mailer_not_supported'] = ' mailer não é suportado.';

View File

@ -23,4 +23,4 @@ $PHPMAILER_LANG['signing'] = 'Грешка приликом при
$PHPMAILER_LANG['smtp_connect_failed'] = 'Повезивање са SMTP сервером није успело.'; $PHPMAILER_LANG['smtp_connect_failed'] = 'Повезивање са SMTP сервером није успело.';
$PHPMAILER_LANG['smtp_error'] = 'Грешка SMTP сервера: '; $PHPMAILER_LANG['smtp_error'] = 'Грешка SMTP сервера: ';
$PHPMAILER_LANG['variable_set'] = 'Није могуће задати променљиву, нити је вратити уназад: '; $PHPMAILER_LANG['variable_set'] = 'Није могуће задати променљиву, нити је вратити уназад: ';
//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; $PHPMAILER_LANG['extension_missing'] = 'Недостаје проширење: ';

View File

@ -6,6 +6,7 @@
* @author Can Yılmaz * @author Can Yılmaz
* @author Mehmet Benlioğlu * @author Mehmet Benlioğlu
* @author @yasinaydin * @author @yasinaydin
* @author Ogün Karakuş
*/ */
$PHPMAILER_LANG['authenticate'] = 'SMTP Hatası: Oturum açılamadı.'; $PHPMAILER_LANG['authenticate'] = 'SMTP Hatası: Oturum açılamadı.';
@ -26,4 +27,4 @@ $PHPMAILER_LANG['signing'] = 'İmzalama hatası: ';
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP connect() fonksiyonu başarısız.'; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP connect() fonksiyonu başarısız.';
$PHPMAILER_LANG['smtp_error'] = 'SMTP sunucu hatası: '; $PHPMAILER_LANG['smtp_error'] = 'SMTP sunucu hatası: ';
$PHPMAILER_LANG['variable_set'] = 'Değişken ayarlanamadı ya da sıfırlanamadı: '; $PHPMAILER_LANG['variable_set'] = 'Değişken ayarlanamadı ya da sıfırlanamadı: ';
//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; $PHPMAILER_LANG['extension_missing'] = 'Eklenti bulunamadı: ';

View File

@ -4,13 +4,14 @@
* @package PHPMailer * @package PHPMailer
* @author liqwei <liqwei@liqwei.com> * @author liqwei <liqwei@liqwei.com>
* @author young <masxy@foxmail.com> * @author young <masxy@foxmail.com>
* @author Teddysun <i@teddysun.com>
*/ */
$PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。'; $PHPMAILER_LANG['authenticate'] = 'SMTP 错误:登录失败。';
$PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。'; $PHPMAILER_LANG['connect_host'] = 'SMTP 错误:无法连接到 SMTP 主机。';
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。'; $PHPMAILER_LANG['data_not_accepted'] = 'SMTP 错误:数据不被接受。';
$PHPMAILER_LANG['empty_message'] = '邮件正文为空。'; $PHPMAILER_LANG['empty_message'] = '邮件正文为空。';
$PHPMAILER_LANG['encoding'] = '未知编码: '; $PHPMAILER_LANG['encoding'] = '未知编码';
$PHPMAILER_LANG['execute'] = '无法执行:'; $PHPMAILER_LANG['execute'] = '无法执行:';
$PHPMAILER_LANG['file_access'] = '无法访问文件:'; $PHPMAILER_LANG['file_access'] = '无法访问文件:';
$PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:'; $PHPMAILER_LANG['file_open'] = '文件错误:无法打开文件:';
@ -22,6 +23,6 @@ $PHPMAILER_LANG['provide_address'] = '必须提供至少一个收件人地
$PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:'; $PHPMAILER_LANG['recipients_failed'] = 'SMTP 错误:收件人地址错误:';
$PHPMAILER_LANG['signing'] = '登录失败:'; $PHPMAILER_LANG['signing'] = '登录失败:';
$PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP服务器连接失败。'; $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP服务器连接失败。';
$PHPMAILER_LANG['smtp_error'] = 'SMTP服务器出错: '; $PHPMAILER_LANG['smtp_error'] = 'SMTP服务器出错';
$PHPMAILER_LANG['variable_set'] = '无法设置或重置变量:'; $PHPMAILER_LANG['variable_set'] = '无法设置或重置变量:';
//$PHPMAILER_LANG['extension_missing'] = 'Extension missing: '; $PHPMAILER_LANG['extension_missing'] = '丢失模块 Extension';

View File

@ -63,6 +63,7 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.php';
include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.php'; include $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.mailbox.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.customize.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.domain_admin.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.policy.inc.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/functions.dkim.inc.php';

View File

@ -53,6 +53,7 @@ if (isset($_SESSION['mailcow_cc_role']) && session_check() === false) {
'msg' => 'Form token invalid or timed out' 'msg' => 'Form token invalid or timed out'
); );
$_POST = array(); $_POST = array();
$_FILES = array();
} }
// Handle logouts // Handle logouts

View File

@ -63,4 +63,14 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
unset_tfa_key($_POST); unset_tfa_key($_POST);
} }
} }
if (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == "admin") {
if (isset($_POST["submit_main_logo"])) {
if ($_FILES['main_logo']['error'] == 0) {
customize('add', 'main_logo', $_FILES);
}
}
if (isset($_POST["reset_main_logo"])) {
customize('delete', 'main_logo');
}
}
?> ?>

View File

@ -30,10 +30,12 @@ if ($https_port === FALSE) {
//$https_port = 1234; //$https_port = 1234;
// Other settings => // Other settings =>
$autodiscover_config = array( $autodiscover_config = array(
// Enable the autodiscover service for Outlook desktop clients
'useEASforOutlook' => 'yes',
// General autodiscover service type: "activesync" or "imap" // General autodiscover service type: "activesync" or "imap"
// emClient uses autodiscover, but does not support ActiveSync. mailcow excludes emClient from ActiveSync.
'autodiscoverType' => 'activesync', 'autodiscoverType' => 'activesync',
// If autodiscoverType => activesync, also use ActiveSync (EAS) for Outlook desktop clients (>= Outlook 2013 on Windows)
// Outlook for Mac does not support ActiveSync
'useEASforOutlook' => 'yes',
// Please don't use STARTTLS-enabled service ports in the "port" variable. // Please don't use STARTTLS-enabled service ports in the "port" variable.
// The autodiscover service will always point to SMTPS and IMAPS (TLS-wrapped services). // The autodiscover service will always point to SMTPS and IMAPS (TLS-wrapped services).
// The autoconfig service will additionally announce the STARTTLS-enabled ports, specified in the "tlsport" variable. // The autoconfig service will additionally announce the STARTTLS-enabled ports, specified in the "tlsport" variable.

View File

@ -23,7 +23,7 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?= $lang['login']['login']; ?></div> <div class="panel-heading"><span class="glyphicon glyphicon-user" aria-hidden="true"></span> <?= $lang['login']['login']; ?></div>
<div class="panel-body"> <div class="panel-body">
<div class="text-center"><img style="max-width: 250px;" src="/img/cow_mailcow.svg" alt="mailcow"></div> <div class="text-center mailcow-logo"><img src="<?=($main_logo = customize('get', 'main_logo')) ? $main_logo : '/img/cow_mailcow.svg';?>" alt="mailcow"></div>
<legend>mailcow UI</legend> <legend>mailcow UI</legend>
<form method="post" autofill="off"> <form method="post" autofill="off">
<div class="form-group"> <div class="form-group">
@ -72,6 +72,14 @@ $_SESSION['return_to'] = $_SERVER['REQUEST_URI'];
<a href="<?= htmlspecialchars($app['link']); ?>" role="button" title="<?= htmlspecialchars($app['description']); ?>" class="btn btn-lg btn-default"><?= htmlspecialchars($app['name']); ?></a>&nbsp; <a href="<?= htmlspecialchars($app['link']); ?>" role="button" title="<?= htmlspecialchars($app['description']); ?>" class="btn btn-lg btn-default"><?= htmlspecialchars($app['name']); ?></a>&nbsp;
<?php <?php
endforeach; endforeach;
$app_links = customize('get', 'app_links');
foreach ($app_links as $row) {
foreach ($row as $key => $val):
?>
<a href="<?= htmlspecialchars($val); ?>" role="button" class="btn btn-lg btn-default"><?= htmlspecialchars($key); ?></a>&nbsp;
<?php
endforeach;
}
?> ?>
</div> </div>
</div> </div>

View File

@ -124,6 +124,10 @@ jQuery(function($){
e.preventDefault(); e.preventDefault();
draw_postfix_logs(); draw_postfix_logs();
}); });
$("#refresh_autodiscover_log").on('click', function(e) {
e.preventDefault();
draw_autodiscover_logs();
});
$("#refresh_dovecot_log").on('click', function(e) { $("#refresh_dovecot_log").on('click', function(e) {
e.preventDefault(); e.preventDefault();
draw_dovecot_logs(); draw_dovecot_logs();
@ -192,6 +196,52 @@ jQuery(function($){
} }
}); });
} }
function draw_autodiscover_logs() {
ft_autodiscover_logs = FooTable.init('#autodiscover_log', {
"columns": [
{"name":"time","formatter":function unix_time_format(tm) { var date = new Date(tm ? tm * 1000 : 0); return date.toLocaleString();},"title":lang.time,"style":{"width":"170px"}},
{"name":"ua","title":"User-Agent","style":{"min-width":"200px"}},
{"name":"user","title":"Username","style":{"min-width":"200px"}},
{"name":"service","title":"Service"},
],
"rows": $.ajax({
dataType: 'json',
url: '/api/v1/get/logs/autodiscover/100',
jsonp: false,
error: function () {
console.log('Cannot draw autodiscover log table');
},
success: function (data) {
$.each(data, function (i, item) {
item.ua = '<span style="font-size:small">' + item.ua + '</span>';
if (item.service == "activesync") {
item.service = '<span class="label label-info">ActiveSync</span>';
}
else if (item.service == "imap") {
item.service = '<span class="label label-success">IMAP, SMTP, Cal-/CardDAV</span>';
}
else {
item.service = '<span class="label label-danger">' + item.service + '</span>';
}
});
}
}),
"empty": lang.empty,
"paging": {
"enabled": true,
"limit": 5,
"size": log_pagination_size
},
"filtering": {
"enabled": true,
"position": "left",
"placeholder": lang.filter_table
},
"sorting": {
"enabled": true
}
});
}
function draw_fail2ban_logs() { function draw_fail2ban_logs() {
ft_fail2ban_logs = FooTable.init('#fail2ban_log', { ft_fail2ban_logs = FooTable.init('#fail2ban_log', {
"columns": [ "columns": [
@ -637,6 +687,7 @@ jQuery(function($){
}); });
} }
draw_postfix_logs(); draw_postfix_logs();
draw_autodiscover_logs();
draw_dovecot_logs(); draw_dovecot_logs();
draw_sogo_logs(); draw_sogo_logs();
draw_fail2ban_logs(); draw_fail2ban_logs();
@ -679,6 +730,24 @@ jQuery(function($){
} }
}); });
}) })
function add_table_row(table_id) {
var row = $('<tr />');
cols = '<td><input class="input-sm form-control" data-id="app_links" type="text" name="app" required></td>';
cols += '<td><input class="input-sm form-control" data-id="app_links" type="text" name="href" required></td>';
cols += '<td><a href="#" role="button" class="btn btn-xs btn-default" type="button">Remove row</a></td>';
row.append(cols);
table_id.append(row);
}
$('#app_link_table').on('click', 'tr a', function (e) {
e.preventDefault();
$(this).parents('tr').remove();
});
$('#add_app_link_row').click(function() {
add_table_row($('#app_link_table'));
});
}); });
$(window).load(function(){ $(window).load(function(){

View File

@ -14,7 +14,7 @@ $(document).ready(function() {
}); });
return o; return o;
}; };
// Collect values of input fields with name "multi_select" an same data-id to js array multi_data[data-id] // Collect values of input fields with name "multi_select" and same data-id to js array multi_data[data-id]
var multi_data = []; var multi_data = [];
$(document).on('change', 'input[name=multi_select]:checkbox', function() { $(document).on('change', 'input[name=multi_select]:checkbox', function() {
if ($(this).is(':checked') && $(this).data('id')) { if ($(this).is(':checked') && $(this).data('id')) {
@ -105,7 +105,8 @@ $(document).ready(function() {
url: '/api/v1/' + api_url, url: '/api/v1/' + api_url,
jsonp: false, jsonp: false,
complete: function(data) { complete: function(data) {
// var reponse = (JSON.parse(data.responseText)); var response = (data.responseText);
// alert(response);
// console.log(reponse.type); // console.log(reponse.type);
// console.log(reponse.msg); // console.log(reponse.msg);
window.location = window.location.href.split("#")[0]; window.location = window.location.href.split("#")[0];

File diff suppressed because one or more lines are too long

View File

@ -769,6 +769,21 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
echo '{}'; echo '{}';
} }
break; break;
case "autodiscover":
if (isset($extra) && !empty($extra)) {
$extra = intval($extra);
$logs = get_logs('autodiscover-mailcow', $extra);
}
else {
$logs = get_logs('autodiscover-mailcow', -1);
}
if (isset($logs) && !empty($logs)) {
echo json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
}
else {
echo '{}';
}
break;
case "sogo": case "sogo":
if (isset($extra) && !empty($extra)) { if (isset($extra) && !empty($extra)) {
$extra = intval($extra); $extra = intval($extra);
@ -1780,6 +1795,48 @@ if (isset($_SESSION['mailcow_cc_role']) || isset($_SESSION['pending_mailcow_cc_u
)); ));
} }
break; break;
case "app_links":
if (isset($_POST['attr'])) {
$attr = (array)json_decode($_POST['attr'], true);
if (is_array($attr)) {
if (customize('edit', 'app_links', $attr) === false) {
if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']);
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Edit failed'
));
}
exit();
}
else {
if (isset($_SESSION['return'])) {
echo json_encode($_SESSION['return']);
}
else {
echo json_encode(array(
'type' => 'success',
'msg' => 'Task completed'
));
}
}
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Incomplete post data'
));
}
}
else {
echo json_encode(array(
'type' => 'error',
'msg' => 'Incomplete post data'
));
}
break;
case "relayhost": case "relayhost":
if (isset($_POST['items']) && isset($_POST['attr'])) { if (isset($_POST['items']) && isset($_POST['attr'])) {
$items = (array)json_decode($_POST['items'], true); $items = (array)json_decode($_POST['items'], true);

View File

@ -512,3 +512,14 @@ $lang['success']['relayhost_removed'] = "Relayhost %s wurde entfernt";
$lang['success']['relayhost_added'] = "Relayhost %s wurde hinzugefügt"; $lang['success']['relayhost_added'] = "Relayhost %s wurde hinzugefügt";
$lang['admin']['relay_from'] = "Absenderadresse"; $lang['admin']['relay_from'] = "Absenderadresse";
$lang['admin']['relay_run'] = "Test durchführen"; $lang['admin']['relay_run'] = "Test durchführen";
$lang['admin']['customize'] = "Anpassung";
$lang['admin']['change_logo'] = "Logo ändern";
$lang['admin']['logo_info'] = "Die hochgeladene Grafik wird für die Navigationsleiste auf eine Höhe von 40px skaliert. Für die Startseite ist eine Skalierung auf eine maximale Breite von 250px programmiert. Eine frei skalierbare Grafik (etwa SVG) wird empfohlen.";
$lang['admin']['upload'] = "Hochladen";
$lang['admin']['app_links'] = "App Links";
$lang['admin']['app_name'] = "App Name";
$lang['admin']['link'] = "Link";
$lang['admin']['remove_row'] = "Zeile entfernen";
$lang['admin']['add_row'] = "Zeile hinzufügen";
$lang['admin']['reset_default'] = "Auf Standard zurücksetzen";
$lang['admin']['merged_vars_hint'] = 'Ausgegraute Zeilen wurden aus der Datei <code>vars.inc.(local.)php</code> gelesen und können nicht mittels UI verändert werden.';

View File

@ -141,9 +141,9 @@ $lang['user']['weeks'] = 'Weeks';
$lang['user']['spamfilter'] = 'Spam filter'; $lang['user']['spamfilter'] = 'Spam filter';
$lang['admin']['spamfilter'] = 'Spam filter'; $lang['admin']['spamfilter'] = 'Spam filter';
$lang['user']['spamfilter_wl'] = 'Whitelist'; $lang['user']['spamfilter_wl'] = 'Whitelist';
$lang['user']['spamfilter_wl_desc'] = 'Whitelisted email addresses to <b>never</b> classify as spam. Wildcards maybe used.'; $lang['user']['spamfilter_wl_desc'] = 'Whitelisted email addresses to <b>never</b> classify as spam. Wildcards may be used.';
$lang['user']['spamfilter_bl'] = 'Blacklist'; $lang['user']['spamfilter_bl'] = 'Blacklist';
$lang['user']['spamfilter_bl_desc'] = 'Blacklisted email addresses to <b>always</b> classify as spam and reject. Wildcards maybe used.'; $lang['user']['spamfilter_bl_desc'] = 'Blacklisted email addresses to <b>always</b> classify as spam and reject. Wildcards may be used.';
$lang['user']['spamfilter_behavior'] = 'Rating'; $lang['user']['spamfilter_behavior'] = 'Rating';
$lang['user']['spamfilter_table_rule'] = 'Rule'; $lang['user']['spamfilter_table_rule'] = 'Rule';
$lang['user']['spamfilter_table_action'] = 'Action'; $lang['user']['spamfilter_table_action'] = 'Action';
@ -525,3 +525,23 @@ $lang['success']['relayhost_removed'] = "Relayhost %s has been removed";
$lang['success']['relayhost_added'] = "Relayhost %s has been added"; $lang['success']['relayhost_added'] = "Relayhost %s has been added";
$lang['admin']['relay_from'] = '"From:" address'; $lang['admin']['relay_from'] = '"From:" address';
$lang['admin']['relay_run'] = "Run test"; $lang['admin']['relay_run'] = "Run test";
$lang['admin']['customize'] = "Customize";
$lang['admin']['change_logo'] = "Change logo";
$lang['admin']['logo_info'] = "Your image will be scaled to a height of 40px for the top navigation bar and a max. width of 250px for the start page. A scalable graphic is highly recommended.";
$lang['admin']['upload'] = "Upload";
$lang['admin']['app_links'] = "App links";
$lang['admin']['app_name'] = "App name";
$lang['admin']['link'] = "Link";
$lang['admin']['remove_row'] = "Remove row";
$lang['admin']['add_row'] = "Add row";
$lang['admin']['reset_default'] = "Reset to default";
$lang['admin']['merged_vars_hint'] = 'Greyed out rows were merged from <code>vars.inc.(local.)php</code> and cannot be modified.';
$lang['edit']['tls_policy'] = "Change TLS policy";
$lang['edit']['spam_score'] = "Set a custom spam score";
$lang['edit']['spam_policy'] = "Add or remove items to white-/blacklist";
$lang['edit']['delimiter_action'] = "Change delimiter action";
$lang['edit']['syncjobs'] = "Add or change sync jobs";
$lang['edit']['eas_reset'] = "Reset EAS devices";
$lang['edit']['spam_alias'] = "Create or change time limited alias addresses";

View File

@ -406,6 +406,13 @@ if (!isset($_SESSION['mailcow_cc_role'])) {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" value="1" name="delete2"> <?=$lang['add']['delete2'];?></label>
</div>
</div>
</div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">
<div class="checkbox"> <div class="checkbox">

View File

@ -268,8 +268,8 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<h4><?=$lang['user']['spamfilter_behavior'];?></h4> <h4><?=$lang['user']['spamfilter_behavior'];?></h4>
<form class="form-horizontal" role="form" data-id="spam_score" method="post"> <form class="form-horizontal" role="form" data-id="spam_score" method="post">
<div class="form-group"> <div class="form-group">
<div class="col-sm-12"> <div class="col-lg-6 col-sm-12">
<input name="spam_score" id="spam_score" type="text" style="width: 100% !important;" <input name="spam_score" id="spam_score" type="text" style="width: 100%;"
data-provide="slider" data-provide="slider"
data-slider-min="1" data-slider-min="1"
data-slider-max="2000" data-slider-max="2000"
@ -321,14 +321,13 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_wl_mailbox" 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="policy_wl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_wl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
</div> </div>
</div> </div>
<form class="form-inline" data-id="add_wl_policy_mailbox"> <form class="form-inline" data-id="add_wl_policy_mailbox">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required> <input type="text" class="form-control" name="object_from" id="object_from" placeholder="*@example.org" required>
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-success" id="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":"<?= $username; ?>","object_list":"wl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button> <button class="btn btn-default" id="add_item" data-id="add_wl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":"<?= $username; ?>","object_list":"wl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button>
</span> </span>
</div> </div>
</form> </form>
@ -349,7 +348,6 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<div class="btn-group"> <div class="btn-group">
<a class="btn btn-sm btn-default" id="toggle_multi_select_all" data-id="policy_bl_mailbox" 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="policy_bl_mailbox" href="#"><span class="glyphicon glyphicon-check" aria-hidden="true"></span> <?=$lang['mailbox']['toggle_all'];?></a>
<a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li> <a class="btn btn-sm btn-danger" id="delete_selected" data-id="policy_bl_mailbox" data-api-url='delete/mailbox-policy' href="#"><?=$lang['mailbox']['remove'];?></a></li>
</ul>
</div> </div>
</div> </div>
<form class="form-inline" data-id="add_bl_policy_mailbox"> <form class="form-inline" data-id="add_bl_policy_mailbox">
@ -358,7 +356,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
<input type="hidden" name="username" value="<?= $username ;?>"> <input type="hidden" name="username" value="<?= $username ;?>">
<input type="hidden" name="object_list" value="bl"> <input type="hidden" name="object_list" value="bl">
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-success" id="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":"<?= $username; ?>","object_list":"bl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button> <button class="btn btn-default" id="add_item" data-id="add_bl_policy_mailbox" data-api-url='add/mailbox-policy' data-api-attr='{"username":"<?= $username; ?>","object_list":"bl"}' href="#"><span class="glyphicon glyphicon-plus"></span> <?=$lang['user']['spamfilter_table_add'];?></button>
</span> </span>
</div> </div>
</form> </form>

View File

@ -5,7 +5,6 @@ services:
image: mailcow/unbound:1.0 image: mailcow/unbound:1.0
build: ./data/Dockerfiles/unbound build: ./data/Dockerfiles/unbound
command: /usr/sbin/unbound command: /usr/sbin/unbound
init: true
depends_on: depends_on:
mysql-mailcow: mysql-mailcow:
condition: service_healthy condition: service_healthy
@ -34,11 +33,9 @@ services:
- MYSQL_DATABASE=${DBNAME} - MYSQL_DATABASE=${DBNAME}
- MYSQL_USER=${DBUSER} - MYSQL_USER=${DBUSER}
- MYSQL_PASSWORD=${DBPASS} - MYSQL_PASSWORD=${DBPASS}
init: true
restart: always restart: always
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
networks: networks:
mailcow-network: mailcow-network:
ipv4_address: 172.22.1.250 ipv4_address: 172.22.1.250
@ -52,7 +49,6 @@ services:
restart: always restart: always
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
networks: networks:
mailcow-network: mailcow-network:
ipv4_address: 172.22.1.249 ipv4_address: 172.22.1.249
@ -60,24 +56,22 @@ services:
- redis - redis
clamd-mailcow: clamd-mailcow:
image: mailcow/clamd:1.3 image: mailcow/clamd:1.5
build: ./data/Dockerfiles/clamd build: ./data/Dockerfiles/clamd
restart: on-failure restart: always
environment: environment:
- SKIP_CLAMD=${SKIP_CLAMD:-n} - SKIP_CLAMD=${SKIP_CLAMD:-n}
init: true
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
networks: networks:
mailcow-network: mailcow-network:
aliases: aliases:
- clamd - clamd
rspamd-mailcow: rspamd-mailcow:
image: mailcow/rspamd:1.8 image: mailcow/rspamd:1.12
build: ./data/Dockerfiles/rspamd build: ./data/Dockerfiles/rspamd
command: "/usr/bin/rspamd -f -u _rspamd -g _rspamd" stop_grace_period: 30s
depends_on: depends_on:
- nginx-mailcow - nginx-mailcow
volumes: volumes:
@ -88,10 +82,8 @@ services:
- dkim-vol-1:/data/dkim - dkim-vol-1:/data/dkim
- rspamd-vol-1:/var/lib/rspamd - rspamd-vol-1:/var/lib/rspamd
restart: always restart: always
init: true
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
hostname: rspamd hostname: rspamd
networks: networks:
mailcow-network: mailcow-network:
@ -100,13 +92,13 @@ services:
- rspamd - rspamd
php-fpm-mailcow: php-fpm-mailcow:
image: mailcow/phpfpm:1.1 image: mailcow/phpfpm:1.3
build: ./data/Dockerfiles/phpfpm build: ./data/Dockerfiles/phpfpm
command: "php-fpm -d date.timezone=${TZ}" command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on: depends_on:
- redis-mailcow - redis-mailcow
volumes: volumes:
- ./data/web:/web:ro - ./data/web:/web:rw
- ./data/conf/rspamd/dynmaps:/dynmaps:ro - ./data/conf/rspamd/dynmaps:/dynmaps:ro
- dkim-vol-1:/data/dkim - dkim-vol-1:/data/dkim
environment: environment:
@ -125,14 +117,13 @@ services:
restart: always restart: always
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
networks: networks:
mailcow-network: mailcow-network:
aliases: aliases:
- phpfpm - phpfpm
sogo-mailcow: sogo-mailcow:
image: mailcow/sogo:1.8 image: mailcow/sogo:1.10
build: ./data/Dockerfiles/sogo build: ./data/Dockerfiles/sogo
environment: environment:
- DBNAME=${DBNAME} - DBNAME=${DBNAME}
@ -145,7 +136,6 @@ services:
restart: always restart: always
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
networks: networks:
mailcow-network: mailcow-network:
ipv4_address: 172.22.1.252 ipv4_address: 172.22.1.252
@ -153,7 +143,7 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: mailcow/dovecot:1.8 image: mailcow/dovecot:1.9
build: ./data/Dockerfiles/dovecot build: ./data/Dockerfiles/dovecot
volumes: volumes:
- ./data/conf/dovecot:/usr/local/etc/dovecot - ./data/conf/dovecot:/usr/local/etc/dovecot
@ -180,7 +170,6 @@ services:
hard: 40000 hard: 40000
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
hostname: ${MAILCOW_HOSTNAME} hostname: ${MAILCOW_HOSTNAME}
networks: networks:
mailcow-network: mailcow-network:
@ -188,7 +177,7 @@ services:
- dovecot - dovecot
postfix-mailcow: postfix-mailcow:
image: mailcow/postfix:1.4 image: mailcow/postfix:1.7
build: ./data/Dockerfiles/postfix build: ./data/Dockerfiles/postfix
volumes: volumes:
- ./data/conf/postfix:/opt/postfix/conf - ./data/conf/postfix:/opt/postfix/conf
@ -206,7 +195,6 @@ services:
restart: always restart: always
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
hostname: ${MAILCOW_HOSTNAME} hostname: ${MAILCOW_HOSTNAME}
networks: networks:
mailcow-network: mailcow-network:
@ -218,7 +206,6 @@ services:
restart: always restart: always
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
networks: networks:
mailcow-network: mailcow-network:
aliases: aliases:
@ -232,6 +219,7 @@ services:
command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active && command: /bin/sh -c "envsubst < /etc/nginx/conf.d/templates/listen_plain.template > /etc/nginx/conf.d/listen_plain.active &&
envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active && envsubst < /etc/nginx/conf.d/templates/listen_ssl.template > /etc/nginx/conf.d/listen_ssl.active &&
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 &&
nginx -qt &&
until ping phpfpm -c1 > /dev/null; do sleep 1; done && until ping phpfpm -c1 > /dev/null; do sleep 1; done &&
exec nginx -g 'daemon off;'" exec nginx -g 'daemon off;'"
environment: environment:
@ -249,7 +237,6 @@ services:
restart: always restart: always
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
networks: networks:
mailcow-network: mailcow-network:
ipv4_address: 172.22.1.251 ipv4_address: 172.22.1.251
@ -259,12 +246,11 @@ services:
acme-mailcow: acme-mailcow:
depends_on: depends_on:
- nginx-mailcow - nginx-mailcow
image: mailcow/acme:1.17 - mysql-mailcow
image: mailcow/acme:1.22
build: ./data/Dockerfiles/acme build: ./data/Dockerfiles/acme
init: true
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
environment: environment:
- ADDITIONAL_SAN=${ADDITIONAL_SAN} - ADDITIONAL_SAN=${ADDITIONAL_SAN}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME} - MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
@ -277,17 +263,16 @@ services:
- ./data/web/.well-known/acme-challenge:/var/www/acme:rw - ./data/web/.well-known/acme-challenge:/var/www/acme:rw
- ./data/assets/ssl:/var/lib/acme/:rw - ./data/assets/ssl:/var/lib/acme/:rw
- ./data/assets/ssl-example:/var/lib/ssl-example/:ro - ./data/assets/ssl-example:/var/lib/ssl-example/:ro
- /var/run/docker.sock:/var/run/docker.sock:ro restart: always
# do not restart the container too often. Things get worse when we hit let's encrypt's ratelimit.
restart: on-failure:1
networks: networks:
mailcow-network: mailcow-network:
aliases: aliases:
- acme - acme
fail2ban-mailcow: fail2ban-mailcow:
image: mailcow/fail2ban:1.6 image: mailcow/fail2ban:1.7
build: ./data/Dockerfiles/fail2ban build: ./data/Dockerfiles/fail2ban
stop_grace_period: 30s
depends_on: depends_on:
- dovecot-mailcow - dovecot-mailcow
- postfix-mailcow - postfix-mailcow
@ -296,17 +281,45 @@ services:
- redis-mailcow - redis-mailcow
restart: always restart: always
privileged: true privileged: true
init: true
environment: environment:
- TZ=${TZ} - TZ=${TZ}
- SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-no} - SKIP_FAIL2BAN=${SKIP_FAIL2BAN:-n}
network_mode: "host" network_mode: "host"
dns: dns:
- 172.22.1.254 - 172.22.1.254
dns_search: mailcow-network
volumes: volumes:
- /lib/modules:/lib/modules:ro - /lib/modules:/lib/modules:ro
watchdog-mailcow:
image: mailcow/watchdog:1.8
build: ./data/Dockerfiles/watchdog
volumes:
- vmail-vol-1:/vmail:ro
restart: always
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
- DBPASS=${DBPASS}
- USE_WATCHDOG=${USE_WATCHDOG:-n}
- WATCHDOG_NOTIFY_EMAIL=${WATCHDOG_NOTIFY_EMAIL}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
networks:
mailcow-network:
ipv4_address: 172.22.1.248
aliases:
- watchdog
dockerapi-mailcow:
image: mailcow/dockerapi:1.0
stop_grace_period: 3s
build: ./data/Dockerfiles/dockerapi
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
mailcow-network:
aliases:
- dockerapi
ipv6nat: ipv6nat:
image: robbertkl/ipv6nat image: robbertkl/ipv6nat
restart: always restart: always

View File

@ -17,9 +17,9 @@ if [ -z "$MAILCOW_HOSTNAME" ]; then
fi fi
if [[ -a /etc/timezone ]]; then if [[ -a /etc/timezone ]]; then
TZ=$(cat /etc/timezone) TZ=$(cat /etc/timezone)
elif [[ -a /etc/localtime ]]; then elif [[ -a /etc/localtime ]]; then
TZ=$(readlink /etc/localtime|sed -n 's|^.*zoneinfo/||p') TZ=$(readlink /etc/localtime|sed -n 's|^.*zoneinfo/||p')
fi fi
if [ -z "$TZ" ]; then if [ -z "$TZ" ]; then
@ -84,18 +84,23 @@ COMPOSE_PROJECT_NAME=mailcow-dockerized
# Additional SAN for the certificate # Additional SAN for the certificate
ADDITIONAL_SAN= ADDITIONAL_SAN=
# To never run acme-mailcow for Let's Encrypt, set this to y
SKIP_LETS_ENCRYPT=n
# Skip IPv4 check in ACME container # Skip running ACME (acme-mailcow, Let's Encrypt certs) - y/n
SKIP_LETS_ENCRYPT=n
# Skip IPv4 check in ACME container - y/n
SKIP_IP_CHECK=n SKIP_IP_CHECK=n
# To never run fail2ban-mailcow # Skip Fail2ban implementation (fail2ban-mailcow) - y/n
SKIP_FAIL2BAN=n SKIP_FAIL2BAN=n
# To never run clamd-mailcow # Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n
SKIP_CLAMD=n SKIP_CLAMD=n
# Enable watchdog (watchdog-mailcow) to restart unhealthy containers (experimental)
USE_WATCHDOG=n
# Send notifications by mail (no DKIM signature, sent from watchdog@MAILCOW_HOSTNAME)
#WATCHDOG_NOTIFY_EMAIL=
EOF EOF
mkdir -p data/assets/ssl mkdir -p data/assets/ssl

View File

@ -0,0 +1,36 @@
#/bin/bash
[[ -f mailcow.conf ]] && source mailcow.conf
[[ -f ../mailcow.conf ]] && source ../mailcow.conf
if [[ -z ${DBUSER} ]] || [[ -z ${DBPASS} ]] || [[ -z ${DBNAME} ]]; then
echo "Cannot find mailcow.conf, make sure this script is run from within the mailcow folder."
exit 1
fi
echo -n "Checking MySQL service... "
if [[ -z $(docker ps -qf name=mysql-mailcow) ]]; then
echo "failed"
echo "MySQL (mysql-mailcow) is not up and running, exiting..."
exit 1
fi
echo "OK"
read -r -p "Are you sure you want to reset the mailcow administrator account? [y/N] " response
response=${response,,} # tolower
if [[ "$response" =~ ^(yes|y)$ ]]; then
echo -e "\nWorking, please wait..."
docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM admin;"
docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "INSERT INTO admin (username, password, superadmin, created, modified, active) VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1);"
docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM domain_admins WHERE username='admin';"
docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "INSERT INTO domain_admins (username, domain, created, active) VALUES ('admin', 'ALL', NOW(), 1);"
docker exec -it $(docker ps -qf name=mysql-mailcow) mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM tfa WHERE username='admin';"
echo "
Reset credentials:
---
Username: admin
Password: moohoo
TFA: none
"
else
echo "Operation canceled."
fi

View File

@ -0,0 +1,116 @@
#!/bin/bash
[[ -z ${1} ]] && { echo "No parameters given"; exit 1; }
for bin in curl dirmngr; do
if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
done
while [ "$1" != '' ]; do
case "${1}" in
-p|--purge) NC_PURGE=y && shift;;
-i|--install) NC_INSTALL=y && shift;;
*) echo "Unknown parameter: ${1}" && shift;;
esac
done
[[ ${NC_PURGE} == "y" ]] && [[ ${NC_INSTALL} == "y" ]] && { echo "Cannot use -p and -i at the same time"; }
source ./mailcow.conf
if [[ ${NC_PURGE} == "y" ]]; then
docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e \
"$(docker exec -it $(docker ps -f name=mysql-mailcow -q) mysql -uroot -p${DBROOT} -e "SELECT GROUP_CONCAT('DROP TABLE ', TABLE_SCHEMA, '.', TABLE_NAME SEPARATOR ';') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME LIKE 'nc_%' AND TABLE_SCHEMA = '${DBNAME}';" -BN)"
docker exec -it $(docker ps -f name=redis-mailcow -q) /bin/sh -c 'redis-cli KEYS "*nextcloud*" | xargs redis-cli DEL'
if [ -d ./data/web/nextcloud/config ]; then
mv ./data/web/nextcloud/config/ ./data/conf/nextcloud-config-folder-$(date +%s).bak
fi
[[ -d ./data/web/nextcloud ]] && rm -rf ./data/web/nextcloud
[[ -f ./data/conf/nginx/site.nextcloud.custom ]] && mv ./data/conf/nginx/site.nextcloud.custom ./data/conf/nginx/site.nextcloud.custom-$(date +%s).bak
[[ -f ./data/conf/nginx/nextcloud.conf ]] && mv ./data/conf/nginx/nextcloud.conf ./data/conf/nginx/nextcloud.conf-$(date +%s).bak
docker-compose restart nginx-mailcow
elif [[ ${NC_INSTALL} == "y" ]]; then
NC_TYPE=
while [[ ! ${NC_TYPE} =~ ^subfolder$|^subdomain$ ]]; do
read -p "Configure as subdomain or subfolder? [subdomain/subfolder] " NC_TYPE
done
if [[ ${NC_TYPE} == "subdomain" ]]; then
NC_SUBD=
while [[ -z ${NC_SUBD} ]]; do
read -p "Which subdomain? [format: nextcloud.domain.tld] " NC_SUBD
done
if ! ping -q -c2 ${NC_SUBD} > /dev/null 2>&1 ; then
read -p "Cannot ping subdomain, continue anyway? [y|N] " NC_CONT_FAIL
[[ ! ${NC_CONT_FAIL,,} =~ ^(yes|y)$ ]] && { echo "Ok, exiting..."; exit 1; }
fi
fi
ADMIN_NC_PASS=$(</dev/urandom tr -dc A-Za-z0-9 | head -c 28)
NEXTCLOUD_VERSION=$(curl -s https://www.servercow.de/nextcloud/latest.php)
[[ -z ${NEXTCLOUD_VERSION} ]] && { echo "Error, cannot determine nextcloud version, exiting..."; exit 1; }
curl -L# -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2" \
&& curl -L# -o nextcloud.tar.bz2.asc "https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2.asc" \
&& export GNUPGHOME="$(mktemp -d)" \
&& gpg --keyserver ha.pool.sks-keyservers.net --recv-keys 28806A878AE423A28372792ED75899B9A724937A \
&& gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2 \
&& rm -r "$GNUPGHOME" nextcloud.tar.bz2.asc \
&& tar -xjf nextcloud.tar.bz2 -C ./data/web/ \
&& rm nextcloud.tar.bz2 \
&& rm -rf ./data/web/nextcloud/updater \
&& mkdir -p ./data/web/nextcloud/data \
&& mkdir -p ./data/web/nextcloud/custom_apps \
&& 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 -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ maintenance:install \
--database mysql \
--database-host mysql \
--database-name ${DBNAME} \
--database-user ${DBUSER} \
--database-pass ${DBPASS} \
--database-table-prefix nc_ \
--admin-user admin \
--admin-pass ${ADMIN_NC_PASS} \
--data-dir /web/nextcloud/data
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) bash -c "/web/nextcloud/occ config:system:set redis host --value=redis --type=string; \
/web/nextcloud/occ config:system:set redis port --value=6379 --type=integer; \
/web/nextcloud/occ config:system:set memcache.locking --value='\OC\Memcache\Redis' --type=string; \
/web/nextcloud/occ config:system:set memcache.local --value='\OC\Memcache\Redis' --type=string; \
/web/nextcloud/occ config:system:set trusted_proxies 0 --value=fd4d:6169:6c63:6f77::1; \
/web/nextcloud/occ config:system:set trusted_proxies 1 --value=172.22.1.0/24; \
/web/nextcloud/occ config:system:set overwritewebroot --value=/nextcloud; \
/web/nextcloud/occ config:system:set overwritehost --value=${MAILCOW_HOSTNAME}; \
/web/nextcloud/occ config:system:set mail_smtpmode --value=smtp; \
/web/nextcloud/occ config:system:set mail_smtpauthtype --value=LOGIN; \
/web/nextcloud/occ config:system:set mail_from_address --value=nextcloud; \
/web/nextcloud/occ config:system:set mail_domain --value=${MAILCOW_HOSTNAME}; \
/web/nextcloud/occ config:system:set mail_smtphost --value=postfix; \
/web/nextcloud/occ config:system:set mail_smtpport --value=588
/web/nextcloud/occ app:enable user_external
/web/nextcloud/occ config:system:set user_backends 0 arguments 0 --value={dovecot:143/imap/tls/novalidate-cert}
/web/nextcloud/occ config:system:set user_backends 0 class --value=OC_User_IMAP"
if [[ ${NC_TYPE} == "subdomain" ]]; then
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritewebroot --value=/
docker exec -it -u www-data $(docker ps -f name=php-fpm-mailcow -q) /web/nextcloud/occ config:system:set overwritehost --value=nextcloud.develcow.de
cp ./data/assets/nextcloud/nextcloud.conf ./data/conf/nginx/
sed sed -i 's/NC_SUBD/${NC_SUBD}/g' ./data/assets/nextcloud/nextcloud.conf
elif [[ ${NC_TYPE} == "subfolder" ]]; then
cp ./data/assets/nextcloud/site.nextcloud.custom ./data/conf/nginx/
fi
docker-compose restart nginx-mailcow
echo "Login as admin with password: ${ADMIN_NC_PASS}"
fi

View File

@ -1,36 +0,0 @@
#/bin/bash
if [[ ! -f mailcow.conf ]]; then
echo "Cannot find mailcow.conf, make sure this script is run from within the mailcow folder."
exit 1
fi
echo -n "Checking MySQL service... "
docker-compose ps -q mysql-mailcow > /dev/null 2>&1
if [[ $? -ne 0 ]]; then
echo "failed"
echo "MySQL (mysql-mailcow) is not up and running, exiting..."
exit 1
fi
echo "OK"
read -r -p "Are you sure you want to reset the mailcow administrator account? [y/N] " response
response=${response,,} # tolower
if [[ "$response" =~ ^(yes|y)$ ]]; then
echo -e "\nWorking, please wait..."
source mailcow.conf
docker-compose exec -T mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM admin;"
docker-compose exec -T mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "INSERT INTO admin (username, password, superadmin, created, modified, active) VALUES ('admin', '{SSHA256}K8eVJ6YsZbQCfuJvSUbaQRLr0HPLz5rC9IAp0PAFl0tmNDBkMDc0NDAyOTAxN2Rk', 1, NOW(), NOW(), 1);"
docker-compose exec -T mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM domain_admins WHERE username='admin';"
docker-compose exec -T mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "INSERT INTO domain_admins (username, domain, created, active) VALUES ('admin', 'ALL', NOW(), 1);"
docker-compose exec -T mysql-mailcow mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM tfa WHERE username='admin';"
echo "
Reset credentials:
---
Username: admin
Password: moohoo
TFA: none
"
else
echo "Operation canceled."
fi

View File

@ -1,76 +0,0 @@
#/bin/bash
if [[ ! -f mailcow.conf ]]; then
echo "Cannot find mailcow.conf, make sure this script is run from within the mailcow folder."
exit 1
fi
echo -n "Checking Postfix service... "
docker-compose ps -q postfix-mailcow > /dev/null 2>&1
if [[ $? -ne 0 ]]; then
echo "failed"
echo "Postfix (postifx-mailcow) is not up and running, exiting..."
exit 1
fi
echo "OK"
if [[ -z ${1} ]]; then
echo "Usage:"
echo
echo "Setup a relayhost:"
echo "${0} relayhost port (username) (password)"
echo "Username and password are optional parameters."
echo
echo "Reset to defaults:"
echo "${0} reset"
exit 1
fi
if [[ ${1} == "reset" ]]; then
# Reset modified values to their defaults
sed -i "s/^relayhost\ \=.*/relayhost\ \=/" data/conf/postfix/main.cf
sed -i "s/^smtp\_sasl\_password\_maps.*/smtp\_sasl\_password\_maps\ \=/" data/conf/postfix/main.cf
sed -i "s/^smtp\_sasl\_security\_options.*/smtp\_sasl\_security\_options\ \=\ noplaintext\,\ noanonymous/" data/conf/postfix/main.cf
sed -i "s/^smtp\_sasl\_auth\_enable.*/smtp\_sasl\_auth\_enable\ \=\ no/" data/conf/postfix/main.cf
# Also delete the plaintext password file
rm -f data/conf/postfix/smarthost_passwd*
docker-compose exec postfix-mailcow postfix reload
# Exit with dc exit code
exit $?
else
# Try a simple connection to host:port but don't recieve any data
# Abort after 3 seconds
if ! nc -z -v -w3 ${1} ${2} 2>/dev/null; then
echo "Connection to relayhost ${1} failed, aborting..."
exit 1
fi
# Use exact hostname as relayhost, don't lookup the MX record of relayhost
sed -i "s/relayhost\ \=.*/relayhost\ \=\ \[${1}\]\:${2}/" data/conf/postfix/main.cf
if grep -q "smtp_sasl_password_maps" data/conf/postfix/main.cf
then
sed -i "s/^smtp\_sasl\_password\_maps.*/smtp\_sasl\_password\_maps\ \=\ hash\:\/opt\/postfix\/conf\/smarthost\_passwd/" data/conf/postfix/main.cf
else
echo "smtp_sasl_password_maps = hash:/opt/postfix/conf/smarthost_passwd" >> data/conf/postfix/main.cf
fi
if grep -q "smtp_sasl_auth_enable" data/conf/postfix/main.cf
then
sed -i "s/^smtp\_sasl\_auth\_enable.*/smtp\_sasl\_auth\_enable\ \=\ yes/" data/conf/postfix/main.cf
else
echo "smtp_sasl_auth_enable = yes" >> data/conf/postfix/main.cf
fi
if grep -q "smtp_sasl_security_options" data/conf/postfix/main.cf
then
sed -i "s/^smtp\_sasl\_security\_options.*/smtp\_sasl\_security\_options\ \=/" data/conf/postfix/main.cf
else
echo "smtp_sasl_security_options =" >> data/conf/postfix/main.cf
fi
if [[ ! -z ${3} ]]; then
echo ${1} ${3}:${4} > data/conf/postfix/smarthost_passwd
docker-compose exec postfix-mailcow postmap /opt/postfix/conf/smarthost_passwd
fi
docker-compose exec postfix-mailcow chown root:postfix /opt/postfix/conf/smarthost_passwd /opt/postfix/conf/smarthost_passwd.db
docker-compose exec postfix-mailcow chmod 660 /opt/postfix/conf/smarthost_passwd /opt/postfix/conf/smarthost_passwd.db
docker-compose exec postfix-mailcow postfix reload
exit $?
fi

View File

@ -4,7 +4,7 @@ for bin in curl docker-compose docker git awk sha1sum; do
if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi if [[ -z $(which ${bin}) ]]; then echo "Cannot find ${bin}, exiting..."; exit 1; fi
done done
CONFIG_ARRAY=("SKIP_LETS_ENCRYPT" "SKIP_CLAMD" "SKIP_IP_CHECK" "SKIP_FAIL2BAN" "ADDITIONAL_SAN" "DOVEADM_PORT") CONFIG_ARRAY=("SKIP_LETS_ENCRYPT" "USE_WATCHDOG" "WATCHDOG_NOTIFY_EMAIL" "SKIP_CLAMD" "SKIP_IP_CHECK" "SKIP_FAIL2BAN" "ADDITIONAL_SAN" "DOVEADM_PORT")
echo >> mailcow.conf echo >> mailcow.conf
for option in ${CONFIG_ARRAY[@]}; do for option in ${CONFIG_ARRAY[@]}; do
if [[ ${option} == "ADDITIONAL_SAN" ]]; then if [[ ${option} == "ADDITIONAL_SAN" ]]; then
@ -22,6 +22,11 @@ for option in ${CONFIG_ARRAY[@]}; do
echo "Adding new option \"${option}\" to mailcow.conf" echo "Adding new option \"${option}\" to mailcow.conf"
echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf echo "DOVEADM_PORT=127.0.0.1:19991" >> mailcow.conf
fi fi
elif [[ ${option} == "WATCHDOG_NOTIFY_EMAIL" ]]; then
if ! grep -q ${option} mailcow.conf; then
echo "Adding new option \"${option}\" to mailcow.conf"
echo "WATCHDOG_NOTIFY_EMAIL=" >> 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