#!/bin/bash
#
# Copyright (c) 2008 SUSE LINUX Products GmbH, Nuernberg, Germany.
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author: Michael Calmer <mc@suse.de>
#         Sven Schober <sschober@suse.de>
#         Marius Tomaschewski <mt@suse.de>
#
##

unset POSIXLY_CORRECT ; set +o posix # we are using non-posix bash features

# The environment variable ROOT indicates the root of the system to be
# managed by SuSEconfig when that root is not '/'
r="$ROOT"

. "$r/usr/libexec/netconfig/functions.netconfig" || exit 1

PROGNAME="${0##*/}"
if test "$UID" != "0" -a "$USER" != root -a -z "$ROOT" ; then
    warn "You must be root to start $0."
    exit 1
fi

SYSFSDIR="/sys/class/net"
STATEDIR="/run/netconfig"
debug "$PROGNAME Module called"

. "$r/etc/sysconfig/network/config"

unset DNS_SERVERS ${!DNS_SERVERS_*}

DESTLINK="/etc/named.d/forwarders.conf"
DESTFILE="$STATEDIR/bind-forwarders.conf"

# *********************
# FUNCTIONS 
# *********************

function dump_named_forwarders ()
{
    local NAMESERVER=()
    local ns os

    cat << EOT
### $DESTLINK is a symlink to $DESTFILE
### autogenerated by netconfig!
#
# Before you change this file manually, consider to define the
# static DNS configuration using the following variables in the
# /etc/sysconfig/network/config file:
#     NETCONFIG_DNS_STATIC_SEARCHLIST
#     NETCONFIG_DNS_STATIC_SERVERS
#     NETCONFIG_DNS_FORWARDER
# or disable DNS configuration updates via netconfig by setting:
#     NETCONFIG_DNS_POLICY=''
#
# See also the netconfig(8) manual page and other documentation.
#
### Call "netconfig update -f" to force adjusting of ${DESTLINK}.
EOT

    for ns in $1; do
        for os in ${NAMESERVER[@]} ; do
            test "x$ns" == "x$os" && continue 2
        done

        # According to Bind 9 Administrators Reference Manual
        # there is no limit for forwarders.
        #[ ${#NAMESERVER[@]} -lt 3 ] || break

        NAMESERVER=(${NAMESERVER[@]} ${ns})
    done
    {
        echo "forwarders {"
        for ns in ${NAMESERVER[@]}; do
            echo "	${ns};"
        done
        echo "};"
    }
}

function write_named_forwarders ()
{
    local TMP_FILE=""
    local changed=0
    local updated=0

    test "X$1" = "X" && return 1

    debug "write_named_forwarders: $1 "

    TMP_FILE=`netconfig_mktemp "$ROOT$DESTFILE" 0644 0755` || return 3
    if ! dump_named_forwarders "$@" >> "$TMP_FILE" ; then
        rm -f -- "$TMP_FILE" ; return 3
    fi
    if cmp -s -- "$TMP_FILE" "$ROOT$DESTFILE"    ; then
        rm -f -- "$TMP_FILE" # unchanged
    elif mv -f -- "$TMP_FILE" "$ROOT$DESTFILE"   ; then
        changed=1
    else
        rm -f -- "$TMP_FILE" ; return 3
    fi

    debug "dns settings written to $DESTFILE"

    netconfig_check_and_link "$DESTLINK" "$DESTFILE" "$ROOT"
    updated=$?

    test $changed -ne 0 -a $updated -eq 1 || return $updated
    return $?
}

function get_dns_settings()
{
    local cfg=$1 ; shift
    test "x$cfg" = x && return 1
    local SERVICE DNSSERVERS
    local var idx DNS_SERVERS
    debug "exec get_dns_settings: $cfg"

    get_variable "SERVICE" "$cfg"
    idx=`get_ranking_idx "$SERVICE" "$@"`
    debug "     get_dns_settings: service '$SERVICE' => rank '$idx'"

    var="DNS_SERVERS_$idx"
    DNS_SERVERS=(${!var})
    get_variable "DNSSERVERS" "$cfg"
    if [ "x$DNSSERVERS" != "x" ]; then
        DNS_SERVERS=(${DNS_SERVERS[@]} $DNSSERVERS)
    fi
    unset DNSSERVERS
    eval "${var}='${DNS_SERVERS[@]}'"
    debug "     get_dns_settings: ${var}='${!var}'"

    debug "exit get_dns_settings: $cfg"
    return 0
}

