#!/bin/sh
#------------------------------------------------------------------------------
# /usr/bin/dyndns-update.sh - Update one DynDNS-provider
# part of Package DYNDNS __FLI4LVER__, see documentation for licence
#
# (c) Copyright 2003-2004 Tobias Gruetzmacher
# (c) copyright 2014-     babel (Claas Hilbrecht)
#
# Creation:    16.09.2003 tobig
# Last Update: $Id$
#------------------------------------------------------------------------------

. /usr/share/circuits/api
. /etc/boot.d/exittrap.inc

LOG4SH_CONFIGURATION="/etc/log4sh/dyndns.conf" . /usr/share/log4sh

#
# Error codes
#
#   connection related errors
ECONN_CONN=1
ECONN_PERM=2
#
#   provider related errors
#
EPROV_TMP=11
EPROV_PERM=12
EPROV_EMPTY=13
EPROV_UNKNOWN=14
#
#   packet related errors
#
EPACK_NOPATTERN=21
EPACK_INVFUNC=22
#
#   try related errors
ETRY_TMP=31
ETRY_PERM=32
ETRY_NOMETHOD=33
#
#
#
ECONF_MISSING=41

# checkip host to use, will be configurable some day
checkip_host="http://checkip.dyndns.org"

cleanup_pid_file=no

dyndns_update_cleanup ()
{
    if [ "$cleanup_pid_file" = "yes" ]
    then
        rm -f $dyn_pid_file
    fi
}

abort ()
{
    logger_error "$1"
    exit 1
}

usage ()
{
    logger_info "Usage:"
    logger_info "    update        <hostname> [ <circuit> ]"
    logger_info "    enable        <hostname>"
    logger_info "    enable_method <hostname> <method>"
    logger_info "    cancel_update <hostname>"
    logger_info "    status        <hostname>"
    logger_info "Additional options:"
    logger_info "    -x        : generate trace in /var/tmp/dyndns-*"
    exit 1
}

comment ()
{
    echo "# DynDNS-Comment: $1"
}

dyndns_date ()
{
    date
}

run_curl ()
{
    local cert=
    if echo "$update_host" | grep -q "^https://"
    then
        cert="--insecure --tlsv1"
        eval ca_file='$update_host_'$idx'_ca'
        if [ -n "$ca_file" ]
        then
            if [ -f "/etc/ssl/$ca_file" ]
            then
                cert="--cacert /etc/ssl/$ca_file"
            elif [ -f "/usr/share/curl/$ca_file" ]
            then
                cert="--cacert /usr/share/curl/$ca_file"
            fi
        fi
    fi

    local cmd="curl $cert --globoff --silent --no-buffer \
--show-error --stderr - --output - \
--user-agent \"dyndns_fli4l/$dyn_ver\" \
--anyauth --user \"$login_username:$login_password\" \
--interface $circ_dev_ip --ipv4 \
--connect-timeout $dyn_timeout \
$curl_line \
\"${update_host}\""
    #logger_debug "$cmd"
    eval "$cmd"

    case $? in
        0) return 0
            ;;
        7 | 28)
            # temporary failure, other methods still might work
            # 7  - Failed to connect to host.
            # 28 - Operation timeout. The specified time-out period
            #      was reached according to the conditions.
            return $ECONN_CONN
            ;;
        *)  return $ECONN_PERM
            ;;
    esac
}

#
# This is a plain http-request with or without basic auth
#
http_update ()
{
    for idx in `seq 1 $update_host_n`
    do
        eval update_host='$update_host_'$idx

        run_curl
        return $?

    done
    return $ECONN_CONN
}

