#!/usr/bin/env bash working_dir="$(dirname $(realpath $0))" conf_file="${working_dir}/conf/bind.conf" log_file="$(mktemp)" backup_date="$(date +%Y-%m-%d-%H%M)" #--------------------------------------- #----------------------------- # Base Function(s) #----------------------------- #--------------------------------------- usage() { echo [ -n "$1" ] && echo -e "Error: $1\n" cat< /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 } fatal(){ echo "" if $terminal ; then echo -e "[ \033[31m\033[1mError\033[m ]: $*" echo "" echo -e "\t\033[31m\033[1mScript was interupted\033[m!" else echo " [ Fatal ]: $*" echo "" echo " Script was terminated...." fi echo "" clean_up 1 } info (){ if $terminal && $LOGGING ; then echo "" echo -e "\t[ \033[32m\033[1mInfo\033[m ]: $*" echo "" else if $LOGGING ; then echo "" echo "Info: $*" echo "" fi fi } warn (){ echo "" if $terminal ; then echo -e "\t[ \033[33m\033[1mWarning\033[m ]: $*" else echo "Warning: $*" fi echo "" } error (){ echo "" if $terminal ; then echo -e "\t[ \033[31m\033[1mError\033[m ]: $*" else echo "Error: $*" fi echo "" } echo_done() { if $terminal && $LOGGING ; then echo -e "\033[75G[ \033[32mdone\033[m ]" else if $LOGGING ; then echo " [ done ]" fi fi } echo_ok() { if $terminal && $LOGGING ; then echo -e "\033[75G[ \033[32mok\033[m ]" else if $LOGGING ; then echo " [ ok ]" fi fi } echo_failed(){ if $terminal && $LOGGING ; then echo -e "\033[75G[ \033[1;31mfailed\033[m ]" else if $LOGGING ; then echo " [ failed ]" fi fi } echo_skipped() { if $terminal && $LOGGING ; then echo -e "\033[75G[ \033[33m\033[1mskipped\033[m ]" else if $LOGGING ; then echo " [ skipped ]" fi fi } trap clean_up SIGHUP SIGINT SIGTERM #--------------------------------------- #----------------------------- # Check some prerequisites #----------------------------- #--------------------------------------- if [[ -t 1 ]] ; then terminal=true LOGGING=true else terminal=false LOGGING=false fi while getopts hq opt ; do case $opt in q) LOGGING=false ;; h) usage ;; *) usage esac done #--------------------------------------- #----------------------------- # Setting Defaults #----------------------------- #--------------------------------------- DEFAULT_CONF_FILE_DIR="/etc/bind" DEFAULT_BIND_CACHE_DIR="/var/cache/bind" #--------------------------------------- #----------------------------- # Load default values from bind.conf # # Overwrites the settings above # #----------------------------- #--------------------------------------- if $LOGGING ; then clear echo "" echo "" 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 echo "" [[ -n "$CONF_FILE_DIR" ]] && DEFAULT_CONF_FILE_DIR="$CONF_FILE_DIR" [[ -n "$BIND_CACHE_DIR" ]] && DEFAULT_BIND_CACHE_DIR="$BIND_CACHE_DIR" if [[ -n "$ZONE_FILE_MASTER_DIR" ]] ; then DEFAULT_ZONE_FILE_MASTER_DIR="$ZONE_FILE_MASTER_DIR" else DEFAULT_ZONE_FILE_MASTER_DIR="${DEFAULT_CONF_FILE_DIR}/master" fi if [[ -n "$ZONE_FILE_SLAVE_DIR" ]] ; then DEFAULT_ZONE_FILE_SLAVE_DIR="$ZONE_FILE_SLAVE_DIR" else DEFAULT_ZONE_FILE_SLAVE_DIR="$DEFAULT_BIND_CACHE_DIR" fi [[ -n "$ZONES_DECLARATION_FILE" ]] && DEFAULT_ZONES_DECLARATION_FILE="$ZONES_DECLARATION_FILE" [[ -n "$SOA_PRIMARY_MASTER" ]] && DEFAULT_SOA_PRIMARY_MASTER=$SOA_PRIMARY_MASTER [[ -n "$SOA_ADMIN_EMAIL" ]] && DEFAULT_SOA_ADMIN_EMAIL=$SOA_ADMIN_EMAIL echo "" echo -e "\033[32m--\033[m" echo "" echo "Insert SOA primary master for all the zones." echo "" SOA_PRIMARY_MASTER="" if [[ -n "$DEFAULT_SOA_PRIMARY_MASTER" ]]; then echononl "Primary Master (SOA-Record) [${DEFAULT_SOA_PRIMARY_MASTER}]" read SOA_PRIMARY_MASTER if [[ "X${SOA_PRIMARY_MASTER}" = "X" ]]; then SOA_PRIMARY_MASTER="$DEFAULT_SOA_PRIMARY_MASTER" fi else echononl "Primary Master (SOA-Record): " read SOA_PRIMARY_MASTER while [ "X$SOA_PRIMARY_MASTER" = "X" ] ; do echo -e "\n\t\033[33m\033[1mSetting 'Primary Master' is required!\033[m\n" echononl "Primary Master (SOA-Record): " read SOA_PRIMARY_MASTER done fi echo "" echo -e "\033[32m--\033[m" echo "" echo "Insert SOA admin e-mail address for all the zones." echo "" SOA_ADMIN_EMAIL="" if [[ -n "$DEFAULT_SOA_ADMIN_EMAIL" ]]; then echononl "Admin E-Mail Address (SOA-Record) [${DEFAULT_SOA_ADMIN_EMAIL}]" read SOA_ADMIN_EMAIL if [[ "X${SOA_ADMIN_EMAIL}" = "X" ]]; then SOA_ADMIN_EMAIL="$DEFAULT_SOA_ADMIN_EMAIL" fi else echononl "Admin E-Mail Address (SOA-Record): " read SOA_ADMIN_EMAIL while [ "X$SOA_ADMIN_EMAIL" = "X" ] ; do echo -e "\n\t\033[33m\033[1mSetting 'Admin E-Mail Address' is required!\033[m\n" echononl "Admin E-Mail Address (SOA-Record): " read SOA_ADMIN_EMAIL done fi echo "" echo "" echo -e "\033[32m--\033[m" echo "Common parameters" echo -e "\033[32m--\033[m" echo "" echo "Insert directory containing the bind configuration files." echo "" CONF_FILE_DIR="" if [[ -n "$DEFAULT_CONF_FILE_DIR" ]] ; then echononl "Bind Configuration Directory [${DEFAULT_CONF_FILE_DIR}]: " read CONF_FILE_DIR if [[ "X$CONF_FILE_DIR" = "X" ]]; then CONF_FILE_DIR="$DEFAULT_CONF_FILE_DIR" fi else echononl "Bind Configuration Directory: " read CONF_FILE_DIR while [ "X$CONF_FILE_DIR" = "X" ] ; do echo -e "\n\t\033[33m\033[1mSetting 'Bind Configuration Directory' is required!\033[m\n" echononl "Bind Configuration Directory: " read CONF_FILE_DIR done fi [[ -n "$ZONES_DECLARATION_FILE" ]] || DEFAULT_ZONES_DECLARATION_FILE="${CONF_FILE_DIR}/named.conf.local" [[ -n "$ZONE_FILE_MASTER_DIR" ]] || ZONE_FILE_MASTER_DIR="${CONF_FILE_DIR}/master" echo "" echo -e "\033[32m--\033[m" echo "" echo "Insert zones declaration file." echo "" ZONES_DECLARATION_FILE="" if [[ -n "$DEFAULT_ZONES_DECLARATION_FILE" ]] ; then echononl "Zones Declaration File [${DEFAULT_ZONES_DECLARATION_FILE}]: " read ZONES_DECLARATION_FILE if [[ "X$ZONES_DECLARATION_FILE" = "X" ]]; then ZONES_DECLARATION_FILE="$DEFAULT_ZONES_DECLARATION_FILE" fi else echononl "Zones Declaration File: " read ZONES_DECLARATION_FILE while [ "X$ZONES_DECLARATION_FILE" = "X" ] ; do echo -e "\n\t\033[33m\033[1mSetting 'Zones Declaration File' is required!\033[m\n" echononl "Zones Declaration File: " read ZONES_DECLARATION_FILE done fi # - replace '@' character with '.' # - SOA_ADMIN_EMAIL="${SOA_ADMIN_EMAIL/@/.}" echo "" echo "" echo -e "\033[1;32mSettings for \033[1;37mChange SOA Record\033[m Script" echo "" echo -e "\tSOA Primary Master..................: $SOA_PRIMARY_MASTER" echo -e "\tSOA Admin E-Mail Address............: $SOA_ADMIN_EMAIL" echo "" echo -e "\tBind Configuration Directory........: $CONF_FILE_DIR" echo -e "\tZones Declaration File..............: $ZONES_DECLARATION_FILE" echo "" info "Repace Primary Master \033[37m\033[1m${SOA_PRIMARY_MASTER}\033[m and Admin E-Mail \033[37m\033[1m${SOA_PRIMARY_MASTER}\033[m in all SOA Records" echo -n "To continue type uppercase 'YES': " read OK echo "" if [[ "$OK" != "YES" ]] ; then fatal "Abort by user request - Answer as not 'YES'" fi else # if $LOGGING if [[ ! -f "$conf_file" ]]; then fatal "Configuration file '$conf_file' not found!" else echononl " Loading default Configuration values from $(basename ${conf_file}).." source "${conf_file}" > $log_file 2>&1 if [[ $? -eq 0 ]]; then echo_ok else echo_failed fatal "$(cat $log_file)" fi fi if [[ -z "$CONF_FILE_DIR" ]]; then CONF_FILE_DIR="$DEFAULT_CONF_FILE_DIR" fi if [[ ! -d "$CONF_FILE_DIR" ]] ; then fatal "Directory contaning bind configurations not found (see CONF_FILE_DIR)!" fi [[ -n "${ZONE_FILE_MASTER_DIR}" ]] || ZONE_FILE_MASTER_DIR="${CONF_FILE_DIR}/master" if [[ -z "$ZONES_DECLARATION_FILE" ]] ; then ZONES_DECLARATION_FILE="${CONF_FILE_DIR}/named.conf.local" fi [[ -z "$SOA_ADMIN_EMAIL" ]] && fatal "Missing SOA Admin E-Mail Address (see SOA_ADMIN_EMAIL)" [[ -z "$SOA_PRIMARY_MASTER" ]] && fatal "Missing SOA Primary Master (see SOA_PRIMARY_MASTER)" # - replace '@' character with '.' # - SOA_ADMIN_EMAIL="${SOA_ADMIN_EMAIL/@/.}" fi if [[ ! -f "$ZONES_DECLARATION_FILE" ]]; then fatal "Zone declaration file '${ZONES_DECLARATION_FILE}' not found!" fi declare -a zone_file_arr=() _found=false _is_master=false zone_file="" regex_master="type[[:space:]]+master" regex_file="^[[:space:]]*file" while IFS='' read -r _line || [[ -n $_line ]] ; do if [[ $_line =~ ^[[:space:]]*zone[[:space:]]+ ]]; then _found=true zone="$(echo $_line | awk '{print$2}')" shopt -s extglob if [[ $zone =~ \; ]]; then zone=${zone%%*(\;)} fi if [[ $zone =~ ^\" ]]; then zone=${zone##*(\")} zone=${zone%%*(\")} fi shopt -u extglob 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 fi if [[ $_line =~ $regex_master ]]; then _is_master=true fi if [[ "$_line" =~ ^[[:space:]]*\}[[:space:]]*\; ]]; then if $_is_master && [[ -n "$zone_file" ]]; then zone_file_arr+=("${zone_file}:$zone") fi _is_master=false _found=false zone_file="" fi fi done < "$ZONES_DECLARATION_FILE" if [[ -d "$ZONE_FILE_MASTER_DIR" ]] ; then echononl "\tBackup directory '${ZONE_FILE_MASTER_DIR}'.." cp -a "${ZONE_FILE_MASTER_DIR}" "${ZONE_FILE_MASTER_DIR}.${backup_date}" > $log_file 2>&1 if [[ $? -eq 0 ]]; then echo_ok else echo_failed fatal "$(cat $log_file)" fi fi for _val in "${zone_file_arr[@]}" ; do IFS=':' read -a _val_arr <<< "${_val}" declare -i _serial_new=`date +%Y%m%d01` zone_file="${_val_arr[0]}" zone="${_val_arr[1]}" if $LOGGING ; then echo "" echo -e "\tPreparing zone file \033[37m\033[1m${zone_file}\033[m" fi ## - calculate new serial ## - echononl "\tCalculate new serial.." declare -i __serial=$(grep -E "^\s*[0-9]{10}\s+;?(serial)?" $zone_file | awk '{print$1}') if [[ -z "$__serial" ]]; then echo_failed continue fi while [[ ! $_serial_new -gt $__serial ]]; do let _serial_new++ done if [[ $? -gt 0 ]]; then echo_failed else echo_ok fi echononl "\tChanging SOA Record.." if ! $(grep -q -E "^.*IN\s+SOA\s+${SOA_PRIMARY_MASTER}\.\s+${SOA_ADMIN_EMAIL}\.\s+\(" $zone_file) ; then perl -i -n -p -e "s/^(.*IN\s+SOA).*$/\1 ${SOA_PRIMARY_MASTER}. ${SOA_ADMIN_EMAIL}. \(/" $zone_file if [[ $? -gt 0 ]]; then echo_failed else echo_ok fi else echo_skipped continue fi ## - Set new serial ## - echononl "\tRenew serial.." perl -i -n -p -e "s#^(\s*) $__serial(.*)#\1 $_serial_new\2#" $zone_file if [[ $? -gt 0 ]]; then echo_failed else echo_ok fi ## - Reload Zone ## - echononl "\tReload Zone ${zone}.." /usr/sbin/rndc reload $zone > /dev/null 2>&1 if [[ $? -gt 0 ]]; then echo_failed else echo_ok fi sleep 1 done if [[ -d "${ZONE_FILE_MASTER_DIR}.${backup_date}" ]] ; then diff -Nur "${ZONE_FILE_MASTER_DIR}" "${ZONE_FILE_MASTER_DIR}.${backup_date}" > /dev/null 2>&1 if [[ $? -eq 0 ]]; then info "No zone file has changed.\n\t Removing previously created backup" echononl "\tDelete '${ZONE_FILE_MASTER_DIR}.${backup_date}'.." rm -rf "${ZONE_FILE_MASTER_DIR}.${backup_date}" > $log_file 2>&1 if [[ $? -eq 0 ]]; then echo_ok else echo_failed fi fi fi $LOGGING && echo "" clean_up 0