function manage_interfaceconfig()
{
    local cfg dir="$1" ; shift
    test "x$dir" != x -a -d "$dir" || return 1

    debug "exec manage_interfaceconfig: $dir"
    for cfg in `ls -X -r "$dir/" 2>/dev/null`; do
        get_dns_settings "$dir/$cfg" "$@"
    done
    debug "exit manage_interfaceconfig: $dir"
    return 0
}

# *********************
# EXECUTION STARTS HERE
# *********************

if [ "$NETCONFIG_DNS_FORWARDER" != "bind" ]; then
    exit 0;
fi

# just for the case we need the original value...
_NETCONFIG_DNS_RANKING="$NETCONFIG_DNS_RANKING"
case "$_NETCONFIG_DNS_RANKING" in
  auto) _NETCONFIG_DNS_RANKING="$NETCONFIG_DNS_RANKING_DEFAULT" ;;
  none) _NETCONFIG_DNS_RANKING=""                               ;;
esac

# just for the case we need the original value...
_NETCONFIG_DNS_POLICY=`netconfig_policy "$NETCONFIG_DNS_POLICY" dns`
if [ "x$_NETCONFIG_DNS_POLICY" = "x" ]; then
    #
    # empty policy means do not touch anything. 
    # successful exit.
    #
    exit 0;
fi


sf=0
_g=1
# disable filename glob expansion if needed
shopt -o -q noglob || _g=0
[ $_g ] && shopt -o -s noglob
for POL in $_NETCONFIG_DNS_POLICY; do 
    shopt -o -u noglob
    case "$POL" in
    (NetworkManager)
        debug "Use NetworkManager policy merged settings"
        cfg="$ROOT$STATEDIR/NetworkManager.netconfig"
        if [ -r "$cfg" ] ; then
            get_dns_settings "$cfg" "$_NETCONFIG_DNS_RANKING"
        fi
        break
    ;;
    (STATIC)
        debug "Keep Static"
        DNS_SERVERS_1="$DNS_SERVERS_1 $NETCONFIG_DNS_STATIC_SERVERS"
    ;;
    (STATIC_FALLBACK)
        debug "Static Fallback"
        sf=1
    ;;
    (*)
        debug "Other: $POL"
        for IFDIR in $ROOT$STATEDIR/$POL; do
            test -d "$IFDIR" -a \
                 -d "$SYSFSDIR/${IFDIR##*/}" || continue
            # proceed every interface we find with this match
            manage_interfaceconfig  "$IFDIR" "$_NETCONFIG_DNS_RANKING"
        done
    ;;
    esac
done
[ $_g ] && shopt -o -u noglob

if [ $sf -eq 1 -a -z "$DNS_SERVERS_0" \
               -a -z "$DNS_SERVERS_1" ]; then
    DNS_SERVERS_2="$DNS_SERVERS_2 $NETCONFIG_DNS_STATIC_SERVERS"
fi

# filter out loopback addresses and all own IPs that would case a loop
own_ips=(`ip addr show 2>/dev/null | \
LANG=C LC_ALL=C gawk '/[ ]+(inet|inet6)[ ]+/ { sub("/.*","",$2); print $2; }' 2>/dev/null`)
for idx in 0 1 2 ; do
    var="DNS_SERVERS_$idx"
    val=(${!var})
    new=()
    for ns in ${val[@]} ; do
        case $ns in
            127.*|::1)      ;;
            *)
                for ip in "${own_ips[@]}" ; do
                    test "x$ip" = "x"    && continue
                    test "x$ip" = "x$ns" && continue 2
                done
                new+=("$ns")
            ;;
        esac
    done
    eval "${var}='${new[@]}'"
done

write_named_forwarders "$DNS_SERVERS_0 $DNS_SERVERS_1 $DNS_SERVERS_2"
RET=$?

if [ $RET -eq 1 ]; then
    # nothing changed; we are finished
    exit 0
elif [ $RET -eq 2 ]; then
   # user modified the config.
    echo "ATTENTION: $DESTLINK is not a link to $DESTFILE"
    echo "call \"netconfig update -f\" to adjust $DESTLINK"
    exit 20
fi

# here we should reload services if needed
if systemctl --quiet is-active named.service &>/dev/null ; then
    systemctl reload named.service &>/dev/null
fi

exit 0;

# vim: set ts=8 sts=4 sw=4 ai et:
