mirror of
https://github.com/docker-mailserver/docker-mailserver
synced 2026-11-04 09:20:12 +01:00
This reverts commit4ff20e7571which reverteddd3e49efor the v16.0.1 release, i.e. the new Rspamd is now in the image again.
336 lines
12 KiB
Bash
Executable File
336 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# shellcheck source=../scripts/helpers/index.sh
|
|
source /usr/local/bin/helpers/index.sh
|
|
|
|
_trap_err_signal
|
|
|
|
set -u -eE -o pipefail
|
|
shopt -s inherit_errexit
|
|
|
|
# shellcheck source=/dev/null
|
|
source /etc/dms-settings
|
|
|
|
function __usage() {
|
|
_log 'trace' 'Showing usage message now'
|
|
echo -e "${PURPLE}RSPAMD-DKIM${RED}(${YELLOW}8${RED})
|
|
|
|
${ORANGE}NAME${RESET}
|
|
rspamd-dkim - Configure DKIM (DomainKeys Identified Mail)
|
|
|
|
${ORANGE}SYNOPSIS${RESET}
|
|
setup config dkim [ OPTIONS${RED}...${RESET} ]
|
|
|
|
${ORANGE}DESCRIPTION${RESET}
|
|
Creates DKIM keys and configures them within DMS for Rspamd.
|
|
OPTIONS can be used when your requirements are not met by the defaults.
|
|
|
|
${ORANGE}OPTIONS${RESET}
|
|
${BLUE}Generic Program Information${RESET}
|
|
-f | --force Overwrite existing files if there are any
|
|
-v Enable verbose logging (setting the log level to 'debug').
|
|
-vv Enable very verbose logging (setting the log level to 'trace').
|
|
help Print the usage information.
|
|
|
|
${BLUE}Configuration adjustments${RESET}
|
|
keytype Set the type of key you want to use.
|
|
Possible values: rsa, ed25519
|
|
Default: rsa
|
|
keysize Set the size of the keys to be generated.
|
|
Possible values: 1024, 2048 and 4096
|
|
Default: 2048
|
|
Only applies when using keytype=rsa
|
|
selector Set a manual selector for the key.
|
|
Default: mail
|
|
domain Provide the domain for which to generate keys for.
|
|
Default: The FQDN assigned to DMS, excluding any subdomain.
|
|
|
|
${ORANGE}EXAMPLES${RESET}
|
|
${LWHITE}setup config dkim keysize 4096${RESET}
|
|
Creates keys with their length increased to a size of 4096-bit.
|
|
|
|
${LWHITE}setup config dkim keysize 1024 selector 2023-dkim${RESET}
|
|
Creates 1024-bit sized keys, and changes the DKIM selector to '2023-dkim'.
|
|
|
|
${LWHITE}setup config dkim domain example.com${RESET}
|
|
Generate the DKIM key for a different domain (example.com).
|
|
|
|
${LWHITE}setup config dkim keytype ed25519 domain edward.com selector elliptic-test${RESET}
|
|
Generate the DKIM key using the ED25519 elliptic curve for the domain
|
|
edward.com and the selector elliptic-test. Ed25519 and RSA cannot share
|
|
a selector (one DNS TXT record per selector). Dual signing needs a
|
|
distinct selector per key and a 'selectors' array in dkim_signing.conf.
|
|
|
|
${ORANGE}EXIT STATUS${RESET}
|
|
Exit status is 0 if command was successful. If wrong arguments are provided or arguments contain
|
|
errors, the script will exit early with a non-zero exit status.
|
|
|
|
"
|
|
}
|
|
|
|
function _parse_arguments() {
|
|
FORCE=0
|
|
KEYTYPE='rsa'
|
|
KEYSIZE='2048'
|
|
SELECTOR='mail'
|
|
DOMAIN=${DOMAINNAME}
|
|
|
|
_log 'trace' "Options given to this script: '${*}'"
|
|
|
|
while [[ ${#} -gt 0 ]]; do
|
|
case "${1}" in
|
|
|
|
( 'keytype' )
|
|
[[ -n ${2:-} ]] || _exit_with_error "No keytype provided after 'keytype' argument"
|
|
if [[ ${2} == 'rsa' ]] || [[ ${2} == 'ed25519' ]]; then
|
|
KEYTYPE=${2}
|
|
_log 'debug' "Keytype set to '${KEYTYPE}'"
|
|
else
|
|
_exit_with_error "Unknown keytype '${2}'"
|
|
fi
|
|
;;
|
|
|
|
( 'keysize' )
|
|
[[ -n ${2:-} ]] || _exit_with_error "No keysize provided after 'keysize' argument"
|
|
KEYSIZE=${2}
|
|
_log 'debug' "Keysize set to '${KEYSIZE}'"
|
|
;;
|
|
|
|
( 'selector' )
|
|
[[ -n ${2:-} ]] || _exit_with_error "No selector provided after 'selector' argument"
|
|
SELECTOR=${2}
|
|
_log 'debug' "Selector set to '${SELECTOR}'"
|
|
;;
|
|
|
|
( 'domain' )
|
|
[[ -n ${2:-} ]] || _exit_with_error "No domain provided after 'domain' argument"
|
|
DOMAIN=${2}
|
|
_log 'debug' "Domain set to '${DOMAIN}'"
|
|
;;
|
|
|
|
( 'help' )
|
|
__usage
|
|
exit 0
|
|
;;
|
|
|
|
( '-f' | '--force' )
|
|
FORCE=1
|
|
shift 1
|
|
continue
|
|
;;
|
|
|
|
( '-vv' )
|
|
# shellcheck disable=SC2034
|
|
LOG_LEVEL='trace'
|
|
shift 1
|
|
_log 'trace' 'Enabled trace-logging'
|
|
continue
|
|
;;
|
|
|
|
( '-v' )
|
|
# shellcheck disable=SC2034
|
|
LOG_LEVEL='debug'
|
|
shift 1
|
|
_log 'debug' 'Enabled debug-logging'
|
|
continue
|
|
;;
|
|
|
|
( * )
|
|
__usage
|
|
_exit_with_error "Unknown option(s) '${1}' ${2:+"and '${2}'"}"
|
|
;;
|
|
esac
|
|
|
|
shift 2
|
|
done
|
|
|
|
return 0
|
|
}
|
|
|
|
function _preflight_checks() {
|
|
if [[ ${KEYTYPE} == 'ed25519' ]] && [[ ${KEYSIZE} -ne 2048 ]]; then
|
|
_exit_with_error "Chosen keytype does not accept the 'keysize' argument"
|
|
fi
|
|
|
|
if [[ ! -d /tmp/docker-mailserver ]]; then
|
|
_log 'warn' "The directory '/tmp/docker-mailserver' does not seem to be mounted by a volume - the Rspamd (DKIM) configuration will not be persisted"
|
|
fi
|
|
|
|
_rspamd_get_envs
|
|
|
|
mkdir -p "${RSPAMD_DMS_DKIM_D}" "${RSPAMD_DMS_OVERRIDE_D}"
|
|
chown _rspamd:_rspamd "${RSPAMD_DMS_DKIM_D}"
|
|
|
|
SELECTOR_MAP_FILE="${RSPAMD_DMS_D}/dkim_selectors.map"
|
|
}
|
|
|
|
function _create_keys() {
|
|
local BASE_FILE_NAME="${RSPAMD_DMS_DKIM_D}/${DOMAIN}-${SELECTOR}"
|
|
if [[ ${KEYTYPE} == 'rsa' ]]; then
|
|
KEYTYPE_OPTIONS=('-b' "${KEYSIZE}")
|
|
_log 'info' "Creating DKIM keys of type '${KEYTYPE}' and length '${KEYSIZE}' with selector '${SELECTOR}' for domain '${DOMAIN}'"
|
|
else
|
|
KEYTYPE_OPTIONS=('-t' "${KEYTYPE}")
|
|
_log 'info' "Creating DKIM keys of type '${KEYTYPE}' with selector '${SELECTOR}' for domain '${DOMAIN}'"
|
|
_log 'info' "Ed25519 and RSA cannot share a selector; generate an RSA fallback with a different selector and add both to a 'selectors' array in dkim_signing.conf"
|
|
fi
|
|
|
|
PUBLIC_KEY_FILE="${BASE_FILE_NAME}.public.txt"
|
|
PUBLIC_KEY_DNS_FILE="${BASE_FILE_NAME}.public.dns.txt"
|
|
PRIVATE_KEY_FILE="${BASE_FILE_NAME}.private"
|
|
|
|
if [[ -f ${PUBLIC_KEY_FILE} ]] || [[ -f ${PUBLIC_KEY_DNS_FILE} ]] || [[ -f ${PRIVATE_KEY_FILE} ]]; then
|
|
if [[ ${FORCE} -eq 0 ]]; then
|
|
_log 'error' "Not overwriting existing files (use '--force' to overwrite existing files)"
|
|
exit 1
|
|
else
|
|
_log 'info' "Overwriting existing files as the '--force' option was supplied"
|
|
[[ -f ${PUBLIC_KEY_FILE} ]] && rm "${PUBLIC_KEY_FILE}"
|
|
[[ -f ${PUBLIC_KEY_DNS_FILE} ]] && rm "${PUBLIC_KEY_DNS_FILE}"
|
|
[[ -f ${PRIVATE_KEY_FILE} ]] && rm "${PRIVATE_KEY_FILE}"
|
|
fi
|
|
fi
|
|
|
|
__create_rspamd_err_log
|
|
|
|
# shellcheck disable=SC2310
|
|
if __do_as_rspamd_user rspamadm \
|
|
dkim_keygen \
|
|
-s "${SELECTOR}" \
|
|
-d "${DOMAIN}" \
|
|
"${KEYTYPE_OPTIONS[@]}" \
|
|
-k "${PRIVATE_KEY_FILE}" \
|
|
>"${PUBLIC_KEY_FILE}" \
|
|
&& ! __filter_rspamd_err_log 'Permission denied' # we also need to check the log for error messages
|
|
then
|
|
_log 'info' 'Successfully created DKIM keys'
|
|
_log 'debug' "Public key written to '${PUBLIC_KEY_FILE}'"
|
|
_log 'debug' "Private key written to '${PRIVATE_KEY_FILE}'"
|
|
else
|
|
__print_rspamd_err_log
|
|
_exit_with_error 'Creating keys failed'
|
|
fi
|
|
}
|
|
|
|
function _check_permissions() {
|
|
# shellcheck disable=SC2310
|
|
if ! __do_as_rspamd_user ls "${RSPAMD_DMS_DKIM_D}" >/dev/null; then
|
|
_log 'warn' "The Rspamd user ('_rspamd') seems to be unable to list files in the keys directory ('${RSPAMD_DMS_DKIM_D}') - Rspamd may experience permission errors later"
|
|
elif ! __do_as_rspamd_user cat "${PRIVATE_KEY_FILE}" >/dev/null; then
|
|
_log 'warn' "The Rspamd user ('_rspamd') seems to be unable to read the private key file - Rspamd may experience permission errors later"
|
|
else
|
|
_log 'debug' 'Permissions on files and directories seem ok'
|
|
fi
|
|
}
|
|
|
|
function _setup_default_signing_conf() {
|
|
local DEFAULT_CONFIG_FILE="${RSPAMD_DMS_OVERRIDE_D}/dkim_signing.conf"
|
|
if [[ -f ${DEFAULT_CONFIG_FILE} ]]; then
|
|
_log 'info' "'${DEFAULT_CONFIG_FILE}' exists, not supplying a default ('--force' does not overwrite this file, manual adjustment may be required)"
|
|
else
|
|
_log 'info' "Supplying a default configuration (to '${DEFAULT_CONFIG_FILE}')"
|
|
cat >"${DEFAULT_CONFIG_FILE}" << EOF
|
|
# documentation: https://rspamd.com/doc/modules/dkim_signing.html
|
|
|
|
enabled = true;
|
|
|
|
sign_authenticated = true;
|
|
sign_local = false;
|
|
|
|
use_domain = "header";
|
|
use_redis = false; # don't change unless Redis also provides the DKIM keys
|
|
use_esld = true;
|
|
allow_username_mismatch = true;
|
|
|
|
check_pubkey = true; # you want to use this in the beginning
|
|
|
|
try_fallback = true;
|
|
selector_map = "${SELECTOR_MAP_FILE}";
|
|
selector = "mail";
|
|
|
|
# The path location is searched for a DKIM key with these variables:
|
|
# - '\$domain' is sourced from the MIME mail message 'From' header
|
|
# - '\$selector' comes from selector_map when present, otherwise the default below
|
|
path = "${RSPAMD_DMS_DKIM_D}/\$domain-\$selector.private";
|
|
|
|
# Dual signing (e.g. RSA + Ed25519) needs a domain-specific selectors array:
|
|
#domain {
|
|
# example.com {
|
|
# selectors [
|
|
# {
|
|
# path = "${RSPAMD_DMS_DKIM_D}/example.com-mail.private";
|
|
# selector = "mail";
|
|
# },
|
|
# {
|
|
# path = "${RSPAMD_DMS_DKIM_D}/example.com-mail-ed25519.private";
|
|
# selector = "mail-ed25519";
|
|
# }
|
|
# ]
|
|
# }
|
|
#}
|
|
|
|
EOF
|
|
|
|
# We copy here immediately in order to not rely on the changedetector - this way, users
|
|
# can immediately use the new keys. The file should not already exist in ${RSPAMD_OVERRIDE_D}
|
|
# since it would have been copied already.
|
|
cp "${DEFAULT_CONFIG_FILE}" "${RSPAMD_OVERRIDE_D}/dkim_signing.conf"
|
|
chown _rspamd:_rspamd "${DEFAULT_CONFIG_FILE}" "${RSPAMD_OVERRIDE_D}/dkim_signing.conf"
|
|
fi
|
|
|
|
# Rspamd fails to load selector_map when the file is missing; keep an empty map around.
|
|
touch "${SELECTOR_MAP_FILE}"
|
|
chown _rspamd:_rspamd "${SELECTOR_MAP_FILE}"
|
|
}
|
|
|
|
# Record DOMAIN -> SELECTOR so a custom selector is used with the path template.
|
|
# One map entry per domain; dual signing needs a 'selectors' array in dkim_signing.conf.
|
|
function _update_selector_map() {
|
|
local TMP_FILE="${SELECTOR_MAP_FILE}.tmp"
|
|
local PREVIOUS_SELECTOR
|
|
|
|
touch "${SELECTOR_MAP_FILE}"
|
|
PREVIOUS_SELECTOR=$(awk -v domain="${DOMAIN}" '$1 == domain { print $2; exit }' "${SELECTOR_MAP_FILE}")
|
|
if [[ -n "${PREVIOUS_SELECTOR}" ]] && [[ "${PREVIOUS_SELECTOR}" != "${SELECTOR}" ]]; then
|
|
_log 'warn' "Selector map for '${DOMAIN}' already used '${PREVIOUS_SELECTOR}'; replacing with '${SELECTOR}'. Dual signing (RSA + Ed25519) needs a 'selectors' array in dkim_signing.conf, not selector_map"
|
|
fi
|
|
|
|
awk -v domain="${DOMAIN}" -v selector="${SELECTOR}" '
|
|
$1 != domain { print }
|
|
END { print domain, selector }
|
|
' "${SELECTOR_MAP_FILE}" >"${TMP_FILE}"
|
|
mv "${TMP_FILE}" "${SELECTOR_MAP_FILE}"
|
|
chown _rspamd:_rspamd "${SELECTOR_MAP_FILE}"
|
|
chmod 644 "${SELECTOR_MAP_FILE}"
|
|
}
|
|
|
|
function _transform_public_key_file_to_dns_record_contents() {
|
|
_log 'trace' 'Transforming DNS zone format to DNS record content now'
|
|
: >"${PUBLIC_KEY_DNS_FILE}"
|
|
grep -o '".*"' "${PUBLIC_KEY_FILE}" | tr -d '"\n' >>"${PUBLIC_KEY_DNS_FILE}"
|
|
echo '' >>"${PUBLIC_KEY_DNS_FILE}"
|
|
|
|
if ! _log_level_is '(warn|error)'; then
|
|
_log 'info' "Here is the content of the TXT DNS record ${SELECTOR}._domainkey.${DOMAIN} that you need to create:\n"
|
|
cat "${PUBLIC_KEY_DNS_FILE}"
|
|
printf '\n'
|
|
fi
|
|
}
|
|
|
|
function _final_steps() {
|
|
# Request Rspamd to reload it's config immediately (instead of waiting on change detection):
|
|
_reload_rspamd
|
|
_log 'trace' 'Finished DKIM key creation'
|
|
}
|
|
|
|
_obtain_hostname_and_domainname
|
|
_require_n_parameters_or_print_usage 0 "${@}"
|
|
_parse_arguments "${@}"
|
|
_preflight_checks
|
|
_create_keys
|
|
_check_permissions
|
|
_setup_default_signing_conf
|
|
_update_selector_map
|
|
_transform_public_key_file_to_dns_record_contents
|
|
_final_steps
|