#!/bin/sh

### static variables
ddns_domain_file=/etc/bind/ddns.conf
ddns_domain_dir=ddns
ddns_key="ddns-key"
ddns_ttl=86400

ddns_std_namelist="ldap" #hostname should be first

. alterator-net-functions
. bind-sh-functions
. alterator-service-functions

### system name
ddns_system_name()
{
    local ddns_hostname="$(hostname)"
    echo "${ddns_hostname%%.*}"
}

ddns_system_zone()
{
    local ddns_hostname="$(hostname)"
    echo "${ddns_hostname#*.}"
}

### shared secret key

# usage: ddns_key_exist <name>
# check for existance of shared secret key
ddns_key_exist()
{
    local name="$1";shift
    [ -s "$bind_root_dir/etc/$name.conf" ]
}

# usage: ddns_create_key <name> [<nsupdate>]
# create shared secret key
# if optional parameter <nsupdate> is equal to 1 then an additional keys for nsupdate will be created
ddns_create_key()
{
    local name="$1";shift
    local nsupdate="${1:-}"
    cd "$bind_root_dir/etc"

    ddns_key_exist "$name" && return 0

    local key="$(/usr/sbin/dnssec-keygen -r /dev/urandom -a HMAC-MD5 -b 512 -n USER "$name")"
    local secret="$(sed -n 's/Key:[[:space:]]\+\([^[:space:]]\+\)/\1/p' "$key.private")"

    cd - >/dev/null

    cat>"$bind_root_dir/etc/$name.conf"<<EOF
key $name {
    algorithm hmac-md5;
    secret "$secret";
};
EOF
    chmod 0644 "$bind_root_dir/etc/$name.conf"
    chown root:named "$bind_root_dir/etc/$name.conf"

    [ "$nsupdate" = "1" ] || rm -f -- "$bind_root_dir/etc/K$name.+157+"*

    bind_local_conf_include /etc/bind/$name.conf
}

# usage: ddns_destroy_key <name>
# destroy shared secret key
ddns_destroy_key()
{
    local name="$1";shift
    cd "$bind_root_dir/etc"

    rm -f -- "$bind_root_dir/etc/K$name.+157+"*
    rm -f -- "$bind_root_dir/etc/$name.conf"

    bind_local_conf_exclude /etc/bind/$name.conf
}

# usage: ddns_print_key <name>
# print shared secret key
ddns_print_key()
{
    local name="$1";shift
    cat "$bind_root_dir/etc/$name.conf"
}

# usage: ddns_check_key <name> <file>
# validate <file> as shared secret key for name <name>
ddns_check_key()
{
    local name
    quote_sed_regexp_variable  name "$1";shift
    local file="$1";shift
    grep -qs "^[[:space:]]*key[[:space:]]\+$name[[:space:]]\+{[[:space:]]*\$" "$file"
}

# usage: ddns_replace_key <name> <file>
# replace shared secret file with a new one
ddns_replace_key()
{
    local name="$1";shift
    local file="$1";shift

    [ -s "$file" ] || return 0

    install -pm 640 --owner root --group named "$file" "$bind_root_dir/etc/$name.conf"
    bind_local_conf_include /etc/bind/$name.conf
}

### update zone information

# usage: ddns_update <command...>
# call nsupdate with appropriate key and action
ddns_record_update()
{
    nsupdate -k "$bind_root_dir/etc/K$ddns_key.+157+"*.private<<EOF
server localhost
update $@
send
EOF
}

### networks

# usage: ddns_net_foreach ip_version proc args
# call procedure for each available static network in system
ddns_net_foreach()
{
    local v="$1";shift
    local prog="$1";shift

    list_iface|
	while read iface; do
	    local ip="$(read_iface_current_addr "/etc/net/ifaces/$iface" "$v")"
	    [ -n "$ip" ] || continue
	    "$prog" "$ip" "$@" "$v"
	done
}

