postfix/create_opendkim_key.sh

799 lines
18 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
script_name="$(basename $(realpath $0))"
working_dir="$(dirname $(realpath $0))"
conf_file="${working_dir}/conf/${script_name%%.*}.conf"
LOCK_DIR="/tmp/$(basename $0).$$.LOCK"
log_file="${LOCK_DIR}/${script_name%%.*}.log"
# -------------
# - Default values
# -------------
# - Give your default values here
# -
LOGGING=false
BATCH_MODE=false
DEFAULT_key_algo="hmac-sha256"
DEFAULT_ttl=360
opendkim_dir="/etc/opendkim"
signing_table_file="${opendkim_dir}/signing.table"
key_table_file="${opendkim_dir}/key.table"
key_base_dir=${opendkim_dir}/keys
# -------------
# --- Functions
# -------------
usage() {
[[ -n "$1" ]] && error "$1"
[[ -z "${dkim_domain}" ]] && dkim_domain='<dkim-domain-name>'
[[ -z "${update_zone}" ]] && update_zone='<update-zone>'
[[ $terminal ]] && echo -e "
\033[1mUsage:\033[m
$(basename $0) [-a <key algorithm>] [-b] [-d <dkim-domain-name> ] [-h] [-n] [-s <secret>]
[-z <update-zone>
\033[1mDescription\033[m
This script generates DKIM key for a given DKIM domain. If the domain name is not given
at command line by using \033[1m-d\033[m flag, the domain name will be requested
interactively unless flag \033[1m-b\033[m (batch mode) is set.
Unless flag \033[1m-n\033[m is set, also DNS entry for DKIM is set/updated at zone file
for given update zone (flag \033[1m-z\033[m), So a CNAME record is needed at zone file for the
given DKIM domain. This record looks like:
\033[1m*._domainkey.${dkim_domain}. IN CNAME ${dkim_domain}.${update_zone}.\033[m
\033[1mOptions\033[m
-a <key algorithm>
Specifies the algorithm to use for the TSIG key. Available choices are: hmac-md5,
hmac-sha1, hmac-sha224, hmac-sha256, hmac-sha384 and hmac-sha512. The default is
hmac-sha256. Options are case-insensitive
-b
Script will run in batch mode, no user interaction is made. Flag \033[1m-n\033[m is
ignored, or in other words: running in batch mode implies updating DNS DKIM record.
Useful for cronjob.
-d <dkim-domain-name>
The domain for which DKIM support will be configured. If not give, the domain will be
requested interactivly.
-D <Domain Server>
Specifies the domain server where to send the dynamic updates.
-h
Prints this help.
-n
Do \033[1mNOT\033[m set/update DNS TXT record for DKIM domain. The default is
to update DNS entry.
-s <secret>
Give the secret for the key used by nsupdate to create/update the DNS TXT record.
-z <update-zone>
The zone which is updated with the TXT entry for DKIM by using 'nsupdate'.
\033[1mFiles\033[m
$conf_file: Configuration file
\033[1mExample:\033[m
Cretate / Update DKIM key for domain \033[1moopen.de\033[m
$(basename $0) -d oopen.de
"
clean_up 1
}
clean_up() {
# Perform program exit housekeeping
rm -rf "$LOCK_DIR"
blank_line
exit $1
}
containsElement () {
local e
for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
# - Remove leading/trailling whitespaces
# -
trim() {
local var="$*"
var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
echononl(){
echo X\\c > /tmp/shprompt$$
if [ `wc -c /tmp/shprompt$$ | awk '{print $1}'` -eq 1 ]; then
echo -e -n "$*\\c" 1>&2
else
echo -e -n "$*" 1>&2
fi
rm /tmp/shprompt$$
}
fatal(){
echo ""
if $terminal ; then
echo -e " [ \033[31m\033[1mFatal\033[m ] $*"
else
echo -e " [ Fatal ] $*"
fi
echo ""
if $terminal ; then
echo -e " \033[1mScript terminated\033[m.."
else
echo -e " Script terminated.."
fi
echo ""
rm -rf $LOCK_DIR
exit 1
}
error (){
echo ""
if $terminal ; then
echo -e " [ \033[31m\033[1mError\033[m ] $*"
else
echo " [ Error ] $*"
fi
echo ""
}
warn (){
if $LOGGING || $terminal ; then
echo ""
if $terminal ; then
echo -e " [ \033[33m\033[1mWarn\033[m ] $*"
else
echo " [ Warn ] $*"
fi
echo ""
fi
}
todo (){
if $LOGGING || $terminal ; then
echo ""
if $terminal ; then
echo -e " [ \033[33m\033[1mToDo\033[m ] $*"
else
echo " [ ToDo ] $*"
fi
echo ""
fi
}
info (){
if $LOGGING || $terminal ; then
echo ""
if $terminal ; then
echo -e " [ \033[32m\033[1mInfo\033[m ] $*"
else
echo " [ Info ] $*"
fi
echo ""
fi
}
echo_done() {
if $terminal ; then
echo -e "\033[75G[ \033[32mdone\033[m ]"
fi
}
echo_ok() {
if $terminal ; then
echo -e "\033[75G[ \033[32mok\033[m ]"
fi
}
echo_failed(){
if $terminal ; then
echo -e "\033[75G[ \033[1;31mfailed\033[m ]"
fi
}
echo_skipped() {
if $terminal ; then
echo -e "\033[75G[ \033[33m\033[1mskipped\033[m ]"
fi
}
echo_wait(){
if $terminal ; then
echo -en "\033[75G[ \033[5m\033[1m...\033[m ]"
fi
}
blank_line() {
if $terminal ; then
echo ""
fi
}
trim() {
local var="$*"
var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
echo -n "$var"
}
# ----------
# - Jobhandling
# ----------
# - Run 'clean_up' for signals SIGHUP SIGINT SIGTERM
# -
trap clean_up SIGHUP SIGINT SIGTERM
# - Create lock directory '$LOCK_DIR"
#
mkdir "$LOCK_DIR"
# -------------
# - Some checks ..
# -------------
# - Running in a terminal?
# -
if [[ -t 1 ]] ; then
terminal=true
else
terminal=false
BATCH_MODE=true
fi
# - Is 'systemd' supported on this system
# -
# -Is systemd supported on this system?
# -
systemd_supported=false
systemd=$(which systemd)
systemctl=$(which systemctl)
if [[ -n "$systemd" ]] && [[ -n "$systemctl" ]] ; then
systemd_supported=true
fi
# ==========
# - Begin Main Script
# ==========
# ----------
# - Headline
# ----------
if $terminal ; then
echo ""
echo -e "\033[1m----------\033[m"
echo -e "\033[32m\033[1mRunning script \033[m\033[1m$script_name\033[32m .. \033[m"
echo -e "\033[1m----------\033[m"
fi
# ----------
# Read Configurations from $conf_file
# ----------
domain=""
update_zone=""
if [[ -f "$conf_file" ]]; then
source "$conf_file"
else
warn "No configuration file '$conf_file' present."
fi
# -------------
# - Read in Commandline arguments
# -------------
while getopts bd:D:hns:t:z: opt ; do
case $opt in
b) BATCH_MODE=true ;;
d) dkim_domain=$OPTARG ;;
D) dns_server=$OPTARG ;;
h) usage ;;
n) update_dns=false ;;
s) key_secret=$OPTARG ;;
t) ttl=$OPTARG ;;
z) update_zone=$OPTARG ;;
\?) usage
esac
done
# - batch mode implies updating DKIM DNS record
# -
if $BATCH_MODE ; then
update_dns=true
fi
if [[ -z "$dkim_domain" ]] && ! $BATCH_MODE ; then
echo ""
echo -e "\033[32m--\033[m"
echo ""
echo " Insert a domain name for which DKIM support should be configured."
echo ""
echo ""
echononl " DKIM domain: "
read dkim_domain
while [ "X$dkim_domain" = "X" ] ; do
echo -e "\n\t\033[33m\033[1mEingabe erforderlich.\033[m\n"
echononl " DKIM domain: "
read dkim_domain
done
elif $terminal ; then
echo -e "\033[32m--\033[m"
info "\033[32mCreate DKIM key/configuration for domain \033[37m\033[1m$dkim_domain\033[m"
fi
if [[ -z "$update_dns" ]] ; then
echo ""
echo -e "\033[32m--\033[m"
echo ""
echononl " Create/Update DKIM DNS record? (yes/no) [yes]: "
read update_dns
if [[ -z "$(trim $update_dns)" ]] ; then
update_dns=true
elif [[ "${update_dns,,}" = "yes" ]] || [[ "${update_dns,,}" = "true" ]] ; then
update_dns=true
else
update_dns=false
fi
blank_line
fi
if $update_dns && [[ -z "$update_zone" ]] && ! $BATCH_MODE ; then
echo ""
echo -e "\033[32m--\033[m"
echo ""
echo " Which zone should contain the DKIM TXT record?"
echo ""
echo ""
echononl " update Zone: "
read update_zone
while [ "X$update_zone" = "X" ] ; do
echo -e "\n\t\033[33m\033[1mEingabe erforderlich.\033[m\n"
echononl " update Zone: "
read update_zone
done
elif $update_dns && $terminal ; then
echo -e "\033[32m--\033[m"
info "Zone \033[37m\033[1m$update_zone\033[m is used for DKIM TXT record"
fi
if $update_dns && [[ -z "$key_secret" ]] && ! $BATCH_MODE ; then
echo ""
echo -e "\033[32m--\033[m"
echo ""
echo " Give the secret of the TSIG update key used by nsupdate."
echo ""
echo ""
echononl " Secret update key: "
read key_secret
while [ "X$key_secret" = "X" ] ; do
echo -e "\n\t\033[33m\033[1mEingabe erforderlich.\033[m\n"
echononl " Secret update key: "
read key_secret
done
fi
if $update_dns && [[ -z "$key_algo" ]] && ! $BATCH_MODE ; then
echo ""
echo -e "\033[32m--\033[m"
echo ""
echo " Specifies the algorithm to use for the TSIG key."
echo ""
echo " [1] hmac-md5"
echo " [2] hmac-sha1"
echo " [3] hmac-sha224"
echo -e " [4] \033[37m\033[1mhmac-sha256\033[m"
echo " [5] hmac-sha384"
echo " [6] hmac-sha512"
echo ""
echo " Type a number or press <RETURN> to choose highlighted value"
echo ""
echononl " Key algorithm [hmac-sha256]: "
while [[ "$key_algo" != "hmac-md5" ]] \
&& [[ "$key_algo" != "hmac-sha1" ]] \
&& [[ "$key_algo" != "hmac-sha224" ]] \
&& [[ "$key_algo" != "hmac-sha256" ]] \
&& [[ "$key_algo" != "hmac-sha384" ]] \
&& [[ "$key_algo" != "hmac-sha512" ]] ; do
read OPTION
case $OPTION in
1) key_algo="hmac-md5" ;;
2) key_algo="hmac-sha1" ;;
3) key_algo="hmac-sha224" ;;
4) key_algo="hmac-sha256" ;;
'') key_algo="hmac-sha256" ;;
5) key_algo="hmac-sha384" ;;
6) key_algo="hmac-sha512" ;;
*) echo ""
echo -e " \033[33m\033[1mFalsche Eingabe ! [ 1 = hmac-md5 | 2 = hmac-sha1 | .. ]\033[m"
echo ""
echononl " Key algorithm [hmac-sha256]:"
;;
esac
done
fi
if [[ -z "$dkim_domain" ]] ; then
fatal "Running in batch mode, but no domain was given!"
fi
if $update_dns && [[ -z "$update_zone" ]] ; then
fatal "No update-zone is given!"
fi
if $update_dns && [[ -z "$key_secret" ]] ; then
fatal "No secret for the update key used by nsupdate is given!"
fi
if $update_dns && [[ -z "$key_algo" ]]; then
key_algo="$DEFAULT_key_algo"
fi
if $update_dns && [[ -z "$key_name" ]]; then
key_name="$update_zone"
fi
if $update_dns && [[ -z "$ttl" ]]; then
ttl="$DEFAULT_ttl"
fi
if $update_dns && [[ -z "$dns_server" ]]; then
fatal "No DNS server for updating given!"
fi
if $update_dns ; then
cname_record="*._domainkey.${dkim_domain}. IN CNAME ${dkim_domain}.${update_zone}."
fi
blank_line
if $terminal ; then
echo ""
echo -e " \033[1m----------\033[m"
echo " DKIM Domain......................: $dkim_domain"
if $update_dns ; then
echo " Create/Update DKIM TXT record....: Yes"
echo " Domain used for DKIM TXT record..: $update_zone"
echo " Secret for the update key........: $key_secret"
echo " Algorithm used for the TSIG key..: $key_algo"
echo " Name of the TSIG key.............: $key_name"
else
echo " Create/Update DKIM TXT record....: No"
fi
echo ""
echo " DNS Server.......................: $dns_server"
echo " TTL for the DKIM TXT Record......: $ttl"
echo ""
echo " OpenDKIM's etc-directory.........: $opendkim_dir"
echo " Key directory....................: ${key_base_dir}/${dkim_domain}"
echo " Signing table file...............: $signing_table_file"
echo " Key table file...................: $key_table_file"
echo -e " \033[1m----------\033[m"
echo ""
fi
# =============
# - Start Configuration
# =============
if $terminal && ! $BATCH_MODE ; then
echo
echo -n " Type upper case 'YES' to start: "
read OK
if [[ "$OK" != "YES" ]] ; then
fatal "Abort by user request - Answer as not 'YES'"
fi
echo ""
fi
time_stamp=$(date +%s)
key_dir=${key_base_dir}/${dkim_domain}
dkim_domain_shortname="${dkim_domain%.*}"
# - Generate private/public keys
# -
if $terminal ; then
echo ""
echo -e " \033[32mGenerate Generate private/public keys\033[m"
echo ""
fi
# - Create Key directory for the given domain
# -
echononl " Create Key Directory '${key_dir}'"
if [[ ! -d "$key_dir" ]]; then
mkdir $key_dir 2> $log_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
else
echo_skipped
fi
# - Generate private key
# -
# - This will give you two files, one containing the key
# - and the other containing the TXT record youll need to
# - set up DNS.
# -
# - Note:
# - The generated TXT record cannot be used directly for
# - 'bind' nameservers (TXT recors are restricted to 255 characters)
# -
echononl " Generate private key for domain '$dkim_domain'.."
opendkim-genkey -D $key_dir -d $dkim_domain -b 2048 -r -s $time_stamp > $log_file 2>&1
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
# - Set up ownership an permissions
# -
echononl " Set ownership on '${key_dir}/${time_stamp}.private'"
chown opendkim ${key_dir}/${time_stamp}.private > $log_file 2>&1
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
echononl " Set permissions on '${key_dir}/${time_stamp}.private'"
chmod 600 ${key_dir}/${time_stamp}.private > $log_file 2>&1
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
echononl " Print out public key key for domain '$dkim_domain'.."
openssl rsa -in ${key_dir}/${time_stamp}.private -pubout -out ${key_dir}/${time_stamp}.public > $log_file 2>&1
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
# - Configure OpenDKIM
# -
if $terminal ; then
echo ""
echo -e " \033[32mConfigure OpenDKIM for domain \033[37m\033[1m$dkim_domain\033[m"
echo ""
fi
# - Configure/Adjust the signing table
# -
echononl " Configure/Adjust the signing table.."
if grep -q -E "^\s*\*@$dkim_domain\s" $signing_table_file 2>/dev/null ; then
perl -i -n -p -e "s/^\*@$dkim_domain\s.*/*@$dkim_domain\t$dkim_domain_shortname/" $signing_table_file 2> $log_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
else
echo -e "*@$dkim_domain\t$dkim_domain_shortname" >> $signing_table_file 2> $log_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
fi
# - Configure/Adjust the key table
# -
echononl " Configure/Adjustkey table"
if grep -q -E "^\s*$dkim_domain_shortname\s" $key_table_file 2>/dev/null ; then
perl -i -n -p -e "s#^\s*$dkim_domain_shortname\s.*#${dkim_domain_shortname}\t\t${dkim_domain}:${time_stamp}:${key_dir}/${time_stamp}.private#" $key_table_file 2> $log_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
else
echo -e "${dkim_domain_shortname}\t\t${dkim_domain}:${time_stamp}:${key_dir}/${time_stamp}.private" >> $key_table_file 2> $log_file
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
fi
# - Generate TXT record for use in bind9
# -
if $terminal ; then
echo ""
echo -e " \033[32mGenerate TXT record for use in bind9\033[m"
echo ""
fi
# - Write file with bind9 dekim TXT record
# -
if $terminal ; then
echo " Write bind9 dekim TXT record to file"
fi
echononl " '${key_dir}/${time_stamp}.bind9'"
echo "; ----- DKIM key $time_stamp for ${dkim_domain}" > ${key_dir}/${time_stamp}.bind9
echo -n "${time_stamp}._domainkey.${dkim_domain}. $ttl IN TXT ( \"v=DKIM1; k=rsa; s=email; p=\"" >> ${key_dir}/${time_stamp}.bind9
while IFS='' read -r _line || [[ -n $_line ]] ; do
if echo "$_line" | grep -i -q -E "^---" 2> /dev/null ; then
continue
fi
echo "" >> ${key_dir}/${time_stamp}.bind9
echo -n " \"$_line\"" >> ${key_dir}/${time_stamp}.bind9
done < "${key_dir}/${time_stamp}.public"
echo " )" >> ${key_dir}/${time_stamp}.bind9
echo_ok
# - Write TXT record as string for 'nsupdate'
# -
if $terminal ; then
echo " Write TXT record as string for 'nsupdate' to file"
fi
echononl " '${key_dir}/${time_stamp}.nsupdate'"
echo -n "\"v=DKIM1; k=rsa; s=email; p=\"" >> ${key_dir}/${time_stamp}.nsupdate
while IFS='' read -r _line || [[ -n $_line ]] ; do
if echo "$_line" | grep -i -q -E "^---" 2> /dev/null ; then
continue
fi
echo -n " \"$_line\"" >> ${key_dir}/${time_stamp}.nsupdate
done < "${key_dir}/${time_stamp}.public"
echo_ok
if $update_dns ; then
# - Update DNS Server
# -
if $terminal ; then
echo ""
echo -e " \033[32mUpdate DNS Server \033[37m\033[1m${dns_server}\033[m"
echo ""
fi
echononl " Update zone '$update_zone' .."
cat <<EOF | nsupdate -v > $log_file 2>&1
server $dns_server
zone $update_zone
key ${key_algo}:$key_name $key_secret
update delete ${dkim_domain}.${update_zone}.
update add ${dkim_domain}.${update_zone}. $ttl TXT $(cat ${key_dir}/${time_stamp}.nsupdate)
send
EOF
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
fi
_wait=false
if ! $update_dns ; then
blank_line
todo "Now you have to add the TXT Record to your zone file.\n\n Copy/Paste the following data:\n\n$(cat ${key_dir}/${time_stamp}.bind9)"
_wait=true
elif [[ "$dkim_domain" != "$update_zone" ]]; then
if [[ -z "$(dig +short ${time_stamp}._domainkey.${dkim_domain}. CNAME)" ]]; then
blank_line
todo "Create a CNAME Record to your zone file.\n\n $cname_record"
_wait=true
fi
fi
if ! $BATCH_MODE && $_wait ; then
echo ""
echo -e "After adjusting your nameserver continue with this script"
echo ""
echo -n "Type <return> to continue: "
read OK
echo
fi
# - Restart OpenDKIM
# -
if $terminal ; then
echo ""
echo -e " \033[32m-----\033[m"
echo ""
fi
echononl " Restart OpenDKIM.."
if $systemd_supported ; then
systemctl restart opendkim > $log_file 2>&1
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
else
/etc/init.d/opendkim restart > $log_file 2>&1
if [[ $? -eq 0 ]] ; then
echo_ok
else
echo_failed
error "$(cat $log_file)"
fi
fi
if [[ -n "$log_file" ]]; then
rm -f "$log_file"
fi
blank_line
clean_up 0
#txt_record="$(cat ${key_dir}/${time_stamp}.txt | awk -F'"' '{print $2}' | tr -d '\n')"
#txt_record_1=${txt_record:0:255}
#txt_record_2=${txt_record:255}
#new_txt_record="\"$txt_record_1\"\"$txt_record_2\""