#!/bin/sh

#Added by Vincent F. 2012/11/21
#Purpose of this script:
#1) create DNS bind config (zone and db file)
#2) activate build-in DNS

#define macro
BIND_PATH="/etc/bind"
PID_FILE="/var/run/named/named.pid"
#--
CONF_PATH="/tmp/named_conf"
DATA_PATH="/tmp/named_data"
LOG_FILE="$CONF_PATH/named.log"
#--
UCI_DN="bind"
UCI_DN_MAX=10
UCI_SUBDN="bindhost"
UCI_SUBDN_MAX=20
NULL_OP="(none)"
#--
DBGLV=$1		#$1: debug level. i.e. 0=print all logs, 5=print least logs
INTERFACE=$2	#$2: state changed interface. i.e. lan1, wan2

# Debug log function -----------
DBGLV_MINOR=0	#least important
DBGLV_LOG=1
DBGLV_NOTICE=2
DBGLV_CORE=3
DBGLV_ERR=4
DBGLV_EMERG=5	#most  important

#default debug level
[ "$DBGLV" ] || DBGLV=$DBGLV_ERR

MYLOG() { #$1: debug level, $2: log
	[ "$1" ] || return
	[ $1 -ge $DBGLV ] || return
	echo "[$1][`date +%Y/%m/%d-%H:%M:%S`] $2" >> $LOG_FILE
}
#-------------------------------
#add_listen_on_if() {
#	local iface
#	local all_if
#	local wan_st
#	local wan_ip
#	local out_str
#
#	all_if=`json show network |grep "physical=" |sed "s/^network\.//g" |sed "s/\.physical=.*//g"`
#	if [ "$all_if" ]; then
#		out_str=" "
#		for iface in $all_if; do
#			wan_st=`json get network.$iface.connection`
#			wan_ip=`json get network.$iface.ipaddr`
#			[ "$wan_st" == "up" -a "$wan_ip" ] || continue #offline, skip this interface
#			out_str="${out_str}${wan_ip}; "
#		done
#		echo $out_str
#	else
#		echo " none; "
#	fi
#	return
#}

# FUNCTIONS --------------------
create_zone() { #$1: zone file
	#local listen_if
	#listen_if=`add_listen_on_if`

	cat > $1 << EOF
options {
    directory "$DATA_PATH";

    auth-nxdomain no; #conform to RFC1035
    rrset-order {order cyclic;};
    allow-transfer {"none";};
    listen-on { !127.0.0.1; any; };
    version "DraytekDNS-v1.1.1731";
};

zone "localhost" {
    type master;
    file "$BIND_PATH/db.local";
};

zone "127.in-addr.arpa" {
    type master;
    file "$BIND_PATH/db.127";
};

zone "0.in-addr.arpa" {
    type master;
    file "$BIND_PATH/db.0";
};

zone "255.in-addr.arpa" {
    type master;
    file "$BIND_PATH/db.255";
};

EOF
}
# //// ----

add_zone() { #$1: dn section name, $2: zone file
	#get=`uci get $UCI_DN.$1`
	#MYLOG $DBGLV_MINOR "$UCI_DN.$1 -------- $get"
	#[ "$get" == "domain_name" ] || return 1

	get=`uci get $UCI_DN.$1.status`
	MYLOG $DBGLV_MINOR "$UCI_DN.$1.status = $get"
	[ "$get" == "enable" ] || return 2

	get=`uci get $UCI_DN.$1.dn`
	MYLOG $DBGLV_MINOR "$UCI_DN.$1.dn     = $get"
	[ "$get" ] || return 3

	dn_name=$get
	db_file="$CONF_PATH/$1.db"
	MYLOG $DBGLV_MINOR "set dn_name: $dn_name, db_file: $db_file"

	cat >> $2 << EOF
zone "$dn_name" {
    type master;
    file "$db_file";
};

EOF
	return 0
}
# //// ----

get_subdn_and_iface_list() { #$1: subdn section name
	#get=`uci get $UCI_SUBDN.$1`
	#MYLOG $DBGLV_MINOR "$UCI_SUBDN.$1 -------- $get"
	#[ "$get" == "sub_domain_name" ] || return 1

	get=`uci get $UCI_SUBDN.$1.status`
	MYLOG $DBGLV_MINOR "$UCI_SUBDN.$1.status = $get"
	[ "$get" == "enable" ] || return 2

	subdn=`uci get $UCI_SUBDN.$1.subdn`
	MYLOG $DBGLV_MINOR "$UCI_SUBDN.$1.subdn  = $subdn"
	[ "$subdn" ] || return 3

	iface_list=`uci get $UCI_SUBDN.$1.iface`
	MYLOG $DBGLV_MINOR "$UCI_SUBDN.$1.iface  = $iface_list"
	#[ "$iface_list" ] || return 4

	weight_list=`uci get $UCI_SUBDN.$1.weight`
	MYLOG $DBGLV_MINOR "$UCI_SUBDN.$1.weight = $weight_list"
	#allow this is NULL
	
	alias_list=`uci get $UCI_SUBDN.$1.alias`
	alias_if=`uci get $UCI_SUBDN.$1.alias_if`
	alias_weight=`uci get $UCI_SUBDN.$1.alias_weight`
	MYLOG $DBGLV_MINOR "$UCI_SUBDN.$1.alias        = $alias_list"
	MYLOG $DBGLV_MINOR "$UCI_SUBDN.$1.alias_if     = $alias_if"
	MYLOG $DBGLV_MINOR "$UCI_SUBDN.$1.alias_weight = $alias_weight"

	return 0
}
# //// ----