# usage: ddns_net_list_all <ip_version>
# list all available networks
ddns_net_list_all()
{
    local v="$1";shift
    list_iface|
	while read iface; do
	    local ip="$(read_iface_current_addr "/etc/net/ifaces/$iface" "$v")"
	    [ -n "$ip" ] || continue
	    printf '%s\t%s\n' "$ip" "$iface"
	done
}


### reverse zones, each reverse zone can contain multiple reverse domains

# usage: ddns_reverse_zone_add_ns <reverse-zone> <domain>
# add ns entry of specified domain to reverse zone
ddns_reverse_zone_add_ns()
{
    local rzone="$1";shift
    local zone="$1";shift

    ddns_record_update add $rzone $ddns_ttl NS ns.$zone
}

# usage: ddns_reverse_zone_del_ns <reverse-zone> <domain>
# remove ns entry of specified domain from reverse zone
ddns_reverse_zone_del_ns()
{
    local rzone="$1";shift
    local zone="$1";shift

    ddns_record_update delete $rzone NS ns.$zone
}

# usage: ddns_reverse_zone_list_ns <reverse-zone>
# list domains supported by reverse zone (using ns records)
ddns_reverse_zone_list_ns()
{
    local rzone="$1";shift

    local sdig  @localhost ns "$rzone" +short|
	sed \
	    -e 's/^ns\.//'\
	    -e 's/\.$//'
}

# usage: ddns_reverse_zone_list_ptr <reverse-zone>
# return list of ptr supported by reverse zone
ddns_reverse_zone_list_ptr()
{
    local rzone="$1";shift
    dig -t AXFR @localhost "$rzone"|
    sed -r -n \
        "s/^([.a-zA-Z0-9_-]+\.)(ip6|in-addr)\.arpa\.[[:space:]]+[0-9]+[[:space:]]+IN[[:space:]]+PTR[[:space:]]+([.a-zA-Z0-9_-]+)\./\1\2.arpa\t\3/p"
}

# usage: ddns_reverse_zone_add_ptr <reverse-ip.in-addr-arpa> <host>
# add ptr into reverse zone
ddns_reverse_zone_add_ptr()
{
    local rzone="$1";shift
    local rname="$1";shift

    ddns_record_update add $rzone $ddns_ttl PTR $rname
}

# usage: ddns_reverse_zone_del_ptr <reverse-ip.in-addr-arpa> <host>
# remove ptr from reverse zone
ddns_reverse_zone_del_ptr()
{
    local rzone="$1";shift
    local rname="$1";shift

    ddns_record_update delete $rzone PTR $rname
}

# usage: ddns_reverse_zone_sync_soa <reverse-zone>
ddns_reverse_zone_sync_soa()
{
    local rzone="$1";shift
    local rest_zone="$(ddns_reverse_zone_list_ns "$rzone"|tail -n1)"
    local new_tail="$(dig -t SOA @localhost "$rzone" +short|awk '{printf "%s %s %s %s %s\n",($3+1),$4,$5,$6,$7;}')"
    ddns_record_update add $rzone $ddns_ttl SOA ns.$rest_zone root.$rest_zone $new_tail
}

### forward zones

# usage: ddns_forward_zone_add_a <ip> <host.domain>
# add A record to zone
ddns_forward_zone_add_a()
{
    local ip="$1";shift
    local host="$1";shift

    ddns_record_update add $host $ddns_ttl A $ip
}

# usage: ddns_forward_zone_del_a <ip> <host.domain>
ddns_forward_zone_del_a()
{
    local ip="$1";shift
    local host="$1";shift

    ddns_record_update delete $host A $ip
}

# usage: ddns_forward_zone_has_a <ip> <host.domain>
ddns_forward_zone_has_a()
{
    local ip="$1";shift
    local host="$1";shift

    dig @localhost a "$host" +short|fgrep -wqs "$ip"
}

# usage: ddns_forward_zone_list_a <domain>
# list available a records from forward zone
ddns_forward_zone_list_a()
{
    local zone="$1";shift

    dig -t AXFR @localhost "$zone"|
	sed -n \
	    -e 's/^\([.a-zA-Z0-9_-]\+\)\.[[:space:]]\+[0-9]\+[[:space:]]\+IN[[:space:]]\+A[[:space:]]\+\([0-9.]\+\)/\2\t\1/p'
}

