#!/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}
        -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).

${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 __do_as_rspamd_user() {
  local COMMAND=${1:?Command required when using __do_as_rspamd_user}
  _log 'trace' "Running '${*}' as user '_rspamd' now"
  shift 1
  su -l '_rspamd' -s "$(command -v "${COMMAND}")" -- "${@}"
}

function _parse_arguments() {
  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
        ;;

      ( '-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

  # Note: Variables not marked with `local` are used
  # in other functions (after this function was called).
  # Also keep in sync with: target/scripts/startup/setup.d/security/rspamd.sh:__rspamd__run_early_setup_and_checks
  local RSPAMD_DMS_D='/tmp/docker-mailserver/rspamd'
  local RSPAMD_OVERRIDE_D='/etc/rspamd/override.d'
  readonly RSPAMD_DMS_DKIM_D="${RSPAMD_DMS_D}/dkim"
  readonly RSPAMD_DMS_OVERRIDE_D="${RSPAMD_DMS_D}/override.d"

  mkdir -p "${RSPAMD_DMS_DKIM_D}" "${RSPAMD_DMS_OVERRIDE_D}"
  chown _rspamd:_rspamd "${RSPAMD_DMS_DKIM_D}"

  # Mimmick target/scripts/startup/setup.d/security/rspamd.sh:__rspamd__run_early_setup_and_checks where
  # ${RSPAMD_OVERRIDE_D} is linked to ${RSPAMD_DMS_OVERRIDE_D}, but not if
  #
  # 1. ${RSPAMD_OVERRIDE_D} has already been linked to ${RSPAMD_DMS_OVERRIDE_D}
  # 2. ${RSPAMD_OVERRIDE_D} has contents already
  #
  # If 1. is true, then we're good since DMS' default setup linked the directory already and we will save
  # a persisted location in every case. If 1. is false, 2. should be false as well since by default,
  # ${RSPAMD_OVERRIDE_D} has no contents - we're good as well. What should logically never happen is
  # that 1. is false but 2. is true; this case is caught nevertheless and a warning is emitted.
  if [[ ! -h "${RSPAMD_OVERRIDE_D}" ]]; then
    if rmdir "${RSPAMD_OVERRIDE_D}" &>/dev/null; then
      ln -s "${RSPAMD_DMS_OVERRIDE_D}" "${RSPAMD_OVERRIDE_D}"
    else
      _log 'warn' "Could not link '${RSPAMD_OVERRIDE_D}' to '${RSPAMD_DMS_OVERRIDE_D}' (as '${RSPAMD_OVERRIDE_D}' does not appear to be empty, which is unexpected) - you will need to restart DMS for changes to take effect"
    fi
  fi
}

function _create_keys() {
  if [[ ${KEYTYPE} == 'rsa' ]]; then
    local BASE_FILE_NAME="${RSPAMD_DMS_DKIM_D}/${KEYTYPE}-${KEYSIZE}-${SELECTOR}-${DOMAIN}"
    KEYTYPE_OPTIONS=('-b' "${KEYSIZE}")
    _log 'info' "Creating DKIM keys of type '${KEYTYPE}' and length '${KEYSIZE}' with selector '${SELECTOR}' for domain '${DOMAIN}'"
  else
    local BASE_FILE_NAME="${RSPAMD_DMS_DKIM_D}/${KEYTYPE}-${SELECTOR}-${DOMAIN}"
    KEYTYPE_OPTIONS=('-t' "${KEYTYPE}")
    _log 'info' "Creating DKIM keys of type '${KEYTYPE}' with selector '${SELECTOR}' for domain '${DOMAIN}'"
  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.txt"

  # shellcheck disable=SC2310
  if __do_as_rspamd_user rspamadm \
      dkim_keygen                 \
      -s "${SELECTOR}"            \
      -d "${DOMAIN}"              \
      "${KEYTYPE_OPTIONS[@]}"     \
      -k "${PRIVATE_KEY_FILE}"    \
      >"${PUBLIC_KEY_FILE}"
  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
    _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 'debug' "'${DEFAULT_CONFIG_FILE}' exists, not supplying a default"
  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;
try_fallback = 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

domain {
    ${DOMAIN} {
        path = "${PRIVATE_KEY_FILE}";
        selector = "${SELECTOR}";
    }
}

EOF
    chown _rspamd:_rspamd "${DEFAULT_CONFIG_FILE}"
  fi
}

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() {
  # We need to restart Rspamd so the changes take effect immediately.
  if ! supervisorctl restart rspamd; then
    _log 'warn' 'Could not restart Rspamd via Supervisord'
  fi

  _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
_transform_public_key_file_to_dns_record_contents
_final_steps