add_db() { # $1: dn section name, $2: domain name, $3: db file name, $4: serial number
	local ttl
	local refresh
	local retry
	local expire
	local nxttl
	local email
	local len

	local wan_st
	local wan_ip
	local check_set
	local set_usr_ns

	local host
	local host_list
	local serv
	local serv_list
	local addr
	local addr_list
	local pref
	local pref_list
	local ref
	local ref_list

	local dm_mode
	local sub_idx
	local sub_mode
	local pri

	MYLOG $DBGLV_LOG "idx: $1, dn_name: $2, db_file: $3"
	[ "$2" -a "$3" ] || return -1

	#Get SOA parameters
	ttl=`uci get $UCI_DN.$1.ttl`
	[ "$ttl" ] || ttl=1800
	refresh=`uci get $UCI_DN.$1.refresh`
	[ "$refresh" ] || refresh=900
	retry=`uci get $UCI_DN.$1.retry`
	[ "$retry" ] || retry=450
	expire=`uci get $UCI_DN.$1.expire`
	[ "$expire" ] || expire=604800
	nxttl=`uci get $UCI_DN.$1.nxttl`
	[ "$nxttl" ] || nxttl=1800
	email=`uci get $UCI_DN.$1.email`
	if [ "$email" ]; then
		email=`echo $email |sed s/@/./g`
		len=${#email}
		[ `echo $email |cut -c $len` == "." ] || email="${email}."
	else
		email="root.$2."
	fi

	#Write SOA
	cat > $3 << EOF
\$TTL	$ttl
@	IN	SOA	$2. $email (
	$4	; Serial
	$refresh		; Refresh
	$retry		; Retry
	$expire		; Expire
	$nxttl )		; Negative Cache TTL
;
EOF

	#Get interface priority
	iface_list=`uci get $UCI_DN.$1.iface`
	alias_if=`uci get $UCI_DN.$1.alias_if`
	pri=100
	dm_mode=`uci get $UCI_DN.$1.mode`
	[ "$dm_mode" == "failover" ] && {
		host_list=""
		for iface in $iface_list; do
			[ "`echo $host_list |grep $iface`" ] || { #this iface not check yet
				host_list="$host_list $iface" #add to already check list
				wan_st=`json get network.$iface.connection`
				wan_ip=`json get network.$iface.ipaddr`
				[ "$wan_st" == "up" -a "$wan_ip" ] || continue #offline, skip this interface
				get=`uci get $UCI_DN.$1.$iface`
				[ "$get" ] || get=3
				[ $get -lt $pri ] && pri=$get
			}
		done
		for iface in $alias_if; do
			[ "$iface" == $NULL_OP ] && continue
			[ "`echo $host_list |grep $iface`" ] || { #this iface not check yet
				host_list="$host_list $iface" #add to already check list
				if [ "$iface" != $NULL_OP ]; then
					wan_st=`json get network.$iface.connection`
					[ "$wan_st" == "up" ] || continue #offline, skip this interface
					get=`uci get $UCI_DN.$1.$iface`
				else
					get=`uci get $UCI_DN.$1.extern` #external alias
				fi
				[ "$get" ] || get=3
				[ $get -lt $pri ] && pri=$get
			}
		done
	}
	MYLOG $DBGLV_LOG "$1 mode: $dm_mode, use_pri: $pri, checked interface:$host_list"

	#Write global A record
	#iface_list=`uci get $UCI_DN.$1.iface` #get this previously
	weight_list=`uci get $UCI_DN.$1.weight`
	len=${#weight_list}
	check_set=0
	for iface in $iface_list; do
		if [ $len -gt 0 ]; then
			weight=`echo $weight_list |cut -c 1`
			weight_list=`echo $weight_list |cut -c 3-$len`
			len=$(($len - 2))
		else
			weight=1
		fi

		#check priority
		[ $pri -ne 100 ] && { #not default 100 -> use priority
			get=`uci get $UCI_DN.$1.$iface`
			[ "$get" ] || get=3
			[ $pri -ne $get ] && continue #not match, this is backup
		}

		wan_st=`json get network.$iface.connection`
		wan_ip=`json get network.$iface.ipaddr`
		[ "$wan_st" == "up" -a "$wan_ip" ] && {
			check_set=1
			for i in `seq 1 1 $weight`; do
				cat >> $3 << EOF
@	IN	A	$wan_ip
EOF
			done
		}
	done

	#Write domain alias
	alias_list=`uci get $UCI_DN.$1.alias`
	#alias_if=`uci get $UCI_DN.$1.alias_if` #get this previously
	alias_if="${alias_if} "
	alias_weight=`uci get $UCI_DN.$1.alias_weight`
	alias_weight="${alias_weight} "
	for addr in $alias_list; do
		addr=${addr%%/*}
		weight=1
		[ "$alias_weight" ] && {
			weight=${alias_weight%% *}
			alias_weight=${alias_weight#* }
		}
		iface=$NULL_OP
		[ "$alias_if" ] && {
			iface=${alias_if%% *}
			alias_if=${alias_if#* }
		}
		if [ "$iface" == $NULL_OP ]; then
			#check priority
			[ $pri -ne 100 ] && { #not default 100 -> use priority
				get=`uci get $UCI_DN.$1.extern`
				[ "$get" ] || get=3
				[ $pri -ne $get ] && continue #not match, this is backup
			}
			wan_st="up"
		else
			#check priority
			[ $pri -ne 100 ] && { #not default 100 -> use priority
				get=`uci get $UCI_DN.$1.$iface`
				[ "$get" ] || get=3
				[ $pri -ne $get ] && continue #not match, this is backup
			}
			wan_st=`json get network.$iface.connection`
		fi
		[ "$wan_st" == "up" ] && {
			check_set=1
			for i in `seq 1 1 $weight`; do
				cat >> $3 << EOF
@	IN	A	$addr
EOF
			done
		}
	done

	#Write global NS record
	host_list=`uci get $UCI_DN.$1.ns_host`
	host_list="${host_list} "
	serv_list=`uci get $UCI_DN.$1.ns_serv`
	addr_list=`uci get $UCI_DN.$1.ns_addr`
	addr_list="${addr_list} "
	set_usr_ns=0
	for serv in $serv_list ; do
		host=""
		[ "$host_list" ] && {
			host=${host_list%% *}
			host_list=${host_list#* }
			[ "$host" == $NULL_OP ] && host=""
		}
		addr=""
		[ "$addr_list" ] && {
			addr=${addr_list%% *}
			addr_list=${addr_list#* }
			[ "$addr" == $NULL_OP ] && addr=""
		}

		#Now write global NS record only
		[ "$host" ] || {
			cat >> $3 << EOF
@	IN	NS	$serv
EOF
			[ "$addr" ] && {
				cat >> $3 << EOF
$serv	IN	A	$addr
EOF
			}
			set_usr_ns=1
		}
	done

	#if no user defined NS record, set default NS
	[ $set_usr_ns -eq 1 ] || {
		[ $check_set -eq 1 ] || {
			cat >> $3 << EOF
@	IN	A	0.0.0.0
EOF
		}
		cat >> $3 << EOF
@	IN	NS	$2.
EOF
	}
	cat >> $3 << EOF

EOF

	#Write sub domain name A record
	for i in `seq 1 1 $UCI_SUBDN_MAX`; do
		sub_idx="${1}sub$i"
		get_subdn_and_iface_list $sub_idx && {
			MYLOG $DBGLV_LOG "Get sub domain ${subdn}, iface_list: ${iface_list}, alias_list: $alias_list"

			#Get interface priority
			pri=100
			sub_mode=`uci get $UCI_SUBDN.$sub_idx.mode`
			ref=0
			[ -z "$sub_mode" -o "$sub_mode" == "dm_setting" ] && {
				sub_mode=$dm_mode
				ref=1 #refer to domain priority setting
			}
			[ "$sub_mode" == "failover" ] && {
				host_list=""
				for iface in $iface_list; do
					[ "`echo $host_list |grep $iface`" ] || { #this iface not check yet
						host_list="$host_list $iface" #add to already check list
						wan_st=`json get network.$iface.connection`
						wan_ip=`json get network.$iface.ipaddr`
						[ "$wan_st" == "up" -a "$wan_ip" ] || continue #offline, skip this interface
						[ $ref -eq 1 ] && get=`uci get $UCI_DN.$1.$iface` || get=`uci get $UCI_SUBDN.$sub_idx.$iface`
						[ "$get" ] || get=3
						[ $get -lt $pri ] && pri=$get
					}
				done
				for iface in $alias_if; do
					[ "`echo $host_list |grep $iface`" ] || { #this iface not check yet
						host_list="$host_list $iface" #add to already check list
						if [ "$iface" != $NULL_OP ]; then
							wan_st=`json get network.$iface.connection`
							[ "$wan_st" == "up" ] || continue #offline, skip this interface
							[ $ref -eq 1 ] && get=`uci get $UCI_DN.$1.$iface` || get=`uci get $UCI_SUBDN.$sub_idx.$iface`
						else
							[ $ref -eq 1 ] && get=`uci get $UCI_DN.$1.extern` || get=`uci get $UCI_SUBDN.$sub_idx.extern` #external alias
						fi
						[ "$get" ] || get=3
						[ $get -lt $pri ] && pri=$get
					}
				done
			}
			MYLOG $DBGLV_LOG "$sub_idx mode: $sub_mode (ref=$ref), use_pri: $pri, checked interface:$host_list"

			len=${#weight_list}
			check_set=0
			for iface in $iface_list; do
				if [ $len -gt 0 ]; then
					weight=`echo $weight_list |cut -c 1`
					weight_list=`echo $weight_list |cut -c 3-$len`
					len=$(($len - 2))
				else
					weight=1
				fi

				#check priority
				[ $pri -ne 100 ] && { #not default 100 -> use priority
					[ $ref -eq 1 ] && get=`uci get $UCI_DN.$1.$iface` || get=`uci get $UCI_SUBDN.$sub_idx.$iface`
					[ "$get" ] || get=3
					[ $pri -ne $get ] && continue #not match, this is backup
				}

				wan_st=`json get network.$iface.connection`
				wan_ip=`json get network.$iface.ipaddr`
				[ "$wan_st" == "up" -a "$wan_ip" ] && {
					check_set=1
					for i in `seq 1 1 $weight`; do
						cat >> $3 << EOF
$subdn	IN	A	$wan_ip
EOF
					done
				}
			done

			#Write sub-domain alias
			alias_if="${alias_if} "
			alias_weight="${alias_weight} "
			for addr in $alias_list; do
				addr=${addr%%/*}
				weight=1
				[ "$alias_weight" ] && {
					weight=${alias_weight%% *}
					alias_weight=${alias_weight#* }
				}
				iface=$NULL_OP
				[ "$alias_if" ] && {
					iface=${alias_if%% *}
					alias_if=${alias_if#* }
				}
				if [ "$iface" == $NULL_OP ]; then
					#check priority
					[ $pri -ne 100 ] && { #not default 100 -> use priority
						[ $ref -eq 1 ] && get=`uci get $UCI_DN.$1.extern` || get=`uci get $UCI_SUBDN.$sub_idx.extern` #external alias
						[ "$get" ] || get=3
						[ $pri -ne $get ] && continue #not match, this is backup
					}
					wan_st="up"
				else
					#check priority
					[ $pri -ne 100 ] && { #not default 100 -> use priority
						[ $ref -eq 1 ] && get=`uci get $UCI_DN.$1.$iface` || get=`uci get $UCI_SUBDN.$sub_idx.$iface`
						[ "$get" ] || get=3
						[ $pri -ne $get ] && continue #not match, this is backup
					}
					wan_st=`json get network.$iface.connection`
				fi
				[ "$wan_st" == "up" ] && {
					check_set=1
					for i in `seq 1 1 $weight`; do
						cat >> $3 << EOF
$subdn	IN	A	$addr
EOF
					done
				}
			done
			[ $check_set -eq 1 ] && {
				cat >> $3 << EOF

EOF
			}
		}
	done

	#Write user defined A/AAAA record
	host_list=`uci get $UCI_DN.$1.a1_host`
	host_list="${host_list} "
	addr_list=`uci get $UCI_DN.$1.a1_addr`
	for addr in $addr_list ; do
		host="@"
		[ "$host_list" ] && {
			host=${host_list%% *}
			host_list=${host_list#* }
			[ "$host" == $NULL_OP ] && host="@"
		}
		cat >> $3 << EOF
$host	IN	A	$addr
EOF
	done
	host_list=`uci get $UCI_DN.$1.a4_host`
	host_list="${host_list} "
	addr_list=`uci get $UCI_DN.$1.a4_addr`
	for addr in $addr_list ; do
		host="@"
		[ "$host_list" ] && {
			host=${host_list%% *}
			host_list=${host_list#* }
			[ "$host" == $NULL_OP ] && host="@"
		}
		cat >> $3 << EOF
$host	IN	AAAA	$addr
EOF
	done
	cat >> $3 << EOF

EOF

	#Write MX record
	host_list=`uci get $UCI_DN.$1.mx_host`
	host_list="${host_list} "
	serv_list=`uci get $UCI_DN.$1.mx_serv`
	addr_list=`uci get $UCI_DN.$1.mx_addr`
	addr_list="${addr_list} "
	pref_list=`uci get $UCI_DN.$1.mx_pref`
	pref_list="${pref_list} "
	for serv in $serv_list ; do
		host="@"
		[ "$host_list" ] && {
			host=${host_list%% *}
			host_list=${host_list#* }
			[ "$host" == $NULL_OP ] && host="@"
		}
		addr=""
		[ "$addr_list" ] && {
			addr=${addr_list%% *}
			addr_list=${addr_list#* }
			[ "$addr" == $NULL_OP ] && addr=""
		}
		pref=1
		[ "$pref_list" ] && {
			pref=${pref_list%% *}
			pref_list=${pref_list#* }
		}
		cat >> $3 << EOF
$host	IN	MX	$pref	$serv
EOF
		[ "$addr" ] && {
			cat >> $3 << EOF
$serv	IN	A	$addr
EOF
		}
	done
	cat >> $3 << EOF

EOF

	#Write CNAME record
	host_list=`uci get $UCI_DN.$1.cn_host`
	ref_list=`uci get $UCI_DN.$1.cn_ref`
	ref_list="${ref_list} "
	for host in $host_list ; do
		[ "$ref_list" ] || break
		ref=${ref_list%% *}
		ref_list=${ref_list#* }
		cat >> $3 << EOF
$host	IN	CNAME	$ref
EOF
	done
	cat >> $3 << EOF

EOF

	#Write sub-domain NS record
	host_list=`uci get $UCI_DN.$1.ns_host`
	host_list="${host_list} "
	serv_list=`uci get $UCI_DN.$1.ns_serv`
	addr_list=`uci get $UCI_DN.$1.ns_addr`
	addr_list="${addr_list} "
	for serv in $serv_list ; do
		host=""
		[ "$host_list" ] && {
			host=${host_list%% *}
			host_list=${host_list#* }
			[ "$host" == $NULL_OP ] && host=""
		}
		addr=""
		[ "$addr_list" ] && {
			addr=${addr_list%% *}
			addr_list=${addr_list#* }
			[ "$addr" == $NULL_OP ] && addr=""
		}
		
		#Now write sub-domain NS record only
		[ "$host" ] && {
			cat >> $3 << EOF
$host	IN	NS	$serv
EOF
			[ "$addr" ] && {
				cat >> $3 << EOF
$serv	IN	A	$addr
EOF
			}
			cat >> $3 << EOF

EOF
		}
	done
}
#//// ----

dns_start() { #$1: zone file name, $2: serial number
	[ -e $PID_FILE ] && {
		MYLOG $DBGLV_CORE "named start failed! PID `cat $PID_FILE` already exist!"
		return 1
	}

	[ -d $DATA_PATH ] || mkdir $DATA_PATH
	/usr/sbin/named -c $1
	[ $? -ne 0 ] && {
		MYLOG $DBGLV_CORE "named start failed! bind failed!"
		return 2
	}
	iptables -I ACC_CTRL -i wan+ -m service --service-flag tcp,udp --dport 53 -j ACCEPT
	MYLOG $DBGLV_CORE "start named with serial $2."
	return 0
}
#//// ----

dns_stop() {
	[ -e $PID_FILE ] && {
		iptables -D ACC_CTRL -i wan+ -m service --service-flag tcp,udp --dport 53 -j ACCEPT
		kill `cat $PID_FILE`
		rm -f $PID_FILE
		MYLOG $DBGLV_CORE "($INTERFACE) kill named."
	}
	[ -d $DATA_PATH ] && rm -rf $DATA_PATH
}
# //// ----

uci_cfg_cleanup() {
	[ -e /tmp/.uci/$UCI_DN ] && {
		[ "`cat /tmp/.uci/$UCI_DN`" ] && uci commit $UCI_DN
	}
	[ -e /tmp/.uci/$UCI_SUBDN ] && {
		[ "`cat /tmp/.uci/$UCI_SUBDN`" ] &&  uci commit $UCI_SUBDN
	}
	/sbin/bind_uci_rearrange $UCI_DN $UCI_DN_MAX $UCI_SUBDN $UCI_SUBDN_MAX
}
#-------------------------------

# Main =========================
[ -d $CONF_PATH ] || exit 0 #wait booting
MYLOG $DBGLV_CORE "################## Start script ##################"

if [ "$INTERFACE" ]; then
	#call from interface up/down, execute only wan
	get=`echo "$INTERFACE" |cut -c 1-3`
	[ "$get" == "wan" -o "$get" == "usb" ] || {
		MYLOG $DBGLV_CORE "----- $INTERFACE status change, do nothing exit!"
		exit 0
	}
else
	#call from web uci change, re-arrange config
	uci_cfg_cleanup
fi

#Stop previous process
dns_stop

#Check built-in DNS is enabled or not
get=`uci get $UCI_DN.root.status`
[ "$get" == "enable" ] || {
	MYLOG $DBGLV_CORE "----- Built-in DNS is disabled, exit!"
	exit 0
}

#Create basic zone file
zone_file="$CONF_PATH/zone.conf"
create_zone $zone_file

#Set new serial number for SOA field
#Use current time as serail number
serial=`date +%s`

#Add user defined zone and db
for i in `seq 1 1 $UCI_DN_MAX`; do
	idx="dn$i"
	#get $dn_name and $db_file in function add_zone()
	add_zone $idx $zone_file && add_db $idx $dn_name $db_file $serial
done

#Activate build-in DNS
dns_start $zone_file $serial
MYLOG $DBGLV_CORE "------------------- End script -------------------"