# usage: ddns_forward_zone_add_aaaa <ipv6> <host.domain>
# add AAAA record to zone
ddns_forward_zone_add_aaaa()
{
    local ip="$1";shift
    local host="$1";shift

    ddns_record_update add $host $ddns_ttl AAAA $ip
}

# usage: ddns_forward_zone_del_aaaa <ipv6> <host.domain>
ddns_forward_zone_del_aaaa()
{
    local ip="$1";shift
    local host="$1";shift

    ddns_record_update delete $host AAAA $ip
}

# usage: ddns_forward_zone_has_aaaa <ipv6> <host.domain>
ddns_forward_zone_has_aaaa()
{
    local ip="$1";shift
    local host="$1";shift

    dig @localhost aaaa "$host" +short|fgrep -wqs "$ip"
}

# usage: ddns_forward_zone_list_aaaa <domain>
# list available aaaa records from forward zone
ddns_forward_zone_list_aaaa()
{
    local zone="$1";shift

    dig -t AXFR @localhost "$zone"|
	sed -n \
        -e 's/^\([.a-zA-Z0-9_-]\+\)\.[[:space:]]\+[0-9]\+[[:space:]]\+IN[[:space:]]\+AAAA[[:space:]]\+\([[:xdigit:]:]\+\)/\2\t\1/p'
}

# usage: ddns_reverse_zone_add_ns <zone> <host>
# add NS entry of specified domain to forward zone
ddns_forward_zone_add_ns()
{
    local zone="$1";shift
    local host="$1";shift

    ddns_record_update add $zone $ddns_ttl NS $host
}

# usage: ddns_reverse_zone_del_ns <zone> <host>
# remove ns entry of specified domain from forward zone
ddns_forward_zone_del_ns()
{
    local zone="$1";shift
    local host="$1";shift

    ddns_record_update delete $zone NS $host
}

# usage: ddns_reverse_zone_list_ns <zone>
# list ns entries of specified domain (according forward zone)
ddns_forward_zone_list_ns()
{
    local zone="$1";shift
    dig  @localhost ns "$zone" +short|
	sed -e 's/\.$//'
}

# usage: ddns_forward_zone_list_mx <zone>
# list available mx records (with weights)
ddns_forward_zone_read_mx()
{
    local zone="$1";shift
    dig -t MX @localhost "$zone" +short|
	sort -k1,1 -n|
	sed -e 's/\.$//'
}

# usage: ddns_forward_zone_write_mx <zone>
# replace mx entries with a new one
# read list from stdin, each line constains weight and name
ddns_forward_zone_write_mx()
{
    local zone="$1";shift
    ddns_forward_zone_read_mx "$zone"|
	while read weight host;do
	    ddns_record_update delete $zone MX $weight $host
	done

    while read weight host; do
	ddns_record_update add $zone $ddns_ttl MX $weight $host
    done
}

### reverse domains, each reverse domain can contain multiple reverse zones

# usage: __reverse_ip <ip> <ip_version>
# reverse ip address, helper for reverse zone processing
__reverse_ip()
{
    local ip="$1"
    local v="$2"
    local out=
    if [ "$v" = 4 ]; then
        local IFS='.'
        set $ip
        out="$4.$3.$2.$1"
    else
        local i=
        local tmp="$(ipv6addr_expand "$ip" | sed -r 's/([[:xdigit:]]):?/\1 /g')"
        for i in $tmp; do
            out="$i${out:+.}$out"
        done
    fi

    echo "$out"
}

# usage: ddns_reverse_domain_add_host <zone> <ip> <name> <ip_version>
# add host<->ip binding to reverse zone of dynamic dns domain
ddns_reverse_domain_add_host()
{
    local zone="$1";shift
    local ip="$1";shift
    local name="$1";shift
    local ipv="$1";shift
    local rzone= rev_map=

    [ "$ipv" = 4 ] && rev_map=in-addr || rev_map=ip6
    rzone="$(__reverse_ip "$ip" "$ipv").$rev_map.arpa"

    if [ "$name" = "." ];then
	name="$zone"
    else
	name="$name.$zone"
    fi

    ddns_reverse_zone_add_ptr "$rzone" "$name"
}

