#!/usr/bin/env bash # - Adds DKIM subdomain delegation for a given domain # - # - Return (Exit) Codes: # - success: # - 0: DKIM subdomain delegation already exists # - 1: DKIM subdomain delegation added # - error: # - 11: No zonefile found # - 15: Hostname/Domain not supported # - 16: Determin Nameservers failed # - 21: Adding Record failed # - 99: Fatal error # - # - Example: # - bind_create_dkim_delegation.sh oopen.de script_name="$(basename $(realpath $0))" working_dir="$(dirname $(realpath $0))" conf_file="${working_dir}/conf/bind.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" # ********** # 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 Scripts add a nameserver delegation for zone _domainkey.. The nameserver for the new zone are the same as for the domain itself. \033[1mReturn (Exit) Codes\033[m success: 0: DKIM subdomain delegation already exists 1: DKIM subdomain delegation added error: 11: No zonefile found 15: Hostname/Domain not supported 16: Determin Nameservers failed 21: Adding Record failed 99: Fatal error \033[1mOptions\033[m -h Prints this help. -q Runs in silent mode. \033[1mFiles\033[m $conf_file: Configuration file \033[1mExample:\033[m Create DKIM delegation for domain oopen.de. \033[1m$(basename $0) oopen.de\033[m This results in NS Records like: _domainkey.oopen.de. IN NS name-server-1 _domainkey.oopen.de. IN NS name-server-2 " clean_up 1 } clean_up() { # Perform program exit housekeeping rm $log_file blank_line exit $1 } 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[75G[ \033[32mok\033[m ]" fi } echo_failed(){ if $verbose ; then echo -e "\033[75G[ \033[1;31mfailed\033[m ]" fi } echo_skipped() { if $verbose ; then echo -e "\033[75G[ \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 bh opt ; do case $opt in q) verbose=true ;; h) usage ;; \?) usage ;; *) ;; esac done #shift $(expr $OPTIND - 1) #[[ $# -eq "1" ]] || usage "Wrong number of arguments!" 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 $conf_file # ---------- blank_line echononl "Loading default Configuration values from $(basename ${conf_file}).." if [[ ! -f "$conf_file" ]]; then echo_skipped else source "${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" # - Determin zone (domain) # - blank_line echononl "Determine zone for '$dkim_domain'" _failed=false _hostname=$dkim_domain _tmp_hostname=$(echo ${_hostname//\./\\.}) while ! grep -e "$_tmp_hostname" $ZONES_DECLARATION_FILE > /dev/null 2>&1 ; do _hostname=${_hostname#*.} _tmp_hostname=$(echo ${_hostname//\./\\.}) if [[ ! $_tmp_hostname =~ \. ]]; then _failed=true break fi done if $_failed ; then echo_failed error "Given hostname/domain \"$dkim_domain\" not supported by this nameserver!" exit 15 else echo_ok domain=$_hostname fi # - Determine zonefile (by reading bind configuration) # - echononl "Determine zonefile (by reading bind configuration)" _found=false declare -i _number=0 regex_zone="^[[:space:]]*zone[[:space:]]+\"$_tmp_hostname\"" 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 if [[ $zone_file =~ \; ]]; then zone_file=${zone_file%%*(\;)} fi 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 if [[ $number -eq 0 ]] ; then echo_failed error "No Zonefile (master) found for domain \"$dkim_domain\" ." clean_up 11 else echo_ok zone_file_dir="$(dirname $zone_file)" fi # - Check if subdomain delegation already exists # - record_name="_domainkey.$dkim_domain" record_type="NS" if grep -E "^(${record_name}\..+$record_type|_domainkey\s+.+$record_type)" $zone_file > /dev/null 2>&1 ; then info "Subdomain delegation for $record_name already exists!" clean_up 0 fi # - We will place the new NS Record after the last existing one. # - # - first we will count the number ox existing NS records # - declare -i _count search_string="^[^;].+\s+IN\s+NS" _count=$(grep -Eo "$search_string" $zone_file | wc -l) if [[ $_count -eq 0 ]]; then error "No existing NS record found. Check and add subdomain delegation manually!" clean_up 99 fi # - Get DNS server # - echononl "Get Namservers for domain '$domain'" dns_servers="$(grep -E "^\s*(@|)\s+IN\s+NS" $zone_file 2>/dev/null | sed 's/@//' | sed 's/\.$//' | awk '{print$3}')" if [[ -n "$dns_servers" ]]; then echo_ok else echo_failed error "Determin DNS servers for domain '$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 # - Backup zone directory # - blank_line backup_dir $zone_file_dir blank_line # - Add subdomain delegation # - CUR_IFS=$IFS IFS='' _tmpfile=`mktemp` > $_tmpfile echononl "Add Subdomain delegation for DKIM TXT Records .." while read -r line || [[ -n "$line" ]]; do echo $line >> $_tmpfile if echo "$line" | grep -E "$search_string" > /dev/null 2>&1 ; then let _count-- fi if [[ $_count -eq 0 ]]; then echo "" >> $_tmpfile echo "; Subdomain delegation for DKIM TXT Records" >> $_tmpfile declare -i _number=0 while [[ $_number -lt ${#dns_server_arr[@]} ]] ; do if [[ "$dkim_domain" = "$domain" ]] ; then echo -e "_domainkey.${dkim_domain}. IN NS ${dns_server_arr[${_number}]}." >> $_tmpfile fi ((_number++)) done echo "" >> $_tmpfile _count=-1 fi done < "$zone_file" if [[ $? -eq 0 ]] ; then echo_ok else echo_failed rm $_tmpfile exit 21 fi IFS=$CUR_IFS mv $_tmpfile $zone_file # - Set Correct Owner/Permission # - blank_line echononl "Correct Owner for $zone_file .." chown $BIND_USER:$BIND_GROUP $zone_file if [[ $? -eq 0 ]] ; then echo_ok else echo_failed error "Setting ownership for '$zone_file' failed!" clean_up 99 fi echononl "Correct permissions on $zone_file .." chmod 644 $zone_file if [[ $? -eq 0 ]] ; then echo_ok else echo_failed error "Correct permissions on '$zone_file' failed!" clean_up 99 fi _return_value=1 # - Remove backup of zonefile directory if no changes are made # - if [[ -d "${zone_file_dir}.$backup_date" ]] ; then diff -Nur "$zone_file_dir" "${zone_file_dir}.$backup_date" > /dev/null 2>&1 if [[ $? -eq 0 ]]; then info "No zone file has changed.\n Removing previously created backup." echononl "Delete '${zone_file_dir}.$backup_date'.." rm -rf "${zone_file_dir}.$backup_date" > $log_file 2>&1 if [[ $? -eq 0 ]]; then echo_ok else echo_failed fi fi fi warn "Serial was not incremented and zone not reloaded.\n Use script \033[1mbind_set_new_serial.sh\033[m to do that." blank_line clean_up $_return_value