#
# Use GnuDip over HTTP (Ask me if you need the direct socket connection)
#
# http://gnudip2.sourceforge.net/gnudip-www/latest/gnudip/html/protocol.html
#
gnudiphttp_update ()
{
    for idx in `seq 1 $update_host_n`
    do
        eval update_host='$update_host_'$idx

        tmpfile="/tmp/dyndns.$$.gnudip"
        {
            run_curl
        }>$tmpfile
        if [ $? -ne 0 ]
        then
            rm -f $tmpfile
            return $ECONN_CONN
        fi

        #
        # Sets meta_salt, meta_time and meta_sign
        #
        eval "`sed -n 's/^<meta name="\([A-Za-z]*\)" content="\([A-Za-z0-9]*\)">.*$/meta_\1='"'\2'"'/; /^meta_/ p' $tmpfile`"
        rm -f $tmpfile

        set -- `echo -n "${dyndns_md5password}.${meta_salt}" | md5sum`
        endpass="$1"

        update_host="$update_host/?salt=${meta_salt}&time=${meta_time}&sign=${meta_sign}&user=${dyndns_username}&pass=${endpass}&domn=${dyndns_domain}&reqc=2"
        run_curl
        return $?
    done
}

netcat_update ()
{
    for port in $provider_port
    do
        if echo -e "$provider_data" |\
            netcat -s $circ_dev_ip -w "$dyn_timeout" "$provider_host" "$port"
        then
            return 0
        fi
    done
    return $ECONN_CONN
}

nsupdate_update ()
{
    logger_setThreadName "nsupdate"
    for idx in `seq 1 $update_host_n`
    do
        eval update_host='$update_host_'$idx
        #logger_info "Trying update host '$update_host' with nsupdate method"
        # split ip/net and port
        set `echo $update_host | sed 's/:/ /'`
        update_host=$1
        port=$2

        # work around a problem with IPv4 and IPv6 host. nsupdate seems to try to reach
        # the update_host with IPv6 first if this is avaiable
        local ns_ipv4=`dig -4 +noall +short $update_host`
        local cmd=$(
            echo ""
            echo "server $ns_ipv4 $port"
            echo "local $circ_dev_ip"
            if [ "$update_a" = "yes" ]
            then
                echo "update delete $dyndns_fqdn_hostname A"
                echo "update add $dyndns_fqdn_hostname $update_a_ttl A ${dyndns_new_ipv4}"
            fi
            if [ "$update_aaaa" = "yes" ]
            then
                echo "update delete $dyndns_fqdn_hostname AAAA"
                echo "update add $dyndns_fqdn_hostname $update_aaaa_ttl AAAA ${dyndns_new_ipv6}"
            fi
            if [ "$update_wilcard" = "yes" ]
            then
                echo "update add *.$dyndns_fqdn_hostname $update_wilcard_ttl CNAME $dyndns_fqdn_hostname"
            fi
            #[ "$update_mx" = "yes" ] && "update add $hostname $update_mx_ttl mx"
            echo "key $dyndns_username $dyndns_password"
            echo "show"
            echo "send"
            echo "answer"
            echo "quit"
        )
        logger_debug "nsupdate called with:$cmd"
        IFS=''
        res=`echo $cmd | nsupdate`
        unset IFS
        logger_debug "$res"
    done

    logger_setThreadName "main"

    return $EPROV_UNKNOWN
}

decode_file ()
{
    echo >> $1
    if grep -q '^Transfer-Encoding: chunked' $1
    then
        sed -e '${/^[[:space:]]*$/d}' $1 | decode_chunked
    else
        sed -e '${/^[[:space:]]*$/d}' $1
    fi
}

grep_meta_info ()
{
    sed -n '/<meta/ {
s/\&/\&amp;/g
s/</\&lt;/g
s/>/\&gt;/g
p
}'
}

strip_html_header ()
{
    sed -e "/<.DOCTYPE/!bb
:a
/<body>/!{
N
ba
}
s:\(<.DOCTYPE.*<body>\):<!--\n\1\n-->:

:b

/<\/body/!bd
:c
/<\/html>/!{
N
bc
}
s:\(</body>.*</html>\):<!--\n\1\n-->:
:d

/^Content-Type.*/r $1"
}

