mirror of
https://github.com/docker-mailserver/docker-mailserver.git
synced 2025-06-21 08:36:00 +02:00
Refactoring this `setup` CLI command as part of the effort to unify our DKIM feature support between OpenDKIM + Rspamd: - Adds a `main()` method similar to other setup CLI commands. - Help text more aligned with equivalent rspamd DKIM setup CLI command. - DRY some repetition such as hard-coded paths to use variables. - OpenDKIM config files are created / initialized early on now with `_create_opendkim_configs()`. `while` loop only needs to append entries, so is easier to grok. - `_create_dkim_key()` to scope just the logic (_and additional notes_) to key generation via `opendkim-genkey` - Now overall logic with the `while` loop of the script occurs in `_generate_dkim_keys()`: - Ownership fixes are now applied after the `while` loop as that seems more appropriate than per iteration. - Temporary VHOST config is now removed since it's no longer useful after running. - Tests adjusted for one new log for adding of default trusted hosts content. Overall this should be nicer to grok/maintain. Some of this logic will be reused for the unified DKIM generation command in future, which is more likely to shift towards all domains using the same keypair by default with rspamd/opendkim config generated at runtime rather than reliant upon DMS config volume to provide that (_still expected for private key_). --------- Co-authored-by: Casper <casperklein@users.noreply.github.com> Co-authored-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
190 lines
7.3 KiB
Bash
190 lines
7.3 KiB
Bash
#!/bin/bash
|
|
|
|
function _escape() {
|
|
echo "${1//./\\.}"
|
|
}
|
|
|
|
# TODO: Not in use currently. Maybe in the future: https://github.com/docker-mailserver/docker-mailserver/pull/3484/files#r1299410851
|
|
# Replaces a string so that it can be used inside
|
|
# `sed` safely.
|
|
#
|
|
# @param ${1} = string to escape
|
|
# @output = prints the escaped string
|
|
function _escape_for_sed() {
|
|
sed -E 's/[]\/$*.^[]/\\&/g' <<< "${1:?String to escape for sed is required}"
|
|
}
|
|
|
|
# Returns input after filtering out lines that are:
|
|
# empty, white-space, comments (`#` as the first non-whitespace character)
|
|
function _get_valid_lines_from_file() {
|
|
_convert_crlf_to_lf_if_necessary "${1}"
|
|
_append_final_newline_if_missing "${1}"
|
|
|
|
grep --extended-regexp --invert-match "^\s*$|^\s*#" "${1}" || true
|
|
}
|
|
|
|
# This is to sanitize configs from users that unknowingly introduced CRLF:
|
|
function _convert_crlf_to_lf_if_necessary() {
|
|
if [[ $(file "${1}") =~ 'CRLF' ]]; then
|
|
_log 'warn' "File '${1}' contains CRLF line-endings"
|
|
|
|
if [[ -w ${1} ]]; then
|
|
_log 'debug' 'Converting CRLF to LF'
|
|
sed -i 's|\r||g' "${1}"
|
|
else
|
|
_log 'warn' "File '${1}' is not writable - cannot change CRLF to LF"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# This is to sanitize configs from users that unknowingly removed the end-of-file LF:
|
|
function _append_final_newline_if_missing() {
|
|
# Correctly detect a missing final newline and fix it:
|
|
# https://stackoverflow.com/questions/38746/how-to-detect-file-ends-in-newline#comment82380232_25749716
|
|
# https://unix.stackexchange.com/questions/31947/how-to-add-a-newline-to-the-end-of-a-file/441200#441200
|
|
# https://unix.stackexchange.com/questions/159557/how-to-non-invasively-test-for-write-access-to-a-file
|
|
if [[ $(tail -c1 "${1}" | wc -l) -eq 0 ]]; then
|
|
# Avoid fixing when the destination is read-only:
|
|
if [[ -w ${1} ]]; then
|
|
printf '\n' >> "${1}"
|
|
|
|
_log 'info' "File '${1}' was missing a final newline - this has been fixed"
|
|
else
|
|
_log 'warn' "File '${1}' is missing a final newline - it is not writable, hence it was not fixed - the last line will not be processed!"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Provide the name of an environment variable to this function
|
|
# and it will return its value stored in /etc/dms-settings
|
|
function _get_dms_env_value() {
|
|
if [[ -f /etc/dms-settings ]]; then
|
|
grep "^${1}=" /etc/dms-settings | cut -d "'" -f 2
|
|
else
|
|
_log 'warn' "Call to '_get_dms_env_value' but '/etc/dms-settings' is not present"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# TODO: `chown -R 5000:5000 /var/mail` has existed since the projects first commit.
|
|
# It later received a depth guard to apply the fix only when it's relevant for a dir.
|
|
# Assess if this still appropriate, it appears to be problematic for some LDAP users.
|
|
#
|
|
# `helpers/accounts.sh:_create_accounts` (mkdir, cp) appears to be the only writer to
|
|
# /var/mail folders (used during startup and change detection handling).
|
|
function _chown_var_mail_if_necessary() {
|
|
# fix permissions, but skip this if 3 levels deep the user id is already set
|
|
if find /var/mail -maxdepth 3 -a \( \! -user "${DMS_VMAIL_UID}" -o \! -group "${DMS_VMAIL_GID}" \) | read -r; then
|
|
_log 'trace' 'Fixing /var/mail permissions'
|
|
chown -R "${DMS_VMAIL_UID}:${DMS_VMAIL_GID}" /var/mail || return 1
|
|
fi
|
|
}
|
|
|
|
function _require_n_parameters_or_print_usage() {
|
|
local COUNT
|
|
COUNT=${1}
|
|
shift
|
|
|
|
[[ ${1:-} == 'help' ]] && { __usage ; exit 0 ; }
|
|
[[ ${#} -lt ${COUNT} ]] && { __usage ; exit 1 ; }
|
|
return 0
|
|
}
|
|
|
|
# NOTE: Postfix commands that read `main.cf` will stall execution,
|
|
# until the config file has not be written to for at least 2 seconds.
|
|
# After we modify the config explicitly, we can safely assume (reasonably)
|
|
# that the write stream has completed, and it is safe to read the config.
|
|
# https://github.com/docker-mailserver/docker-mailserver/issues/2985
|
|
function _adjust_mtime_for_postfix_maincf() {
|
|
if [[ $(( $(date '+%s') - $(stat -c '%Y' '/etc/postfix/main.cf') )) -lt 2 ]]; then
|
|
touch -d '2 seconds ago' /etc/postfix/main.cf
|
|
fi
|
|
}
|
|
|
|
function _reload_postfix() {
|
|
_adjust_mtime_for_postfix_maincf
|
|
postfix reload
|
|
}
|
|
|
|
# Replaces values in configuration files given a set of specific environment
|
|
# variables. The environment variables follow a naming pattern, whereby every
|
|
# variable that is taken into account has a given prefix. The new value in the
|
|
# configuration will be the one the environment variable had at the time of
|
|
# calling this function.
|
|
#
|
|
# @option --shutdown-on-error = shutdown in case an error is detected
|
|
# @param ${1} = prefix for environment variables
|
|
# @param ${2} = file in which substitutions should take place
|
|
#
|
|
# ## Example
|
|
#
|
|
# If you want to set a new value for `readme_directory` in Postfix's `main.cf`,
|
|
# you can set the environment variable `POSTFIX_README_DIRECTORY='/new/dir/'`
|
|
# (`POSTFIX_` is an arbitrary prefix, you can choose the one you like),
|
|
# and then call this function:
|
|
# `_replace_by_env_in_file 'POSTFIX_' '<PATH TO POSTFIX's main.cf>`
|
|
#
|
|
# ## Panics
|
|
#
|
|
# This function will panic, i.e. shut down the whole container, if:
|
|
#
|
|
# 1. No first and second argument is supplied
|
|
# 2. The second argument is a path to a file that does not exist
|
|
function _replace_by_env_in_file() {
|
|
if [[ -z ${1:-} ]]; then
|
|
_dms_panic__invalid_value 'first argument unset' 'utils.sh:_replace_by_env_in_file'
|
|
elif [[ -z ${2:-} ]]; then
|
|
_dms_panic__invalid_value 'second argument unset' 'utils.sh:_replace_by_env_in_file'
|
|
elif [[ ! -f ${2} ]]; then
|
|
_dms_panic__invalid_value "file '${2}' does not exist" 'utils.sh:_replace_by_env_in_file'
|
|
fi
|
|
|
|
local ENV_PREFIX=${1} CONFIG_FILE=${2}
|
|
local ESCAPED_VALUE ESCAPED_KEY
|
|
|
|
while IFS='=' read -r KEY VALUE; do
|
|
KEY=${KEY#"${ENV_PREFIX}"} # strip prefix
|
|
ESCAPED_KEY=$(sed -E 's#([\=\&\|\$\.\*\/\[\\^]|\])#\\\1#g' <<< "${KEY,,}")
|
|
ESCAPED_VALUE=$(sed -E 's#([\=\&\|\$\.\*\/\[\\^]|\])#\\\1#g' <<< "${VALUE}")
|
|
[[ -n ${ESCAPED_VALUE} ]] && ESCAPED_VALUE=" ${ESCAPED_VALUE}"
|
|
_log 'trace' "Setting value of '${KEY}' in '${CONFIG_FILE}' to '${VALUE}'"
|
|
sed -i -E "s#^${ESCAPED_KEY}[[:space:]]*=.*#${ESCAPED_KEY} =${ESCAPED_VALUE}#g" "${CONFIG_FILE}"
|
|
done < <(env | grep "^${ENV_PREFIX}")
|
|
}
|
|
|
|
# Check if an environment variable's value is zero or one. This aids in checking variables
|
|
# that act as "booleans" for enabling or disabling a service, configuration option, etc.
|
|
#
|
|
# This function will log a warning and return with exit code 1 in case the variable's value
|
|
# is not zero or one.
|
|
#
|
|
# @param ${1} = name of the ENV variable to check
|
|
function _env_var_expect_zero_or_one() {
|
|
local ENV_VAR_NAME=${1:?ENV var name must be provided to _env_var_expect_zero_or_one}
|
|
|
|
if [[ ! -v ${ENV_VAR_NAME} ]]; then
|
|
_log 'warn' "'${ENV_VAR_NAME}' is not set, but was expected to be"
|
|
return 1
|
|
fi
|
|
|
|
if [[ ! ${!ENV_VAR_NAME} =~ ^(0|1)$ ]]; then
|
|
_log 'warn' "The value of '${ENV_VAR_NAME}' (= '${!ENV_VAR_NAME}') is not 0 or 1, but was expected to be"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Check if an environment variable's value is an integer.
|
|
#
|
|
# This function will log a warning and return with exit code 1 in case the variable's value
|
|
# is not an integer.
|
|
#
|
|
# @param ${1} = name of the ENV variable to check
|
|
function _env_var_expect_integer() {
|
|
local ENV_VAR_NAME=${1:?ENV var name must be provided to _env_var_expect_integer}
|
|
|
|
[[ ${!ENV_VAR_NAME} =~ ^-?[0-9][0-9]*$ ]] && return 0
|
|
_log 'warn' "The value of '${ENV_VAR_NAME}' is not an integer ('${!ENV_VAR_NAME}'), but was expected to be"
|
|
return 1
|
|
}
|