# usage: ddns_reverse_domain_del_host <zone> <ip> <name> <ip_version>
# remove host<->ip binding from reverse zone of dynamic dns domain
ddns_reverse_domain_del_host()
{
    local zone="$1";shift
    local ip="$1";shift
    local name="$1";shift
    local ipv="$1";shift
    local rzone= rev_map=

    [ "$ipv" = 4 ] && rev_map=in-addr || rev_map=ip6
    rzone="$(__reverse_ip "$ip" "$ipv").$rev_map.arpa"

    if [ "$name" = "." ];then
	name="$zone"
    else
	name="$name.$zone"
    fi

    ddns_reverse_zone_del_ptr "$rzone" "$name"
}

__ipv6_revdns()
{
	local ip="${1%/*}"
	local prefix="${1#*/}"
	local pos n t net_head ip_s hex_mask s tmp out i

	[ $prefix -gt 0 -a $prefix -lt 128 ] || return 1
	ip="$(ipv6addr_expand $ip)"
    pos=$(($prefix / 16))
	t="$(($pos * 4 + $pos))"
	net_head="${ip:0:$t}"
	ip_s="${ip#$net_head}"
	ip_s="${ip_s%%:*}"
	n=$(($prefix % 16))
	hex_mask="$((0xffff >> $n))"
	s=$((0x$ip_s & $hex_mask))
	tmp="$(printf "%s%04x" "$net_head" "$s" | sed -r 's/([[:xdigit:]]):?/\1 /g')"
	for i in $tmp; do
		out="$i${out:+.}$out"
	done

	echo "$out"
}

