Complete Refactor for target/bin (#1654)

* documentation and script updates trying to fix #1647
* preparations for refactoring target/bin/
* complete refactor for target/bin/
* changing script output slightly
* outsourcing functions in `bin-helper.sh`
* re-wrote linting to allow for proper shellcheck -x execution
* show explanation for shellcheck ignore
* adding some more information
This commit is contained in:
Georg Lauterbach 2020-10-21 18:16:32 +02:00 committed by GitHub
parent 0ada57d87c
commit da8171388f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 579 additions and 504 deletions

View file

@ -189,7 +189,7 @@ function _setup_postfix_aliases
DOMAIN=$(echo "${FROM}" | cut -d @ -f2)
# if they are equal it means the line looks like: "user1 other@domain.tld"
[ "${UNAME}" != "${DOMAIN}" ] && echo "${DOMAIN}" >> /tmp/vhost.tmp
[[ "${UNAME}" != "${DOMAIN}" ]] && echo "${DOMAIN}" >> /tmp/vhost.tmp
done < <(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-virtual.cf || true)
else
_notify 'inf' "Warning 'config/postfix-virtual.cf' is not provided. No mail alias/forward created."

View file

@ -228,7 +228,7 @@ RUN curl -s https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > /et
COPY ./target/bin /usr/local/bin
# Start-mailserver script
COPY ./target/helper_functions.sh ./target/check-for-changes.sh ./target/start-mailserver.sh ./target/fail2ban-wrapper.sh ./target/postfix-wrapper.sh ./target/postsrsd-wrapper.sh ./target/docker-configomat/configomat.sh /usr/local/bin/
COPY ./target/bin-helper.sh ./target/helper-functions.sh ./target/check-for-changes.sh ./target/start-mailserver.sh ./target/fail2ban-wrapper.sh ./target/postfix-wrapper.sh ./target/postsrsd-wrapper.sh ./target/docker-configomat/configomat.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/*
# Configure supervisor

View file

@ -19,8 +19,8 @@ build:
backup:
# if backup directories exist, clean hasn't been called, therefore
# we shouldn't overwrite it. It still contains the original content.
@ if [ ! -d config.bak ]; then cp -rp config config.bak; fi
@ if [ ! -d testconfig.bak ]; then cp -rp test/config testconfig.bak; fi
@ if [[ ! -d config.bak ]]; then cp -rp config config.bak; fi
@ if [[ ! -d testconfig.bak ]]; then cp -rp test/config testconfig.bak; fi
generate-accounts:
@ docker run --rm -e MAIL_USER=user1@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' > test/config/postfix-accounts.cf

View file

@ -3,6 +3,6 @@ VCS_REF=$(git rev-parse --short HEAD)
VCS_VERSION=$(git describe --tags --contains --always)
docker build \
--build-arg VCS_REF="$VCS_REF" \
--build-arg VCS_VERSION="$VCS_VERSION" \
-f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" .
--build-arg VCS_REF="$VCS_REF" \
--build-arg VCS_VERSION="$VCS_VERSION" \
-f "$DOCKERFILE_PATH" -t "$IMAGE_NAME" .

View file

@ -7,6 +7,7 @@ SCRIPT='SETUP'
set -euEo pipefail
trap '__log_err ${FUNCNAME[0]:-"?"} ${_:-"?"} ${LINENO:-"?"} ${?:-"?"}' ERR
trap '_unset_vars || :' EXIT
function __log_err
{
@ -20,14 +21,13 @@ function __log_err
" function = ${FUNC_NAME}" \
" line = ${LINE}" \
" exit code = ${EXIT_CODE}"
_unset_vars
}
function _unset_vars
{
unset CDIR CRI INFO IMAGE_NAME CONTAINER_NAME DEFAULT_CONFIG_PATH
unset USE_CONTAINER WISHED_CONFIG_PATH CONFIG_PATH VOLUME USE_TTY
unset SCRIPT
}
function _get_current_directory
@ -340,16 +340,13 @@ function _main
_docker_container /bin/bash -c "${@}"
fi
;;
* ) _usage ; _unset_vars ; exit 1 ;;
* ) _usage ; exit 1 ;;
esac
;;
help) _usage ;;
* ) _usage ; _unset_vars ; exit 1 ;;
help ) _usage ;;
* ) _usage ; exit 1 ;;
esac
_unset_vars
}
_main "${@}"

16
target/bin-helper.sh Executable file
View file

@ -0,0 +1,16 @@
#! /bin/bash
# version 0.1.0
# executed from scripts in target/bin/
# task provides frequently used functions
function errex
{
echo "${@}" 1>&2
exit 1
}
function escape
{
echo "${1//./\\.}"
}

View file

@ -1,31 +1,25 @@
#! /bin/bash
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-virtual.cf}
EMAIL="$1"
RECIPIENT="$2"
EMAIL="${1}"
RECIPIENT="${2}"
usage() {
echo "Usage: addalias <alias@domain> <recipient@other>"
}
function usage { echo "Usage: addalias <alias@domain> <recipient@other>" ; }
errex() {
echo "$@" 1>&2
exit 1
}
[[ -z ${EMAIL} ]] && { usage ; errex "Error: No alias specified" ; }
[[ -z ${RECIPIENT} ]] && { usage ; errex "Error: No recipient specified" ; }
escape() {
echo "${1//./\\.}"
}
grep \
-qi "^$(escape "${EMAIL}")[a-zA-Z@.\ ]*$(escape "${RECIPIENT}")" \
"${DATABASE}" 2>/dev/null && errex "Alias \"${EMAIL} ${RECIPIENT}\" already exists"
[ -z "$EMAIL" ] && { usage; errex "Error: No alias specified"; }
[ -z "$RECIPIENT" ] && { usage; errex "Error: No recipient specified"; }
grep -qi "^$(escape $EMAIL)[a-zA-Z@.\ ]*$(escape $RECIPIENT)" $DATABASE 2>/dev/null &&
errex "Alias \"$EMAIL $RECIPIENT\" already exists"
if grep -qi "^$(escape $EMAIL)" $DATABASE 2>/dev/null; then
sed -i "/$EMAIL/s/$/,$RECIPIENT/" $DATABASE
else
echo "$EMAIL $RECIPIENT" >> $DATABASE
if grep -qi "^$(escape "${EMAIL}")" "${DATABASE}" 2>/dev/null
then
sed -i "/${EMAIL}/s/$/,${RECIPIENT}/" "${DATABASE}"
else
echo "${EMAIL} ${RECIPIENT}" >> "${DATABASE}"
fi

View file

@ -1,41 +1,36 @@
#! /bin/bash
# shellcheck disable=SC2094
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-accounts.cf}
USER="$1"
USER="${1}"
shift
PASSWD="$@"
PASSWD="${*}"
usage() {
echo "Usage: addmailuser <user@domain> [<password>]"
}
function usage { echo "Usage: addmailuser <user@domain> [<password>]" ; }
errex() {
echo "$@" 1>&2
exit 1
}
escape() {
echo "${1//./\\.}"
}
[ -z "$USER" ] && { usage; errex "no username specified"; }
expr index "$USER" "@" >/dev/null || { usage; errex "username must include the domain"; }
[[ -z ${USER} ]] && { usage ; errex "no username specified" ; }
[[ "${USER}" =~ .*\@.* ]] || { usage ; errex "username must include the domain" ; }
# Protect config file with lock to avoid race conditions
touch $DATABASE
touch "${DATABASE}"
(
flock -e 200
grep -qi "^$(escape "$USER")|" $DATABASE 2>/dev/null &&
errex "User \"$USER\" already exists"
grep -qi "^$(escape "${USER}")|" "${DATABASE}" 2>/dev/null &&
errex "User \"${USER}\" already exists"
if [ -z "$PASSWD" ]; then
read -s -p "Enter Password: " PASSWD
if [[ -z ${PASSWD} ]]
then
read -r -s -p "Enter Password: " PASSWD
echo
[ -z "$PASSWD" ] && errex "Password must not be empty"
[[ -z ${PASSWD} ]] && errex "Password must not be empty"
fi
HASH="$(doveadm pw -s SHA512-CRYPT -u "$USER" -p "$PASSWD")"
echo "$USER|$HASH" >> $DATABASE
) 200<$DATABASE
HASH="$(doveadm pw -s SHA512-CRYPT -u "${USER}" -p "${PASSWD}")"
echo "${USER}|${HASH}" >> "${DATABASE}"
) 200< "${DATABASE}"

View file

@ -1,33 +1,26 @@
#! /bin/bash
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-relaymap.cf}
DOMAIN="$1"
HOST="$2"
PORT="$3"
DOMAIN="${1}"
HOST="${2}"
PORT="${3}"
usage() {
echo "Usage: addrelayhost <domain> <host> [<port>]"
}
function usage { echo "Usage: addrelayhost <domain> <host> [<port>]" ; }
errex() {
echo "$@" 1>&2
exit 1
}
[[ -z ${DOMAIN} ]] && { usage ; errex "no domain specified" ; }
[[ -z ${HOST} ]] && { usage ; errex "no relay host specified" ; }
escape() {
echo "${1//./\\.}"
}
[[ -z ${PORT} ]] && PORT=25
[ -z "$DOMAIN" ] && { usage; errex "no domain specified"; }
[ -z "$HOST" ] && { usage; errex "no relay host specified"; }
if [ -z "$PORT" ]; then
PORT=25
fi
if grep -qi "^@$DOMAIN" $DATABASE 2>/dev/null; then
sed -i "s ^@"$DOMAIN".* "@$DOMAIN"\t\t["$HOST"]:"$PORT" " $DATABASE
if grep -qi "^@${DOMAIN}" "${DATABASE}" 2>/dev/null
then
# TODO check if fixed
# sed -i "s ^@${DOMAIN}.* @${DOMAIN}\t\t[${HOST}]:${PORT}" "${DATABASE}"
sed -i "s ^@""${DOMAIN}"".* ""@""${DOMAIN}""\t\t[""${HOST}""]:""${PORT}"" " "${DATABASE}"
else
echo -e "@$DOMAIN\t\t[$HOST]:$PORT" >> $DATABASE
echo -e "@${DOMAIN}\t\t[${HOST}]:${PORT}" >> "${DATABASE}"
fi

View file

@ -1,35 +1,29 @@
#! /bin/bash
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-sasl-password.cf}
DOMAIN="$1"
USER="$2"
PASSWD="$3"
DOMAIN="${1}"
USER="${2}"
PASSWD="${3}"
usage() {
echo "Usage: addsaslpassword <domain> <username> <password>"
}
function usage { echo "Usage: addsaslpassword <domain> <username> <password>" ; }
errex() {
echo "$@" 1>&2
exit 1
}
[[ -z ${DOMAIN} ]] && { usage ; errex "no domain specified" ; }
[[ -z ${USER} ]] && { usage ; errex "no username specified" ; }
escape() {
echo "${1//./\\.}"
}
[ -z "$DOMAIN" ] && { usage; errex "no domain specified"; }
[ -z "$USER" ] && { usage; errex "no username specified"; }
if [ -z "$PASSWD" ]; then
read -s -p "Enter Password: " PASSWD
echo
[ -z "$PASSWD" ] && errex "Password must not be empty"
if [[ -z ${PASSWD} ]]
then
read -r -s -p "Enter Password: " PASSWD
echo
[[ -z ${PASSWD} ]] && errex "Password must not be empty"
fi
if grep -qi "^@$DOMAIN" $DATABASE 2>/dev/null; then
sed -i "s ^@"$DOMAIN".* "@$DOMAIN"\t\t"$USER":"$PASSWD" " $DATABASE
if grep -qi "^@${DOMAIN}" "${DATABASE}" 2>/dev/null
then
sed -i "s ^@""${DOMAIN}"".* ""@""${DOMAIN}""\t\t""${USER}"":""${PASSWD}"" " "${DATABASE}"
else
echo -e "@$DOMAIN\t\t$USER:$PASSWD" >> $DATABASE
echo -e "@${DOMAIN}\t\t${USER}:${PASSWD}" >> "${DATABASE}"
fi

View file

@ -1,11 +1,11 @@
#! /bin/sh
#! /bin/bash
/usr/local/bin/setup-fetchmail
su -s /bin/sh -c "/usr/bin/fetchmail \
--verbose \
--daemon 0 \
--check \
--nosyslog \
--nodetach \
-f /etc/fetchmailrc" fetchmail <&- 2>&1
--verbose \
--daemon 0 \
--check \
--nosyslog \
--nodetach \
-f /etc/fetchmailrc" fetchmail <&- 2>&1

View file

@ -1,29 +1,19 @@
#! /bin/bash
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-virtual.cf}
EMAIL="$1"
RECIPIENT="$2"
EMAIL="${1}"
RECIPIENT="${2}"
usage() {
echo "Usage: delalias <alias@domain> <recipient@other>"
}
function usage { echo "Usage: delalias <alias@domain> <recipient@other>" ; }
errex() {
echo "$@" 1>&2
exit 1
}
[[ -z ${EMAIL} ]] && { usage ; errex "Error: No alias specified" ; }
[[ -z ${RECIPIENT} ]] && { usage ; errex "Error: No recipient specified" ; }
[[ -s ${DATABASE} ]] || exit 0
escape() {
echo "${1//./\\.}"
}
[ -z "$EMAIL" ] && { usage; errex "Error: No alias specified"; }
[ -z "$RECIPIENT" ] && { usage; errex "Error: No recipient specified"; }
[ -s "$DATABASE" ] || exit 0
#CNT=$(grep "^$EMAIL" $DATABASE | wc -w | awk '{print $1}')
sed -i -e "/^$EMAIL *$RECIPIENT$/d" \
-e "/^$EMAIL/s/,$RECIPIENT//g" \
-e "/^$EMAIL/s/$RECIPIENT,//g" $DATABASE
sed -i -e "/^${EMAIL} *${RECIPIENT}$/d" \
-e "/^${EMAIL}/s/,${RECIPIENT}//g" \
-e "/^${EMAIL}/s/${RECIPIENT},//g" "${DATABASE}"

View file

@ -1,69 +1,93 @@
#! /bin/bash
# ? This is done to ignore the message "Make sure not to read and write
# ? the same file in the same pipeline", which is a result of ${DATABASE}
# ? being used below. (This disables the message file-wide.)
# shellcheck disable=SC2094
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-accounts.cf}
ALIAS_DATABASE="/tmp/docker-mailserver/postfix-virtual.cf"
QUOTA_DATABASE="/tmp/docker-mailserver/dovecot-quotas.cf"
usage() {
echo "Usage: delmailuser <-y> <user@domain> <user2@anotherdomain> ..."
echo " -y: don't prompt for confirmations"
function usage
{
echo "Usage: delmailuser <-y> <user@domain> <user2@anotherdomain> ..."
echo " -y: don't prompt for confirmations"
}
errex() {
echo -e "$@" 1>&2
exit 1
}
escape() {
echo "${1//./\\.}"
}
while getopts ":y" OPT; do
case $OPT in
while getopts ":y" OPT
do
case ${OPT} in
y)
MAILDEL="y"
;;
\?)
usage; errex "Invalid option: -$OPTARG"
;;
\?)
usage
errex "Invalid option: -${OPTARG}"
;;
*)
usage
errex "Invalid option: -${OPTARG}"
;;
esac
done
shift $((OPTIND-1))
[ -z "$@" ] && { usage; errex "No user specifed"; }
[ -s "$DATABASE" ] || exit 0
[[ -z ${*} ]] && { usage ; errex "No user specifed" ; }
[[ -s ${DATABASE} ]] || exit 0
# Protect config file with lock to avoid race conditions
(
flock -e 200
for USER in "$@"; do
for USER in "${@}"
do
# very simple plausibility check
[[ "$USER" != *"@"*"."* ]] && errex "No valid address: $USER"
MAILARR=(${USER//@/ })
# XXX $USER must not contain /s and other syntactic characters
USER=$(escape "$USER")
sed -i "/^"$USER"|/d" $DATABASE
[ $? != 0 ] && errex "$USER couldn't be deleted in $DATABASE. $?"
# Delete all aliases where the user is the only recipient( " $USER$" )
# Delete user only for all aliases that deliver to multiple recipients ( ",$USER" "$USER," )
sed -i -e "/ "$USER"$/d" \
-e "s/,"$USER"//g" \
-e "s/"$USER",//g" $ALIAS_DATABASE
[ $? = 0 ] && echo "$USER and potential aliases deleted." || errex "Aliases for $USER couldn't be deleted in $ALIAS_DATABASE. $?"
# remove quota directives
if [ -f "$QUOTA_DATABASE" ]; then
sed -i -e "/^$USER:.*$/d" $QUOTA_DATABASE || errex "Quota for $USER couldn't be deleted in $QUOTA_DATABASE. $?"
[[ ${USER} != *"@"*"."* ]] && errex "No valid address: ${USER}"
declare -a MAILARR
MAILARR[0]="${USER%@*}"
MAILARR[1]="${USER#*@}"
# XXX ${USER} must not contain /s and other syntactic characters
USER=$(escape "${USER}")
if ! sed -i "/^""${USER}""|/d" "${DATABASE}"
then
errex "${USER} couldn't be deleted in ${DATABASE}. ${?}"
fi
if [ "$MAILDEL" != "y" ]; then
read -p "Do you want to delete the mailbox as well(all mails will be removed)?(y/n) " MAILDEL
# Delete all aliases where the user is the only recipient( " ${USER$}" )
# Delete user only for all aliases that deliver to multiple recipients ( ",${USER}" "${USER,}" )
if sed -i -e "/ ""${USER}""$/d" \
-e "s/,""${USER}""//g" \
-e "s/""${USER}"",//g" "${ALIAS_DATABASE}"
then
echo "${USER} and potential aliases deleted." || errex "Aliases for ${USER} couldn't be deleted in ${ALIAS_DATABASE}. ${?}"
fi
# remove quota directives
if [[ -f ${QUOTA_DATABASE} ]]
then
sed -i -e "/^${USER}:.*$/d" "${QUOTA_DATABASE}" || errex "Quota for ${USER} couldn't be deleted in ${QUOTA_DATABASE}. ${?}"
fi
if [[ ${MAILDEL} != "y" ]]
then
read -r -p "Do you want to delete the mailbox as well(all mails will be removed)?(y/n) " MAILDEL
echo
fi
[ "$MAILDEL" != "y" ] && errex "Leaving the mailbox untouched. If you want to delete it at a later point use \"sudo docker exec mail rm -R /var/mail/${MAILARR[1]}/${MAILARR[0]}\""
rm -r -f /var/mail/${MAILARR[1]}/${MAILARR[0]}
[ $? = 0 ] && echo "Mailbox deleted." || errex "Mailbox couldn't be deleted: $?"
[[ ${MAILDEL} != "y" ]] && errex "Leaving the mailbox untouched. If you want to delete it at a later point use \"sudo docker exec mail rm -R /var/mail/${MAILARR[1]}/${MAILARR[0]}\""
if rm -r -f "/var/mail/${MAILARR[1]}/${MAILARR[0]}"
then
echo "Mailbox deleted." || errex "Mailbox couldn't be deleted: ${?}"
fi
done
) 200<$DATABASE
) 200< "${DATABASE}"

View file

@ -1,28 +1,24 @@
#! /bin/bash
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/dovecot-quotas.cf}
USER_DATABASE=${USER_DATABASE:-/tmp/docker-mailserver/postfix-accounts.cf}
USER="$1"
USER="${1}"
usage() {
echo "Usage: delquota <username@domain>"
}
function usage { echo "Usage: delquota <username@domain>" ; }
errex() {
echo "$@" 1>&2
exit 1
}
[[ -z ${USER} ]] && { usage ; errex "No username specified" ; }
[[ "${USER}" =~ .*\@.* ]] || { usage ; errex "username must include the domain"; }
escape() {
echo "${1//./\\.}"
}
[ -z "$USER" ] && { usage; errex "No username specified"; }
expr index "$USER" "@" >/dev/null || { usage; errex "username must include the domain"; }
if ! grep -qE "^$USER\|" "$USER_DATABASE"; then
usage; errex "user $USER does not exist"
if ! grep -qE "^${USER}\|" "${USER_DATABASE}"
then
usage
errex "user ${USER} does not exist"
fi
[ -s "$DATABASE" ] || exit 0
sed -i -e "/^$USER:.*$/d" $DATABASE
[[ -s ${DATABASE} ]] || exit 0
sed -i -e "/^${USER}:.*$/d" "${DATABASE}"

View file

@ -1,22 +1,19 @@
#! /bin/bash
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-relaymap.cf}
DOMAIN="$1"
DOMAIN="${1}"
usage() {
echo "Usage: excluderelayhost <domain>"
}
function usage { echo "Usage: excluderelayhost <domain>" ; }
errex() {
echo "$@" 1>&2
exit 1
}
[[ -z ${DOMAIN} ]] && { usage ; errex "no domain specified" ; }
[ -z "$DOMAIN" ] && { usage; errex "no domain specified"; }
if grep -qi "^@$DOMAIN" $DATABASE 2>/dev/null; then
sed -i "s/^@"$DOMAIN".*/@"$DOMAIN"/" $DATABASE
if grep -qi "^@${DOMAIN}" "${DATABASE}" 2>/dev/null
then
sed -i "s/^@${DOMAIN}.*/@${DOMAIN}/" "${DATABASE}"
else
echo -e "@$DOMAIN" >> $DATABASE
echo -e "@${DOMAIN}" >> "${DATABASE}"
fi

72
target/bin/fail2ban Normal file → Executable file
View file

@ -1,46 +1,68 @@
#! /bin/bash
usage() {
echo "Usage: $0 [<unban> <ip-address>]"
}
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
raise() {
echo "$@" 1>&2
exit 1
}
function usage { echo "Usage: ${0} [<unban> <ip-address>]" ; }
JAILS=$(fail2ban-client status | grep "Jail list" | cut -f2- | sed 's/,//g')
if [ -z "$1" ]; then
declare -a JAILS
for LIST in $(fail2ban-client status | grep "Jail list" | cut -f2- | sed 's/,/ /g')
do
JAILS+=("${LIST}")
done
if [[ -z ${1} ]]
then
IP_COUNT=0
for JAIL in $JAILS; do
BANNED_IP=$(iptables -L f2b-$JAIL -n | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | grep -v '0.0.0.0')
if [ -n "$BANNED_IP" ]; then
BANNED_IP=$(echo $BANNED_IP | sed -e 's/\n/,/g')
echo "Banned in $JAIL: $BANNED_IP"
IP_COUNT=$((IP_COUNT+1))
for JAIL in "${JAILS[@]}"
do
declare -a BANNED_IPS
while read -r LINE
do
BANNED_IPS+=("$(echo "${LINE}" | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | grep -v '0.0.0.0')")
done < <(iptables -L f2b-"${JAIL}" -n)
if [[ ${#BANNED_IPS[@]} -ne 0 ]]
then
for BANNED_IP in "${BANNED_IPS[@]}"
do
echo "Banned in ${JAIL}: ${BANNED_IP}"
IP_COUNT=$(( IP_COUNT + 1 ))
done
fi
done
if [ "$IP_COUNT" -eq 0 ]; then
if [[ ${IP_COUNT} -eq 0 ]]
then
echo "No IPs have been banned"
fi
else
case $1 in
case ${1} in
unban)
shift
if [ -n "$1" ]; then
for JAIL in $JAILS; do
RESULT=`fail2ban-client set $JAIL unbanip $@`
if [[ "$RESULT" != *"is not banned"* ]] && [[ "$RESULT" != *"NOK"* ]]; then
echo -n "unbanned IP from $JAIL: "
echo "$RESULT"
if [[ -n ${1} ]]
then
for JAIL in "${JAILS[@]}"
do
RESULT="$(fail2ban-client set "${JAIL}" unbanip "${@}")"
if [[ ${RESULT} != *"is not banned"* ]] && [[ ${RESULT} != *"NOK"* ]]
then
echo -n "unbanned IP from ${JAIL}: "
echo "${RESULT}"
fi
done
else
raise "You need to specify an IP address. Run \"./setup.sh debug fail2ban\" to get a list of banned IP addresses."
errex "You need to specify an IP address. Run \"./setup.sh debug fail2ban\" to get a list of banned IP addresses."
fi
;;
*)
usage; raise "unknown command: $1"
usage
errex "unknown command: ${1}"
;;
esac
fi

View file

@ -1,76 +1,89 @@
#!/bin/sh
#! /bin/bash
touch /tmp/vhost.tmp
# if no keysize is provided, 2048 is default.
keysize=${1:-2048}
KEYSIZE=${1:-2048}
# Getting domains from mail accounts
if [ -f /tmp/docker-mailserver/postfix-accounts.cf ]; then
(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf || true) | while IFS=$'|' read login pass
# Getting domains FROM mail accounts
if [[ -f /tmp/docker-mailserver/postfix-accounts.cf ]]
then
# shellcheck disable=SC2034
while IFS=$'|' read -r LOGIN PASS
do
domain=$(echo ${login} | cut -d @ -f2)
echo ${domain} >> /tmp/vhost.tmp
done
DOMAIN=$(echo "${LOGIN}" | cut -d @ -f2)
echo "${DOMAIN}" >>/tmp/vhost.tmp
done < <(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf || true)
fi
# Getting domains from mail aliases
if [ -f /tmp/docker-mailserver/postfix-virtual.cf ]; then
(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-virtual.cf || true) | while read from to
# Getting domains FROM mail aliases
if [[ -f /tmp/docker-mailserver/postfix-virtual.cf ]]
then
# shellcheck disable=SC2034
while read -r FROM TO
do
# Setting variables for better readability
uname=$(echo ${from} | cut -d @ -f1)
domain=$(echo ${from} | cut -d @ -f2)
# if they are equal it means the line looks like: "user1 other@domain.tld"
test "$uname" != "$domain" && echo ${domain} >> /tmp/vhost.tmp
done
UNAME=$(echo "${FROM}" | cut -d @ -f1)
DOMAIN=$(echo "${FROM}" | cut -d @ -f2)
test "${UNAME}" != "${DOMAIN}" && echo "${DOMAIN}" >>/tmp/vhost.tmp
done < <(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-virtual.cf || true)
fi
# Keeping unique entries
if [ -f /tmp/vhost.tmp ]; then
cat /tmp/vhost.tmp | sort | uniq > /tmp/vhost && rm /tmp/vhost.tmp
# keeping unique entries
if [[ -f /tmp/vhost.tmp ]]
then
sort < /tmp/vhost.tmp | uniq >/tmp/vhost && rm /tmp/vhost.tmp
fi
# Exit if no entries found
if [ ! -f /tmp/vhost ]; then
echo "No entries found, no keys to make"
exit 0
# exit if no entries found
if [[ ! -f /tmp/vhost ]]
then
echo "No entries found, no keys to make"
exit 0
fi
grep -vE '^(\s*$|#)' /tmp/vhost | while read domainname; do
mkdir -p /tmp/docker-mailserver/opendkim/keys/$domainname
while read -r DOMAINNAME
do
mkdir -p "/tmp/docker-mailserver/opendkim/keys/${DOMAINNAME}"
if [ ! -f "/tmp/docker-mailserver/opendkim/keys/$domainname/mail.private" ]; then
echo "Creating DKIM private key /tmp/docker-mailserver/opendkim/keys/$domainname/mail.private"
opendkim-genkey --bits=$keysize --subdomains --domain=$domainname --selector=mail -D /tmp/docker-mailserver/opendkim/keys/$domainname
if [[ ! -f "/tmp/docker-mailserver/opendkim/keys/${DOMAINNAME}/mail.private" ]]
then
echo "Creating DKIM private key /tmp/docker-mailserver/opendkim/keys/${DOMAINNAME}/mail.private"
opendkim-genkey --bits="${KEYSIZE}" --subdomains --DOMAIN="${DOMAINNAME}" --selector=mail -D "/tmp/docker-mailserver/opendkim/keys/${DOMAINNAME}"
fi
# Write to KeyTable if necessary
keytableentry="mail._domainkey.$domainname $domainname:mail:/etc/opendkim/keys/$domainname/mail.private"
if [ ! -f "/tmp/docker-mailserver/opendkim/KeyTable" ]; then
# write to KeyTable if necessary
KEYTABLEENTRY="mail._domainkey.${DOMAINNAME} ${DOMAINNAME}:mail:/etc/opendkim/keys/${DOMAINNAME}/mail.private"
if [[ ! -f "/tmp/docker-mailserver/opendkim/KeyTable" ]]
then
echo "Creating DKIM KeyTable"
echo $keytableentry > /tmp/docker-mailserver/opendkim/KeyTable
echo "${KEYTABLEENTRY}" > /tmp/docker-mailserver/opendkim/KeyTable
else
if ! grep -q "$keytableentry" "/tmp/docker-mailserver/opendkim/KeyTable" ; then
echo $keytableentry >> /tmp/docker-mailserver/opendkim/KeyTable
if ! grep -q "${KEYTABLEENTRY}" "/tmp/docker-mailserver/opendkim/KeyTable"
then
echo "${KEYTABLEENTRY}" >>/tmp/docker-mailserver/opendkim/KeyTable
fi
fi
# Write to SigningTable if necessary
signingtableentry="*@$domainname mail._domainkey.$domainname"
if [ ! -f "/tmp/docker-mailserver/opendkim/SigningTable" ]; then
# write to SigningTable if necessary
SIGNINGTABLEENTRY="*@${DOMAINNAME} mail._domainkey.${DOMAINNAME}"
if [[ ! -f /tmp/docker-mailserver/opendkim/SigningTable ]]
then
echo "Creating DKIM SigningTable"
echo "*@$domainname mail._domainkey.$domainname" > /tmp/docker-mailserver/opendkim/SigningTable
echo "*@${DOMAINNAME} mail._domainkey.${DOMAINNAME}" >/tmp/docker-mailserver/opendkim/SigningTable
else
if ! grep -q "$signingtableentry" "/tmp/docker-mailserver/opendkim/SigningTable" ; then
echo $signingtableentry >> /tmp/docker-mailserver/opendkim/SigningTable
if ! grep -q "${SIGNINGTABLEENTRY}" /tmp/docker-mailserver/opendkim/SigningTable
then
echo "${SIGNINGTABLEENTRY}" >> /tmp/docker-mailserver/opendkim/SigningTable
fi
fi
done
done < <(grep -vE '^(\s*$|#)' /tmp/vhost)
# Creates TrustedHosts if missing
if [ -d "/tmp/docker-mailserver/opendkim" ] && [ ! -f "/tmp/docker-mailserver/opendkim/TrustedHosts" ]; then
echo "Creating DKIM TrustedHosts";
echo "127.0.0.1" > /tmp/docker-mailserver/opendkim/TrustedHosts
echo "localhost" >> /tmp/docker-mailserver/opendkim/TrustedHosts
# creates TrustedHosts if missing
if [[ -d /tmp/docker-mailserver/opendkim ]] && [[ ! -f /tmp/docker-mailserver/opendkim/TrustedHosts ]]
then
echo "Creating DKIM TrustedHosts"
echo "127.0.0.1" >/tmp/docker-mailserver/opendkim/TrustedHosts
echo "localhost" >>/tmp/docker-mailserver/opendkim/TrustedHosts
fi

53
target/bin/generate-dkim-domain Normal file → Executable file
View file

@ -1,39 +1,44 @@
#!/bin/sh
#! /bin/bash
set -e
if [ $# -ne 1 ]; then
echo $0: "usage: generate-dkim-domain domain"
exit 1
if [[ ${#} -ne 1 ]]
then
echo "${0}: usage: generate-dkim-domain domain"
exit 1
fi
domainname=$1
DOMAINNAME=${1}
mkdir -p "/tmp/docker-mailserver/opendkim/keys/${DOMAINNAME}"
mkdir -p /tmp/docker-mailserver/opendkim/keys/$domainname
if [ ! -f "/tmp/docker-mailserver/opendkim/keys/$domainname/mail.private" ]; then
echo "Creating DKIM private key /tmp/docker-mailserver/opendkim/keys/$domainname/mail.private"
opendkim-genkey --subdomains --domain=$domainname --selector=mail -D /tmp/docker-mailserver/opendkim/keys/$domainname
if [[ ! -f "/tmp/docker-mailserver/opendkim/keys/${DOMAINNAME}/mail.private" ]]
then
echo "Creating DKIM private key /tmp/docker-mailserver/opendkim/keys/${DOMAINNAME}/mail.private"
opendkim-genkey --subdomains --domain="${DOMAINNAME}" --selector=mail -D "/tmp/docker-mailserver/opendkim/keys/${DOMAINNAME}"
fi
# Write to KeyTable if necessary
keytableentry="mail._domainkey.$domainname $domainname:mail:/etc/opendkim/keys/$domainname/mail.private"
if [ ! -f "/tmp/docker-mailserver/opendkim/KeyTable" ]; then
echo "Creating DKIM KeyTable"
echo $keytableentry > /tmp/docker-mailserver/opendkim/KeyTable
KEYTABLEENTRY="mail._domainkey.${DOMAINNAME} ${DOMAINNAME}:mail:/etc/opendkim/keys/${DOMAINNAME}/mail.private"
if [[ ! -f "/tmp/docker-mailserver/opendkim/KeyTable" ]]
then
echo "Creating DKIM KeyTable"
echo "${KEYTABLEENTRY}" > /tmp/docker-mailserver/opendkim/KeyTable
else
if ! grep -q "$keytableentry" "/tmp/docker-mailserver/opendkim/KeyTable" ; then
echo $keytableentry >> /tmp/docker-mailserver/opendkim/KeyTable
fi
if ! grep -q "${KEYTABLEENTRY}" "/tmp/docker-mailserver/opendkim/KeyTable"
then
echo "${KEYTABLEENTRY}" >> /tmp/docker-mailserver/opendkim/KeyTable
fi
fi
# Write to SigningTable if necessary
signingtableentry="*@$domainname mail._domainkey.$domainname"
if [ ! -f "/tmp/docker-mailserver/opendkim/SigningTable" ]; then
echo "Creating DKIM SigningTable"
echo "*@$domainname mail._domainkey.$domainname" > /tmp/docker-mailserver/opendkim/SigningTable
SIGNINGTABLEENTRY="*@${DOMAINNAME} mail._domainkey.${DOMAINNAME}"
if [[ ! -f /tmp/docker-mailserver/opendkim/SigningTable ]]
then
echo "Creating DKIM SigningTable"
echo "*@${DOMAINNAME} mail._domainkey.${DOMAINNAME}" > /tmp/docker-mailserver/opendkim/SigningTable
else
if ! grep -q "$signingtableentry" "/tmp/docker-mailserver/opendkim/SigningTable" ; then
echo $signingtableentry >> /tmp/docker-mailserver/opendkim/SigningTable
fi
if ! grep -q "${SIGNINGTABLEENTRY}" "/tmp/docker-mailserver/opendkim/SigningTable"
then
echo "${SIGNINGTABLEENTRY}" >>/tmp/docker-mailserver/opendkim/SigningTable
fi
fi

View file

@ -1,26 +1,32 @@
#!/bin/bash
#! /bin/bash
set -e
# check if FQDN was passed as arguement in setup.sh
if [ -z "$1" ]; then
FQDN="$(hostname --fqdn)"
if [[ -z ${1} ]]
then
FQDN="$(hostname --fqdn)"
else
FQDN="$1"
FQDN="${1}"
fi
ssl_cfg_path="/tmp/docker-mailserver/ssl"
SSL_CFG_PATH="/tmp/docker-mailserver/ssl"
if [ ! -d "$ssl_cfg_path" ]; then
mkdir "$ssl_cfg_path"
if [[ ! -d ${SSL_CFG_PATH} ]]
then
mkdir "${SSL_CFG_PATH}"
fi
cd "$ssl_cfg_path" || { echo "cd $ssl_cfg_path error"; exit; }
cd "${SSL_CFG_PATH}" || { echo "cd ${SSL_CFG_PATH} error" ; exit ; }
# Create CA certificate
/usr/lib/ssl/misc/CA.pl -newca
# Create an unpassworded private key and create an unsigned public key certificate
openssl req -new -nodes -keyout "$ssl_cfg_path"/"$FQDN"-key.pem -out "$ssl_cfg_path"/"$FQDN"-req.pem -days 3652
openssl req -new -nodes -keyout "${SSL_CFG_PATH}"/"${FQDN}"-key.pem -out "${SSL_CFG_PATH}"/"${FQDN}"-req.pem -days 3652
# Sign the public key certificate with CA certificate
openssl ca -out "$ssl_cfg_path"/"$FQDN"-cert.pem -infiles "$ssl_cfg_path"/"$FQDN"-req.pem
openssl ca -out "${SSL_CFG_PATH}"/"${FQDN}"-cert.pem -infiles "${SSL_CFG_PATH}"/"${FQDN}"-req.pem
# Combine certificates for courier
cat "$ssl_cfg_path"/"$FQDN"-key.pem "$ssl_cfg_path"/"$FQDN"-cert.pem > "$ssl_cfg_path"/"$FQDN"-combined.pem
cat "${SSL_CFG_PATH}"/"${FQDN}"-key.pem "${SSL_CFG_PATH}"/"${FQDN}"-cert.pem > "${SSL_CFG_PATH}"/"${FQDN}"-combined.pem

View file

@ -1,13 +1,11 @@
#! /bin/bash
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-virtual.cf}
errex() {
echo "$@" 1>&2
exit 1
}
[[ -f ${DATABASE} ]] || errex "Error: No postfix-virtual.cf file"
[[ -s ${DATABASE} ]] || errex "Error: Empty postfix-virtual.cf - no aliases have been added"
[ -f $DATABASE ] || errex "Error: No postfix-virtual.cf file"
[ -s $DATABASE ] || errex "Error: Empty postfix-virtual.cf - no aliases have been added"
(grep -v "^\s*$\|^\s*\#" $DATABASE || true)
( grep -v "^\s*$\|^\s*\#" "${DATABASE}" || true )

View file

@ -1,18 +1,20 @@
#! /bin/bash
# ? This is done to ignore the message "Make sure not to read and write
# ? the same file in the same pipeline", which is a result of ${DATABASE}
# ? being used below. (This disables the message file-wide.)
# shellcheck disable=SC2094
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-accounts.cf}
errex() {
echo "$@" 1>&2
exit 1
}
[ -f $DATABASE ] || errex "No postfix-accounts.cf file"
[ -s $DATABASE ] || errex "Empty postfix-accounts.cf - no users have been added"
[[ -f ${DATABASE} ]] || errex "Error: No postfix-virtual.cf file"
[[ -s ${DATABASE} ]] || errex "Error: Empty postfix-virtual.cf - no aliases have been added"
# Lock database even though we are only reading
(
flock -e 200
(grep -v "^\s*$\|^\s*\#" "$DATABASE" || true) | awk -F '|' '{ print $1; }'
) 200<$DATABASE
( grep -v "^\s*$\|^\s*\#" "${DATABASE}" || true ) | awk -F '|' '{ print $1; }'
) 200< "${DATABASE}"

View file

@ -1,17 +1,19 @@
#!/bin/bash
#! /bin/bash
HOSTNAME=$1
RECIPIENT=$2
SENDER=$3
errex() {
echo -e "$@" 1>&2
exit 1
}
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
test -x /usr/sbin/pflogsumm || errex "Critical: /usr/sbin/pflogsumm not found"
HOSTNAME=${1}
RECIPIENT=${2}
SENDER=${3}
# The case that the mail.log.1 file isn't readable shouldn't actually be possible with logrotate not rotating empty files.. But you never know!
if [ -r "/var/log/mail/mail.log.1" ]; then
[[ -x /usr/sbin/pflogsumm ]] || errex "Critical: /usr/sbin/pflogsumm not found"
# The case that the mail.log.1 file isn't readable shouldn't
# actually be possible with logrotate not rotating empty files..
# But you never know!
if [[ -r "/var/log/mail/mail.log.1" ]]
then
BODY=$(/usr/sbin/pflogsumm /var/log/mail/mail.log.1 --problems-first)
else
BODY="Error: Mail log not readable or not found: /var/log/mail/mail.log.1
@ -19,15 +21,15 @@ else
In case of mail inactivity since the last report, this might be considered a nuisance warning.
Yours faithfully,
The $HOSTNAME Mailserver"
The ${HOSTNAME} Mailserver"
fi
sendmail -t <<EOF
From: $SENDER
To: $RECIPIENT
Subject: Postfix Summary for $HOSTNAME
From: ${SENDER}
To: ${RECIPIENT}
Subject: Postfix Summary for ${HOSTNAME}
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8
$BODY
${BODY}
EOF

View file

@ -1,27 +1,27 @@
#!/bin/bash
#! /bin/bash
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
set -o errexit
set -o pipefail
set -o nounset
HOSTNAME=$1
RECIPIENT=$2
SENDER=$3
errex() {
echo -e "$@" 1>&2
exit 1
}
HOSTNAME=${1}
RECIPIENT=${2}
SENDER=${3}
test -x /usr/sbin/pflogsumm || errex "Critical: /usr/sbin/pflogsumm not found"
[[ -x /usr/sbin/pflogsumm ]] || errex "Critical: /usr/sbin/pflogsumm not found"
# shellcheck disable=SC2046
BODY=$(gzip -cdfq $(ls -tr /var/log/mail/mail.log*) | /usr/sbin/pflogsumm --problems_first -d yesterday)
sendmail -t <<EOF
From: $SENDER
To: $RECIPIENT
Subject: Postfix summary for $HOSTNAME sent $(date '+%Y-%m-%d_%H%M%S')
From: ${SENDER}
To: ${RECIPIENT}
Subject: Postfix summary for ${HOSTNAME} sent $(date '+%Y-%m-%d_%H%M%S')
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8
$BODY
${BODY}
EOF

View file

@ -1,64 +1,62 @@
#! /bin/bash
MODE="$1"
USER="$3"
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
usage() {
echo "Usage: $0 <add|del|list> <send|receive> [<email@domain.com>]"
}
MODE="${1}"
USER="${3}"
raise() {
echo "$@" 1>&2
exit 1
}
function usage { echo "Usage: ${0} <add|del|list> <send|receive> [<email@domain.com>]" ; }
escape() {
echo "${1//./\\.}"
}
[ -z "$MODE" ] && raise "missing parameters: <add|del|list> <send|receive> [<email@domain.com>]"
[[ -z ${MODE} ]] && errex "missing parameters: <add|del|list> <send|receive> [<email@domain.com>]"
case $2 in
send)
DATABASE="/tmp/docker-mailserver/postfix-send-access.cf"
;;
receive)
DATABASE="/tmp/docker-mailserver/postfix-receive-access.cf"
;;
*)
usage; raise "missing parameters. Specify \"send\" or \"receive\"";
;;
case ${2} in
send)
DATABASE="/tmp/docker-mailserver/postfix-send-access.cf"
;;
receive)
DATABASE="/tmp/docker-mailserver/postfix-receive-access.cf"
;;
*)
usage
errex "missing parameters. Specify \"send\" or \"receive\""
;;
esac
if [ -z "$USER" ] && [ "$MODE" != list ]; then
read -p "User(user@domain.com): " USER
echo
[ -z "$USER" ] && raise "User must not be empty"
if [[ -z ${USER} ]] && [[ ${MODE} != list ]]
then
read -r -p "User(user@domain.com): " USER
echo
[[ -z ${USER} ]] && errex "User must not be empty"
fi
case ${MODE} in
add)
grep -qi "^$(escape "${USER}")" "${DATABASE}" 2>/dev/null && errex "User \"${USER}\" already denied to ${2} mails"
if [[ ! -f ${DATABASE} ]]
then
# shellcheck disable=SC2015
[[ ${DATABASE} = *"send"* ]] && \
sed -i 's|smtpd_sender_restrictions =|smtpd_sender_restrictions = check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf \
|| sed -i 's|smtpd_recipient_restrictions =|smtpd_recipient_restrictions = check_recipient_access texthash:/tmp/docker-mailserver/postfix-receive-access.cf,|' /etc/postfix/main.cf
case $MODE in
add)
grep -qi "^$(escape "$USER")" $DATABASE 2>/dev/null &&
raise "User \"$USER\" already denied to $2 mails"
if [ ! -f $DATABASE ]; then
[[ $DATABASE = *"send"* ]] && \
sed -i 's|smtpd_sender_restrictions =|smtpd_sender_restrictions = check_sender_access texthash:/tmp/docker-mailserver/postfix-send-access.cf,|' /etc/postfix/main.cf \
|| sed -i 's|smtpd_recipient_restrictions =|smtpd_recipient_restrictions = check_recipient_access texthash:/tmp/docker-mailserver/postfix-receive-access.cf,|' /etc/postfix/main.cf
service postfix reload > /dev/null
fi
echo -e "$USER \t\t REJECT" >>$DATABASE
;;
del)
sed -ie "/^$(escape "$USER")/d" $DATABASE 2>/dev/null ||
raise "User \"$USER\" not found."
;;
list)
grep "REJECT" $DATABASE 2>/dev/null ||
echo "Everyone is allowed to $2 mails."
service postfix reload >/dev/null
fi
echo -e "${USER} \t\t REJECT" >>"${DATABASE}"
;;
del)
sed -ie "/^$(escape "${USER}")/d" "${DATABASE}" 2>/dev/null || errex "User \"${USER}\" not found."
;;
list)
grep "REJECT" "${DATABASE}" 2>/dev/null || echo "Everyone is allowed to ${2} mails."
;;
*)
usage; raise "missing mode. Specify \"add\", \"del\" or \"list\"";
;;
esac
*)
usage
errex "missing mode. Specify \"add\", \"del\" or \"list\""
;;
esac

View file

@ -1,49 +1,56 @@
#! /bin/bash
# ? This is done to ignore the message "Make sure not to read and write
# ? the same file in the same pipeline", which is a result of ${DATABASE}
# ? being used below. (This disables the message file-wide.)
# shellcheck disable=SC2094
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/dovecot-quotas.cf}
USER_DATABASE=${USER_DATABASE:-/tmp/docker-mailserver/postfix-accounts.cf}
USER="$1"
USER="${1}"
shift
QUOTA="$@"
QUOTA="${*}"
usage() {
echo "Usage: setquota <user@domain> [<quota>]"
}
function usage { echo "Usage: setquota <user@domain> [<quota>]" ; }
errex() {
echo "$@" 1>&2
exit 1
}
[[ -z ${USER} ]] && { usage ; errex "no username specified" ; }
[[ "${USER}" =~ .*\@.* ]] || { usage ; errex "username must include the domain" ; }
[ -z "$USER" ] && { usage; errex "no username specified"; }
expr index "$USER" "@" >/dev/null || { usage; errex "username must include the domain"; }
if ! grep -qE "^$USER\|" "$USER_DATABASE"; then
usage; errex "user $USER does not exist"
if ! grep -qE "^${USER}\|" "${USER_DATABASE}"
then
usage; errex "user ${USER} does not exist"
fi
# check quota
if [ -n "$QUOTA" ] && ! echo "$QUOTA" | grep -qE "^([0-9]+(B|k|M|G|T)|0)\$"; then
usage; errex "invalid quota format. e.g. 302M (B (byte), k (kilobyte), M (megabyte), G (gigabyte) or T (terabyte))"
if [[ -n ${QUOTA} ]] && ! echo "${QUOTA}" | grep -qE "^([0-9]+(B|k|M|G|T)|0)\$"
then
usage
errex "invalid quota format. e.g. 302M (B (byte), k (kilobyte), M (megabyte), G (gigabyte) or T (terabyte))"
fi
# Protect config file with lock to avoid race conditions
touch $DATABASE
touch "${DATABASE}"
(
flock -e 200
if [ -z "$QUOTA" ]; then
read -s "Enter quota (e.g. 10M): " QUOTA
if [ -z "${QUOTA}" ]; then
read -r -s "Enter quota (e.g. 10M): " QUOTA
echo
[ -z "$QUOTA" ] && errex "Quota must not be empty. Use 0 for unlimited quota"
[[ -z "${QUOTA}" ]] && errex "Quota must not be empty. Use 0 for unlimited quota"
fi
# check quota
if [ -n "$QUOTA" ] && ! echo "$QUOTA" | grep -qE "^([0-9]+(B|k|M|G|T)|0)\$"; then
usage; errex "invalid quota format. e.g. 302M (B (byte), k (kilobyte), M (megabyte), G (gigabyte) or T (terabyte))"
if [[ -n ${QUOTA} ]] && ! echo "${QUOTA}" | grep -qE "^([0-9]+(B|k|M|G|T)|0)\$"
then
usage
errex "invalid quota format. e.g. 302M (B (byte), k (kilobyte), M (megabyte), G (gigabyte) or T (terabyte))"
fi
delquota "$USER"
echo "$USER:$QUOTA" >> $DATABASE
) 200<$DATABASE
delquota "${USER}"
echo "${USER}:${QUOTA}" >>"${DATABASE}"
) 200< "${DATABASE}"

