#!/usr/bin/env bash # - # - Adds/Replaces a TLSA Record # - # - Return (Exit) Codes: # - success: # - 0: TLSA record is up to date # - 1: TLSA record replaced # - 2: New TLSA record written # - error: # - 10: Invalid TLSA record given # - 11: No zonefile for TLSA record found # - 15: Hostname/Domain not supported # - 20: Replacing record failed # 21: Adding Record failed # - 99: Fatal error # - # - Example: # - bind_set_renew_tlsa.sh _25._tcp.mail.initiativenserver.de. IN TLSA 3 1 1 aab3a46b387dd543ed8d... #--------------------------------------- #----------------------------- # Setting Defaults #----------------------------- #--------------------------------------- DEFAULT_CONF_FILE_DIR="/etc/bind" DEFAULT_BIND_USER="bind" DEFAULT_BIND_GROUP="bind" #*************************************** #----------------------------- # Don't make changes after this #----------------------------- #*************************************** working_dir="$(dirname $(realpath $0))" conf_file="${working_dir}/conf/bind.conf" log_file="$(mktemp)" #--------------------------------------- #----------------------------- # Base Function(s) #----------------------------- #--------------------------------------- usage() { echo [ -n "$1" ] && echo -e "Error: $1\n" cat< | Script adds a new or updates an existing TLSA Record Parameter "check" can be used, to test whether this script is accessable (e.g. from a further script on a remote host). Nothing will be done, scripts returns '0'. Return (Exit) Codes: success: 0: TLSA record is up to date 1: TLSA record replaced 2: New TLSA record written error: 10: Invalid TLSA record given 11: No zonefile for TLSA record found 15: Hostname/Domain not supported 20: Replacing record failed 21: Adding Record failed 99: Fatal error Options: -h Prints this help. -q Rund in silent mode. Example: $(basename $0) _25._tcp.mail.initiativenserver.de. IN TLSA 3 1 1 aab3a46b387dd543ed8d... EOF clean_up 1 } clean_up() { # Perform program exit housekeeping rm $log_file 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 "\t[ \033[33m\033[1mWarning\033[m ]: $*" echo "" fi } info (){ if $verbose ; then echo "" echo -e "\t[ \033[33m\033[1mInfo\033[m ]: $*" echo "" fi } error(){ if $verbose ; then echo "" echo -e "\t[ \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 "\tBackup existing directory \"$dir_to_backup\" .." if [[ -d "$dir_to_backup" ]] ; then cp -a $dir_to_backup ${dir_to_backup}.BAK.`date +%Y-%m-%d-%H%M` 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 } trap clean_up SIGHUP SIGINT SIGTERM # - Test whether stdout (file descriptor 1) is a terminal or not (e.g. cron # - or if you pipe the output to some other program) # if [[ -t 1 ]] ; then verbose=true else verbose=false fi while getopts hq opt ; do case $opt in q) verbose=false ;; h) usage ;; *) ;; esac done shift $(expr $OPTIND - 1) #if [[ $# -ne 1 ]] ; then # if $verbose ; then # usage "wrong number of arguments" # else # clean_up 99 # fi #fi # - Parameter "check" can be used, to test whether this script # - is accessable (e.g. from a script on a remote host) # - if [[ "$1" = "check" ]]; then info "Script \033[1m$(basename $0)\033[m was successfully invoked, but its only a test." clean_up 0 fi #--------------------------------------- #----------------------------- # Load default values from bind.conf # # Overwrites the settings above # #----------------------------- #--------------------------------------- if $verbose ; then clear echo "" echo -e "\033[32mRunning script \033[1m"$(basename $0)"\033[m .." echo "" fi info "Given TLSA Record: \n\t\033[1m$@\033[m" echononl "\t 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" $verbose && echo "" # - Split given Record into an array # - declare -a record_arr=($@); if [[ ${#record_arr[@]} -eq 7 ]]; then record_name=${record_arr[0]} record_ttl="" record_type="${record_arr[1]} ${record_arr[2]} ${record_arr[3]} ${record_arr[4]} ${record_arr[5]}" record_hash=${record_arr[6]} elif [[ ${#record_arr[@]} -eq 8 ]]; then record_name=${record_arr[0]} record_ttl=${record_arr[1]} record_type="${record_arr[2]} ${record_arr[3]} ${record_arr[4]} ${record_arr[5]} ${record_arr[6]}" record_hash=${record_arr[7]} else error "Invalid TLSA record given!" clean_up 10 fi # - Split record_name, to get port,protocol,hostnaem,domain # - CUR_IFS=$IFS IFS='\.' declare -a split_record_name_arr=($record_name) IFS=$CUR_IFS _port=${split_record_name_arr[0]} port=${_port##*_} _protocol=${split_record_name_arr[1]} protocol=${_protocol##*_} hostname="${split_record_name_arr[2]}" declare -i _index=3 while [[ $_index -lt ${#split_record_name_arr[@]} ]] ; do hostname="${hostname}.${split_record_name_arr[$_index]}" let _index++ done # - Determin zone (domain) # - _failed=false _hostname=$hostname _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 error "Given hostname/domain \"$hostname\" not supported by this nameserver!" exit 15 else domain=$_hostname fi # - 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 error "No Zonefile (master) found for domain \"$domain\" ." clean_up 11 fi zone_file_dir="$(dirname $zone_file)" # - Backup existing zone file directory # - # - Update/Add TLSA recotd if needed # - if grep -E "^$record_name.+$record_type" $zone_file > /dev/null 2>&1 ; then if [[ -n "$record_ttl" ]]; then search_string="^$record_name\\s+$record_ttl\\s+$record_type" else search_string="^$record_name\\s+$record_type" fi if grep -E "$search_string" $zone_file | grep $record_hash > /dev/null 2>&1 ; then info "TLSA record is already up to date.." clean_up 0 else _replac_string=${record_arr[@]} # - Backup Zone directory backup_dir $zone_file_dir # - Replace TLSA Record echononl "\tGoing to replace TLSA Record.." perl -i -n -p -e "s#^${record_name}.+${record_type}.*#$_replac_string#" $zone_file if [[ $? -eq 0 ]] ; then echo_ok $verbose && echo "" clean_up 1 else echo_failed error "Replacing TLSA Record failed!" exit 20 fi fi else warn "No Record for replacing fount in zonefile \"$(basename $zone_file)\"!" declare -i _count search_string="^_${port}\._(tcp|udp)\.$hostname" _count=`grep -Eo "$search_string" $zone_file | wc -l` _tlsa_record_found=true if [[ $_count -eq 0 ]]; then search_string="^_[0-9]{1,4}\._(tcp|udp)\.$hostname" _count=`grep -Eo "$search_string" $zone_file | wc -l` if [[ $_count -eq 0 ]]; then search_string="^_[0-9]{1,4}\._(tcp|udp).*TLSA" _count=`grep -Eo "$search_string" $zone_file | wc -l` if [[ $_count -eq 0 ]]; then _tlsa_record_found=false search_string="^[^;].+\s+IN\s+MX" _count=`grep -Eo "$search_string" $zone_file | wc -l` if [[ $_count -eq 0 ]]; then search_string="^[^;].+\s+IN\s+NS" _count=`grep -Eo "$search_string" $zone_file | wc -l` if [[ $_count -eq 0 ]]; then error "No place for adding a new TLSA record found. Check manually!" clean_up 99 fi fi fi fi fi CUR_IFS=$IFS IFS='' _tmpfile=`mktemp` > $_tmpfile # - backup zone directory backup_dir $zone_file_dir # - Add new TLSA record echononl "\tAdd new TLSA record to zonefile \"\".." 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 if ! $_tlsa_record_found ; then echo ";" >> $_tmpfile echo "; DANE" >> $_tmpfile echo ";" >> $_tmpfile fi echo "${record_arr[@]}" >> $_tmpfile 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 Coorect Owner/Permission echo "" echononl "\tCorrect 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 "\tCorrect 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 $verbose && echo "" clean_up 2 fi $verbose && echo "" clean_up 99