# usage: ddns_reverse_domain_add_net <zone> <ip-address/mask> <ip_version>
# add network to reverse zone of dynamic dns domain
ddns_reverse_domain_add_net()
{
    local zone="$1";shift
    local ip="$1";shift
	local ipv="$1";shift
    local rev_ip="$(__reverse_ip "${ip%%/*}" "$ipv")"
    local tail_ip=
    local rev_zone=
    local ddns_rzone=
    local system_name="$(ddns_system_name)"
	local revdns_list=
	local rev_map=

	if [ "$ipv" = 4 ]; then
		revdns_list="$(revdns $ip)"
		rev_map='in-addr'
	else
		revdns_list="$(__ipv6_revdns "$ip")"
		rev_map='ip6'
	fi

	for rev_zone in $revdns_list;do
	    ddns_rzone="$rev_zone.$rev_map.arpa"
	    tail_ip="${rev_ip%.$rev_zone}"

	    if bind_domain_exists "$ddns_rzone";then
		# add an additional ns entry and binding to hostname to existing reverse zone
		ddns_reverse_zone_add_ns "$ddns_rzone" "$zone"
		[ "$tail_ip" = "$rev_ip" ] || ddns_reverse_domain_add_host "$zone" "${ip%%/*}" "$system_name" "$ipv"
	    else
		# create new reverse zone
		bind_domain_add "$ddns_domain_file" "$ddns_rzone"<<EOF
    type master;
    file "$ddns_domain_dir/$ddns_rzone";
    allow-update { key $ddns_key; };
EOF

		local ddns_ns_record="ns.$zone."
		local ddns_user_record="root.$zone."

		bind_zone_add "$ddns_domain_dir/$ddns_rzone"<<EOF
\$TTL	1D
@	IN	SOA	$ddns_ns_record $ddns_user_record ($(bind_serial_first) 12H 1H 1W 1H)
	IN	NS	$ddns_ns_record
$([ "$tail_ip" = "$rev_ip" ] || printf '%s\t\t\tPTR\t%s.' "$tail_ip" "$system_name.$zone")
EOF
	    fi
	done
}

# usage: ddns_reverse_domain_del_net <zone> <ip-address/mask> <ip_version>
# remove network from reverse zone of dynamic dns domain
ddns_reverse_domain_del_net()
{
    local zone="$1";shift
    local ip="$1";shift
	local ipv="$1";shift
    local ddns_rzone=
	local revdns_list=
	local rev_map=

	if [ "$ipv" = 4 ]; then
		revdns_list="$(revdns $ip)"
		rev_map='in-addr'
	else
		revdns_list="$(__ipv6_revdns "$ip")"
		rev_map='ip6'
	fi

	for rev_zone in $revdns_list;do
	    ddns_rzone="$ddns_rzone.$rev_map.arpa"

	    local rzone_count="$(ddns_reverse_zone_list_ns "$ddns_rzone"|wc -l)"
	    if [ "$rzone_count" = "1" ];then
		# no more domains in this reverse zone, free resource
		bind_domain_del "$ddns_domain_file" "$ddns_rzone"
		bind_zone_del "$ddns_domain_dir/$ddns_rzone"
	    else
		# remove appropriate domain from this zone

		local rptr=
		local rname=
		#clear pointers for our domain
		ddns_reverse_zone_list_ptr "$ddns_rzone"|
		    while read rptr rname; do
			[ "${rname%%.$zone}" = "$rname" ] ||
			    ddns_reverse_zone_del_ptr "$rptr" "$rname"
		    done

		# remove ns
		ddns_reverse_zone_del_ns "$ddns_rzone" "$zone"

		# update soa, soa should be equal to rest ns
		ddns_reverse_zone_sync_soa "$ddns_rzone"
	    fi
	done
}

# usage: ddns_calc_master_ip <ip_version>
ddns_calc_master_ip()
{
	local ipv="$1"; shift
	local ip=''
	for iface in $(list_static_iface); do
		ip="$(read_iface_addr "/etc/net/ifaces/$iface" "$ipv")"
		[ -n "$ip" ] && break
	done
	# look for nonstatic interface if no static found
	if [ -z "$ip" ]; then
		for iface in $(list_iface); do
			ip="$(read_iface_current_addr "/etc/net/ifaces/$iface" "$ipv")"
			[ -n "$ip" ] && break
		done
	fi
	echo "$ip"
}

### forward domains, each forward domain contain one forward zone

# usage: ddns_create_forward_domain <zone> <type>
# create new forward zone of dynamic dns domain
# type is equal to master or slave
ddns_create_forward_domain()
{
    local zone="$1";shift
    local type="$1";shift

    case "$type" in
	master)
	    ddns_create_key "$zone-transfer-key"
	    bind_domain_add "$ddns_domain_file" "$zone"<<EOF
    type master;
    file "$ddns_domain_dir/$zone";
    allow-update { key $ddns_key; };
    allow-transfer { localhost; key $zone-transfer-key; };
    notify yes;
EOF

	    local ddns_ns_record="ns.$zone."
	    local ddns_user_record="root.$zone."
	    local system_name="$(ddns_system_name)"
	    local system_ipv4="$(ddns_calc_master_ip 4)"
	    local system_ipv6="$(ddns_calc_master_ip 6)"
	    system_ipv4="${system_ipv4%%/*}"
	    system_ipv6="${system_ipv6%%/*}"
	    bind_zone_add "$ddns_domain_dir/$zone"<<EOF
\$TTL	1D
@	IN	SOA	$ddns_ns_record	$ddns_user_record ($(bind_serial_first) 12H 1H 1W 1H)
	IN	NS	$ddns_ns_record
;
$([ -n "$system_ipv4" ] && echo "ns	A	$system_ipv4")
$([ -n "$system_ipv6" ] && echo "ns	AAAA	$system_ipv6")
_kerberos._udp		SRV	0	0	88	$system_name
_kerberos._tcp		SRV	0	0	88	$system_name
_kerberos-adm._tcp	SRV	0	0	749	$system_name
_kerberos		TXT	$(echo $zone| tr '[[:lower:]]' '[[:upper:]]')
EOF
	;;
	slave)
	    control bind-slave enabled
	    bind_domain_add "$ddns_domain_file" "$zone"<<EOF
    type slave;
    file "slave/$zone";
    allow-transfer { localhost; key $zone-transfer-key; };
    masters { };
EOF
	;;
    esac
}

# usage: ddns_destroy_forward_domain <zone>
# destroy forward zone of dynamic dns domain
ddns_destroy_forward_domain()
{
    local zone="$1";shift

    ddns_destroy_key "$zone-transfer-key";
    bind_domain_del "$ddns_domain_file" "$zone"
    bind_zone_del "$ddns_domain_dir/$zone"
    bind_zone_del "slave/$zone"
}

# usage: ddns_list_forward_domain
# list available forward domains
ddns_list_forward_domain()
{
	bind_domain_list "$ddns_domain_file"|egrep -v '\.(in-addr|ip6)\.arpa$'
}

# usage: ddns_forward_domain_list_host <zone> <ip_version>
# return list of hosts of dynamic dns domain (forward zone)
ddns_forward_domain_list_host()
{
    local zone="$1";shift
	local ipv="$1";shift

	if [ "$ipv" = 4 ]; then
	    ddns_forward_zone_list_a "$zone"
	else
	    ddns_forward_zone_list_aaaa "$zone"
	fi | sed -e "s/\.$zone//" \
	    -e "s/$zone/./"
}

__full_name()
{
    local zone="$1";shift
    local name="$1";shift

    if [ "$name" = "." ];then
	echo "$zone"
    else
	echo "$name.$zone"
    fi
}

# usage: ddns_forward_domain_del_host <zone> <ip> <host> <ip_version>
# remove host<->ip binding from dynamic dns domain (forward zone)
ddns_forward_domain_del_host()
{
    local zone="$1";shift
    local ip="$1";shift
    local host="$1";shift
    local ipv="$1";shift
    local name="$(__full_name "$zone" "$host")";shift

	if [ "$ipv" = 4 ]; then
		ddns_forward_zone_del_a "$ip" "$name"
	else
		ddns_forward_zone_del_aaaa "$ip" "$name"
	fi
}

# usage: ddns_forward_domain_add_host <zone> <ip> <host> <ip_version>
# add host<->ip binding into dynamic dns domain (forward zone)
ddns_forward_domain_add_host()
{
    local zone="$1";shift
    local ip="$1";shift
    local host="$1";shift
    local ipv="$1";shift
    local name="$(__full_name "$zone" "$host")";shift

	if [ "$ipv" = 4 ]; then
		ddns_forward_zone_add_a "$ip" "$name"
	else
		ddns_forward_zone_add_aaaa "$ip" "$name"
	fi
}

# usage: ddns_forward_domain_has_host <ip> <host> <ip_version>
# check that some ip<->host binding exists
ddns_forward_domain_has_host()
{
    local zone="$1";shift
    local ip="$1";shift
    local host="$1";shift
    local ipv="$1";shift
    local name="$(__full_name "$zone" "$host")";shift

	[ "$ipv" = 4 ] &&
		ddns_forward_zone_has_a "$ip" "$name" ||
		ddns_forward_zone_has_aaaa "$ip" "$name"
}

# usage: ddns_forward_domain_add_net <zone> <ip-address/mask> <ip_version>
# add network to forward zone of dynamic dns domain,
# add map standard zone names to this ip address
ddns_forward_domain_add_net()
{
    local zone="$1";shift
    local ip="$1";shift
    local ipv="$1";shift
    local simple_ip="${ip%%/*}"

    for name in '.' "$(ddns_system_name)" $ddns_std_namelist; do
	ddns_forward_domain_add_host "$zone" "$simple_ip" "$name" "$ipv"
    done
}

# usage: ddns_forward_domain_del_net <zone> <ip-address/mask> <ip_version>
# remove network to forward zone of dynamic dns domain,
# see zone and remove mappings to ip addresses from this network
ddns_forward_domain_del_net()
{
    local zone="$1";shift
    local ip="$1";shift
    local ipv="$1";shift

    local name
    local address

	ddns_forward_domain_list_host "$zone" "$ipv" |
	while read address name; do
		if [ "$ipv" = 4 ]; then
			ipv4_ip_subnet "$address" "$ip" || continue
		else
			ipv6addr_is_in_subnet "$address" "$ip" || continue
		fi
		ddns_forward_domain_del_host "$zone" "$address" "$name" "$ipv"
	done
}

### joint reverse and forward domains

# usage: ddns_domain_del_net <zone> <ip-address/mask> <ip_version>
# remove network both from forward and reverse zones
ddns_domain_del_net()
{
    local zone="$1";shift
	local ip="$1";shift
    local ipv="$1";shift

    ddns_forward_domain_del_net "$zone" "$ip" "$ipv"
    ddns_reverse_domain_del_net "$zone" "$ip" "$ipv"
}

# usage: ddns_domain_add_net <zone> <ip-address/mask> <ip_version>
# add network both to forward and reverse zones
ddns_domain_add_net()
{
    local zone="$1";shift
	local ip="$1";shift
    local ipv="$1";shift

    ddns_forward_domain_add_net "$zone" "$ip" "$ipv"
    ddns_reverse_domain_add_net "$zone" "$ip" "$ipv"
}

__check_forward()
{
    local ip="$1";shift
    local zone="$1";shift
    local ipv="$1";shift
    ddns_forward_domain_has_host "$zone" "${ip%%/*}" "." "$ipv" && echo "$ip" 2>/dev/null
}

ddns_domain_list_net()
{
    local zone="$1";shift
    local ipv="$1";shift

    ddns_net_foreach "$ipv" __check_forward "$zone"
}

# usage: ddns_domain_list_host <zone> <ip_version>
# get domain contents
ddns_domain_list_host()
{
    local zone="$1";shift
    local ipv="$1";shift

    ddns_forward_domain_list_host "$zone" "$ipv"
}

# usage: ddns_domain_has_host <zone> <ip> <host> <ip_version>
# check for host existance
ddns_domain_has_host()
{
    local zone="$1";shift

    ddns_forward_domain_has_host "$zone" "$@"
}

# usage: ddns_domain_is_reserved_host host
# check for reserved host names
ddns_domain_is_reserved_host()
{
    local name="$1";shift
    local namelist=" . $ddns_std_namelist localhost $(ddns_system_name) "

    [ -z "${namelist##* $name *}" ]
}

__valid_ip()
{
    local zone="$1";shift
    local ip="$1";shift
    local ipv="$1";shift
    local rip="$(__reverse_ip "$ip" "$ipv")"
    local net=
    local rzone=
	local rev_map=

    [ "$ipv" = 4 ] && rev_map=in-addr || rev_map=ip6
    # check for appropriate domain in all available reverse networks
    for rzone in "$bind_root_dir/zone/$ddns_domain_dir/"*.$rev_map.arpa; do
	rzone="${rzone##*/}"
	net="${rzone%%.$rev_map.arpa}"
	[ "${rip%%$net}" != "$rip" ] || continue
	ddns_reverse_zone_list_ns "$rzone"|fgrep -wqs "$zone" && return 0
    done

    return 1
}


