From eeae035ce2656533c0041cad2808cacc94d8d6d9 Mon Sep 17 00:00:00 2001 From: Christoph Date: Fri, 4 Jan 2019 04:51:31 +0100 Subject: [PATCH] Add script 'bind_add_dkim_zone_master.sh'. --- bind_add_dkim_zone_master.sh | 542 +++++++++++++++++++++ conf/bind.conf.sample | 6 +- conf/bind_add_dkim_zone_master.conf.sample | 38 ++ 3 files changed, 584 insertions(+), 2 deletions(-) create mode 100755 bind_add_dkim_zone_master.sh create mode 100644 conf/bind_add_dkim_zone_master.conf.sample diff --git a/bind_add_dkim_zone_master.sh b/bind_add_dkim_zone_master.sh new file mode 100755 index 0000000..39c055a --- /dev/null +++ b/bind_add_dkim_zone_master.sh @@ -0,0 +1,542 @@ +#!/usr/bin/env bash + +# - Adds DKIM subdomain delegation for a given domain +# - +# - Return (Exit) Codes: +# - success: +# - 0: Master zone and zone file added +# - 1: Master zone already exists zonefile created +# - 2: Master zone and zone file already exists +# - error: +# - 10: Missing option for zone definition +# - 15: DKIM domain not supported by this nameserver +# - 20: Add Zone definition failed +# - 21: Adding Zonefile failed +# - 22: Change owner for newly created zonefile failed +# - 23: Reload bind configuration +# - 99: Fatal error +# - +# - Example: +# - bind_create_dkim_delegation.sh oopen.de + +script_name="$(basename $(realpath $0))" +working_dir="$(dirname $(realpath $0))" + +base_conf_file="${working_dir}/conf/bind.conf" +script_conf_file="${working_dir}/conf/${script_name%%.*}.conf" + +log_file="$(mktemp)" + +backup_date="$(date +%Y-%m-%d-%H%M)" + +# ---------- +# Setting Defaults +# ---------- + +DEFAULT_CONF_FILE_DIR="/etc/bind" +DEFAULT_BIND_USER="bind" +DEFAULT_BIND_GROUP="bind" + +DEFAULT_ZONE_FILE_SUFFIX="zone" +DEFAULT_SOA_ADMIN_EMAIL="domreg@oopen.de" +DEFAULT_TSIG_KEY_NAME="update-dkim" + + +# ********** +# Don't make changes after this +# ********** + + +# ---------- +# Base Function(s) +# ---------- + +usage() { + + + [[ -n "$1" ]] && error "$1" + + + [[ $verbose ]] && echo -e " +\033[1mUsage:\033[m + + $(basename $0) [Options] | check + +\033[1mDescription\033[m + + +\033[1mReturn (Exit) Codes\033[m + + success: + + 0: Master zone and zone file added + 1: Master zone already exists zonefile created + 2: Master zone and zone file already exists + error: + + 10: Missing option for zone definition + 20: Add Zone definition failed + 21: Adding Zonefile failed + 22: Change owner for newly created zonefile failed + 23: Reload bind configuration + 99: Fatal error + +\033[1mOptions\033[m + + -h + Prints this help. + + -k + Name of the TSIG key used for dynamical updates. + + -t + allow-transfer for zone declaration. Possible values are ip-address(es) + or existing 'acl' defininition(s). + + -q + Runs in silent mode. + +\033[1mFiles\033[m + + Basic Configuration file: $base_conf_file + Script specific Configuration file: $script_conf_file + +\033[1mExample:\033[m + + +" + + clean_up 100 + +} + +clean_up() { + + # Perform program exit housekeeping + rm $log_file + blank_line + if [[ -n "$1" ]] ; then + ret_val=$1 + else + ret_val=99 + fi + exit $ret_val +} + +echononl(){ + if $verbose ; then + 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$$ + fi +} + +warn (){ + if $verbose ; then + echo "" + echo -e " [ \033[33m\033[1mWarning\033[m ]: $*" + echo "" + fi +} + +info (){ + if $verbose ; then + echo "" + echo -e " [ \033[32m\033[1mInfo\033[m ]: $*" + echo "" + fi +} + +error(){ + if $verbose ; then + echo "" + echo -e " [ \033[31m\033[1mFehler\033[m ]: $*" + echo "" + fi +} + +echo_ok() { + if $verbose ; then + echo -e "\033[80G[ \033[32mok\033[m ]" + fi +} +echo_failed(){ + if $verbose ; then + echo -e "\033[80G[ \033[1;31mfailed\033[m ]" + fi +} +echo_skipped() { + if $verbose ; then + echo -e "\033[80G[ \033[33m\033[1mskipped\033[m ]" + fi +} + +backup_dir () { + dir_to_backup=$1 + echononl "Backup existing directory \"$dir_to_backup\" .." + if [[ -d "$dir_to_backup" ]] ; then + cp -a "$dir_to_backup" "${dir_to_backup}.$backup_date" > $log_file 2>&1 + if [[ $? -eq 0 ]]; then + echo_ok + else + echo_failed + error "Backup directory \"$dir_to_backup\" failed!" + clean_up 99 + fi + else + echo_failed + error "Directory \"$dir_to_backup\" not found. No Backup written!" + clean_up 99 + fi + +} + +blank_line() { + if $verbose ; 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 + + +# ---------- +# - Some checks .. +# ---------- + +# - Running in a terminal? +# - +if [[ -t 1 ]] ; then + verbose=true +else + verbose=false +fi + +# -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 + + +# - Print help? +# - +if [[ "$(trim $*)" = " -h" ]] || [[ "$(trim $*)" = " --help" ]] ; then + usage +fi + +if [[ -z "$(which basename)" ]]; then + fatal 'It seems "basename" is not installed, but needed!' +fi + +if [[ -z "$(which realpath)" ]]; then + fatal 'It seems "realpath" is not installed, but needed!' +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 commandline parameter +# ---------- + +while getopts bhk:t: opt ; do + case $opt in + q) verbose=true + ;; + h) usage + ;; + k) TSIG_KEY_NAME="$OPTARG" + ;; + t) ALLOW_TRANSFER_OPTION="$OPTARG" + ;; + \?) usage + ;; + *) ;; + esac +done + + +if [[ "$1" = "check" ]]; then + info "Script \033[1m$(basename $0)\033[m was successfully invoked, but its only a test." + clean_up 0 +fi + +dkim_domain=$1 + + +# ---------- +# Read Configurations from $base_conf_file +# ---------- + +blank_line +echononl "Loading default basic configurations from $(basename ${base_conf_file}).." +if [[ ! -f "$base_conf_file" ]]; then + echo_skipped +else + source "${base_conf_file}" > $log_file 2>&1 + if [[ $? -eq 0 ]]; then + echo_ok + else + echo_failed + fatal "$(cat $log_file)" + fi +fi + +echononl "Loading script specific configurations from $(basename ${script_conf_file}).." +if [[ ! -f "$script_conf_file" ]]; then + echo_skipped +else + source "${script_conf_file}" > $log_file 2>&1 + if [[ $? -eq 0 ]]; then + echo_ok + else + echo_failed + fatal "$(cat $log_file)" + fi +fi + +[[ -n "$CONF_FILE_DIR" ]] || CONF_FILE_DIR="$DEFAULT_CONF_FILE_DIR" +[[ -n "$ZONES_DECLARATION_FILE" ]] || ZONES_DECLARATION_FILE="${CONF_FILE_DIR}/named.conf.local" +[[ -n "$BIND_USER" ]] || BIND_USER="$DEFAULT_BIND_USER" +[[ -n "$BIND_GROUP" ]] || BIND_GROUP="$DEFAULT_BIND_GROUP" + +[[ -n "$ZONE_FILE_MASTER_DIR" ]] || ZONE_FILE_MASTER_DIR="${CONF_FILE_DIR}/master" +[[ -n "$ZONE_FILE_SUFFIX" ]] || ZONE_FILE_SUFFIX="$DEFAULT_ZONE_FILE_SUFFIX" +[[ -n "$SOA_ADMIN_EMAIL" ]] || SOA_ADMIN_EMAIL="$DEFAULT_SOA_ADMIN_EMAIL" + +[[ -n "$SOA_PRIMARY_MASTER" ]] || SOA_PRIMARY_MASTER="$(hostname --long)" +[[ -n "$TSIG_KEY_NAME" ]] || TSIG_KEY_NAME="$DEFAULT_TSIG_KEY_NAME" + +if [[ -z "$ALLOW_TRANSFER_OPTION" ]] ; then + error "Missing 'allow-update' option for zone definition .." + clean_up +else + # - Eliminate trailing ';' characters + # - + ALLOW_TRANSFER_OPTION="${ALLOW_TRANSFER_OPTION%"${ALLOW_TRANSFER_OPTION##*[!;]}"}" + + # - Add one trailing ';' character + # - + ALLOW_TRANSFER_OPTION="${ALLOW_TRANSFER_OPTION};" +fi + +# - replace '@' character with '.' +# - +SOA_ADMIN_EMAIL="${SOA_ADMIN_EMAIL/@/.}" + + + +zone="_domainkey.$dkim_domain" +zone_file="${ZONE_FILE_MASTER_DIR}/${zone}.${ZONE_FILE_SUFFIX}" +_zone_configuration_exists=false + + + +if $(grep -q -E "^\s*zone\s+\"$zone\"" $ZONES_DECLARATION_FILE 2>/dev/null) ; then + + info "Zone file declaration for "$zone" already exists." + _zone_configuration_exists=true + + # - Determine zonefile (by reading bind configuration) + # - + echononl "Check if zonefile already exists (by reading bind configuration)" + _found=false + declare -i _number=0 + regex_zone="^[[:space:]]*zone[[:space:]]+\"$zone\"" + regex_file="^[[:space:]]*file" + while IFS='' read -r line || [[ -n "$line" ]] ; do + if [[ $line =~ $regex_zone ]]; then + _found=true + fi + if $_found ; then + if [[ $line =~ $regex_file ]]; then + zone_file=`echo $line | awk '{print$2}'` + shopt -s extglob + # - Eliminate trailing ';' + # - + if [[ $zone_file =~ \; ]]; then + zone_file=${zone_file%%*(\;)} + fi + # - Eliminate double quotes + # - + if [[ $zone_file =~ ^\" ]]; then + zone_file=${zone_file##*(\")} + zone_file=${zone_file%%*(\")} + fi + shopt -u extglob + let number++ + break + fi + fi + done < $ZONES_DECLARATION_FILE + echo_ok + + if [[ $number -eq 0 ]] ; then + error "Found zone file declaration but no filename definition inside." + clean_up 17 + else + if [[ -n "$zone_file" ]] && [[ -f "$zone_file" ]]; then + info "Also zone file '$zone_file' exists" + clean_up 1 + fi + fi +fi + +# - Get DNS server +# - +echononl "Get Namservers for domain '$dkim_domain'" +dns_servers="$(dig +short $dkim_domain NS)" +if [[ -n "$dns_servers" ]]; then + echo_ok +else + echo_failed + error "Determin DNS servers for domain '$dkim_domain' failed!" + clean_up 16 +fi + +# - This is needed, because the dns servers are requested above, in +# - an IFS='' environment! +# - +declare -i i=0 +for _dns_server in $dns_servers ; do + dns_server_arr+=("$_dns_server") +done + + +if ! $_zone_configuration_exists ; then + + # - Backup ZONES_DECLARATION_FILE (/etc/bind/named.conf.local) + # - + echononl "Backup file '$ZONES_DECLARATION_FILE'.." + cp -a "$ZONES_DECLARATION_FILE" "${ZONES_DECLARATION_FILE}.$backup_date" > $log_file 2>&1 + if [[ $? -eq 0 ]]; then + echo_ok + else + echo_failed + fatal "$(cat $log_file)" + fi + + + + # - Add zone definition to ZONES_DECLARATION_FILE (/etc/bind/named.conf.local) + # - + echononl "Add zone definition to '$ZONES_DECLARATION_FILE' .." + cat <> $ZONES_DECLARATION_FILE 2> $log_file + +zone "${zone}" { + type master; + file "${zone_file}"; + allow-update { key ${TSIG_KEY_NAME}. ; }; + allow-transfer {$ALLOW_TRANSFER_OPTION}; +}; +EOF + if [[ $? -eq 0 ]]; then + echo_ok + else + echo_failed + error "$(cat $log_file)" + clean_up 20 + fi + +fi + + +# - Write new zonefile +# - +echononl "Add zone definition to '$ZONES_DECLARATION_FILE' .." +_failed=false +cat < "${zone_file}" 2> $log_file +\$TTL 43200 +@ IN SOA ${SOA_PRIMARY_MASTER}. ${SOA_ADMIN_EMAIL}. ( + 0 ; serial + 14400 ; refresh (4 hours) + 3600 ; retry (1 hour) + 604800 ; expire (1 week) + 86400 ; minimum (1 day) +) +EOF +if [[ $? -eq 0 ]]; then + + for _dns_server in ${dns_server_arr[@]} ; do + echo "@ IN NS $_dns_server" >> "${zone_file}" 2> $log_file + if [[ $? -ne 0 ]] ; then + _failed=true + fi + done + +else + _failed=true +fi + +if $_failed ; then + echo_failed + error "$(cat $log_file)" + clean_up 21 +else + echo_ok +fi + + +# - Change owner for newly created zone file +# - +echononl "Change owner for newly created zone file.." +chown ${BIND_USER}:$BIND_GROUP "${ZONE_FILE_MASTER_DIR}/${zone}.zone" > $log_file 2>&1 +if [[ $? -eq 0 ]]; then + echo_ok +else + echo_failed + error "$(cat $log_file)" + clean_up 22 +fi + + +# - (re)Load Bind base configuration +# - +echononl "Reload bind configuration.." +rndc reconfig > $log_file 2>&1 +if [[ $? -eq 0 ]]; then + echo_ok +else + echo_failed + error "$(cat $log_file)" + clean_up 23 +fi + +clean_up 0 diff --git a/conf/bind.conf.sample b/conf/bind.conf.sample index 8bc6359..29f6b42 100644 --- a/conf/bind.conf.sample +++ b/conf/bind.conf.sample @@ -78,7 +78,7 @@ CONF_FILE_DIR="" # - # - Example: SOA_PRIMARY_MASTER="a.ns.oopen.de" # - -SOA_PRIMARY_MASTER="" +#SOA_PRIMARY_MASTER="" # - SOA_ADMIN_EMAIL # - @@ -86,4 +86,6 @@ SOA_PRIMARY_MASTER="" # - # - Example: SOA_ADMIN_EMAIL="domreg@oopen.de" # - -SOA_ADMIN_EMAIL="" +# - Defaults to 'domreg@oopen.de' +# - +#SOA_ADMIN_EMAIL="" diff --git a/conf/bind_add_dkim_zone_master.conf.sample b/conf/bind_add_dkim_zone_master.conf.sample new file mode 100644 index 0000000..9caeecc --- /dev/null +++ b/conf/bind_add_dkim_zone_master.conf.sample @@ -0,0 +1,38 @@ +# -------------------------------------------------------------- +# - Parameter Settings for script 'bind_add_dkim_zone_master.sh'. +# --------------------------------------------------------------- + +# - SOA_PRIMARY_MASTER +# - +# - Primary master for all zones +# - +# - Example: SOA_PRIMARY_MASTER="a.ns.oopen.de" +# - +# - Defaults to '$(hostname --long)' +# - +#SOA_PRIMARY_MASTER="" + +# - SOA_ADMIN_EMAIL +# - +# - mail address of the responsible of all zones +# - +# - Example: SOA_ADMIN_EMAIL="domreg@oopen.de" +# - +# - Defaults to 'domreg@oopen.de' +# - +#SOA_ADMIN_EMAIL="domreg@oopen.de" + +# - TSIG_KEY_NAME +# - +# - Name of the key used for dynamical updates +# - +# - Defaults to 'update-dkim' +# - +#TSIG_KEY_NAME='update-dkim' + +# - ALLOW_TRANSFER_OPTION +# - +# - Example: +# - ALLOW_TRANSFER_OPTION="oopen" +# - +#ALLOW_TRANSFER_OPTION=""