keep_copy () {
    local arch=$dyn_logdir/archive
    local arch_copy=$arch/${1##*/}.`date +%d.%m.%Y`
    [ -d $arch ] || mkdir -p $arch
    cp $1 $arch_copy
    comment "Can not decide, whether this update was ok or not - considering it as ok."
    comment "Success: '$success', Failure: '$failure'"
    echo "# DynDNS-Report: $arch_copy"
}

try_method ()
{
    local sim=

    echo "# DynDNS-Method: '$update_method'"
    echo -n "# DynDNS-Date: "
    dyndns_date

    eval sim=\$provider_${update_method}_sim_error
    [ "$sim" ] && return $sim

    res=
    #
    # contact provider
    #
    {
        ${update_method}_update
        res=$?
    } > $dyn_trylog 2> $dyn_trylog.err

    echo "----- start answer -----"
    if grep -q "Content-Type:.*text/html" $dyn_trylog
    then
        sed_tmp=/tmp/dyndns.sed.$$
        grep_meta_info < $dyn_trylog > $sed_tmp
        decode_file $dyn_trylog | strip_html_header $sed_tmp
        rm -f $sed_tmp
    else
        decode_file $dyn_trylog
    fi
    echo "------ end answer ------"

    # if there's any content from stderr
    if [ -s $dyn_trylog.err ]
    then
        echo "----- start error -----"
        cat $dyn_trylog.err
        echo "------ end error ------"
        #if grep -q "$update_failure" $dyn_trylog.err
        #then
        #    res=$EPROV_PERM
        #fi
    fi

    res=0
    if [ $res -eq 0 ]
    then
        if grep -q -v '^[[:space:]]*$' $dyn_trylog
        then
            #
            # check for success
            #
            case "$update_success" in
            no_check)
                #
                # do not check at all
                #
                res=$EPROV_UNKNOWN
                ;;
            none)
                #
                # success pattern unknown, only check failure pattern
                #
                if grep -q "$update_failure" $dyn_trylog
                then
                    res=$EPROV_PERM
                else
                    res=$EPROV_UNKNOWN
                fi
                ;;
            '|'*)
                #
                # complex check, invoke check function
                #
                ${update_success#|} $dyn_trylog
                res=$?
                ;;
            *)
                #
                # success and failure pattern available, check success first, then failure
                #
                if grep -q "$update_success" $dyn_trylog
                then
                    res=0
                elif grep -q "$update_failure" $dyn_trylog
                then
                    res=$EPROV_PERM
                else
                    res=$EPROV_UNKNOWN
                fi
                ;;
            esac
        else
            res=$EPROV_EMPTY
        fi
    fi
    return $res
}

disable_method ()
{
    update_method=`echo "$update_method" | sed -e "s/$1\([[:space:]]\+\|$\)//g"`
}

try_update ()
{
    if [ -f $dyn_disabled.$method ]
    then
        comment "All methods disabled, no method left to try."
        disable_method $method
        return $ETRY_NOMETHOD
    fi
    while true
    do
        try_method
        # errors
        #   ECONN_CONN, ECONN_PERM,
        #   EPROV_TMP, EPROV_PERM, EPROV_EMPTY, EPROV_UNKNOWN
        #   EPACK_NOPATTERN, EPACK_INVFUNC

        case $? in
            0)
                return 0
                ;;
            $ECONN_CONN)
                # connection problem - try one of the other methods
                comment 'Failed to connect provider.'
                break
                ;;
            $ECONN_PERM)
                # connection problem, disable method
                comment "Permanent connection error, disabling method '$update_method'."
                cp $dyn_trylog.err $dyn_disabled.$method
                disable_method $method
                break
                ;;
            $EPROV_TMP)
                # temporary problem, try again after some time
                comment "Temporary problem, re-trying again after timeout (${dyn_method_timeout}s)."
                sleep $dyn_method_timeout
                continue
                ;;
            $EPROV_PERM)
                # permanent error, stop updating account, until
                # the problem is fixed
                comment "Permanent error, disabling host '$dyndns_fqdn_hostname'."
                cp $dyn_trylog.err $dyn_disabled
                return $ETRY_PERM
                ;;
            $EPROV_EMPTY)
                # empty reply from provider, try other method
                comment 'Empty reply from provider.'
                break
                ;;
            $EPROV_UNKNOWN)
                # can not decide whether this was ok or not, consider this to be ok
                keep_copy $dyn_trylog
                return 0
                ;;
            $EPACK_NOPATTERN | $EPACK_INVFUNC)
                # packet problem, disable method, try another one
                comment "Packet error, check pattern or check function missing or broken, disabling method '$update_method'."
                cp $dyn_trylog.err $dyn_disabled.$method
                disable_method $method
                break
                ;;
            *)
                # permanent error, stop updating account, until
                # the problem is fixed
                comment 'Unkown error, disabling host.'
                cp $dyn_trylog.err $dyn_disabled
                return $ETRY_PERM
                ;;
        esac
    done
    if [ ! "$update_method" ]
    then
        comment "All methods disabled, no method left to try."
        return $ETRY_NOMETHOD
    fi
    return $ETRY_TMP
}