# usage: ddns_domain_add_host <zone> <ip> <host> <ip_version>
# add ip<->hostname binding
ddns_domain_add_host()
{
    local zone="$1";shift
    local ip="$1";shift
    local name="$1";shift
    local ipv="$1";shift

    ! __valid_ip "$zone" "$ip" "$ipv" || ddns_reverse_domain_add_host "$zone" "$ip" "$name" "$ipv"
    ddns_forward_domain_add_host "$zone" "$ip" "$name" "$ipv"
}

# usage: ddns_domain_del_host <zone> <ip> <host> <ip_version>
# add ip<->hostname binding
ddns_domain_del_host()
{
    local zone="$1";shift
    local ip="$1";shift
    local name="$1";shift
    local ipv="$1";shift

    ddns_reverse_domain_del_host "$zone" "$ip" "$name" "$ipv" || :
    ddns_forward_domain_del_host "$zone" "$ip" "$name" "$ipv"
}

__add_reverse()
{
    local ip="$1";shift
    local zone="$1";shift
    local ipv="$1";shift
    ddns_reverse_domain_add_net "$zone" "$ip" "$ipv"
}

__add_forward()
{
    local ip="$1";shift
    local zone="$1";shift
    local ipv="$1";shift
    ddns_forward_domain_add_net "$zone" "$ip" "$ipv"
}

