packages/net/ddns-scripts/files/dynamic_dns_functions.sh
Karl Vogel 9e79e1b668 ddns-scripts: sanitize host charset and shell escape characters
Since certain characters are dangerous to pass as-is to a sub shell,
sanitize the character set and only allow characters that are considered
valid for DNS hosts and filter shell escape characters on generic parameters.

Disable pathname expansion on RUNPROG evals to disable the shell expanding *,
? and [ in the arguments.

Signed-off-by: Karl Vogel <karl.vogel@gmail.com>
2018-07-25 08:18:46 +02:00

1411 lines
50 KiB
Bash
Executable file

#!/bin/sh
# /usr/lib/ddns/dynamic_dns_functions.sh
#
#.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
#.2014-2018 Christian Schoenebeck <christian dot schoenebeck at gmail dot com>
#
# function timeout
# copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh
# @author Anthony Thyssen 6 April 2011
#
# variables in small chars are read from /etc/config/ddns
# variables in big chars are defined inside these scripts as global vars
# variables in big chars beginning with "__" are local defined inside functions only
# set -vx #script debugger
. /lib/functions.sh
. /lib/functions/network.sh
# GLOBAL VARIABLES #
VERSION="2.7.7-2"
SECTION_ID="" # hold config's section name
VERBOSE=0 # default mode is log to console, but easily changed with parameter
MYPROG=$(basename $0) # my program call name
LOGFILE="" # logfile - all files are set in dynamic_dns_updater.sh
PIDFILE="" # pid file
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
IPFILE="" # store registered IP for read by LuCI status
TLDFILE=/usr/share/public_suffix_list.dat.gz # TLD file used by split_FQDN
CHECK_SECONDS=0 # calculated seconds out of given
FORCE_SECONDS=0 # interval and unit
RETRY_SECONDS=0 # in configuration
LAST_TIME=0 # holds the uptime of last successful update
CURR_TIME=0 # holds the current uptime
NEXT_TIME=0 # calculated time for next FORCED update
EPOCH_TIME=0 # seconds since 1.1.1970 00:00:00
REGISTERED_IP="" # holds the IP read from DNS
LOCAL_IP="" # holds the local IP read from the box
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
UPD_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
PID_SLEEP=0 # ProcessID of current background "sleep"
# regular expression to detect IPv4 / IPv6
# IPv4 0-9 1-3x "." 0-9 1-3x "." 0-9 1-3x "." 0-9 1-3x
IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}"
# IPv6 ( ( 0-9a-f 1-4char ":") min 1x) ( ( 0-9a-f 1-4char )optional) ( (":" 0-9a-f 1-4char ) min 1x)
IPV6_REGEX="\(\([0-9A-Fa-f]\{1,4\}:\)\{1,\}\)\(\([0-9A-Fa-f]\{1,4\}\)\{0,1\}\)\(\(:[0-9A-Fa-f]\{1,4\}\)\{1,\}\)"
# characters that are dangerous to pass to a shell command line
SHELL_ESCAPE="[\"\'\`\$\!();><{}?|\[\]\*\\\\]"
# dns character set
DNS_CHARSET="[@a-zA-Z0-9._-]"
# detect if called by ddns-lucihelper.sh script, disable retrys (empty variable == false)
LUCI_HELPER=$(printf %s "$MYPROG" | grep -i "luci")
# Name Server Lookup Programs
BIND_HOST=$(which host)
KNOT_HOST=$(which khost)
DRILL=$(which drill)
HOSTIP=$(which hostip)
NSLOOKUP=$(which nslookup)
# Transfer Programs
WGET=$(which wget)
WGET_SSL=$(which wget-ssl)
CURL=$(which curl)
# CURL_PROXY not empty then Proxy support available
CURL_PROXY=$(find /lib /usr/lib -name libcurl.so* -exec strings {} 2>/dev/null \; | grep -im1 "all_proxy")
UCLIENT_FETCH=$(which uclient-fetch)
# Global configuration settings
# allow NON-public IP's
upd_privateip=$(uci -q get ddns.global.upd_privateip) || upd_privateip=0
# directory to store run information to.
ddns_rundir=$(uci -q get ddns.global.ddns_rundir) || ddns_rundir="/var/run/ddns"
[ -d $ddns_rundir ] || mkdir -p -m755 $ddns_rundir
# directory to store log files
ddns_logdir=$(uci -q get ddns.global.ddns_logdir) || ddns_logdir="/var/log/ddns"
[ -d $ddns_logdir ] || mkdir -p -m755 $ddns_logdir
# number of lines to before rotate logfile
ddns_loglines=$(uci -q get ddns.global.ddns_loglines) || ddns_loglines=250
ddns_loglines=$((ddns_loglines + 1)) # correct sed handling
# format to show date information in log and luci-app-ddns default ISO 8601 format
ddns_dateformat=$(uci -q get ddns.global.ddns_dateformat) || ddns_dateformat="%F %R"
DATE_PROG="date +'$ddns_dateformat'"
# USE_CURL if GNU Wget and cURL installed normally Wget is used by do_transfer()
# to change this use global option use_curl '1'
USE_CURL=$(uci -q get ddns.global.use_curl) || USE_CURL=0 # read config
[ -n "$CURL" ] || USE_CURL=0 # check for cURL
# loads all options for a given package and section
# also, sets all_option_variables to a list of the variable names
# $1 = ddns, $2 = SECTION_ID
load_all_config_options()
{
local __PKGNAME="$1"
local __SECTIONID="$2"
local __VAR
local __ALL_OPTION_VARIABLES=""
# this callback loads all the variables in the __SECTIONID section when we do
# config_load. We need to redefine the option_cb for different sections
# so that the active one isn't still active after we're done with it. For reference
# the $1 variable is the name of the option and $2 is the name of the section
config_cb()
{
if [ ."$2" = ."$__SECTIONID" ]; then
option_cb()
{
__ALL_OPTION_VARIABLES="$__ALL_OPTION_VARIABLES $1"
}
else
option_cb() { return 0; }
fi
}
config_load "$__PKGNAME"
# Given SECTION_ID not found so no data, so return 1
[ -z "$__ALL_OPTION_VARIABLES" ] && return 1
for __VAR in $__ALL_OPTION_VARIABLES
do
config_get "$__VAR" "$__SECTIONID" "$__VAR"
done
return 0
}
# read's all service sections from ddns config
# $1 = Name of variable to store
load_all_service_sections() {
local __DATA=""
config_cb()
{
# only look for section type "service", ignore everything else
[ "$1" = "service" ] && __DATA="$__DATA $2"
}
config_load "ddns"
eval "$1=\"$__DATA\""
return
}
# starts updater script for all given sections or only for the one given
# $1 = interface (Optional: when given only scripts are started
# configured for that interface)
# used by /etc/hotplug.d/iface/95-ddns on IFUP
# and by /etc/init.d/ddns start
start_daemon_for_all_ddns_sections()
{
local __EVENTIF="$1"
local __SECTIONS=""
local __SECTIONID=""
local __IFACE=""
load_all_service_sections __SECTIONS
for __SECTIONID in $__SECTIONS; do
config_get __IFACE "$__SECTIONID" interface "wan"
[ -z "$__EVENTIF" -o "$__IFACE" = "$__EVENTIF" ] || continue
if [ $VERBOSE -eq 0 ]; then # start in background
/usr/lib/ddns/dynamic_dns_updater.sh -v 0 -S "$__SECTIONID" -- start &
else
/usr/lib/ddns/dynamic_dns_updater.sh -v "$VERBOSE" -S "$__SECTIONID" -- start
fi
done
}
# stop sections process incl. childs (sleeps)
# $1 = section
stop_section_processes() {
local __PID=0
local __PIDFILE="$ddns_rundir/$1.pid"
[ $# -ne 1 ] && write_log 12 "Error calling 'stop_section_processes()' - wrong number of parameters"
[ -e "$__PIDFILE" ] && {
__PID=$(cat $__PIDFILE)
ps | grep "^[\t ]*$__PID" >/dev/null 2>&1 && kill $__PID || __PID=0 # terminate it
}
[ $__PID -eq 0 ] # report if process was running
}
# stop updater script for all defines sections or only for one given
# $1 = interface (optional)
# used by /etc/hotplug.d/iface/95-ddns on 'ifdown'
# and by /etc/init.d/ddns stop
# needed because we also need to kill "sleep" child processes
stop_daemon_for_all_ddns_sections() {
local __EVENTIF="$1"
local __SECTIONS=""
local __SECTIONID=""
local __IFACE=""
load_all_service_sections __SECTIONS
for __SECTIONID in $__SECTIONS; do
config_get __IFACE "$__SECTIONID" interface "wan"
[ -z "$__EVENTIF" -o "$__IFACE" = "$__EVENTIF" ] || continue
stop_section_processes "$__SECTIONID"
done
}
# reports to console, logfile, syslog
# $1 loglevel 7 == Debug to 0 == EMERG
# value +10 will exit the scripts
# $2..n text to report
write_log() {
local __LEVEL __EXIT __CMD __MSG
local __TIME=$(date +%H%M%S)
[ $1 -ge 10 ] && {
__LEVEL=$(($1-10))
__EXIT=1
} || {
__LEVEL=$1
__EXIT=0
}
shift # remove loglevel
[ $__EXIT -eq 0 ] && __MSG="$*" || __MSG="$* - TERMINATE"
case $__LEVEL in # create log message and command depending on loglevel
0) __CMD="logger -p user.emerg -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME EMERG : $__MSG" ;;
1) __CMD="logger -p user.alert -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME ALERT : $__MSG" ;;
2) __CMD="logger -p user.crit -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME CRIT : $__MSG" ;;
3) __CMD="logger -p user.err -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME ERROR : $__MSG" ;;
4) __CMD="logger -p user.warn -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME WARN : $__MSG" ;;
5) __CMD="logger -p user.notice -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME note : $__MSG" ;;
6) __CMD="logger -p user.info -t ddns-scripts[$$] $SECTION_ID: $__MSG"
__MSG=" $__TIME info : $__MSG" ;;
7) __MSG=" $__TIME : $__MSG";;
*) return;;
esac
# verbose echo
[ $VERBOSE -gt 0 -o $__EXIT -gt 0 ] && echo -e "$__MSG"
# write to logfile
if [ ${use_logfile:-1} -eq 1 -o $VERBOSE -gt 1 ]; then
[ -n "$password" ] && __MSG=$( printf "%s" "$__MSG" | sed -e "s/$password/*password*/g" )
[ -n "$URL_PASS" ] && __MSG=$( printf "%s" "$__MSG" | sed -e "s/$URL_PASS/*URL_PASS*/g" )
printf "%s\n" "$__MSG" >> $LOGFILE
# VERBOSE > 1 then NO loop so NO truncate log to $ddns_loglines lines
[ $VERBOSE -gt 1 ] || sed -i -e :a -e '$q;N;'$ddns_loglines',$D;ba' $LOGFILE
fi
[ -n "$LUCI_HELPER" ] && return # nothing else todo when running LuCI helper script
[ $__LEVEL -eq 7 ] && return # no syslog for debug messages
__CMD=$(echo -e "$__CMD" | tr -d '\n' | tr '\t' ' ') # remove \n \t chars
[ $__EXIT -eq 1 ] && {
$__CMD # force syslog before exit
exit 1
}
[ $use_syslog -eq 0 ] && return
[ $((use_syslog + __LEVEL)) -le 7 ] && $__CMD
return
}
# replace all special chars to their %hex value
# used for USERNAME and PASSWORD in update_url
# unchanged: "-"(minus) "_"(underscore) "."(dot) "~"(tilde)
# to verify: "'"(single quote) '"'(double quote) # because shell delimiter
# "$"(Dollar) # because used as variable output
# tested with the following string stored via Luci Application as password / username
# A B!"#AA$1BB%&'()*+,-./:;<=>?@[\]^_`{|}~ without problems at Dollar or quotes
urlencode() {
# $1 Name of Variable to store encoded string to
# $2 string to encode
local __STR __LEN __CHAR __OUT
local __ENC=""
local __POS=1
[ $# -ne 2 ] && write_log 12 "Error calling 'urlencode()' - wrong number of parameters"
__STR="$2" # read string to encode
__LEN=${#__STR} # get string length
while [ $__POS -le $__LEN ]; do
# read one chat of the string
__CHAR=$(expr substr "$__STR" $__POS 1)
case "$__CHAR" in
[-_.~a-zA-Z0-9] )
# standard char
__OUT="${__CHAR}"
;;
* )
# special char get %hex code
__OUT=$(printf '%%%02x' "'$__CHAR" )
;;
esac
__ENC="${__ENC}${__OUT}" # append to encoded string
__POS=$(( $__POS + 1 )) # increment position
done
eval "$1=\"$__ENC\"" # transfer back to variable
return 0
}
# extract url or script for given DDNS Provider from
# file /etc/ddns/services for IPv4 or from
# file /etc/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() {
[ $# -ne 3 ] && write_log 12 "Error calling 'get_service_data()' - wrong number of parameters"
__FILE="/etc/ddns/services" # IPv4
[ $use_ipv6 -ne 0 ] && __FILE="/etc/ddns/services_ipv6" # IPv6
# 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]*$/d; s/\"//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 < pipe_$$
rm pipe_$$
eval "$1=\"\"" # no service match clear variables
eval "$2=\"\""
eval "$3=\"\""
return 1
}
# Calculate seconds from interval and unit
# $1 Name of Variable to store result in
# $2 Number and
# $3 Unit of time interval
get_seconds() {
[ $# -ne 3 ] && write_log 12 "Error calling 'get_seconds()' - wrong number of parameters"
case "$3" in
"days" ) eval "$1=$(( $2 * 86400 ))";;
"hours" ) eval "$1=$(( $2 * 3600 ))";;
"minutes" ) eval "$1=$(( $2 * 60 ))";;
* ) eval "$1=$2";;
esac
return 0
}
timeout() {
#.copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh
# only did the following changes
# - commented out "#!/bin/bash" and usage section
# - replace exit by return for usage as function
# - some reformatting
#
# timeout [-SIG] time [--] command args...
#
# Run the given command until completion, but kill it if it runs too long.
# Specifically designed to exit immediately (no sleep interval) and clean up
# nicely without messages or leaving any extra processes when finished.
#
# Example use
# timeout 5 countdown
#
# Based on notes in my "Shell Script Hints", section "Command Timeout"
# http://www.ict.griffith.edu.au/~anthony/info/shell/script.hints
#
# This script uses a lot of tricks to terminate both the background command,
# the timeout script, and even the sleep process. It also includes trap
# commands to prevent sub-shells reporting expected "Termination Errors".
#
# It took years of occasional trials, errors and testing to get a pure bash
# timeout command working as well as this does.
#
#.Anthony Thyssen 6 April 2011
#
# PROGNAME=$(type $0 | awk '{print $3}') # search for executable on path
# PROGDIR=$(dirname $PROGNAME) # extract directory of program
# PROGNAME=$(basename $PROGNAME) # base name of program
# output the script comments as docs
# Usage() {
# echo >&2 "$PROGNAME:" "$@"
# sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 3s/^/Usage: /; 2,$ p' "$PROGDIR/$PROGNAME"
# exit 10;
# }
SIG=-TERM
while [ $# -gt 0 ]; do
case "$1" in
--)
# forced end of user options
shift;
break ;;
# -\?|--help|--doc*)
# Usage ;;
[0-9]*)
TIMEOUT="$1" ;;
-*)
SIG="$1" ;;
*)
# unforced end of user options
break ;;
esac
shift # next option
done
# run main command in backgrounds and get its pid
"$@" &
command_pid=$!
# timeout sub-process abort countdown after ABORT seconds! also backgrounded
sleep_pid=0
(
# cleanup sleep process
trap 'kill -TERM $sleep_pid; return 1' 1 2 3 15
# sleep timeout period in background
sleep $TIMEOUT &
sleep_pid=$!
wait $sleep_pid
# Abort the command
kill $SIG $command_pid >/dev/null 2>&1
return 1
) &
timeout_pid=$!
# Wait for main command to finished or be timed out
wait $command_pid
status=$?
# Clean up timeout sub-shell - if it is still running!
kill $timeout_pid 2>/dev/null
wait $timeout_pid 2>/dev/null
# Uncomment to check if a LONG sleep still running (no sleep should be)
# sleep 1
# echo "-----------"
# /bin/ps j # uncomment to show if abort "sleep" is still sleeping
return $status
}
# sanitize a variable
# $1 variable name
# $2 allowed shell pattern
# $3 disallowed shell pattern
sanitize_variable() {
local __VAR=$1
eval __VALUE=\$$__VAR
local __ALLOWED=$2
local __REJECT=$3
# removing all allowed should give empty string
if [ -n "$__ALLOWED" ]; then
[ -z "${__VALUE//$__ALLOWED}" ] || write_log 12 "sanitize on $__VAR found characters outside allowed subset"
fi
# removing rejected pattern should give the same string as the input
if [ -n "$__REJECT" ]; then
[ "$__VALUE" = "${__VALUE//$__REJECT}" ] || write_log 12 "sanitize on $__VAR found rejected characters"
fi
}
# verify given host and port is connectable
# $1 Host/IP to verify
# $2 Port to verify
verify_host_port() {
local __HOST=$1
local __PORT=$2
local __NC=$(which nc)
local __NCEXT=$($(which nc) --help 2>&1 | grep "\-w" 2>/dev/null) # busybox nc compiled with extensions
local __IP __IPV4 __IPV6 __RUNPROG __PROG __ERR
# return codes
# 1 system specific error
# 2 nslookup/host error
# 3 nc (netcat) error
# 4 unmatched IP version
[ $# -ne 2 ] && write_log 12 "Error calling 'verify_host_port()' - wrong number of parameters"
# check if ip or FQDN was given
__IPV4=$(echo $__HOST | grep -m 1 -o "$IPV4_REGEX$") # do not detect ip in 0.0.0.0.example.com
__IPV6=$(echo $__HOST | grep -m 1 -o "$IPV6_REGEX")
# if FQDN given get IP address
[ -z "$__IPV4" -a -z "$__IPV6" ] && {
if [ -n "$BIND_HOST" ]; then # use BIND host if installed
__PROG="BIND host"
__RUNPROG="$BIND_HOST $__HOST >$DATFILE 2>$ERRFILE"
elif [ -n "$KNOT_HOST" ]; then # use Knot host if installed
__PROG="Knot host"
__RUNPROG="$KNOT_HOST $__HOST >$DATFILE 2>$ERRFILE"
elif [ -n "$DRILL" ]; then # use drill if installed
__PROG="drill"
__RUNPROG="$DRILL -V0 $__HOST A >$DATFILE 2>$ERRFILE" # IPv4
__RUNPROG="$__RUNPROG; $DRILL -V0 $__HOST AAAA >>$DATFILE 2>>$ERRFILE" # IPv6
elif [ -n "$HOSTIP" ]; then # use hostip if installed
__PROG="hostip"
__RUNPROG="$HOSTIP $__HOST >$DATFILE 2>$ERRFILE" # IPv4
__RUNPROG="$__RUNPROG; $HOSTIP -6 $__HOST >>$DATFILE 2>>$ERRFILE" # IPv6
else # use BusyBox nslookup
__PROG="BusyBox nslookup"
__RUNPROG="$NSLOOKUP $__HOST >$DATFILE 2>$ERRFILE"
fi
write_log 7 "#> $__RUNPROG"
(
set -o noglob
eval $__RUNPROG
)
__ERR=$?
# command error
[ $__ERR -gt 0 ] && {
write_log 3 "DNS Resolver Error - $__PROG Error '$__ERR'"
write_log 7 "$(cat $ERRFILE)"
return 2
}
# extract IP address
if [ -n "$BIND_HOST" -o -n "$KNOT_HOST" ]; then # use BIND host or Knot host if installed
__IPV4=$(cat $DATFILE | awk -F "address " '/has address/ {print $2; exit}' )
__IPV6=$(cat $DATFILE | awk -F "address " '/has IPv6/ {print $2; exit}' )
elif [ -n "$DRILL" ]; then # use drill if installed
__IPV4=$(cat $DATFILE | awk '/^'"$lookup_host"'/ {print $5}' | grep -m 1 -o "$IPV4_REGEX")
__IPV6=$(cat $DATFILE | awk '/^'"$lookup_host"'/ {print $5}' | grep -m 1 -o "$IPV6_REGEX")
elif [ -n "$HOSTIP" ]; then # use hostip if installed
__IPV4=$(cat $DATFILE | grep -m 1 -o "$IPV4_REGEX")
__IPV6=$(cat $DATFILE | grep -m 1 -o "$IPV6_REGEX")
else # use BusyBox nslookup
__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
}
# check IP version if forced
if [ $force_ipversion -ne 0 ]; then
__ERR=0
[ $use_ipv6 -eq 0 -a -z "$__IPV4" ] && __ERR=4
[ $use_ipv6 -eq 1 -a -z "$__IPV6" ] && __ERR=6
[ $__ERR -gt 0 ] && {
[ -n "$LUCI_HELPER" ] && return 4
write_log 14 "Verify host Error '4' - Forced IP Version IPv$__ERR don't match"
}
fi
# verify nc command
# busybox nc compiled without -l option "NO OPT l!" -> critical error
$__NC --help 2>&1 | grep -i "NO OPT l!" >/dev/null 2>&1 && \
write_log 12 "Busybox nc (netcat) compiled without '-l' option, error 'NO OPT l!'"
# busybox nc compiled with extensions
$__NC --help 2>&1 | grep "\-w" >/dev/null 2>&1 && __NCEXT="TRUE"
# connectivity test
# run busybox nc to HOST PORT
# busybox might be compiled with "FEATURE_PREFER_IPV4_ADDRESS=n"
# then nc will try to connect via IPv6 if there is any IPv6 available on any host interface
# not worrying, if there is an IPv6 wan address
# so if not "force_ipversion" to use_ipv6 then connect test via ipv4, if available
[ $force_ipversion -ne 0 -a $use_ipv6 -ne 0 -o -z "$__IPV4" ] && __IP=$__IPV6 || __IP=$__IPV4
if [ -n "$__NCEXT" ]; then # BusyBox nc compiled with extensions (timeout support)
__RUNPROG="$__NC -w 1 $__IP $__PORT </dev/null >$DATFILE 2>$ERRFILE"
write_log 7 "#> $__RUNPROG"
(
set -o noglob
eval $__RUNPROG
)
__ERR=$?
[ $__ERR -eq 0 ] && return 0
write_log 3 "Connect error - BusyBox nc (netcat) Error '$__ERR'"
write_log 7 "$(cat $ERRFILE)"
return 3
else # nc compiled without extensions (no timeout support)
__RUNPROG="timeout 2 -- $__NC $__IP $__PORT </dev/null >$DATFILE 2>$ERRFILE"
write_log 7 "#> $__RUNPROG"
(
set -o noglob
eval $__RUNPROG
)
__ERR=$?
[ $__ERR -eq 0 ] && return 0
write_log 3 "Connect error - BusyBox nc (netcat) timeout Error '$__ERR'"
return 3
fi
}
# verify given DNS server if connectable
# $1 DNS server to verify
verify_dns() {
local __ERR=255 # last error buffer
local __CNT=0 # error counter
[ $# -ne 1 ] && write_log 12 "Error calling 'verify_dns()' - wrong number of parameters"
write_log 7 "Verify DNS server '$1'"
while [ $__ERR -ne 0 ]; do
# DNS uses port 53
verify_host_port "$1" "53"
__ERR=$?
if [ -n "$LUCI_HELPER" ]; then # no retry if called by LuCI helper script
return $__ERR
elif [ $__ERR -ne 0 -a $VERBOSE -gt 1 ]; then # VERBOSE > 1 then NO retry
write_log 4 "Verify DNS server '$1' failed - Verbose Mode: $VERBOSE - NO retry on error"
return $__ERR
elif [ $__ERR -ne 0 ]; then
__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 "Verify DNS server '$1' failed after $retry_count retries"
write_log 4 "Verify DNS server '$1' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
fi
done
return 0
}
# analyze and verify given proxy string
# $1 Proxy-String to verify
verify_proxy() {
# complete entry user:password@host:port
# inside user and password NO '@' of ":" allowed
# host and port only host:port
# host only host ERROR unsupported
# IPv4 address instead of host 123.234.234.123
# IPv6 address instead of host [xxxx:....:xxxx] in square bracket
local __TMP __HOST __PORT
local __ERR=255 # last error buffer
local __CNT=0 # error counter
[ $# -ne 1 ] && write_log 12 "Error calling 'verify_proxy()' - wrong number of parameters"
write_log 7 "Verify Proxy server 'http://$1'"
# try to split user:password "@" host:port
__TMP=$(echo $1 | awk -F "@" '{print $2}')
# no "@" found - only host:port is given
[ -z "$__TMP" ] && __TMP="$1"
# now lets check for IPv6 address
__HOST=$(echo $__TMP | grep -m 1 -o "$IPV6_REGEX")
# IPv6 host address found read port
if [ -n "$__HOST" ]; then
# IPv6 split at "]:"
__PORT=$(echo $__TMP | awk -F "]:" '{print $2}')
else
__HOST=$(echo $__TMP | awk -F ":" '{print $1}')
__PORT=$(echo $__TMP | awk -F ":" '{print $2}')
fi
# No Port detected - EXITING
[ -z "$__PORT" ] && {
[ -n "$LUCI_HELPER" ] && return 5
write_log 14 "Invalid Proxy server Error '5' - proxy port missing"
}
while [ $__ERR -gt 0 ]; do
verify_host_port "$__HOST" "$__PORT"
__ERR=$?
if [ -n "$LUCI_HELPER" ]; then # no retry if called by LuCI helper script
return $__ERR
elif [ $__ERR -gt 0 -a $VERBOSE -gt 1 ]; then # VERBOSE > 1 then NO retry
write_log 4 "Verify Proxy server '$1' failed - Verbose Mode: $VERBOSE - NO retry on error"
return $__ERR
elif [ $__ERR -gt 0 ]; then
__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 "Verify Proxy server '$1' failed after $retry_count retries"
write_log 4 "Verify Proxy server '$1' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
fi
done
return 0
}
do_transfer() {
# $1 # URL to use
local __URL="$1"
local __ERR=0
local __CNT=0 # error counter
local __PROG __RUNPROG
[ $# -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
if [ -n "$WGET_SSL" -a $USE_CURL -eq 0 ]; then # except global option use_curl is set to "1"
__PROG="$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
# set correct program to detect IP
[ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" || __RUNPROG="network_get_ipaddr6"
( set -o noglob ; eval "$__RUNPROG __BINDIP $bind_network" ) || \
write_log 13 "Can not detect local IP using '$__RUNPROG $bind_network' - Error: '$?'"
write_log 7 "Force communication via IP '$__BINDIP'"
__PROG="$__PROG --bind-address=$__BINDIP"
fi
# 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
# set certificate parameters
if [ $use_https -eq 1 ]; then
if [ "$cacert" = "IGNORE" ]; then # idea from Ticket #15327 to ignore server cert
__PROG="$__PROG --no-check-certificate"
elif [ -f "$cacert" ]; then
__PROG="$__PROG --ca-certificate=${cacert}"
elif [ -d "$cacert" ]; then
__PROG="$__PROG --ca-directory=${cacert}"
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
# disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
[ -z "$proxy" ] && __PROG="$__PROG --no-proxy"
__RUNPROG="$__PROG '$__URL'" # build final command
__PROG="GNU Wget" # reuse for error logging
# 2nd choice is cURL IPv4/IPv6/HTTPS
# libcurl might be compiled without Proxy or HTTPS Support
elif [ -n "$CURL" ]; then
# CURL_SSL not empty then SSL support available
CURL_SSL=$($(which curl) -V 2>/dev/null | grep "Protocols:" | grep -F "https")
__PROG="$CURL -RsS -o $DATFILE --stderr $ERRFILE"
# check HTTPS support
[ -z "$CURL_SSL" -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
network_get_physdev __DEVICE $bind_network || \
write_log 13 "Can not detect local device using 'network_get_physdev $bind_network' - Error: '$?'"
write_log 7 "Force communication via device '$__DEVICE'"
__PROG="$__PROG --interface $__DEVICE"
fi
# 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
# set certificate parameters
if [ $use_https -eq 1 ]; then
if [ "$cacert" = "IGNORE" ]; then # idea from Ticket #15327 to ignore server cert
__PROG="$__PROG --insecure" # but not empty better to use "IGNORE"
elif [ -f "$cacert" ]; then
__PROG="$__PROG --cacert $cacert"
elif [ -d "$cacert" ]; then
__PROG="$__PROG --capath $cacert"
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
# disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
# or check if libcurl compiled with proxy support
if [ -z "$proxy" ]; then
__PROG="$__PROG --noproxy '*'"
elif [ -z "$CURL_PROXY" ]; then
# if libcurl has no proxy support and proxy should be used then force ERROR
write_log 13 "cURL: libcurl compiled without Proxy support"
fi
__RUNPROG="$__PROG '$__URL'" # build final command
__PROG="cURL" # reuse for error logging
# uclient-fetch possibly with ssl support if /lib/libustream-ssl.so installed
elif [ -n "$UCLIENT_FETCH" ]; then
# UCLIENT_FETCH_SSL not empty then SSL support available
UCLIENT_FETCH_SSL=$(find /lib /usr/lib -name libustream-ssl.so* 2>/dev/null)
__PROG="$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 -z "$UCLIENT_FETCH_SSL" ] && \
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 "$WGET" ]; then
__PROG="$WGET -q -O $DATFILE"
# force network/ip not supported
[ -n "$__BINDIP" ] && \
write_log 14 "BusyBox Wget: FORCE binding to specific address not supported"
# force ip version not supported
[ $force_ipversion -eq 1 ] && \
write_log 14 "BusyBox Wget: Force connecting to IPv4 or IPv6 addresses not supported"
# https not supported
[ $use_https -eq 1 ] && \
write_log 14 "BusyBox Wget: no HTTPS support"
# disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set)
[ -z "$proxy" ] && __PROG="$__PROG -Y off"
__RUNPROG="$__PROG '$__URL' 2>$ERRFILE" # build final command
__PROG="Busybox Wget" # reuse for error logging
else
write_log 13 "Neither 'Wget' nor 'cURL' nor 'uclient-fetch' installed or executable"
fi
while : ; do
write_log 7 "#> $__RUNPROG"
(
set -o noglob
eval $__RUNPROG # DO transfer
)
__ERR=$? # save error code
[ $__ERR -eq 0 ] && return 0 # no error leave
[ -n "$LUCI_HELPER" ] && return 1 # no retry if called by LuCI helper script
write_log 3 "$__PROG Error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)" # report error
[ $VERBOSE -gt 1 ] && {
# VERBOSE > 1 then NO retry
write_log 4 "Transfer failed - Verbose Mode: $VERBOSE - NO retry on error"
return 1
}
__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 "Transfer failed after $retry_count retries"
write_log 4 "Transfer failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
done
# we should never come here there must be a programming error
write_log 12 "Error in 'do_transfer()' - program coding error"
}
send_update() {
# $1 # IP to set at DDNS service provider
local __IP
[ $# -ne 1 ] && write_log 12 "Error calling 'send_update()' - wrong number of parameters"
if [ $upd_privateip -eq 0 ]; then
# verify given IP / no private IPv4's / no IPv6 addr starting with fxxx of with ":"
[ $use_ipv6 -eq 0 ] && __IP=$(echo $1 | grep -v -E "(^0|^10\.|^100\.6[4-9]\.|^100\.[7-9][0-9]\.|^100\.1[0-1][0-9]\.|^100\.12[0-7]\.|^127|^169\.254|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168)")
[ $use_ipv6 -eq 1 ] && __IP=$(echo $1 | grep "^[0-9a-eA-E]")
else
__IP=$(echo $1 | grep -m 1 -o "$IPV4_REGEX") # valid IPv4 or
[ -z "$__IP" ] && __IP=$(echo $1 | grep -m 1 -o "$IPV6_REGEX") # IPv6
fi
[ -z "$__IP" ] && {
write_log 3 "No or private or invalid IP '$1' given! Please check your configuration"
return 127
}
if [ -n "$update_script" ]; then
write_log 7 "parsing script '$update_script'"
. $update_script
else
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#\[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)"
[ -z "$UPD_ANSWER" ] && return 0 # not set then ignore
grep -i -E "$UPD_ANSWER" $DATFILE >/dev/null 2>&1
return $? # "0" if found
fi
}
get_local_ip () {
# $1 Name of Variable to store local IP (LOCAL_IP)
local __CNT=0 # error counter
local __RUNPROG __DATA __URL __ERR
[ $# -ne 1 ] && write_log 12 "Error calling 'get_local_ip()' - wrong number of parameters"
write_log 7 "Detect local IP on '$ip_source'"
while : ; do
if [ -n "$ip_network" ]; then
# set correct program
network_flush_cache # force re-read data from ubus
[ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" \
|| __RUNPROG="network_get_ipaddr6"
( set -o noglob ; eval "$__RUNPROG __DATA $ip_network" ) || \
write_log 13 "Can not detect local IP using $__RUNPROG '$ip_network' - Error: '$?'"
[ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected on network '$ip_network'"
elif [ -n "$ip_interface" ]; then
local __DATA4=""; local __DATA6=""
if [ -n "$(which ip)" ]; then # ip program installed
write_log 7 "#> ip -o addr show dev $ip_interface scope global >$DATFILE 2>$ERRFILE"
ip -o addr show dev $ip_interface scope global >$DATFILE 2>$ERRFILE
__ERR=$?
if [ $__ERR -eq 0 ]; then
# DATFILE (sample)
# 10: l2tp-inet: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1456 qdisc fq_codel state UNKNOWN qlen 3\ link/ppp
# 10: l2tp-inet inet 95.30.176.51 peer 95.30.176.1/32 scope global l2tp-inet\ valid_lft forever preferred_lft forever
# 5: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP qlen 1000\ link/ether 08:00:27:d0:10:32 brd ff:ff:ff:ff:ff:ff
# 5: eth1 inet 172.27.10.128/24 brd 172.27.10.255 scope global eth1\ valid_lft forever preferred_lft forever
# 5: eth1 inet 172.55.55.155/24 brd 172.27.10.255 scope global eth1\ valid_lft 12345sec preferred_lft 12345sec
# 5: eth1 inet6 2002:b0c7:f326::806b:c629:b8b9:433/128 scope global dynamic \ valid_lft 8026sec preferred_lft 8026sec
# 5: eth1 inet6 fd43:5368:6f6d:6500:806b:c629:b8b9:433/128 scope global dynamic \ valid_lft 8026sec preferred_lft 8026sec
# 5: eth1 inet6 fd43:5368:6f6d:6500:a00:27ff:fed0:1032/64 scope global dynamic \ valid_lft 14352sec preferred_lft 14352sec
# 5: eth1 inet6 2002:b0c7:f326::a00:27ff:fed0:1032/64 scope global dynamic \ valid_lft 14352sec preferred_lft 14352sec
# remove remove remove replace replace
# link inet6 fxxx sec forever=>-1 / => ' ' to separate subnet from ip
sed "/link/d; /inet6 f/d; s/sec//g; s/forever/-1/g; s/\// /g" $DATFILE | \
awk '{ print $3" "$4" "$NF }' > $ERRFILE # temp reuse ERRFILE
# we only need inet? IP prefered time
local __TIME4=0; local __TIME6=0
local __TYP __ADR __TIME
while read __TYP __ADR __TIME; do
__TIME=${__TIME:-0} # supress shell errors on last (empty) line of DATFILE
# IPversion no "-1" record stored - now "-1" record or new time > oldtime
[ "$__TYP" = "inet6" -a $__TIME6 -ge 0 -a \( $__TIME -lt 0 -o $__TIME -gt $__TIME6 \) ] && {
__DATA6="$__ADR"
__TIME6="$__TIME"
}
[ "$__TYP" = "inet" -a $__TIME4 -ge 0 -a \( $__TIME -lt 0 -o $__TIME -gt $__TIME4 \) ] && {
__DATA4="$__ADR"
__TIME4="$__TIME"
}
done < $ERRFILE
else
write_log 3 "ip Error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)" # report error
fi
else # use deprecated ifconfig
write_log 7 "#> ifconfig $ip_interface >$DATFILE 2>$ERRFILE"
ifconfig $ip_interface >$DATFILE 2>$ERRFILE
__ERR=$?
if [ $__ERR -eq 0 ]; then
__DATA4=$(awk '
/inet addr:/ { # Filter IPv4
# inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0
$1=""; # remove inet
$3=""; # remove Bcast: ...
$4=""; # remove Mask: ...
FS=":"; # separator ":"
$0=$0; # reread to activate separator
$1=""; # remove addr
FS=" "; # set back separator to default " "
$0=$0; # reread to activate separator (remove whitespaces)
print $1; # print IPv4 addr
}' $DATFILE
)
__DATA6=$(awk '
/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=""; # remove everything behind "/"
FS=" "; # set back separator to default " "
$0=$0; # reread to activate separator
print $3; # print IPv6 addr
}' $DATFILE
)
else
write_log 3 "ifconfig Error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)" # report error
fi
fi
[ $use_ipv6 -eq 0 ] && __DATA="$__DATA4" || __DATA="$__DATA6"
[ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected on interface '$ip_interface'"
elif [ -n "$ip_script" ]; then
write_log 7 "#> $ip_script >$DATFILE 2>$ERRFILE"
(
set -o noglob
eval $ip_script >$DATFILE 2>$ERRFILE
)
__ERR=$?
if [ $__ERR -eq 0 ]; then
__DATA=$(cat $DATFILE)
[ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected via script '$ip_script'"
else
write_log 3 "$ip_script Error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)" # report error
fi
elif [ -n "$ip_url" ]; then
do_transfer "$ip_url"
# use correct regular expression
[ $use_ipv6 -eq 0 ] \
&& __DATA=$(grep -m 1 -o "$IPV4_REGEX" $DATFILE) \
|| __DATA=$(grep -m 1 -o "$IPV6_REGEX" $DATFILE)
[ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected on web at '$ip_url'"
else
write_log 12 "Error in 'get_local_ip()' - unhandled ip_source '$ip_source'"
fi
# valid data found return here
[ -n "$__DATA" ] && {
eval "$1=\"$__DATA\""
return 0
}
[ -n "$LUCI_HELPER" ] && return 1 # no retry if called by LuCI helper script
write_log 7 "Data detected:"
write_log 7 "$(cat $DATFILE)"
[ $VERBOSE -gt 1 ] && {
# VERBOSE > 1 then NO retry
write_log 4 "Get local IP via '$ip_source' failed - Verbose Mode: $VERBOSE - NO retry on error"
return 1
}
__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 local IP via '$ip_source' failed after $retry_count retries"
write_log 4 "Get local IP via '$ip_source' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds"
sleep $RETRY_SECONDS &
PID_SLEEP=$!
wait $PID_SLEEP # enable trap-handler
PID_SLEEP=0
done
# we should never come here there must be a programming error
write_log 12 "Error in 'get_local_ip()' - program coding error"
}
get_registered_ip() {
# $1 Name of Variable to store public IP (REGISTERED_IP)
# $2 (optional) if set, do not retry on error
local __CNT=0 # error counter
local __ERR=255
local __REGEX __PROG __RUNPROG __DATA __IP
# return codes
# 1 no IP detected
[ $# -lt 1 -o $# -gt 2 ] && write_log 12 "Error calling 'get_registered_ip()' - wrong number of parameters"
[ $is_glue -eq 1 -a -z "$BIND_HOST" ] && write_log 14 "Lookup of glue records is only supported using BIND host"
write_log 7 "Detect registered/public IP"
# set correct regular expression
[ $use_ipv6 -eq 0 ] && __REGEX="$IPV4_REGEX" || __REGEX="$IPV6_REGEX"
if [ -n "$BIND_HOST" ]; then
__PROG="$BIND_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
[ $is_glue -eq 1 ] && __PROG="$__PROG -v" # use verbose output to get additional section
__RUNPROG="$__PROG $lookup_host $dns_server >$DATFILE 2>$ERRFILE"
__PROG="BIND host"
elif [ -n "$KNOT_HOST" ]; then
__PROG="$KNOT_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 $lookup_host $dns_server >$DATFILE 2>$ERRFILE"
__PROG="Knot host"
elif [ -n "$DRILL" ]; then
__PROG="$DRILL -V0" # drill options name @server type
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" || __PROG="$__PROG -u" # force TCP
__PROG="$__PROG $lookup_host"
[ -n "$dns_server" ] && __PROG="$__PROG @$dns_server"
[ $use_ipv6 -eq 0 ] && __PROG="$__PROG A" || __PROG="$__PROG AAAA"
__RUNPROG="$__PROG >$DATFILE 2>$ERRFILE"
__PROG="drill"
elif [ -n "$HOSTIP" ]; then # hostip package installed
__PROG="$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="\`$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 "$NSLOOKUP" ]; then # last use BusyBox nslookup
NSLOOKUP_MUSL=$($(which nslookup) localhost 2>&1 | grep -F "(null)") # not empty busybox compiled with musl
[ $force_dnstcp -ne 0 ] && \
write_log 14 "Busybox nslookup - no support for 'DNS over TCP'"
[ -n "$NSLOOKUP_MUSL" -a -n "$dns_server" ] && \
write_log 14 "Busybox compiled with musl - nslookup don't support the use of DNS Server"
[ $force_ipversion -ne 0 ] && \
write_log 5 "Busybox nslookup - no support to 'force IP Version' (ignored)"
__RUNPROG="$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"
fi
while : ; do
write_log 7 "#> $__RUNPROG"
(
set -o noglob
eval $__RUNPROG
)
__ERR=$?
if [ $__ERR -ne 0 ]; then
write_log 3 "$__PROG error: '$__ERR'"
write_log 7 "$(cat $ERRFILE)"
else
if [ -n "$BIND_HOST" -o -n "$KNOT_HOST" ]; then
if [ $is_glue -eq 1 ]; then
__DATA=$(cat $DATFILE | grep "^$lookup_host" | grep -om1 "$__REGEX" )
else
__DATA=$(cat $DATFILE | awk -F "address " '/has/ {print $2; exit}' )
fi
elif [ -n "$DRILL" ]; then
__DATA=$(cat $DATFILE | awk '/^'"$lookup_host"'/ {print $5; exit}' )
elif [ -n "$HOSTIP" ]; then
__DATA=$(cat $DATFILE | grep -om1 "$__REGEX")
elif [ -n "$NSLOOKUP" ]; then
__DATA=$(cat $DATFILE | sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($__REGEX\).*$/\\1/p }" )
fi
[ -n "$__DATA" ] && {
write_log 7 "Registered IP '$__DATA' detected"
[ -z "$IPFILE" ] || echo "$__DATA" > $IPFILE
eval "$1=\"$__DATA\"" # valid data found
return 0 # leave here
}
write_log 4 "NO valid IP found"
__ERR=127
fi
[ -z "$IPFILE" ] || echo "" > $IPFILE
[ -n "$LUCI_HELPER" ] && return $__ERR # no retry if called by LuCI helper script
[ -n "$2" ] && return $__ERR # $2 is given -> no retry
[ $VERBOSE -gt 1 ] && {
# VERBOSE > 1 then NO retry
write_log 4 "Get registered/public IP for '$lookup_host' failed - Verbose Mode: $VERBOSE - 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 '$lookup_host' failed after $retry_count retries"
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
PID_SLEEP=0
done
# we should never come here there must be a programming error
write_log 12 "Error in 'get_registered_ip()' - program coding error"
}
get_uptime() {
# $1 Variable to store result in
[ $# -ne 1 ] && write_log 12 "Error calling 'verify_host_port()' - wrong number of parameters"
local __UPTIME=$(cat /proc/uptime)
eval "$1=\"${__UPTIME%%.*}\""
}
trap_handler() {
# $1 trap signal
# $2 optional (exit status)
local __PIDS __PID
local __ERR=${2:-0}
local __OLD_IFS=$IFS
local __NEWLINE_IFS='
' # __NEWLINE_IFS
[ $PID_SLEEP -ne 0 ] && kill -$1 $PID_SLEEP 2>/dev/null # kill pending sleep if exist
case $1 in
0) if [ $__ERR -eq 0 ]; then
write_log 5 "PID '$$' exit normal at $(eval $DATE_PROG)\n"
else
write_log 4 "PID '$$' exit WITH ERROR '$__ERR' at $(eval $DATE_PROG)\n"
fi ;;
1) write_log 6 "PID '$$' received 'SIGHUP' at $(eval $DATE_PROG)"
# reload config via starting the script again
/usr/lib/ddns/dynamic_dns_updater.sh -v "0" -S "$__SECTIONID" -- start || true
exit 0 ;; # and leave this one
2) write_log 5 "PID '$$' terminated by 'SIGINT' at $(eval $DATE_PROG)\n";;
3) write_log 5 "PID '$$' terminated by 'SIGQUIT' at $(eval $DATE_PROG)\n";;
15) write_log 5 "PID '$$' terminated by 'SIGTERM' at $(eval $DATE_PROG)\n";;
*) write_log 13 "Unhandled signal '$1' in 'trap_handler()'";;
esac
__PIDS=$(pgrep -P $$) # get my childs (pgrep prints with "newline")
IFS=$__NEWLINE_IFS
for __PID in $__PIDS; do
kill -$1 $__PID # terminate it
done
IFS=$__OLD_IFS
# remove out and err file
[ -f $DATFILE ] && rm -f $DATFILE
[ -f $ERRFILE ] && rm -f $ERRFILE
# exit with correct handling:
# remove trap handling settings and send kill to myself
trap - 0 1 2 3 15
[ $1 -gt 0 ] && kill -$1 $$
}
split_FQDN() {
# $1 FQDN to split
# $2 name of variable to store TLD
# $3 name of variable to store (reg)Domain
# $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'"
local _HOST _FDOM _CTLD _FTLD
local _SET="$@" # save given function parameters
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
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
}
[ -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 "$_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 "$3=''" # clear registrable domain
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 to 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
}