#!/bin/sh
# /usr/lib/ddns/dynamic_dns_functions.sh
#
-# Original written by Eric Paul Bishop, January 2008
#.Distributed under the terms of the GNU General Public License (GPL) version 2.0
+# Original written by Eric Paul Bishop, January 2008
# (Loosely) based on the script on the one posted by exobyte in the forums here:
# http://forum.openwrt.org/viewtopic.php?id=14040
-#
-# extended and partial rewritten in August 2014 by
-#.Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
-# to support:
-# - IPv6 DDNS services
-# - setting DNS Server to retrieve current IP including TCP transport
-# - Proxy Server to send out updates or retrieving WEB based IP detection
-# - force_interval=0 to run once (useful for cron jobs etc.)
-# - the usage of BIND's host instead of BusyBox's nslookup if installed (DNS via TCP)
-# - extended Verbose Mode and log file support for better error detection
+# extended and partial rewritten
+#.2014-2016 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
#
# function timeout
# copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh
. /lib/functions/network.sh
# GLOBAL VARIABLES #
+VERSION="2.6.1-1"
SECTION_ID="" # hold config's section name
VERBOSE_MODE=1 # default mode is log to console, but easily changed with parameter
UPDFILE="" # store UPTIME of last update
DATFILE="" # save stdout data of WGet and other external programs called
ERRFILE="" # save stderr output of WGet and other external programs called
-TLDFILE=/usr/lib/ddns/tld_names.dat # TLD file used by split_FQDN
+TLDFILE=/usr/lib/ddns/tld_names.dat.gz # TLD file used by split_FQDN
CHECK_SECONDS=0 # calculated seconds out of given
FORCE_SECONDS=0 # interval and unit
URL_USER="" # url encoded $username from config file
URL_PASS="" # url encoded $password from config file
+URL_PENC="" # url encoded $param_enc from config file
+
+SRV_ANSWER="" # Answer given by service on success
ERR_LAST=0 # used to save $? return code of program and function calls
ERR_UPDATE=0 # error counter on different local and registered ip
# file /usr/lib/ddns/services_ipv6 for IPv6
# $1 Name of Variable to store url to
# $2 Name of Variable to store script to
+# $3 Name of Variable to store service answer to
get_service_data() {
- local __LINE __FILE __NAME __URL __SERVICES __DATA
- local __SCRIPT=""
- local __OLD_IFS=$IFS
- local __NEWLINE_IFS='
-' # __NEWLINE_IFS
- [ $# -ne 2 ] && write_log 12 "Error calling 'get_service_data()' - wrong number of parameters"
+ [ $# -ne 3 ] && write_log 12 "Error calling 'get_service_data()' - wrong number of parameters"
__FILE="/usr/lib/ddns/services" # IPv4
[ $use_ipv6 -ne 0 ] && __FILE="/usr/lib/ddns/services_ipv6" # IPv6
- # remove any lines not containing data, and then make sure fields are enclosed in double quotes
- __SERVICES=$(cat $__FILE | grep "^[\t ]*[^#]" | \
- awk ' gsub("\x27", "\"") { if ($1~/^[^\"]*$/) $1="\""$1"\"" }; { if ( $NF~/^[^\"]*$/) $NF="\""$NF"\"" }; { print $0 }')
-
- IFS=$__NEWLINE_IFS
- for __LINE in $__SERVICES; do
- # grep out proper parts of data and use echo to remove quotes
- __NAME=$(echo $__LINE | grep -o "^[\t ]*\"[^\"]*\"" | xargs -r -n1 echo)
- __DATA=$(echo $__LINE | grep -o "\"[^\"]*\"[\t ]*$" | xargs -r -n1 echo)
-
- if [ "$__NAME" = "$service_name" ]; then
- break # found so leave for loop
+ # workaround with variables; pipe create subshell with no give back of variable content
+ mkfifo pipe_$$
+ # only grep without # or whitespace at linestart | remove "
+# grep -v -E "(^#|^[[:space:]]*$)" $__FILE | sed -e s/\"//g > pipe_$$ &
+ sed '/^#/d/^[ \t]*$/ds/\"//g' $__FILE > pipe_$$ &
+
+ while read __SERVICE __DATA __ANSWER; do
+ if [ "$__SERVICE" = "$service_name" ]; then
+ # check if URL or SCRIPT is given
+ __URL=$(echo "$__DATA" | grep "^http")
+ [ -z "$__URL" ] && __SCRIPT="/usr/lib/ddns/$__DATA"
+
+ eval "$1=\"$__URL\""
+ eval "$2=\"$__SCRIPT\""
+ eval "$3=\"$__ANSWER\""
+ rm pipe_$$
+ return 0
fi
- done
- IFS=$__OLD_IFS
-
- # check if URL or SCRIPT is given
- __URL=$(echo "$__DATA" | grep "^http")
- [ -z "$__URL" ] && __SCRIPT="/usr/lib/ddns/$__DATA"
+ done < pipe_$$
+ rm pipe_$$
- eval "$1=\"$__URL\""
- eval "$2=\"$__SCRIPT\""
- return 0
+ eval "$1=\"\"" # no service match clear variables
+ eval "$2=\"\""
+ eval "$3=\"\""
+ return 1
}
# Calculate seconds from interval and unit
__IPV6=$(echo $__HOST | grep -m 1 -o "$IPV6_REGEX")
# if FQDN given get IP address
[ -z "$__IPV4" -a -z "$__IPV6" ] && {
- if [ -x /usr/bin/host ]; then # use BIND host if installed
+ if [ -n "$(which host)" ]; then # use BIND host if installed
__PROG="BIND host"
- __RUNPROG="/usr/bin/host -t ANY $__HOST >$DATFILE 2>$ERRFILE"
+ __RUNPROG="$(which host) -t ANY $__HOST >$DATFILE 2>$ERRFILE"
else # use BusyBox nslookup
__PROG="BusyBox nslookup"
- __RUNPROG="/usr/bin/nslookup $__HOST >$DATFILE 2>$ERRFILE"
+ __RUNPROG="$(which nslookup) $__HOST >$DATFILE 2>$ERRFILE"
fi
write_log 7 "#> $__RUNPROG"
eval $__RUNPROG
__IPV4=$(cat $DATFILE | awk -F "address " '/has address/ {print $2; exit}' )
__IPV6=$(cat $DATFILE | awk -F "address " '/has IPv6/ {print $2; exit}' )
else # use BusyBox nslookup
- __IPV4=$(cat $DATFILE | sed -ne "3,\$ { s/^Address[0-9 ]\{0,\}: \($IPV4_REGEX\).*$/\\1/p }")
- __IPV6=$(cat $DATFILE | sed -ne "3,\$ { s/^Address[0-9 ]\{0,\}: \($IPV6_REGEX\).*$/\\1/p }")
+ __IPV4=$(cat $DATFILE | sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($IPV4_REGEX\).*$/\\1/p }")
+ __IPV6=$(cat $DATFILE | sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($IPV6_REGEX\).*$/\\1/p }")
fi
}
[ $# -ne 1 ] && write_log 12 "Error in 'do_transfer()' - wrong number of parameters"
# lets prefer GNU Wget because it does all for us - IPv4/IPv6/HTTPS/PROXY/force IP version
- grep -i "\+ssl" /usr/bin/wget >/dev/null 2>&1 # check for Wget with SSL support
- if [ $? -eq 0 -a $USE_CURL -eq 0 ]; then # except global option use_curl is set to "1"
- __PROG="/usr/bin/wget -nv -t 1 -O $DATFILE -o $ERRFILE" # non_verbose no_retry outfile errfile
+ if [ -n "$(which wget-ssl)" -a $USE_CURL -eq 0 ]; then # except global option use_curl is set to "1"
+ __PROG="$(which wget-ssl) -nv -t 1 -O $DATFILE -o $ERRFILE" # non_verbose no_retry outfile errfile
# force network/ip to use for communication
if [ -n "$bind_network" ]; then
local __BINDIP
__PROG="$__PROG --ca-certificate=${cacert}"
elif [ -d "$cacert" ]; then
__PROG="$__PROG --ca-directory=${cacert}"
- else # exit here because it makes no sense to start loop
+ elif [ -n "$cacert" ]; then # it's not a file and not a directory but given
write_log 14 "No valid certificate(s) found at '$cacert' for HTTPS communication"
fi
fi
__PROG="GNU Wget" # reuse for error logging
# 2nd choice is cURL IPv4/IPv6/HTTPS
- # libcurl might be compiled without Proxy Support (default in trunk)
- elif [ -x /usr/bin/curl ]; then
- __PROG="/usr/bin/curl -RsS -o $DATFILE --stderr $ERRFILE"
+ # libcurl might be compiled without Proxy or HTTPS Support
+ elif [ -n "$(which curl)" ]; then
+ __PROG="$(which curl) -RsS -o $DATFILE --stderr $ERRFILE"
+ # check HTTPS support
+ /usr/bin/curl -V | grep "Protocols:" | grep -F "https" >/dev/null 2>&1
+ [ $? -eq 1 -a $use_https -eq 1 ] && \
+ write_log 13 "cURL: libcurl compiled without https support"
# force network/interface-device to use for communication
if [ -n "$bind_network" ]; then
local __DEVICE
__PROG="$__PROG --cacert $cacert"
elif [ -d "$cacert" ]; then
__PROG="$__PROG --capath $cacert"
- else # exit here because it makes no sense to start loop
+ elif [ -n "$cacert" ]; then # it's not a file and not a directory but given
write_log 14 "No valid certificate(s) found at '$cacert' for HTTPS communication"
fi
fi
__RUNPROG="$__PROG '$__URL'" # build final command
__PROG="cURL" # reuse for error logging
- # busybox Wget (did not support neither IPv6 nor HTTPS)
- elif [ -x /usr/bin/wget ]; then
- __PROG="/usr/bin/wget -q -O $DATFILE"
+ # uclient-fetch possibly with ssl support if /lib/libustream-ssl.so installed
+ elif [ -n "$(which uclient-fetch)" ]; then
+ __PROG="$(which uclient-fetch) -q -O $DATFILE"
+ # force network/ip not supported
+ [ -n "$__BINDIP" ] && \
+ write_log 14 "uclient-fetch: FORCE binding to specific address not supported"
+ # force ip version to use
+ if [ $force_ipversion -eq 1 ]; then
+ [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" # force IPv4/IPv6
+ fi
+ # https possibly not supported
+ [ $use_https -eq 1 -a ! -f /lib/libustream-ssl.so ] && \
+ write_log 14 "uclient-fetch: no HTTPS support! Additional install one of ustream-ssl packages"
+ # proxy support
+ [ -z "$proxy" ] && __PROG="$__PROG -Y off" || __PROG="$__PROG -Y on"
+ # https & certificates
+ if [ $use_https -eq 1 ]; then
+ if [ "$cacert" = "IGNORE" ]; then
+ __PROG="$__PROG --no-check-certificate"
+ elif [ -f "$cacert" ]; then
+ __PROG="$__PROG --ca-certificate=$cacert"
+ elif [ -n "$cacert" ]; then # it's not a file; nothing else supported
+ write_log 14 "No valid certificate file '$cacert' for HTTPS communication"
+ fi
+ fi
+ __RUNPROG="$__PROG '$__URL' 2>$ERRFILE" # build final command
+ __PROG="uclient-fetch" # reuse for error logging
+
+ # Busybox Wget or any other wget in search $PATH (did not support neither IPv6 nor HTTPS)
+ elif [ -n "$(which wget)" ]; then
+ __PROG="$(which wget) -q -O $DATFILE"
# force network/ip not supported
[ -n "$__BINDIP" ] && \
write_log 14 "BusyBox Wget: FORCE binding to specific address not supported"
__PROG="Busybox Wget" # reuse for error logging
else
- write_log 13 "Neither 'Wget' nor 'cURL' installed or executable"
+ write_log 13 "Neither 'Wget' nor 'cURL' nor 'uclient-fetch' installed or executable"
fi
while : ; do
local __URL __ERR
# do replaces in URL
- __URL=$(echo $update_url | sed -e "s#\[USERNAME\]#$URL_USER#g" -e "s#\[PASSWORD\]#$URL_PASS#g" \
- -e "s#\[DOMAIN\]#$domain#g" -e "s#\[IP\]#$__IP#g")
+ __URL=$(echo $update_url | sed -e "s#\[USERNAME\]#$URL_USER#g" -e "s#\[PASSWORD\]#$URL_PASS#g" \
+ -e "s#\[PARAMENC\]#$URL_PENC#g" -e "s#\[PARAMOPT\]#$param_opt#g" \
+ -e "s#\[DOMAIN\]#$domain#g" -e "s#\[IP\]#$__IP#g")
[ $use_https -ne 0 ] && __URL=$(echo $__URL | sed -e 's#^http:#https:#')
do_transfer "$__URL" || return 1
write_log 7 "DDNS Provider answered:\n$(cat $DATFILE)"
- return 0
- # TODO analyze providers answer
- # "good" or "nochg" = dyndns.com compatible API
- # grep -i -E "good|nochg" $DATFILE >/dev/null 2>&1
- # return $? # "0" if found
+ [ -z "$SRV_ANSWER" ] && return 0 # not set then ignore
+
+ grep -i -E "$SRV_ANSWER" $DATFILE >/dev/null 2>&1
+ return $? # "0" if found
fi
}
)
else
__DATA=$(awk '
- /inet6/ && /: [0-9a-eA-E]/ && !/\/128/ { # Filter IPv6 exclude fxxx and /128 prefix
+ /inet6/ && /: [0-9a-eA-E]/ { # Filter IPv6 exclude fxxx
# inet6 addr: 2001:db8::xxxx:xxxx/32 Scope:Global
FS="/"; # separator "/"
$0=$0; # reread to activate separator
# $2 (optional) if set, do not retry on error
local __CNT=0 # error counter
local __ERR=255
- local __REGEX __PROG __RUNPROG __DATA
+ local __REGEX __PROG __RUNPROG __DATA __IP
+ local __MUSL=$(nslookup localhost 2>&1 | grep -qF "(null)"; echo $?) # 0 == busybox compiled with musl "(null)" found
# return codes
# 1 no IP detected
# set correct regular expression
[ $use_ipv6 -eq 0 ] && __REGEX="$IPV4_REGEX" || __REGEX="$IPV6_REGEX"
- if [ -x /usr/bin/host ]; then
- __PROG="/usr/bin/host"
+ if [ -n "$(which host)" ]; then
+ __PROG="$(which host)"
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -t A" || __PROG="$__PROG -t AAAA"
if [ $force_ipversion -eq 1 ]; then # force IP version
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6"
fi
[ $force_dnstcp -eq 1 ] && __PROG="$__PROG -T" # force TCP
- __RUNPROG="$__PROG $domain $dns_server >$DATFILE 2>$ERRFILE"
+ __RUNPROG="$__PROG $lookup_host $dns_server >$DATFILE 2>$ERRFILE"
__PROG="BIND host"
- elif [ -x /usr/bin/nslookup ]; then # last use BusyBox nslookup
+ elif [ -n "$(which hostip)" ]; then # hostip package installed
+ __PROG="$(which hostip)"
+ [ $force_dnstcp -ne 0 ] && \
+ write_log 14 "hostip - no support for 'DNS over TCP'"
+
+ # is IP given as dns_server ?
+ __IP=$(echo $dns_server | grep -m 1 -o "$IPV4_REGEX")
+ [ -z "$__IP" ] && __IP=$(echo $dns_server | grep -m 1 -o "$IPV6_REGEX")
+
+ # we got NO ip for dns_server, so build command
+ [ -z "$__IP" -a -n "$dns_server" ] && {
+ __IP="\`/usr/bin/hostip"
+ [ $use_ipv6 -eq 1 -a $force_ipversion -eq 1 ] && __IP="$__IP -6"
+ __IP="$__IP $dns_server | grep -m 1 -o"
+ [ $use_ipv6 -eq 1 -a $force_ipversion -eq 1 ] \
+ && __IP="$__IP '$IPV6_REGEX'" \
+ || __IP="$__IP '$IPV4_REGEX'"
+ __IP="$__IP \`"
+ }
+
+ [ $use_ipv6 -eq 1 ] && __PROG="$__PROG -6"
+ [ -n "$dns_server" ] && __PROG="$__PROG -r $__IP"
+ __RUNPROG="$__PROG $lookup_host >$DATFILE 2>$ERRFILE"
+ __PROG="hostip"
+ elif [ -n "$(which nslookup)" ]; then # last use BusyBox nslookup
[ $force_ipversion -ne 0 -o $force_dnstcp -ne 0 ] && \
write_log 14 "Busybox nslookup - no support to 'force IP Version' or 'DNS over TCP'"
+ [ $__MUSL -eq 0 -a -n "$dns_server" ] && \
+ write_log 14 "Busybox compiled with musl - nslookup - no support to set/use DNS Server"
- __RUNPROG="/usr/bin/nslookup $domain $dns_server >$DATFILE 2>$ERRFILE"
+ __RUNPROG="$(which nslookup) $lookup_host $dns_server >$DATFILE 2>$ERRFILE"
__PROG="BusyBox nslookup"
else # there must be an error
write_log 12 "Error in 'get_registered_ip()' - no supported Name Server lookup software accessible"
else
if [ "$__PROG" = "BIND host" ]; then
__DATA=$(cat $DATFILE | awk -F "address " '/has/ {print $2; exit}' )
+ elif [ "$__PROG" = "hostip" ]; then
+ __DATA=$(cat $DATFILE | grep -m 1 -o "$__REGEX")
else
- __DATA=$(cat $DATFILE | sed -ne "3,\$ { s/^Address[0-9 ]\{0,\}: \($__REGEX\).*$/\\1/p }" )
+ __DATA=$(cat $DATFILE | sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($__REGEX\).*$/\\1/p }" )
fi
[ -n "$__DATA" ] && {
write_log 7 "Registered IP '$__DATA' detected"
[ -n "$2" ] && return $__ERR # $2 is given -> no retry
[ $VERBOSE_MODE -gt 1 ] && {
# VERBOSE_MODE > 1 then NO retry
- write_log 4 "Get registered/public IP for '$domain' failed - Verbose Mode: $VERBOSE_MODE - NO retry on error"
+ write_log 4 "Get registered/public IP for '$lookup_host' failed - Verbose Mode: $VERBOSE_MODE - NO retry on error"
return $__ERR
}
__CNT=$(( $__CNT + 1 )) # increment error counter
# if error count > retry_count leave here
[ $retry_count -gt 0 -a $__CNT -gt $retry_count ] && \
- write_log 14 "Get registered/public IP for '$domain' failed after $retry_count retries"
+ write_log 14 "Get registered/public IP for '$lookup_host' failed after $retry_count retries"
- write_log 4 "Get registered/public IP for '$domain' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds"
+ write_log 4 "Get registered/public IP for '$lookup_host' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
# $4 name of variable to store Host/Subdomain
[ $# -ne 4 ] && write_log 12 "Error calling 'split_FQDN()' - wrong number of parameters"
+ [ -z "$1" ] && write_log 12 "Error calling 'split_FQDN()' - missing FQDN to split"
+ [ -f $TLDFILE ] || write_log 12 "Error calling 'split_FQDN()' - missing file '$TLDFILE'"
- _SET="$@" # save given parameters
- local _FHOST _FTLD _FOUND
- local _FDOM=$(echo "$1" | tr [A-Z] [a-z]) # to lower
+ local _HOST _FDOM _CTLD _FTLD
+ local _SET="$@" # save given function parameters
- set -- $(echo "$_FDOM" | tr "." " ") # replace DOT with SPACE and set as script parameters
- _FDOM="" # clear variable for later reuse
+ local _PAR=$(echo "$1" | tr [A-Z] [a-z] | tr "." " ") # to lower and replace DOT with SPACE
+ set -- $_PAR # set new as function parameters
+ _PAR="" # clear variable for later reuse
+ while [ -n "$1" ] ; do # as long we have parameters
+ _PAR="$1 $_PAR" # invert order of parameters
+ shift
+ done
+ set -- $_PAR # use new as function parameters
+ _PAR="" # clear variable
while [ -n "$1" ] ; do # as long we have parameters
- _FTLD=$(echo $@ | tr " " ".") # build back dot separated as TLD
- # look if match excludes "!" in tld_names.dat
- grep -E "^!$_FTLD$" $TLDFILE >/dev/null 2>&1 || {
- # Don't match excludes
- # check if match any "*" in tld_names.dat
- grep -E "^*.$_FTLD$" $TLDFILE >/dev/null 2>&1 && {
- _FOUND="VALID"
- break # found leave while
- }
- # check if exact match in tld_names.dat
- grep -E "^$_FTLD$" $TLDFILE >/dev/null 2>&1 && {
- _FOUND="VALID"
- break # found leave while
- }
+ if [ -z "$_CTLD" ]; then # first loop
+ _CTLD="$1" # CURRENT TLD to look at
+ shift
+ else
+ _CTLD="$1.$_CTLD" # Next TLD to look at
+ shift
+ fi
+ # check if TLD exact match in tld_names.dat, save TLD
+ zcat $TLDFILE | grep -E "^$_CTLD$" >/dev/null 2>&1 && {
+ _FTLD="$_CTLD" # save found
+ _FDOM="$1" # save domain next step might be invalid
+ continue
+ }
+ # check if match any "*" in tld_names.dat,
+ zcat $TLDFILE | grep -E "^\*.$_CTLD$" >/dev/null 2>&1 && {
+ [ -z "$1" ] && break # no more data break
+ # check if next level TLD match excludes "!" in tld_names.dat
+ if zcat $TLDFILE | grep -E "^!$1.$_CTLD$" >/dev/null 2>&1 ; then
+ _FTLD="$_CTLD" # Yes
+ else
+ _FTLD="$1.$_CTLD"
+ shift
+ fi
+ _FDOM="$1"; shift
}
- # nothing match so
- _FHOST="$_FHOST $_FDOM" # append DOMAIN to last found HOST
- _FDOM="$1" # set 1st parameter as DOMAIN
- _FTLD="" # clear TLD
- shift # delete 1st parameter and retry with the rest
+ [ -n "$_FTLD" ] && break # we have something valid, break
done
+ # the leftover parameters are the HOST/SUBDOMAIN
+ while [ -n "$1" ]; do
+ _HOST="$1 $_HOST" # remember we need to invert
+ shift
+ done
+ _HOST=$(echo $_HOST | tr " " ".") # insert DOT
+
set -- $_SET # set back parameters from function call
- [ -n "$_FHOST" ] && _FHOST=$(echo $_FHOST | tr " " ".") # put dots back into HOST
- [ -n "$_FOUND" ] && {
- eval "$2=$_FTLD" # set found TLD
- eval "$3=$_FDOM" # set found registrable domain
- eval "$4=$_FHOST" # set found HOST/SUBDOMAIN
+ [ -n "$_FTLD" ] && {
+ eval "$2=$_FTLD" # set TLD
+ eval "$3=$_FDOM" # set registrable domain
+ eval "$4=$_HOST" # set HOST/SUBDOMAIN
return 0
}
eval "$2=''" # clear TLD
eval "$4=''" # clear HOST/SUBDOMAIN
return 1
}
+
+expand_ipv6() {
+ # Original written for bash by
+ # Author: Florian Streibelt <florian@f-streibelt.de>
+ # Date: 08.04.2012
+ # License: Public Domain, but please be fair and
+ # attribute the original author(s) and provide
+ # a link to the original source for corrections:
+ #. https://github.com/mutax/IPv6-Address-checks
+
+ # $1 IPv6 t0 expand
+ # $2 name of variable to store expanded IPv6
+ [ $# -ne 2 ] && write_log 12 "Error calling 'expand_ipv6()' - wrong number of parameters"
+
+ INPUT="$(echo "$1" | tr 'A-F' 'a-f')"
+ [ "$INPUT" = "::" ] && INPUT="::0" # special case ::
+
+ O=""
+
+ while [ "$O" != "$INPUT" ]; do
+ O="$INPUT"
+
+ # fill all words with zeroes
+ INPUT=$( echo "$INPUT" | sed -e 's|:\([0-9a-f]\{3\}\):|:0\1:|g' \
+ -e 's|:\([0-9a-f]\{3\}\)$|:0\1|g' \
+ -e 's|^\([0-9a-f]\{3\}\):|0\1:|g' \
+ -e 's|:\([0-9a-f]\{2\}\):|:00\1:|g' \
+ -e 's|:\([0-9a-f]\{2\}\)$|:00\1|g' \
+ -e 's|^\([0-9a-f]\{2\}\):|00\1:|g' \
+ -e 's|:\([0-9a-f]\):|:000\1:|g' \
+ -e 's|:\([0-9a-f]\)$|:000\1|g' \
+ -e 's|^\([0-9a-f]\):|000\1:|g' )
+
+ done
+
+ # now expand the ::
+ ZEROES=""
+
+ echo "$INPUT" | grep -qs "::"
+ if [ "$?" -eq 0 ]; then
+ GRPS="$( echo "$INPUT" | sed 's|[0-9a-f]||g' | wc -m )"
+ GRPS=$(( GRPS-1 )) # remove carriage return
+ MISSING=$(( 8-GRPS ))
+ while [ $MISSING -gt 0 ]; do
+ ZEROES="$ZEROES:0000"
+ MISSING=$(( MISSING-1 ))
+ done
+
+ # be careful where to place the :
+ INPUT=$( echo "$INPUT" | sed -e 's|\(.\)::\(.\)|\1'$ZEROES':\2|g' \
+ -e 's|\(.\)::$|\1'$ZEROES':0000|g' \
+ -e 's|^::\(.\)|'$ZEROES':0000:\1|g;s|^:||g' )
+ fi
+
+ # an expanded address has 39 chars + CR
+ if [ $(echo $INPUT | wc -m) != 40 ]; then
+ write_log 4 "Error in 'expand_ipv6()' - invalid IPv6 found: '$1' expanded: '$INPUT'"
+ eval "$2='invalid'"
+ return 1
+ fi
+
+ # echo the fully expanded version of the address
+ eval "$2=$INPUT"
+ return 0
+}