acquire_lock ()
{
    #
    # update already running?
    #
    if [ -f $dyn_pid_file ]
    then
        read pid < $dyn_pid_file
        if kill -0 $pid 2> /dev/null
        then
            # kill whole process group
            logger_error "Update still running (pid $pid), terminating previous update ..."
            pg=`cut -d' ' -f 5 /proc/$pid/stat`
            [ "$pg" ] && kill -TERM -$pg 2> /dev/null
        else
            logger_warn "ignoring stale pid file (pid $pid) ..."
        fi
    fi
    cleanup_pid_file=yes
    echo $$ > $dyn_pid_file
}

generic_detect_ext_ipv4 ()
{
    dyndns_new_ipv4=
    case "$detect_ext_ipv4" in
    none)
        dyndns_new_ipv4=$circ_dev_ip
        ;;
    checkip)
        dyndns_new_ipv4=`curl --silent --globoff --interface $circ_dev_ip --ipv4 --connect-timeout $dyn_timeout ${checkip_host} |
            sed -n -e 's/.*Current IP Address:[[:space:]]*\([0-9.]\+\).*/\1/p'`
        ;;
    stun)
        dyndns_new_ipv4=`get_ext_ip_via_stun $circ_dev_ip`
        ;;
    *)
        abort "Unknown DYNDNS_x_DETECT_EXT_IPV4 setting <$detect_ext_ipv4>!"
    esac

    if [ -n "$dyndns_new_ipv4" ]
    then
        set $dyndns_new_ipv4
        [ $# -eq 1 ] || abort "Interface '$circ_dev' of circuit '$circuit' has multiple IPv4 addresses ($*). Don't know which one should be used to update the host!"
    else
        abort "Unable to determine IPv4 address of interface '$circ_dev' for circuit '$circuit'!"
    fi

    logger_info "Using IPv4 address ${dyndns_new_ipv4} determined with method '${detect_ext_ipv4}' via device '${circ_dev} (${circ_dev_ip})' for update."
}

generic_check_with_ipv4_nslookup ()
{
    set -- `nslookup $dyndns_fqdn_hostname | sed -n -e '/^Server:/,/^Address/d;s/^Address[^:]*://p'`
    dyndns_old_ipv4=$1
}

generic_check_with_ipv4_dig ()
{
    dyndns_old_ipv4=`dig -4 +noall +short $dyndns_fqdn_hostname`
}

generic_check_with_ipv4_dig_authoritative ()
{
    dyndns_old_ipv4=
    authoritative_ns=`dig -4 +noall +authority $dyndns_fqdn_hostname NS|cut -f 1`
    if [ -n "$authoritative_ns" ]
    then
        authoritative_ns=`dig -4 +noall +answer +short $authoritative_ns NS|shuf|head -n 1`
    else
        authoritative_ns=`dig -4 +noall +answer +short $dyndns_fqdn_hostname NS|shuf|head -n 1`
    fi
    if [ -n "$authoritative_ns" ]
    then
        logger_info "Using authorative nameserver $authoritative_ns to lookup current IPv4 address."
        dyndns_old_ipv4=`dig -4 +noall +short @$authoritative_ns $dyndns_fqdn_hostname`
    else
        abort "No usable authorative nameserver found, give up!"
    fi
}

check_dyn_runfile ()
{
    dyndns_old_ipv4=
    if [ -f $confdir/lookup_names ]
    then
        eval local get_ip=\$${provider}_get_ip
        : ${get_ip:=generic_check_with_ipv4_nslookup}
        $get_ip
    fi
    echo -e "dyndns_old_ipv4='$dyndns_old_ipv4'\nregistered_date='`date +%s`'\nregistered_checked_only=yes" > $dyn_runfile
}