View file

@ -1,13 +1,14 @@
#! /bin/sh
#! /bin/bash
CONF=/tmp/docker-mailserver/fetchmail.cf
RC=/etc/fetchmailrc
if [ -f "$CONF" ]; then
cat /etc/fetchmailrc_general $CONF > $RC
if [[ -f ${CONF} ]]
then
cat /etc/fetchmailrc_general "${CONF}" >"${RC}"
else
cat /etc/fetchmailrc_general > $RC
cat /etc/fetchmailrc_general >"${RC}"
fi
chmod 700 $RC
chown fetchmail:root $RC
chmod 700 "${RC}"
chown fetchmail:root "${RC}"

View file

@ -1,39 +1,35 @@
#! /bin/bash
# ? This is done to ignore the message "Make sure not to read and write
# ? the same file in the same pipeline", which is a result of ${DATABASE}
# ? being used below. (This disables the message file-wide.)
# shellcheck disable=SC2094
# shellcheck source=../bin-helper.sh
. /usr/local/bin/bin-helper.sh
DATABASE=${DATABASE:-/tmp/docker-mailserver/postfix-accounts.cf}
USER="$1"
USER="${1}"
shift
PASSWD="$@"
PASSWD="${*}"
usage() {
echo "Usage: updatemailuser <user@domain.tld> [password]"
}
function usage { echo "Usage: updatemailuser <user@domain.tld> [password]" ; }
errex() {
echo "$@" 1>&2
exit 1
}
[[ -z ${USER} ]] && { usage ; errex "no username specified" ; }
escape() {
echo "${1//./\\.}"
}
[ -z "$USER" ] && { usage; errex "no username specified"; }
if [ -z "$PASSWD" ]; then
read -s -p "Enter Password: " PASSWD
echo
[ -z "$PASSWD" ] && errex "Password must not be empty"
if [[ -z ${PASSWD} ]]
then
read -r -s -p "Enter Password: " PASSWD
echo
[[ -z ${PASSWD} ]] && errex "Password must not be empty"
fi
HASH="$(doveadm pw -s SHA512-CRYPT -u "$USER" -p "$PASSWD")"
HASH="$(doveadm pw -s SHA512-CRYPT -u "${USER}" -p "${PASSWD}")"
# Protect config file with lock to avoid race conditions
(
flock -e 200
grep -qi "^$(escape "$USER")|" $DATABASE 2>/dev/null ||
errex "User \"$USER\" does not exist"
sed -i "s ^"$USER"|.* "$USER"|"$HASH" " $DATABASE
) 200<$DATABASE
grep -qi "^$(escape "${USER}")|" "${DATABASE}" 2>/dev/null || errex "User \"${USER}\" does not exist"
sed -i "s ^""${USER}""|.* ""${USER}""|""${HASH}"" " "${DATABASE}"
) 200< "${DATABASE}"

