Ticket #12500: 20141002-dynamic_dns_functions.sh

File 20141002-dynamic_dns_functions.sh, 25.3 KB (added by chris5560, 3 years ago)
Line 
1#!/bin/sh
2# /usr/lib/ddns/dynamic_dns_functions.sh
3#
4# Original written by Eric Paul Bishop, January 2008
5# Distributed under the terms of the GNU General Public License (GPL) version 2.0
6# (Loosely) based on the script on the one posted by exobyte in the forums here:
7# http://forum.openwrt.org/viewtopic.php?id=14040
8#
9# extended and partial rewritten by Christian Schoenebeck in August 2014 to support:
10# - IPv6 DDNS services
11# - setting DNS Server to retrieve current IP including TCP transport
12# - Proxy Server to send out updates or retrieving WEB based IP detection
13# - force_interval=0 to run once (usefull for cron jobs etc.)
14# - the usage of BIND's host instead of BusyBox's nslookup if installed (DNS via TCP)
15# - extended Verbose Mode and log file support for better error detection
16#
17# function __timeout
18# copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh
19# @author Anthony Thyssen  6 April 2011
20#
21# variables in small chars are read from /etc/config/ddns
22# variables in big chars are defined inside these scripts as global vars
23# variables in big chars beginning with "__" are local defined inside functions only
24#set -vx        #script debugger
25
26. /lib/functions.sh
27. /lib/functions/network.sh
28
29# GLOBAL VARIABLES #
30SECTION_ID=""           # hold config's section name
31VERBOSE_MODE=1          # default mode is log to console, but easily changed with parameter
32LUCI_HELPER=""          # set by dynamic_dns_lucihelper.sh, if filled supress all error logging
33
34PIDFILE=""              # pid file
35UPDFILE=""              # store UPTIME of last update
36
37# directory to store run information to.
38RUNDIR=$(uci -q get ddns.global.run_dir) || RUNDIR="/var/run/ddns"
39# NEW # directory to store log files
40LOGDIR=$(uci -q get ddns.global.log_dir) || LOGDIR="/var/log/ddns"
41LOGFILE=""              # NEW # logfile can be enabled as new option
42# number of lines to before rotate logfile
43LOGLINES=$(uci -q get ddns.global.log_lines) || LOGLINES=250
44
45CHECK_SECONDS=0         # calculated seconds out of given
46FORCE_SECONDS=0         # interval and unit
47RETRY_SECONDS=0         # in configuration
48
49OLD_PID=0               # Holds the PID of already running process for the same config section
50
51LAST_TIME=0             # holds the uptime of last successful update
52CURR_TIME=0             # holds the current uptime
53NEXT_TIME=0             # calculated time for next FORCED update
54EPOCH_TIME=0            # seconds since 1.1.1970 00:00:00
55
56REGISTERED_IP=""        # holds the IP read from DNS
57LOCAL_IP=""             # holds the local IP read from the box
58
59ERR_LAST=0              # used to save $? return code of program and function calls
60ERR_LOCAL_IP=0          # error counter on getting local ip
61ERR_REG_IP=0            # error counter on getting DNS registered ip
62ERR_SEND=0              # error counter on sending update to DNS provider
63ERR_UPDATE=0            # error counter on different local and registered ip
64
65# format to show date information in log and luci-app-ddns default ISO 8601 format
66DATE_FORMAT=$(uci -q get ddns.global.date_format) || DATE_FORMAT="%F %R"
67DATE_PROG="date +'$DATE_FORMAT'"
68
69# regular expression to detect IPv4 / IPv6
70# IPv4       0-9   1-3x "." 0-9  1-3x "." 0-9  1-3x "." 0-9  1-3x
71IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}"
72# IPv6       ( ( 0-9a-f  1-4char ":") min 1x) ( ( 0-9a-f  1-4char   )optional) ( (":" 0-9a-f 1-4char  ) min 1x)
73IPV6_REGEX="\(\([0-9A-Fa-f]\{1,4\}:\)\{1,\}\)\(\([0-9A-Fa-f]\{1,4\}\)\{0,1\}\)\(\(:[0-9A-Fa-f]\{1,4\}\)\{1,\}\)"
74
75# loads all options for a given package and section
76# also, sets all_option_variables to a list of the variable names
77# $1 = ddns, $2 = SECTION_ID
78load_all_config_options()
79{
80        local __PKGNAME="$1"
81        local __SECTIONID="$2"
82        local __VAR
83        local __ALL_OPTION_VARIABLES=""
84
85        # this callback loads all the variables in the __SECTIONID section when we do
86        # config_load. We need to redefine the option_cb for different sections
87        # so that the active one isn't still active after we're done with it.  For reference
88        # the $1 variable is the name of the option and $2 is the name of the section
89        config_cb()
90        {
91                if [ ."$2" = ."$__SECTIONID" ]; then
92                        option_cb()
93                        {
94                                __ALL_OPTION_VARIABLES="$__ALL_OPTION_VARIABLES $1"
95                        }
96                else
97                        option_cb() { return 0; }
98                fi
99        }
100
101        config_load "$__PKGNAME"
102       
103        # Given SECTION_ID not found so no data, so return 1
104        [ -z "$__ALL_OPTION_VARIABLES" ] && return 1
105
106        for __VAR in $__ALL_OPTION_VARIABLES
107        do
108                config_get "$__VAR" "$__SECTIONID" "$__VAR"
109        done
110        return 0
111}
112
113# starts updater script for all given sections or only for the one given
114# $1 = interface (Optional: when given only scripts are started
115# configured for that interface)
116start_daemon_for_all_ddns_sections()
117{
118        local __EVENTIF="$1"
119        local __SECTIONS=""
120        local __SECTIONID=""
121        local __IFACE=""
122
123        config_cb() 
124        {
125                # only look for section type "service", ignore everything else
126                [ "$1" == "service" ] && __SECTIONS="$__SECTIONS $2"
127        }
128        config_load "ddns"
129
130        for __SECTIONID in $__SECTIONS
131        do
132                config_get __IFACE "$__SECTIONID" interface "wan"
133                [ -z "$__EVENTIF" -o "$__IFACE" = "$__EVENTIF" ] || continue
134                /usr/lib/ddns/dynamic_dns_updater.sh $__SECTIONID 0 > /dev/null 2>&1 &
135        done
136}
137
138verbose_echo()
139{
140        [ -n "$LUCI_HELPER" ] && return # nothing to report when used by LuCI helper script
141        [ $VERBOSE_MODE -gt 0 ] && echo -e " $*"
142        if [ ${use_logfile:-0} -eq 1 -o $VERBOSE_MODE -gt 1 ]; then
143                [ -d $LOGDIR ] || mkdir -p -m 755 $LOGDIR
144                echo -e " $*" >> $LOGFILE
145                # VERBOSE_MODE > 1 then NO loop so NO truncate log to $LOGLINES lines
146                [ $VERBOSE_MODE -gt 1 ] || sed -i -e :a -e '$q;N;'$LOGLINES',$D;ba' $LOGFILE
147        fi
148        return
149}
150
151syslog_info(){
152        [ $use_syslog -eq 1 ] && logger -p user.info -t ddns-scripts[$$] "$SECTION_ID: $*"
153        return
154}
155syslog_notice(){
156        [ $use_syslog -ge 1 -a $use_syslog -le 2 ] && logger -p user.notice -t ddns-scripts[$$] "$SECTION_ID: $*"
157        return
158}
159syslog_warn(){
160        [ $use_syslog -ge 1 -a $use_syslog -le 3 ] && logger -p user.warn -t ddns-scripts[$$] "$SECTION_ID: $*"
161        return
162}
163syslog_err(){
164        [ $use_syslog -ge 1 ] && logger -p user.err -t ddns-scripts[$$] "$SECTION_ID: $*"
165        return
166}
167
168critical_error() {
169        [ -n "$LUCI_HELPER" ] && return # nothing to report when used by LuCI helper script
170        verbose_echo "\n CRITICAL ERROR =: $* - EXITING\n"
171        [ $VERBOSE_MODE -eq 0 ] && echo -e "\n$SECTION_ID: CRITICAL ERROR - $* - EXITING\n"
172        logger -t ddns-scripts[$$] -p user.crit "$SECTION_ID: CRITICAL ERROR - $* - EXITING"
173        exit 1          # critical error -> leave here
174}
175
176# replace all special chars to their %hex value
177# used for USERNAME and PASSWORD in update_url
178# unchanged: "-"(minus) "_"(underscore) "."(dot) "~"(tilde)
179# to verify: "'"(single quote) '"'(double quote)        # because shell delimiter
180#            "$"(Dollar)                                # because used as variable output
181# tested with the following string stored via Luci Application as password / username
182# A B!"#AA$1BB%&'()*+,-./:;<=>?@[\]^_`{|}~      without problems at Dollar or quotes
183__urlencode() {
184        # $1    Name of Variable to store encoded string to
185        # $2    string to encode
186        local __STR __LEN __CHAR __OUT
187        local __ENC=""
188        local __POS=1
189
190        __STR="$2"              # read string to encode
191        __LEN=${#__STR}         # get string length
192
193        while [ $__POS -le $__LEN ]; do
194                # read one chat of the string
195                __CHAR=$(expr substr "$__STR" $__POS 1)
196
197                case "$__CHAR" in
198                        [-_.~a-zA-Z0-9] )
199                                # standard char
200                                __OUT="${__CHAR}"
201                                ;;
202                        * )
203                                # special char get %hex code
204                               __OUT=$(printf '%%%02x' "'$__CHAR" )
205                                ;;
206                esac
207                __ENC="${__ENC}${__OUT}"        # append to encoded string
208                __POS=$(( $__POS + 1 ))         # increment position
209        done
210
211        eval "$1='$__ENC'"      # transfer back to variable
212        return 0
213}
214
215# extract update_url for given DDNS Provider from
216# file /usr/lib/ddns/services for IPv4 or from
217# file /usr/lib/ddns/services_ipv6 for IPv6
218get_service_url() {
219        # $1    Name of Variable to store url to
220        local __LINE __FILE __NAME __URL __SERVICES
221        local __OLD_IFS=$IFS
222        local __NEWLINE_IFS='
223' #__NEWLINE_IFS
224
225        __FILE="/usr/lib/ddns/services"                                 # IPv4
226        [ $use_ipv6 -ne 0 ] && __FILE="/usr/lib/ddns/services_ipv6"     # IPv6
227
228        #remove any lines not containing data, and then make sure fields are enclosed in double quotes
229        __SERVICES=$(cat $__FILE | grep "^[\t ]*[^#]" | \
230                awk ' gsub("\x27", "\"") { if ($1~/^[^\"]*$/) $1="\""$1"\"" }; { if ( $NF~/^[^\"]*$/) $NF="\""$NF"\""  }; { print $0 }')
231
232        IFS=$__NEWLINE_IFS
233        for __LINE in $__SERVICES
234        do
235                #grep out proper parts of data and use echo to remove quotes
236                __NAME=$(echo $__LINE | grep -o "^[\t ]*\"[^\"]*\"" | xargs -r -n1 echo)
237                __URL=$(echo $__LINE | grep -o "\"[^\"]*\"[\t ]*$" | xargs -r -n1 echo)
238
239                if [ "$__NAME" = "$service_name" ]; then
240                        break                   # found so leave for loop
241                fi
242        done
243        IFS=$__OLD_IFS
244       
245        eval "$1='$__URL'"
246        return 0
247}
248
249get_seconds() {
250        # $1    Name of Variable to store result in
251        # $2    Number and
252        # $3    Unit of time interval
253        case "$3" in
254                "days" )        eval "$1=$(( $2 * 86400 ))";;
255                "hours" )       eval "$1=$(( $2 * 3600 ))";;
256                "minutes" )     eval "$1=$(( $2 * 60 ))";;
257                * )             eval "$1=$2";;
258        esac
259        return 0
260}
261
262__timeout() {
263        # copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh
264        # only did the folloing changes
265        #       - commented out "#!/bin/bash" and usage section
266        #       - replace exit by return for usage as function
267        #       - some reformating
268        #
269        # timeout [-SIG] time [--] command args...
270        #
271        # Run the given command until completion, but kill it if it runs too long.
272        # Specifically designed to exit immediatally (no sleep interval) and clean up
273        # nicely without messages or leaving any extra processes when finished.
274        #
275        # Example use
276        #    timeout 5 countdown
277        #
278        ###
279        #
280        # Based on notes in my "Shell Script Hints", section "Command Timeout"
281        #   http://www.ict.griffith.edu.au/~anthony/info/shell/script.hints
282        #
283        # This script uses a lot of tricks to terminate both the background command,
284        # the timeout script, and even the sleep process.  It also includes trap
285        # commands to prevent sub-shells reporting expected "Termination Errors".
286        #
287        # It took years of occasional trials, errors and testing to get a pure bash
288        # timeout command working as well as this does.
289        #
290        ###
291        #
292        # Anthony Thyssen     6 April 2011
293        #
294#       PROGNAME=$(type $0 | awk '{print $3}')  # search for executable on path
295#       PROGDIR=$(dirname $PROGNAME)            # extract directory of program
296#       PROGNAME=$(basename $PROGNAME)          # base name of program
297
298        # output the script comments as docs
299#       Usage() {
300#               echo >&2 "$PROGNAME:" "$@"
301#               sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 3s/^/Usage: /; 2,$ p' "$PROGDIR/$PROGNAME"
302#               exit 10;
303#       }
304
305        SIG=-TERM
306
307        while [  $# -gt 0 ]; do
308                case "$1" in
309                        --)
310                                # forced end of user options
311                                shift;
312                                break ;;
313#                       -\?|--help|--doc*)
314#                               Usage ;;
315                        [0-9]*)
316                                TIMEOUT="$1" ;;
317                        -*)
318                                SIG="$1" ;;
319                        *)
320                                # unforced  end of user options
321                                break ;;
322                esac
323                shift   # next option
324        done
325
326        # run main command in backgrouds and get its pid
327        "$@" &
328        command_pid=$!
329
330        # timeout sub-process abort countdown after ABORT seconds! also backgrounded
331        sleep_pid=0
332        (
333                # cleanup sleep process
334                trap 'kill -TERM $sleep_pid; return 1' 1 2 3 15
335                # sleep timeout period in background
336                sleep $TIMEOUT &
337                sleep_pid=$!
338                wait $sleep_pid
339                # Abort the command
340                kill $SIG $command_pid >/dev/null 2>&1
341                return 1
342        ) &
343        timeout_pid=$!
344
345        # Wait for main command to finished or be timed out
346        wait $command_pid
347        status=$?
348
349        # Clean up timeout sub-shell - if it is still running!
350        kill $timeout_pid 2>/dev/null
351        wait $timeout_pid 2>/dev/null
352
353        # Uncomment to check if a LONG sleep still running (no sleep should be)
354        # sleep 1
355        # echo "-----------"
356        # /bin/ps j  # uncomment to show if abort "sleep" is still sleeping
357
358        return $status
359}
360
361__verify_host_port() {
362        # $1    Host/IP to verify
363        # $2    Port to verify
364        local __HOST=$1
365        local __PORT=$2
366        local __TMP __IP __IPV4 __IPV6 __RUNPROG __ERRPROG __ERR
367        # return codes
368        # 1     system specific error
369        # 2     nslookup error
370        # 3     nc (netcat) error
371        # 4     unmatched IP version
372
373        __RUNPROG="nslookup $__HOST 2>/dev/null"
374        __ERRPROG="nslookup $__HOST 2>&1"
375        verbose_echo " resolver prog =: '$__RUNPROG'"
376        __TMP=$(eval $__RUNPROG)        # test if nslookup runs without errors
377        __ERR=$?
378        # command error
379        [ $__ERR -gt 0 ] && {
380                verbose_echo "\n!!!!!!!!! ERROR =: BusyBox nslookup Error '$__ERR'\n$(eval $__ERRPROG)\n"
381                syslog_err "DNS Resolver Error - BusyBox nslookup Error: '$__ERR'"
382                return 2
383        } || {
384                # we need to run twice because multi-line output needs to be directly piped to grep because
385                # pipe returns return code of last prog in pipe but we need errors from nslookup command
386                __IPV4=$(eval $__RUNPROG | sed '1,2d' | grep -o "Name:\|Address.*" | grep -m 1 -o "$IPV4_REGEX")
387                __IPV6=$(eval $__RUNPROG | sed '1,2d' | grep -o "Name:\|Address.*" | grep -m 1 -o "$IPV6_REGEX")
388        }
389
390        # check IP version if forced
391        if [ $force_ipversion -ne 0 ]; then
392                [ $use_ipv6 -eq 0 -a -z "$__IPV4" ] && return 4
393                [ $use_ipv6 -eq 1 -a -z "$__IPV6" ] && return 4
394        fi
395
396        # verify nc command
397        # busybox nc compiled without -l option "NO OPT l!" -> critical error
398        nc --help 2>&1 | grep -iq "NO OPT l!" && \
399                critical_error "Busybox nc: netcat compiled with errors"
400        # busybox nc compiled with extensions
401        nc --help 2>&1 | grep -q "\-w" && __NCEXT="TRUE"
402
403        # connectivity test
404        # run busybox nc to HOST PORT
405        # busybox might be compiled with "FEATURE_PREFER_IPV4_ADDRESS=n"
406        # then nc will try to connect via IPv6 if there is an IPv6 availible for host
407        # not worring if there is an IPv6 wan address
408        # so if not "forced_ipversion" to use ipv6 then connect test via ipv4 if availible
409        [ $force_ipversion -ne 0 -a $use_ipv6 -ne 0 -o -z "$__IPV4" ] && {
410                # force IPv6
411                __IP=$__IPV6
412        } || __IP=$__IPV4
413
414        if [ -n "$__NCEXT" ]; then      # nc compiled with extensions (timeout support)
415                __RUNPROG="nc -w 1 $__IP $__PORT </dev/null >/dev/null 2>&1"
416                __ERRPROG="nc -vw 1 $__IP $__PORT </dev/null 2>&1"
417                verbose_echo "  connect prog =: '$__RUNPROG'"
418                eval $__RUNPROG
419                __ERR=$?
420                [ $__ERR -eq 0 ] && return 0
421                verbose_echo "\n!!!!!!!!! ERROR =: BusyBox nc Error '$__ERR'\n$(eval $__ERRPROG)\n"
422                syslog_err "host verify Error - BusyBox nc Error: '$__ERR'"
423                return 3
424        else            # nc compiled without extensions (no timeout support)
425                __RUNPROG="__timeout 2 -- nc $__IP $__PORT </dev/null >/dev/null 2>&1"
426                verbose_echo "  connect prog =: '$__RUNPROG'"
427                eval $__RUNPROG
428                __ERR=$?
429                [ $__ERR -eq 0 ] && return 0
430                verbose_echo "\n!!!!!!!!! ERROR =: BusyBox nc Error '$__ERR' (timeout)"
431                syslog_err "host verify Error - BusyBox nc Error: '$__ERR' (timeout)"
432                return 3
433        fi
434}
435
436verify_dns() {
437        # $1    DNS server to verify
438        # we need DNS server to verify otherwise exit with ERROR 1
439        [ -z "$1" ] && return 1
440
441        # DNS uses port 53
442        __verify_host_port "$1" "53"
443}
444
445verify_proxy() {
446        # $1    Proxy-String to verify
447        #               complete entry          user:password@host:port
448        #               host and port only      host:port
449        #               host only               host            unsupported
450        #               IPv4 address instead of host    123.234.234.123
451        #               IPv6 address instead of host    [xxxx:....:xxxx]        in square bracket
452        local __TMP __HOST __PORT
453
454        # we need Proxy-Sting to verify otherwise exit with ERROR 1
455        [ -z "$1" ] && return 1
456
457        # try to split user:password "@" host:port
458        __TMP=$(echo $1 | awk -F "@" '{print $2}')
459        # no "@" found - only host:port is given
460        [ -z "$__TMP" ] && __TMP="$1"
461        # now lets check for IPv6 address
462        __HOST=$(echo $__TMP | grep -m 1 -o "$IPV6_REGEX")
463        # IPv6 host address found read port
464        if [ -n "$__HOST" ]; then
465                # IPv6 split at "]:"
466                __PORT=$(echo $__TMP | awk -F "]:" '{print $2}')
467        else
468                __HOST=$(echo $__TMP | awk -F ":" '{print $1}')
469                __PORT=$(echo $__TMP | awk -F ":" '{print $2}')
470        fi
471        # No Port detected ERROR 5
472        [ -z "$__PORT" ] && return 5
473
474        __verify_host_port "$__HOST" "$__PORT"
475}
476
477__do_transfer() {
478        # $1    # Variable to store Answer of transfer
479        # $2    # URL to use
480        local __URL="$2"
481        local __ERR=0
482        local __PROG  __RUNPROG  __ERRPROG  __DATA
483
484        # lets prefer GNU Wget because it does all for us - IPv4/IPv6/HTTPS/PROXY/force IP version
485        if /usr/bin/wget --version 2>&1 | grep -q "\+ssl"; then
486                __PROG="/usr/bin/wget -t 2 -O -"        # standard output only 2 retrys on error
487                # force ip version to use
488                if [ $force_ipversion -eq 1 ]; then     
489                        [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6"       # force IPv4/IPv6
490                fi
491                # set certificate parameters
492                if [ $use_https -eq 1 ]; then
493                        if [ "$cacert" = "IGNORE" ]; then       # idea from Ticket #15327 to ignore server cert
494                                __PROG="$__PROG --no-check-certificate"
495                        elif [ -f "$cacert" ]; then
496                                __PROG="$__PROG --ca-certificate=${cacert}"
497                        elif [ -d "$cacert" ]; then
498                                __PROG="$__PROG --ca-directory=${cacert}"
499                        else    # exit here because it makes no sense to start loop
500                                critical_error "Wget: No valid certificate(s) found for running HTTPS"
501                        fi
502                fi
503                # disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
504                [ -z "$proxy" ] && __PROG="$__PROG --no-proxy"
505
506                __RUNPROG="$__PROG -q '$__URL' 2>/dev/null"     # do transfer with "-q" to suppress not needed output
507                __ERRPROG="$__PROG -d '$__URL' 2>&1"            # do transfer with "-d" for debug mode
508                verbose_echo " transfer prog =: $__RUNPROG"
509                __DATA=$(eval $__RUNPROG)
510                __ERR=$?
511                [ $__ERR -gt 0 ] && {
512                        verbose_echo "\n!!!!!!!!! ERROR =: GNU Wget Error '$__ERR'\n$(eval $__ERRPROG)\n"
513                        syslog_err "Communication Error - GNU Wget Error: '$__ERR'"
514                        return 1
515                }
516
517        # 2nd choice is cURL IPv4/IPv6/HTTPS
518        # libcurl might be compiled without Proxy Support (default in trunk)
519        elif [ -x /usr/bin/curl ]; then
520                __PROG="/usr/bin/curl"
521                # force ip version to use
522                if [ $force_ipversion -eq 1 ]; then     
523                        [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6"       # force IPv4/IPv6
524                fi
525                # set certificate parameters
526                if [ $use_https -eq 1 ]; then
527                        if [ "$cacert" = "IGNORE" ]; then       # idea from Ticket #15327 to ignore server cert
528                                __PROG="$__PROG --insecure"     # but not empty better to use "IGNORE"
529                        elif [ -f "$cacert" ]; then
530                                __PROG="$__PROG --cacert $cacert"
531                        elif [ -d "$cacert" ]; then
532                                __PROG="$__PROG --capath $cacert"
533                        else    # exit here because it makes no sense to start loop
534                                critical_error "cURL: No valid certificate(s) found for running HTTPS"
535                        fi
536                fi
537                # disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
538                # or check if libcurl compiled with proxy support
539                if [ -z "$proxy" ]; then
540                        __PROG="$__PROG --noproxy '*'"
541                else
542                        # if libcurl has no proxy support and proxy should be used then force ERROR
543                        # libcurl currently no proxy support by default
544                        grep -iq all_proxy /usr/lib/libcurl.so* || \
545                                critical_error "cURL: libcurl compiled without Proxy support"
546                fi
547
548                __RUNPROG="$__PROG -q '$__URL' 2>/dev/null"     # do transfer with "-s" to suppress not needed output
549                __ERRPROG="$__PROG -v '$__URL' 2>&1"            # do transfer with "-v" for verbose mode
550                verbose_echo " transfer prog =: $__RUNPROG"
551                __DATA=$(eval $__RUNPROG)
552                __ERR=$?
553                [ $__ERR -gt 0 ] && {
554                        verbose_echo "\n!!!!!!!!! ERROR =: cURL Error '$__ERR'\n$(eval $__ERRPROG)\n"
555                        syslog_err "Communication Error - cURL Error: '$__ERR'"
556                        return 1
557                }
558
559        # busybox Wget (did not support neither IPv6 nor HTTPS)
560        elif [ -x /usr/bin/wget ]; then
561                __PROG="/usr/bin/wget -O -"
562                # force ip version not supported
563                [ $force_ipversion -eq 1 ] && \
564                        critical_error "BusyBox Wget: can not force IP version to use"
565                # https not supported
566                [ $use_https -eq 1 ] && \
567                        critical_error "BusyBox Wget: no HTTPS support"
568                # disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
569                [ -z "$proxy" ] && __PROG="$__PROG -Y off"
570               
571                __RUNPROG="$__PROG -q '$__URL' 2>/dev/null"     # do transfer with "-q" to suppress not needed output
572                __ERRPROG="$__PROG '$__URL' 2>&1"
573                verbose_echo " transfer prog =: $__RUNPROG"
574                __DATA=$(eval $__RUNPROG)
575                __ERR=$?
576                [ $__ERR -gt 0 ] && {
577                        verbose_echo "\n!!!!!!!!! ERROR =: BusyBox Wget Error '$__ERR'\n$(eval $__ERRPROG)\n"
578                        syslog_err "Communication Error - BusyBox Wget Error: '$__ERR'"
579                        return 1
580                }
581
582        else
583                critical_error "Program not found - Neither 'Wget' nor 'cURL' installed or executable"
584        fi
585
586        eval "$1='$__DATA'"
587        return 0
588}
589
590send_update() {
591        # $1    # IP to set at DDNS service provider
592        local __IP
593
594        # verify given IP / no private IPv4's / no IPv6 addr starting with fxxx of with ":"
595        [ $use_ipv6 -eq 0 ] && __IP=$(echo $1 | grep -v -E "(^0|^10\.|^127|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168)")
596        [ $use_ipv6 -eq 1 ] && __IP=$(echo $1 | grep "^[0-9a-eA-E]")
597        [ -z "$__IP" ] && critical_error "Invalid or no IP '$1' given"
598
599        if [ -z "$service_name" -a -n "$update_script" ]; then
600                verbose_echo " custom update =: parsing script '$update_script'"
601                . $update_script
602        else
603                local __URL __ANSWER __ERR __USER __PASS
604
605                # do replaces in URL
606                __urlencode __USER "$username"  # encode username, might be email or something like this
607                __urlencode __PASS "$password"  # encode password, might have special chars for security reason
608                __URL=$(echo $update_url | sed -e "s#\[USERNAME\]#$__USER#g" -e "s#\[PASSWORD\]#$__PASS#g" \
609                                               -e "s#\[DOMAIN\]#$domain#g" -e "s#\[IP\]#$__IP#g")
610                [ $use_https -ne 0 ] && __URL=$(echo $__URL | sed -e 's#^http:#https:#')
611
612                __do_transfer __ANSWER "$__URL"
613                __ERR=$?
614                [ $__ERR -gt 0 ] && {
615                        verbose_echo "\n!!!!!!!!! ERROR =: Error sending update to DDNS Provider\n"
616                        return 1
617                }
618                verbose_echo "   update send =: DDNS Provider answered\n$__ANSWER"
619                return 0
620        fi
621}
622
623get_local_ip () {
624        # $1    Name of Variable to store local IP (LOCAL_IP)
625        local __RUNPROG __IP __URL __ANSWER
626
627        case $ip_source in
628                network )
629                        # set correct program
630                        [ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" \
631                                            || __RUNPROG="network_get_ipaddr6"
632                        $__RUNPROG __IP "$ip_network"
633                        verbose_echo "      local ip =: '$__IP' detected on network '$ip_network'"
634                        ;;
635                interface )
636                        if [ $use_ipv6 -eq 0 ]; then
637                                __IP=$(ifconfig $ip_interface | awk '
638                                        /inet addr:/ {  # Filter IPv4
639                                        #   inet addr:192.168.1.1  Bcast:192.168.1.255  Mask:255.255.255.0
640                                        $1="";          # remove inet
641                                        $3="";          # remove Bcast: ...
642                                        $4="";          # remove Mask: ...
643                                        FS=":";         # separator ":"
644                                        $0=$0;          # reread to activate separator
645                                        $1="";          # remove addr
646                                        FS=" ";         # set back separator to default " "
647                                        $0=$0;          # reread to activate separator (remove whitespaces)
648                                        print $1;       # print IPv4 addr
649                                        }'
650                                )
651                        else
652                                __IP=$(ifconfig $ip_interface | awk '
653                                        /inet6/ && /: [0-9a-eA-E]/ && !/\/128/ {        # Filter IPv6 exclude fxxx and /128 prefix
654                                        #   inet6 addr: 2001:db8::xxxx:xxxx/32 Scope:Global
655                                        FS="/";         # separator "/"
656                                        $0=$0;          # reread to activate separator
657                                        $2="";          # remove everything behind "/"
658                                        FS=" ";         # set back separator to default " "
659                                        $0=$0;          # reread to activate separator
660                                        print $3;       # print IPv6 addr
661                                        }'
662                                )
663                        fi
664                        verbose_echo "      local ip =: '$__IP' detected on interface '$ip_interface'"
665                        ;;
666                script )
667                        # get ip from script
668                        __IP=$($ip_script)
669                        verbose_echo "      local ip =: '$__IP' detected via script '$ip_script'"
670                        ;;
671                * )
672                        for __URL in $ip_url; do
673                                __do_transfer __ANSWER "$__URL"
674                                [ -n "$__IP" ] && break # Answer detected, leave for loop
675                        done
676                        # use correct regular expression
677                        [ $use_ipv6 -eq 0 ] \
678                                && __IP=$(echo "$__ANSWER" | grep -m 1 -o "$IPV4_REGEX") \
679                                || __IP=$(echo "$__ANSWER" | grep -m 1 -o "$IPV6_REGEX")
680                        verbose_echo "      local ip =: '$__IP' detected via web at '$__URL'"
681                        ;;
682        esac
683
684        # if NO IP was found
685        [ -z "$__IP" ] && return 1
686
687        eval "$1='$__IP'"
688        return 0
689}
690
691get_registered_ip() {
692        # $1    Name of Variable to store public IP (REGISTERED_IP)
693        local __IP  __REGEX  __PROG  __RUNPROG  __ERRPROG  __ERR
694        # return codes
695        # 1     no IP detected
696
697        # set correct regular expression
698        [ $use_ipv6 -eq 0 ] && __REGEX="$IPV4_REGEX" || __REGEX="$IPV6_REGEX"
699
700        if [ -x /usr/bin/host ]; then           # otherwise try to use BIND host
701                __PROG="/usr/bin/host"
702                [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -t A"  || __PROG="$__PROG -t AAAA"
703                if [ $force_ipversion -eq 1 ]; then                     # force IP version
704                        [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4"  || __PROG="$__PROG -6"
705                fi                     
706                [ $force_dnstcp -eq 1 ] && __PROG="$__PROG -T"  # force TCP
707
708                __RUNPROG="$__PROG $domain $dns_server 2>/dev/null"
709                __ERRPROG="$__PROG -v $domain $dns_server 2>&1"
710                verbose_echo " resolver prog =: $__RUNPROG"
711                __IP=$(eval $__RUNPROG)
712                __ERR=$?
713                # command error
714                [ $__ERR -gt 0 ] && {
715                        verbose_echo "\n!!!!!!!!! ERROR =: BIND host Error '$__ERR'\n$(eval $__ERRPROG)\n"
716                        syslog_err "DNS Resolver Error - BIND host Error: '$__ERR'"
717                        return 1
718                } || {
719                        # we need to run twice because multi-line output needs to be directly piped to grep because
720                        # pipe returns return code of last prog in pipe but we need errors from host command
721                        __IP=$(eval $__RUNPROG | awk -F "address " '/has/ {print $2; exit}' )
722                }
723
724        elif [ -x /usr/bin/nslookup ]; then     # last use BusyBox nslookup
725                [ $force_ipversion -ne 0 -o $force_dnstcp -ne 0 ] && \
726                        critical_error "nslookup - no support to 'force IP Version' or 'DNS over TCP'"
727
728                __RUNPROG="nslookup $domain $dns_server 2>/dev/null"
729                __ERRPROG="nslookup $domain $dns_server 2>&1"
730                verbose_echo " resolver prog =: $__RUNPROG"
731                __IP=$(eval $__RUNPROG)
732                __ERR=$?
733                # command error
734                [ $__ERR -gt 0 ] && {
735                        verbose_echo "\n!!!!!!!!! ERROR =: BusyBox nslookup Error '$__ERR'\n$(eval $__ERRPROG)\n"
736                        syslog_err "DNS Resolver Error - BusyBox nslookup Error: '$__ERR'"
737                        return 1
738                } || {
739                        # we need to run twice because multi-line output needs to be directly piped to grep because
740                        # pipe returns return code of last prog in pipe but we need errors from nslookup command
741                        __IP=$(eval $__RUNPROG | sed -ne "3,\$ { s/^Address [0-9]*: \($__REGEX\).*$/\\1/p }" )
742                }
743
744        else                                    # there must be an error
745                critical_error "No program found to request public registered IP"
746        fi
747
748        verbose_echo "   resolved ip =: '$__IP'"
749
750        # if NO IP was found
751        [ -z "$__IP" ] && return 1
752
753        eval "$1='$__IP'"
754        return 0
755}
756
757get_uptime() {
758        # $1    Variable to store result in
759        local __UPTIME=$(cat /proc/uptime)
760        eval "$1='${__UPTIME%%.*}'"
761}