# usage: ddns_create_domain <zone> <type> [<fill-reverse>]
# create specified domain,
# type is equal to master or slave
# if optional parameter is equal to 1 then appropriate reverse zones will be also created
ddns_create_domain()
{
    local zone="$1";shift
    local type="$1";shift
    local fill_reverse="${1:-}"

    ddns_key_exist "$ddns_key" || ddns_create_key "$ddns_key" 1
    ddns_create_forward_domain "$zone" "$type"
    if [ "$type" = "master" -a "$fill_reverse" = "1" ] ;then
	    ddns_net_foreach 4 __add_reverse "$zone"
	    ddns_net_foreach 6 __add_reverse "$zone"
    fi
    service_control bind condreload # update forward zone info
    if [ "$type" = "master" ] ;then
		ddns_forward_domain_add_net "$zone" "$(ddns_calc_master_ip 4)" 4
		ddns_forward_domain_add_net "$zone" "$(ddns_calc_master_ip 6)" 6
    fi
}

# usage: ddns_destroy_domain <zone>
# destroy specified domain and release all it's resources
ddns_destroy_domain()
{
    local zone="$1";shift
    local net= ipv=

	for ipv in 4 6; do
		# release networks
		ddns_domain_list_net "$zone" "$ipv"|
		while read net;do
			ddns_reverse_domain_del_net "$zone" "$net" "$ipv"
			ddns_forward_domain_del_net "$zone" "$net" "$ipv"
		done
	done
	# destroy forward zone
	ddns_destroy_forward_domain "$zone"
}