4
target/bin/virus-wiper Normal file → Executable file
View file

@ -1,3 +1,3 @@
#!/bin/bash
#! /bin/bash
find /var/lib/amavis/virusmails/ -type f -mtime +$VIRUSMAILS_DELETE_DELAY -delete
find /var/lib/amavis/virusmails/ -type f -mtime +"${VIRUSMAILS_DELETE_DELAY}" -delete

View file

@ -1,11 +1,11 @@
#!/bin/bash
#! /bin/bash
# version 0.2.0
#
# <INSERT TASK HERE>
# shellcheck source=/dev/null
. /usr/local/bin/helper_functions.sh
# shellcheck source=./helper-functions.sh
. /usr/local/bin/helper-functions.sh
LOG_DATE=$(date +"%Y-%m-%d %H:%M:%S ")
echo "${LOG_DATE} Start check-for-changes script."

2
target/fail2ban-wrapper.sh Normal file → Executable file
View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#! /bin/bash
# version 0.1.0
#

View file

@ -1,4 +1,4 @@
#!/bin/bash
#! /bin/bash
# version 0.1.1
#

2
target/postfix-wrapper.sh Normal file → Executable file
View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#! /bin/bash
# version 0.1.0
#

2
target/postsrsd-wrapper.sh Normal file → Executable file
View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash
#! /bin/bash
# version 0.1.0

10
target/start-mailserver.sh Normal file → Executable file
View file

@ -1,6 +1,6 @@
#!/bin/bash
#! /bin/bash
# version 0.2.0
# version 0.2.1
#
# Starts the mailserver.
@ -49,7 +49,7 @@ DEFAULT_VARS["SPAMASSASSIN_SPAM_TO_INBOX"]="${SPAMASSASSIN_SPAM_TO_INBOX:=0}"
DEFAULT_VARS["MOVE_SPAM_TO_JUNK"]="${MOVE_SPAM_TO_JUNK:=1}"
DEFAULT_VARS["VIRUSMAILS_DELETE_DELAY"]="${VIRUSMAILS_DELETE_DELAY:=7}"
DEFAULT_VARS["NETWORK_INTERFACE"]="${NETWORK_INTERFACE:="eth0"}"
# DEFAULT_VARS["DMS_DEBUG"] defined in helper_functions.sh
# DEFAULT_VARS["DMS_DEBUG"] defined in helper-functions.sh
##########################################################################
# << DEFAULT VARS
@ -2088,8 +2088,8 @@ function _start_changedetector
# ! CARE --> DON'T CHANGE, unless you exactly know what you are doing
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# shellcheck source=/dev/null
. /usr/local/bin/helper_functions.sh
# shellcheck source=./helper-functions.sh
. /usr/local/bin/helper-functions.sh
if [[ ${DEFAULT_VARS["DMS_DEBUG"]} -eq 1 ]]
then