generic_check_with_ipv4_cache ()
{
    dyndns_old_ipv4=
    [ -f $dyn_runfile ] || check_dyn_runfile
    [ -f $dyn_runfile ] || return

    . $dyn_runfile

    if [ "$dyndns_new_ipv4" = "$dyndns_old_ipv4" ]
    then
        time=`date +%s`
        if [ $time -gt $registered_date  ]
        then
            time_diff=`expr $time - $registered_date`
            #
            # make renew time configurable
            #
            if [ "$dyn_force" = "yes" ]
            then
                : ${dns_refresh:=29}
                if echo "$dns_refresh" | grep -q "^[[:digit:]]\+$"
                then
                    renew_time=`expr $dns_refresh \* 86400` # 86400 seconds per day
                    if [ $time_diff -ge $renew_time ]
                    then
                        logger_info "Forcing update, last update was more than $dns_refresh days ago."
                        do_update=yes
                    fi
                fi
            else
                renew_time=1800 # 30 min
                if [ $time_diff -lt $renew_time ]
                then
                    logger_warning "Ignoring forced update, minimum wait time isn't over yet."
                else
                    do_update=yes
                fi
            fi
        fi
    else
        do_update=yes
    fi
}

check_update_needed ()
{
    # the previous/old IPv4 is already fetched now try to compare with the current IPv4 address

    case "$check_with" in
        # we simply use our internal IP address cache file
        none)
            logger_info "IPv4 change address check is disabled with DYNDNS_x_CHECK_WITH='none'!"
	    return
            ;;
        cache)
            generic_check_with_ipv4_cache
        ;;
        # we do a simple nslookup
        nslookup)
            generic_check_with_ipv4_nslookup
        ;;
        # we use dig to query the standard DNS server
        dig)
            generic_check_with_ipv4_dig
        ;;
        # we use dig to query the authoritative DNS server (automatically determined)
        dig-query-authority)
            generic_check_with_ipv4_dig_authoritative
        ;;
    esac

    if [ -z "$dyndns_old_ipv4" ]
    then
        logger_error "Failed to get current IPv4 address via ${check_with}, giving up!"
        exit $ETRY_TMP
    fi

    if [ "$dyndns_new_ipv4" = "$dyndns_old_ipv4" ]
    then
        logger_info "No update needed, IPv4 address (detected: ${dyndns_new_ipv4} via ${detect_ext_ipv4} / DNS: ${dyndns_old_ipv4}) didn't changed."
        if [ ! -f $dyn_runfile ]
        then
            logger_info "No previous dyndns result is available, creating fake result file for webgui."
            echo "registered_ipv4=${dyndns_new_ipv4}" >$dyn_runfile
            echo "registered_date=`dyndns_date`" >>$dyn_runfile
        else
            exit 0
        fi
    fi
}

do_update ()
{
    #
    # account disabled?
    #
    [ -f $dyn_disabled ] && abort "Disabled until problem is fixed, see log for mor details ($dyn_disabled or dyndns web interface)!"

    acquire_lock

    #
    # get external ipv4
    #
    generic_detect_ext_ipv4
    if [ "$force_update" != "yes" ]
    then
	# if no update is nessary this check never return but exits the update script
        check_update_needed
    fi

    while true
    do
        {
            echo "# DynDNS-Provider: $provider"
            echo "# DynDNS-Forced-Update: $force_update"
            echo "# DynDNS-Registered-IP: $dyndns_new_ipv4"
            echo -n "# DynDNS-Begin: "
            dyndns_date
            echo -e "# DynDNS-End: unfinished\n# DynDNS-Result: unknown"

            try_update
            res=$?
            [ $res -eq $ETRY_TMP ] && comment "All methods failed, restarting after timeout (${dyn_retry_timeout}s)."
        } > $dyn_logfile

        if [ $res -eq 0 ]
        then
            res_txt=Success
        else
            res_txt=Failed
        fi
        sed -i -e "s/\(# DynDNS-End: \).*/\1`dyndns_date`/;s/\(# DynDNS-Result: \).*/\1$res_txt/" $dyn_logfile
        cat $dyn_logfile >> $dyn_history
        chmod a+r $dyn_logfile

        if [ "$dyn_lock" ]
        then
            if [ -f "$dyn_lock" ]
            then
                rm -f $dyn_lock
                logger_info "released lock file ${dyn_lock}"
            else
                [ "$dyn_clear_log" ] && logger_warn "lock already released by someone else"
            fi
            dyn_clear_log=
        fi

        case $res in
            $ETRY_TMP)
                sleep $dyn_retry_timeout
                continue
                ;;
            0)
                echo -e "registered_ipv4='$dyndns_new_ipv4'\nregistered_date='`date +%s`'" > $dyn_runfile
                logger_info "Update successful"
                ;;
            $ETRY_PERM)
                logger_error "Provider signaled an error, update failed!"
                ;;
            $ETRY_NOMETHOD)
                logger_error "No methods left to try, update failed!"
                ;;
            *)
                logger_error "Unknown error '$res', update failed!"
                ;;
        esac

        rm -f $dyn_trylog $dyn_trylog.err
        exit $res
    done
}