ddns_domain_exists()
{
    bind_domain_exists "$1"
}

ddns_list_domain()
{
    ddns_list_forward_domain
}

ddns_domain_list_ns()
{
    local zone="$1";shift
    ddns_forward_zone_list_ns "$zone"
}

ddns_domain_add_ns()
{
    ddns_forward_zone_add_ns "$@"
}

ddns_domain_del_ns()
{
    ddns_forward_zone_del_ns "$@"
}

ddns_domain_read_mx()
{
    local zone="$1";shift
    local mx="$(ddns_forward_zone_read_mx "$zone"|sed 's/^[[:space:]]*[0-9]\+[[:space:]]\+//'|tr '\n' ' ')"
    echo "${mx% }"
}

ddns_domain_write_mx()
{
    local zone="$1";shift
    local hostlist="$1";shift
    local weight=10;

    echo "$hostlist"|
	tr ' ' '\n'|
	sed '/^[[:space:]]*$/d'|
	while read host; do
	    printf '%s %s\n' "$weight" "$host"
	    weight=$(($weight + 10))
	done|
	ddns_forward_zone_write_mx "$zone"
}

# usage: ddns_domain_read_type <zone>
# read domain type (master or slave)
ddns_domain_read_type()
{
    local zone="$1";shift
    bind_domain_get "$ddns_domain_file" "$zone" type
}

# usage: ddns_domain_read_master <zone>
# read masters of slave domain
# function returns space separated values
ddns_domain_read_master()
{
    local zone="$1";shift

    bind_domain_get "$ddns_domain_file" "$zone" masters|
	sed -e 's/^[[:space:]]*{[[:space:]]*//' \
	    -e 's/[[:space:]]*}[[:space:]]*$//' \
	    -e "s/[[:space:]]\+key[[:space:]]\+[^[:space:]]\+[[:space:]]*;[[:space:]]*/ /g"
}

# usage: ddns_domain_write_master <zone> <slave-list>
# write masters of slave domain
# <slave-list> is a space separated host list
ddns_domain_write_master()
{
    local zone="$1";shift
    local value="$1";shift

    local str="{"
    for i in $value;do
	str="$str $i key $zone-transfer-key;"
    done
    str="$str }"

    bind_domain_set "$ddns_domain_file" "$zone" masters "$str"
}