View file

@ -2,7 +2,7 @@ load 'test_helper/bats-support/load'
load 'test_helper/bats-assert/load'
# load the helper function into current context
. ./target/helper_functions.sh
. ./target/helper-functions.sh
@test "check helper function: _sanitize_ipv4_to_subnet_cidr" {
output=$(_sanitize_ipv4_to_subnet_cidr 255.255.255.255/0)
@ -11,4 +11,4 @@ load 'test_helper/bats-assert/load'
assert_output "192.168.240.0/20"
output=$(_sanitize_ipv4_to_subnet_cidr 192.168.255.14/32)
assert_output "192.168.255.14/32"
}
}

View file

@ -81,7 +81,8 @@ function __which { command -v "${@}" &>/dev/null ; }
function _eclint
{
local LINT=(eclint -exclude "(.*\.git.*|.*\.md$|\.bats$)")
local SCRIPT='EDITORCONFIG LINTER'
local LINT=(eclint -exclude "(.*\.git.*|.*\.md$|\.bats$|\.cf$|\.conf$|\.init$)")
if ! __in_path "${LINT[0]}"
then
@ -93,7 +94,6 @@ function _eclint
'type: editorconfig' \
'(linter version:' "$(${LINT[0]} --version))"
local SCRIPT='EDITORCONFIG LINTER'
if "${LINT[@]}"
then
__log_success 'no errors detected'
@ -105,6 +105,7 @@ function _eclint
function _hadolint
{
local SCRIPT='HADOLINT'
local LINT=(hadolint -c "${CDIR}/.hadolint.yaml")
if ! __in_path "${LINT[0]}"
@ -117,7 +118,6 @@ function _hadolint
'type: Dockerfile' \
'(linter version:' "$(${LINT[0]} --version | grep -E -o "v[0-9\.]*"))"
local SCRIPT='HADOLINT'
if git ls-files --exclude='Dockerfile*' --ignored | \
xargs --max-lines=1 "${LINT[@]}"
then
@ -130,7 +130,9 @@ function _hadolint
function _shellcheck
{
local LINT=(/usr/bin/shellcheck -S style -Cauto -o all -e SC2154 -W 50)
local SCRIPT='SHELLCHECK'
local ERR=0
local LINT=(/usr/bin/shellcheck -x -S style -Cauto -o all -e SC2154 -W 50)
if ! __in_path "${LINT[0]}"
then
@ -142,17 +144,44 @@ function _shellcheck
'type: shellcheck' '(linter version:' \
"$(${LINT[0]} --version | grep -m 2 -o "[0-9.]*"))"
local FIND=(
find . -iname "*.sh"
-not -path "./test/*"
-not -path "./target/docker-configomat/*"
-exec "${LINT[@]}" {} \;)
# an overengineered solution to allow shellcheck -x to
# properly follow `source=<SOURCE FILE>` when sourcing
# files with `. <FILE>` in shell scripts.
while read -r FILE
do
if ! (
cd "$(realpath "$(dirname "$(readlink -f "${FILE}")")")"
if ! "${LINT[@]}" "$(basename -- "${FILE}")"
then
return 1
fi
)
then
ERR=1
fi
done < <(find . -type f -iname "*.sh" \
-not -path "./test/bats/*" \
-not -path "./test/test_helper/*" \
-not -path "./target/docker-configomat/*")
local SCRIPT='SHELLCHECK'
if "${FIND[@]}" | grep -q .
# the same for executables in target/bin/
while read -r FILE
do
if ! (
cd "$(realpath "$(dirname "$(readlink -f "${FILE}")")")"
if ! "${LINT[@]}" "$(basename -- "${FILE}")"
then
return 1
fi
)
then
ERR=1
fi
done < <(find target/bin -executable -type f)
if [[ ERR -eq 1 ]]
then
"${FIND[@]}"
__log_abort
__log_abort 'errors encountered'
return 101
else
__log_success 'no errors detected'

View file

@ -25,7 +25,7 @@ function setup_file() {
tail -f /var/log/faillog
wait_for_finished_setup_in_container mail_fail2ban
}
function teardown_file() {
@ -125,11 +125,11 @@ function teardown_file() {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client set dovecot banip 192.0.66.5"
sleep 10
run ./setup.sh -c mail_fail2ban debug fail2ban
assert_output --regexp "^Banned in dovecot: 192.0.66.5 192.0.66.4.*"
assert_output -p "Banned in dovecot: 192.0.66.5" -p "Banned in dovecot: 192.0.66.4"
run ./setup.sh -c mail_fail2ban debug fail2ban unban 192.0.66.4
assert_output --partial "unbanned IP from dovecot: 192.0.66.4"
run ./setup.sh -c mail_fail2ban debug fail2ban
assert_output --regexp "^Banned in dovecot: 192.0.66.5.*"
assert_output --partial "Banned in dovecot: 192.0.66.5"
run ./setup.sh -c mail_fail2ban debug fail2ban unban 192.0.66.5
run ./setup.sh -c mail_fail2ban debug fail2ban unban
assert_output --partial "You need to specify an IP address. Run"
@ -146,4 +146,4 @@ function teardown_file() {
@test "last" {
skip 'this test is only there to reliably mark the end for the teardown_file'
}
}