cancel_update ()
{
    if [ -f $dyn_pid_file ]
    then
        read pid < $dyn_pid_file
        rm -f $dyn_pid_file

        if kill -0 $pid 2> /dev/null
        then
            children=`sed -n -e "/^Pid:/h;/^PPid:.*$pid/{g;s/^Pid:[[:space:]]*//p}" /proc/*/status`
            if kill $pid 2> /dev/null
            then
                #
                # terminate any pending child processes too
                #
                kill $children
                str="# DynDNS-Cancel: `dyndns_date`"
                echo "$str" >> $dyn_logfile
                cat $dyn_logfile >> $dyn_history
                return 0
            else
                logger_error "failed to terminate update (pid: $pid)!"
            fi
        fi
    fi
    logger_info "No running update found."
    return 1
}

enable_host()
{
    if [ -f $dyn_disabled ]
    then
        rm -f $dyn_disabled
        set $service_update_method
        if [ $# -eq 1 ]
        then
            [ -f $dyn_disabled.$method ] && rm -f $dyn_disabled.$method
        fi
        str="# DynDNS-Enable: `dyndns_date`"
        echo "$str" >> $dyn_history
        echo "$str" >> $dyn_logfile
    fi
    return 0
}

enable_method()
{
    method=$ip
    [ -f $dyn_disabled.$method ] && rm -f $dyn_disabled.$method
}

do_status ()
{
    if [ -f $dyn_disabled ]
    then
        echo "$dyndns_fqdn_hostname: disabled"
    else
        if [ -f $dyn_runfile.pid ]
        then
            read pid < $dyn_runfile.pid
            echo "$dyndns_fqdn_hostname: update running ($pid)"
        else
            if [ -f $dyn_logfile ]
            then
                echo "$dyndns_fqdn_hostname: ok"
            else
                echo "$dyndns_fqdn_hostname: not updated yet"
            fi
        fi
    fi
}

set_defaults ()
{
    #
    # set default values
    #
    confdir="/etc/dyndns"
    dyn_ver="4.0.0-trunk-x86-r48178"

    dyn_logdir="/var/log/dyndns"
    dyn_histdir="${dyn_logdir}/history"
    dyn_rundir="/var/run/dyndns"
    dyn_logfile="$dyn_logdir/$dyndns_fqdn_hostname"
    dyn_runfile="$dyn_rundir/$dyndns_fqdn_hostname"
    dyn_history="$dyn_histdir/$dyndns_fqdn_hostname"
    dyn_disabled="${dyn_runfile}.disabled"

    dyn_pid_file="$dyn_rundir/$dyndns_fqdn_hostname.pid"

    dyn_timeout="60"
    dyn_method_timeout=1800
    dyn_retry_timeout=1800

    dyn_trylog=/tmp/dyndns-update.$$.$dyndns_fqdn_hostname

    dyn_clear_log=yes
}

get_dyndns_new_ipv4_for_update ()
{
    generic_detect_ext_ipv4
}

get_config ()
{
    #
    # get config for hostname
    #
    if [ ! -f $confdir/host.d/$dyndns_fqdn_hostname.conf ]
    then
        logger_fatal "The configuration for the host $dyndns_fqdn_hostname is missing!"
        exit $ECONF_MISSING
    fi

    . $confdir/host.d/$dyndns_fqdn_hostname.conf

    # no circuit is given, trying to find suitable circuit
    if [ -z "$circuit" ]
    then
        logger_info "No circuit is given, trying to find active circuit to update host."
        for idx in `seq 1 $circuit_n`
        do
            eval circ='$circuit_'$idx
            for c in $(circuit_resolve $circ)
            do
                # hack multiple concurrent online circuits
                #if [ `ip rule show | wc -l` > 3 ]
                #then
                #    circuit=$c
                #    circuit_read_field $circuit circ_name
                #    logger_info "The circuit '$circ_name' (ID: '$circuit') seems to be online, using this circuit to update the host."
                #    break
                #fi

                # TODO: implemented support for updates through IPv6
                if circuit_is_l3prot_up $c ipv4
                then
                    circuit=$c
                    circuit_read_field $circuit circ_name
                    logger_info "The circuit '$circ_name' (ID: '$circuit') seems to be online, using this circuit to update the host."
                    break
                fi
            done
            [ -n "$circuit" ] && break
        done
        if [ -z "$circuit" ]
        then
            abort "There's no circuit online matching name, alias or class $circ!"
        fi
    fi
    # maybe this is an alias, try resolve alias first
    circuit_resolve_alias circuit
    # get device used by this circuit
    circuit_read_field $circuit circ_dev
    if [ -z "$circ_dev" ]
    then
        abort "Can't get circuit ID for circuit <$circuit>!"
    fi

    # get IP of our outgoing WAN device to use this for source routing with netcat and STUN
    # TODO: implemented support for updates through IPv6
    circ_dev_ip=$(circuit_resolve_address "{$circuit}#address" ipv4)
    if [ -z "$circ_dev_ip" ]
    then
        abort "Can't get IPv4 address of interface ${circ_dev} belonging to circuit ${circuit}!"
    fi

    [ -f $dyn_rundir/inv_user ] && user="inv_$dyndns_username"
    [ -f $dyn_rundir/inv_pass ] && user="inv_$dyndns_password"
    [ -f $dyn_rundir/inv_host ] && user="inv_$dyndns_fqdn_hostname"

    #
    # read config for provider
    #
    . $confdir/provider.d/$provider
    [ "$update_method" ] || abort "Empty update type in $confdir/provider.d/$provider!"
}

exit_trap_add dyndns_update_cleanup

#
# check parameters
#
prog=${0##*/}
force_update="no"

logger_debug "Starting dyndns-update script..."

action=update
args=2
while [ "$1" ]
do
    case "$1" in
        -x | -vx)
            deb_flag=$1
            ;;
        -f)
            force_update="yes"
	    logger_info "DynDNS update forced, please be careful to not get banned!"
            ;;
        -l) if [ "$2" ]
            then
                dyn_lock=$2
                shift
            else
                usage
            fi
            ;;
        -s)
            syslog=yes
            ;;
        update | enable_method)
            action=$1
            args=2
            ;;
        enable | cancel | status)
            action=$1
            args=1
            ;;
        *)  break ;;
    esac
    shift
done

if [ $# -ne $args ]
then
    [ $action != update -o $# -ne 1 ] && usage
fi

if [ "$deb_flag" ]
then
    file=/var/tmp/dyndns-update_`date +%y-%m-%d_%H-%M-%S`_${action}_${1}_trace.log
    exec 2> $file
    set $deb_flag
fi

dyndns_fqdn_hostname="$1"
circuit="$2"

tmpmsg="$2"
: ${tmpmsg:="autodetect"}
logger_info "Using '${dyndns_fqdn_hostname}' as the hostname to work with command '${action}' and the circuit '${tmpmsg}'"

#
# divide hostname in hostname and domain
#
dyndns_hostname=${dyndns_fqdn_hostname%.*.*}
dyndns_domain=${dyndns_fqdn_hostname#$dyndns_hostname.}

set_defaults
get_config

#
# create md5 password
#
set -- `echo -n "$dyndns_password" | md5sum`
dyndns_md5password="$1"

case $action in
    update) do_update ;;
    enable) enable_host ;;
    enable_method) enable_method ;;
    cancel) cancel_update ;;
    status) do_status ;;
esac