dynamic_dns_functions.sh (49414B)
1 #!/bin/sh 2 # /usr/lib/ddns/dynamic_dns_functions.sh 3 # 4 #.Distributed under the terms of the GNU General Public License (GPL) version 2.0 5 # Original written by Eric Paul Bishop, January 2008 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 # extended and partial rewritten 9 #.2014-2017 Christian Schoenebeck <christian dot schoenebeck at gmail dot com> 10 # 11 # function timeout 12 # copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh 13 # @author Anthony Thyssen 6 April 2011 14 # 15 # variables in small chars are read from /etc/config/ddns 16 # variables in big chars are defined inside these scripts as global vars 17 # variables in big chars beginning with "__" are local defined inside functions only 18 # set -vx #script debugger 19 20 . /lib/functions.sh 21 . /lib/functions/network.sh 22 23 # GLOBAL VARIABLES # 24 VERSION="2.7.6-13" 25 SECTION_ID="" # hold config's section name 26 VERBOSE=0 # default mode is log to console, but easily changed with parameter 27 MYPROG=$(basename $0) # my program call name 28 29 LOGFILE="" # logfile - all files are set in dynamic_dns_updater.sh 30 PIDFILE="" # pid file 31 UPDFILE="" # store UPTIME of last update 32 DATFILE="" # save stdout data of WGet and other external programs called 33 ERRFILE="" # save stderr output of WGet and other external programs called 34 TLDFILE=/usr/share/public_suffix_list.dat.gz # TLD file used by split_FQDN 35 36 CHECK_SECONDS=0 # calculated seconds out of given 37 FORCE_SECONDS=0 # interval and unit 38 RETRY_SECONDS=0 # in configuration 39 40 LAST_TIME=0 # holds the uptime of last successful update 41 CURR_TIME=0 # holds the current uptime 42 NEXT_TIME=0 # calculated time for next FORCED update 43 EPOCH_TIME=0 # seconds since 1.1.1970 00:00:00 44 45 REGISTERED_IP="" # holds the IP read from DNS 46 LOCAL_IP="" # holds the local IP read from the box 47 48 URL_USER="" # url encoded $username from config file 49 URL_PASS="" # url encoded $password from config file 50 URL_PENC="" # url encoded $param_enc from config file 51 52 UPD_ANSWER="" # Answer given by service on success 53 54 ERR_LAST=0 # used to save $? return code of program and function calls 55 ERR_UPDATE=0 # error counter on different local and registered ip 56 57 PID_SLEEP=0 # ProcessID of current background "sleep" 58 59 # regular expression to detect IPv4 / IPv6 60 # IPv4 0-9 1-3x "." 0-9 1-3x "." 0-9 1-3x "." 0-9 1-3x 61 IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" 62 # IPv6 ( ( 0-9a-f 1-4char ":") min 1x) ( ( 0-9a-f 1-4char )optional) ( (":" 0-9a-f 1-4char ) min 1x) 63 IPV6_REGEX="\(\([0-9A-Fa-f]\{1,4\}:\)\{1,\}\)\(\([0-9A-Fa-f]\{1,4\}\)\{0,1\}\)\(\(:[0-9A-Fa-f]\{1,4\}\)\{1,\}\)" 64 65 # detect if called by ddns-lucihelper.sh script, disable retrys (empty variable == false) 66 LUCI_HELPER=$(printf %s "$MYPROG" | grep -i "luci") 67 68 # Name Server Lookup Programs 69 BIND_HOST=$(which host) 70 KNOT_HOST=$(which khost) 71 DRILL=$(which drill) 72 HOSTIP=$(which hostip) 73 NSLOOKUP=$(which nslookup) 74 NSLOOKUP_MUSL=$($(which nslookup) localhost 2>&1 | grep -F "(null)") # not empty busybox compiled with musl 75 76 # Transfer Programs 77 WGET=$(which wget) 78 WGET_SSL=$(which wget-ssl) 79 80 CURL=$(which curl) 81 # CURL_SSL not empty then SSL support available 82 CURL_SSL=$($(which curl) -V 2>/dev/null | grep "Protocols:" | grep -F "https") 83 # CURL_PROXY not empty then Proxy support available 84 CURL_PROXY=$(find /lib /usr/lib -name libcurl.so* -exec grep -i "all_proxy" {} 2>/dev/null \;) 85 86 UCLIENT_FETCH=$(which uclient-fetch) 87 # UCLIENT_FETCH_SSL not empty then SSL support available 88 UCLIENT_FETCH_SSL=$(find /lib /usr/lib -name libustream-ssl.so* 2>/dev/null) 89 90 # Global configuration settings 91 # allow NON-public IP's 92 upd_privateip=$(uci -q get ddns.global.upd_privateip) || upd_privateip=0 93 94 # directory to store run information to. 95 ddns_rundir=$(uci -q get ddns.global.ddns_rundir) || ddns_rundir="/var/run/ddns" 96 [ -d $ddns_rundir ] || mkdir -p -m755 $ddns_rundir 97 98 # directory to store log files 99 ddns_logdir=$(uci -q get ddns.global.ddns_logdir) || ddns_logdir="/var/log/ddns" 100 [ -d $ddns_logdir ] || mkdir -p -m755 $ddns_logdir 101 102 # number of lines to before rotate logfile 103 ddns_loglines=$(uci -q get ddns.global.ddns_loglines) || ddns_loglines=250 104 ddns_loglines=$((ddns_loglines + 1)) # correct sed handling 105 106 # format to show date information in log and luci-app-ddns default ISO 8601 format 107 ddns_dateformat=$(uci -q get ddns.global.ddns_dateformat) || ddns_dateformat="%F %R" 108 DATE_PROG="date +'$ddns_dateformat'" 109 110 # USE_CURL if GNU Wget and cURL installed normally Wget is used by do_transfer() 111 # to change this use global option use_curl '1' 112 USE_CURL=$(uci -q get ddns.global.use_curl) || USE_CURL=0 # read config 113 [ -n "$CURL" ] || USE_CURL=0 # check for cURL 114 115 # loads all options for a given package and section 116 # also, sets all_option_variables to a list of the variable names 117 # $1 = ddns, $2 = SECTION_ID 118 load_all_config_options() 119 { 120 local __PKGNAME="$1" 121 local __SECTIONID="$2" 122 local __VAR 123 local __ALL_OPTION_VARIABLES="" 124 125 # this callback loads all the variables in the __SECTIONID section when we do 126 # config_load. We need to redefine the option_cb for different sections 127 # so that the active one isn't still active after we're done with it. For reference 128 # the $1 variable is the name of the option and $2 is the name of the section 129 config_cb() 130 { 131 if [ ."$2" = ."$__SECTIONID" ]; then 132 option_cb() 133 { 134 __ALL_OPTION_VARIABLES="$__ALL_OPTION_VARIABLES $1" 135 } 136 else 137 option_cb() { return 0; } 138 fi 139 } 140 141 config_load "$__PKGNAME" 142 143 # Given SECTION_ID not found so no data, so return 1 144 [ -z "$__ALL_OPTION_VARIABLES" ] && return 1 145 146 for __VAR in $__ALL_OPTION_VARIABLES 147 do 148 config_get "$__VAR" "$__SECTIONID" "$__VAR" 149 done 150 return 0 151 } 152 153 # read's all service sections from ddns config 154 # $1 = Name of variable to store 155 load_all_service_sections() { 156 local __DATA="" 157 config_cb() 158 { 159 # only look for section type "service", ignore everything else 160 [ "$1" = "service" ] && __DATA="$__DATA $2" 161 } 162 config_load "ddns" 163 164 eval "$1=\"$__DATA\"" 165 return 166 } 167 168 # starts updater script for all given sections or only for the one given 169 # $1 = interface (Optional: when given only scripts are started 170 # configured for that interface) 171 # used by /etc/hotplug.d/iface/95-ddns on IFUP 172 # and by /etc/init.d/ddns start 173 start_daemon_for_all_ddns_sections() 174 { 175 local __EVENTIF="$1" 176 local __SECTIONS="" 177 local __SECTIONID="" 178 local __IFACE="" 179 180 load_all_service_sections __SECTIONS 181 for __SECTIONID in $__SECTIONS; do 182 config_get __IFACE "$__SECTIONID" interface "wan" 183 [ -z "$__EVENTIF" -o "$__IFACE" = "$__EVENTIF" ] || continue 184 if [ $VERBOSE -eq 0 ]; then # start in background 185 /usr/lib/ddns/dynamic_dns_updater.sh -v 0 -S "$__SECTIONID" -- start & 186 else 187 /usr/lib/ddns/dynamic_dns_updater.sh -v "$VERBOSE" -S "$__SECTIONID" -- start 188 fi 189 done 190 } 191 192 # stop sections process incl. childs (sleeps) 193 # $1 = section 194 stop_section_processes() { 195 local __PID=0 196 local __PIDFILE="$ddns_rundir/$1.pid" 197 [ $# -ne 1 ] && write_log 12 "Error calling 'stop_section_processes()' - wrong number of parameters" 198 199 [ -e "$__PIDFILE" ] && { 200 __PID=$(cat $__PIDFILE) 201 ps | grep "^[\t ]*$__PID" >/dev/null 2>&1 && kill $__PID || __PID=0 # terminate it 202 } 203 [ $__PID -eq 0 ] # report if process was running 204 } 205 206 # stop updater script for all defines sections or only for one given 207 # $1 = interface (optional) 208 # used by /etc/hotplug.d/iface/95-ddns on 'ifdown' 209 # and by /etc/init.d/ddns stop 210 # needed because we also need to kill "sleep" child processes 211 stop_daemon_for_all_ddns_sections() { 212 local __EVENTIF="$1" 213 local __SECTIONS="" 214 local __SECTIONID="" 215 local __IFACE="" 216 217 load_all_service_sections __SECTIONS 218 for __SECTIONID in $__SECTIONS; do 219 config_get __IFACE "$__SECTIONID" interface "wan" 220 [ -z "$__EVENTIF" -o "$__IFACE" = "$__EVENTIF" ] || continue 221 stop_section_processes "$__SECTIONID" 222 done 223 } 224 225 # reports to console, logfile, syslog 226 # $1 loglevel 7 == Debug to 0 == EMERG 227 # value +10 will exit the scripts 228 # $2..n text to report 229 write_log() { 230 local __LEVEL __EXIT __CMD __MSG 231 local __TIME=$(date +%H%M%S) 232 [ $1 -ge 10 ] && { 233 __LEVEL=$(($1-10)) 234 __EXIT=1 235 } || { 236 __LEVEL=$1 237 __EXIT=0 238 } 239 shift # remove loglevel 240 [ $__EXIT -eq 0 ] && __MSG="$*" || __MSG="$* - TERMINATE" 241 case $__LEVEL in # create log message and command depending on loglevel 242 0) __CMD="logger -p user.emerg -t ddns-scripts[$$] $SECTION_ID: $__MSG" 243 __MSG=" $__TIME EMERG : $__MSG" ;; 244 1) __CMD="logger -p user.alert -t ddns-scripts[$$] $SECTION_ID: $__MSG" 245 __MSG=" $__TIME ALERT : $__MSG" ;; 246 2) __CMD="logger -p user.crit -t ddns-scripts[$$] $SECTION_ID: $__MSG" 247 __MSG=" $__TIME CRIT : $__MSG" ;; 248 3) __CMD="logger -p user.err -t ddns-scripts[$$] $SECTION_ID: $__MSG" 249 __MSG=" $__TIME ERROR : $__MSG" ;; 250 4) __CMD="logger -p user.warn -t ddns-scripts[$$] $SECTION_ID: $__MSG" 251 __MSG=" $__TIME WARN : $__MSG" ;; 252 5) __CMD="logger -p user.notice -t ddns-scripts[$$] $SECTION_ID: $__MSG" 253 __MSG=" $__TIME note : $__MSG" ;; 254 6) __CMD="logger -p user.info -t ddns-scripts[$$] $SECTION_ID: $__MSG" 255 __MSG=" $__TIME info : $__MSG" ;; 256 7) __MSG=" $__TIME : $__MSG";; 257 *) return;; 258 esac 259 260 # verbose echo 261 [ $VERBOSE -gt 0 -o $__EXIT -gt 0 ] && echo -e "$__MSG" 262 # write to logfile 263 if [ ${use_logfile:-1} -eq 1 -o $VERBOSE -gt 1 ]; then 264 echo -e "$__MSG" >> $LOGFILE 265 # VERBOSE > 1 then NO loop so NO truncate log to $ddns_loglines lines 266 [ $VERBOSE -gt 1 ] || sed -i -e :a -e '$q;N;'$ddns_loglines',$D;ba' $LOGFILE 267 fi 268 [ -n "$LUCI_HELPER" ] && return # nothing else todo when running LuCI helper script 269 [ $__LEVEL -eq 7 ] && return # no syslog for debug messages 270 __CMD=$(echo -e "$__CMD" | tr -d '\n' | tr '\t' ' ') # remove \n \t chars 271 [ $__EXIT -eq 1 ] && { 272 $__CMD # force syslog before exit 273 exit 1 274 } 275 [ $use_syslog -eq 0 ] && return 276 [ $((use_syslog + __LEVEL)) -le 7 ] && $__CMD 277 278 return 279 } 280 281 # replace all special chars to their %hex value 282 # used for USERNAME and PASSWORD in update_url 283 # unchanged: "-"(minus) "_"(underscore) "."(dot) "~"(tilde) 284 # to verify: "'"(single quote) '"'(double quote) # because shell delimiter 285 # "$"(Dollar) # because used as variable output 286 # tested with the following string stored via Luci Application as password / username 287 # A B!"#AA$1BB%&'()*+,-./:;<=>?@[\]^_`{|}~ without problems at Dollar or quotes 288 urlencode() { 289 # $1 Name of Variable to store encoded string to 290 # $2 string to encode 291 local __STR __LEN __CHAR __OUT 292 local __ENC="" 293 local __POS=1 294 295 [ $# -ne 2 ] && write_log 12 "Error calling 'urlencode()' - wrong number of parameters" 296 297 __STR="$2" # read string to encode 298 __LEN=${#__STR} # get string length 299 300 while [ $__POS -le $__LEN ]; do 301 # read one chat of the string 302 __CHAR=$(expr substr "$__STR" $__POS 1) 303 304 case "$__CHAR" in 305 [-_.~a-zA-Z0-9] ) 306 # standard char 307 __OUT="${__CHAR}" 308 ;; 309 * ) 310 # special char get %hex code 311 __OUT=$(printf '%%%02x' "'$__CHAR" ) 312 ;; 313 esac 314 __ENC="${__ENC}${__OUT}" # append to encoded string 315 __POS=$(( $__POS + 1 )) # increment position 316 done 317 318 eval "$1=\"$__ENC\"" # transfer back to variable 319 return 0 320 } 321 322 # extract url or script for given DDNS Provider from 323 # file /etc/ddns/services for IPv4 or from 324 # file /etc/ddns/services_ipv6 for IPv6 325 # $1 Name of Variable to store url to 326 # $2 Name of Variable to store script to 327 # $3 Name of Variable to store service answer to 328 get_service_data() { 329 [ $# -ne 3 ] && write_log 12 "Error calling 'get_service_data()' - wrong number of parameters" 330 331 __FILE="/etc/ddns/services" # IPv4 332 [ $use_ipv6 -ne 0 ] && __FILE="/etc/ddns/services_ipv6" # IPv6 333 334 # workaround with variables; pipe create subshell with no give back of variable content 335 mkfifo pipe_$$ 336 # only grep without # or whitespace at linestart | remove " 337 # grep -v -E "(^#|^[[:space:]]*$)" $__FILE | sed -e s/\"//g > pipe_$$ & 338 sed '/^#/d; /^[ \t]*$/d; s/\"//g' $__FILE > pipe_$$ & 339 340 while read __SERVICE __DATA __ANSWER; do 341 if [ "$__SERVICE" = "$service_name" ]; then 342 # check if URL or SCRIPT is given 343 __URL=$(echo "$__DATA" | grep "^http") 344 [ -z "$__URL" ] && __SCRIPT="/usr/lib/ddns/$__DATA" 345 346 eval "$1=\"$__URL\"" 347 eval "$2=\"$__SCRIPT\"" 348 eval "$3=\"$__ANSWER\"" 349 rm pipe_$$ 350 return 0 351 fi 352 done < pipe_$$ 353 rm pipe_$$ 354 355 eval "$1=\"\"" # no service match clear variables 356 eval "$2=\"\"" 357 eval "$3=\"\"" 358 return 1 359 } 360 361 # Calculate seconds from interval and unit 362 # $1 Name of Variable to store result in 363 # $2 Number and 364 # $3 Unit of time interval 365 get_seconds() { 366 [ $# -ne 3 ] && write_log 12 "Error calling 'get_seconds()' - wrong number of parameters" 367 case "$3" in 368 "days" ) eval "$1=$(( $2 * 86400 ))";; 369 "hours" ) eval "$1=$(( $2 * 3600 ))";; 370 "minutes" ) eval "$1=$(( $2 * 60 ))";; 371 * ) eval "$1=$2";; 372 esac 373 return 0 374 } 375 376 timeout() { 377 #.copied from http://www.ict.griffith.edu.au/anthony/software/timeout.sh 378 # only did the following changes 379 # - commented out "#!/bin/bash" and usage section 380 # - replace exit by return for usage as function 381 # - some reformatting 382 # 383 # timeout [-SIG] time [--] command args... 384 # 385 # Run the given command until completion, but kill it if it runs too long. 386 # Specifically designed to exit immediately (no sleep interval) and clean up 387 # nicely without messages or leaving any extra processes when finished. 388 # 389 # Example use 390 # timeout 5 countdown 391 # 392 # Based on notes in my "Shell Script Hints", section "Command Timeout" 393 # http://www.ict.griffith.edu.au/~anthony/info/shell/script.hints 394 # 395 # This script uses a lot of tricks to terminate both the background command, 396 # the timeout script, and even the sleep process. It also includes trap 397 # commands to prevent sub-shells reporting expected "Termination Errors". 398 # 399 # It took years of occasional trials, errors and testing to get a pure bash 400 # timeout command working as well as this does. 401 # 402 #.Anthony Thyssen 6 April 2011 403 # 404 # PROGNAME=$(type $0 | awk '{print $3}') # search for executable on path 405 # PROGDIR=$(dirname $PROGNAME) # extract directory of program 406 # PROGNAME=$(basename $PROGNAME) # base name of program 407 408 # output the script comments as docs 409 # Usage() { 410 # echo >&2 "$PROGNAME:" "$@" 411 # sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 3s/^/Usage: /; 2,$ p' "$PROGDIR/$PROGNAME" 412 # exit 10; 413 # } 414 415 SIG=-TERM 416 417 while [ $# -gt 0 ]; do 418 case "$1" in 419 --) 420 # forced end of user options 421 shift; 422 break ;; 423 # -\?|--help|--doc*) 424 # Usage ;; 425 [0-9]*) 426 TIMEOUT="$1" ;; 427 -*) 428 SIG="$1" ;; 429 *) 430 # unforced end of user options 431 break ;; 432 esac 433 shift # next option 434 done 435 436 # run main command in backgrounds and get its pid 437 "$@" & 438 command_pid=$! 439 440 # timeout sub-process abort countdown after ABORT seconds! also backgrounded 441 sleep_pid=0 442 ( 443 # cleanup sleep process 444 trap 'kill -TERM $sleep_pid; return 1' 1 2 3 15 445 # sleep timeout period in background 446 sleep $TIMEOUT & 447 sleep_pid=$! 448 wait $sleep_pid 449 # Abort the command 450 kill $SIG $command_pid >/dev/null 2>&1 451 return 1 452 ) & 453 timeout_pid=$! 454 455 # Wait for main command to finished or be timed out 456 wait $command_pid 457 status=$? 458 459 # Clean up timeout sub-shell - if it is still running! 460 kill $timeout_pid 2>/dev/null 461 wait $timeout_pid 2>/dev/null 462 463 # Uncomment to check if a LONG sleep still running (no sleep should be) 464 # sleep 1 465 # echo "-----------" 466 # /bin/ps j # uncomment to show if abort "sleep" is still sleeping 467 468 return $status 469 } 470 471 # verify given host and port is connectable 472 # $1 Host/IP to verify 473 # $2 Port to verify 474 verify_host_port() { 475 local __HOST=$1 476 local __PORT=$2 477 local __NC=$(which nc) 478 local __NCEXT=$($(which nc) --help 2>&1 | grep "\-w" 2>/dev/null) # busybox nc compiled with extensions 479 local __IP __IPV4 __IPV6 __RUNPROG __PROG __ERR 480 # return codes 481 # 1 system specific error 482 # 2 nslookup/host error 483 # 3 nc (netcat) error 484 # 4 unmatched IP version 485 486 [ $# -ne 2 ] && write_log 12 "Error calling 'verify_host_port()' - wrong number of parameters" 487 488 # check if ip or FQDN was given 489 __IPV4=$(echo $__HOST | grep -m 1 -o "$IPV4_REGEX$") # do not detect ip in 0.0.0.0.example.com 490 __IPV6=$(echo $__HOST | grep -m 1 -o "$IPV6_REGEX") 491 # if FQDN given get IP address 492 [ -z "$__IPV4" -a -z "$__IPV6" ] && { 493 if [ -n "$BIND_HOST" ]; then # use BIND host if installed 494 __PROG="BIND host" 495 __RUNPROG="$BIND_HOST $__HOST >$DATFILE 2>$ERRFILE" 496 elif [ -n "$KNOT_HOST" ]; then # use Knot host if installed 497 __PROG="Knot host" 498 __RUNPROG="$KNOT_HOST $__HOST >$DATFILE 2>$ERRFILE" 499 elif [ -n "$DRILL" ]; then # use drill if installed 500 __PROG="drill" 501 __RUNPROG="$DRILL -V0 $__HOST A >$DATFILE 2>$ERRFILE" # IPv4 502 __RUNPROG="$__RUNPROG; $DRILL -V0 $__HOST AAAA >>$DATFILE 2>>$ERRFILE" # IPv6 503 elif [ -n "$HOSTIP" ]; then # use hostip if installed 504 __PROG="hostip" 505 __RUNPROG="$HOSTIP $__HOST >$DATFILE 2>$ERRFILE" # IPv4 506 __RUNPROG="$__RUNPROG; $HOSTIP -6 $__HOST >>$DATFILE 2>>$ERRFILE" # IPv6 507 else # use BusyBox nslookup 508 __PROG="BusyBox nslookup" 509 __RUNPROG="$NSLOOKUP $__HOST >$DATFILE 2>$ERRFILE" 510 fi 511 write_log 7 "#> $__RUNPROG" 512 eval $__RUNPROG 513 __ERR=$? 514 # command error 515 [ $__ERR -gt 0 ] && { 516 write_log 3 "DNS Resolver Error - $__PROG Error '$__ERR'" 517 write_log 7 "$(cat $ERRFILE)" 518 return 2 519 } 520 # extract IP address 521 if [ -n "$BIND_HOST" -o -n "$KNOT_HOST" ]; then # use BIND host or Knot host if installed 522 __IPV4=$(cat $DATFILE | awk -F "address " '/has address/ {print $2; exit}' ) 523 __IPV6=$(cat $DATFILE | awk -F "address " '/has IPv6/ {print $2; exit}' ) 524 elif [ -n "$DRILL" ]; then # use drill if installed 525 __IPV4=$(cat $DATFILE | awk '/^'"$lookup_host"'/ {print $5}' | grep -m 1 -o "$IPV4_REGEX") 526 __IPV6=$(cat $DATFILE | awk '/^'"$lookup_host"'/ {print $5}' | grep -m 1 -o "$IPV6_REGEX") 527 elif [ -n "$HOSTIP" ]; then # use hostip if installed 528 __IPV4=$(cat $DATFILE | grep -m 1 -o "$IPV4_REGEX") 529 __IPV6=$(cat $DATFILE | grep -m 1 -o "$IPV6_REGEX") 530 else # use BusyBox nslookup 531 __IPV4=$(cat $DATFILE | sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($IPV4_REGEX\).*$/\\1/p }") 532 __IPV6=$(cat $DATFILE | sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($IPV6_REGEX\).*$/\\1/p }") 533 fi 534 } 535 536 # check IP version if forced 537 if [ $force_ipversion -ne 0 ]; then 538 __ERR=0 539 [ $use_ipv6 -eq 0 -a -z "$__IPV4" ] && __ERR=4 540 [ $use_ipv6 -eq 1 -a -z "$__IPV6" ] && __ERR=6 541 [ $__ERR -gt 0 ] && { 542 [ -n "$LUCI_HELPER" ] && return 4 543 write_log 14 "Verify host Error '4' - Forced IP Version IPv$__ERR don't match" 544 } 545 fi 546 547 # verify nc command 548 # busybox nc compiled without -l option "NO OPT l!" -> critical error 549 $__NC --help 2>&1 | grep -i "NO OPT l!" >/dev/null 2>&1 && \ 550 write_log 12 "Busybox nc (netcat) compiled without '-l' option, error 'NO OPT l!'" 551 # busybox nc compiled with extensions 552 $__NC --help 2>&1 | grep "\-w" >/dev/null 2>&1 && __NCEXT="TRUE" 553 554 # connectivity test 555 # run busybox nc to HOST PORT 556 # busybox might be compiled with "FEATURE_PREFER_IPV4_ADDRESS=n" 557 # then nc will try to connect via IPv6 if there is any IPv6 available on any host interface 558 # not worrying, if there is an IPv6 wan address 559 # so if not "force_ipversion" to use_ipv6 then connect test via ipv4, if available 560 [ $force_ipversion -ne 0 -a $use_ipv6 -ne 0 -o -z "$__IPV4" ] && __IP=$__IPV6 || __IP=$__IPV4 561 562 if [ -n "$__NCEXT" ]; then # BusyBox nc compiled with extensions (timeout support) 563 __RUNPROG="$__NC -w 1 $__IP $__PORT </dev/null >$DATFILE 2>$ERRFILE" 564 write_log 7 "#> $__RUNPROG" 565 eval $__RUNPROG 566 __ERR=$? 567 [ $__ERR -eq 0 ] && return 0 568 write_log 3 "Connect error - BusyBox nc (netcat) Error '$__ERR'" 569 write_log 7 "$(cat $ERRFILE)" 570 return 3 571 else # nc compiled without extensions (no timeout support) 572 __RUNPROG="timeout 2 -- $__NC $__IP $__PORT </dev/null >$DATFILE 2>$ERRFILE" 573 write_log 7 "#> $__RUNPROG" 574 eval $__RUNPROG 575 __ERR=$? 576 [ $__ERR -eq 0 ] && return 0 577 write_log 3 "Connect error - BusyBox nc (netcat) timeout Error '$__ERR'" 578 return 3 579 fi 580 } 581 582 # verify given DNS server if connectable 583 # $1 DNS server to verify 584 verify_dns() { 585 local __ERR=255 # last error buffer 586 local __CNT=0 # error counter 587 588 [ $# -ne 1 ] && write_log 12 "Error calling 'verify_dns()' - wrong number of parameters" 589 write_log 7 "Verify DNS server '$1'" 590 591 while [ $__ERR -ne 0 ]; do 592 # DNS uses port 53 593 verify_host_port "$1" "53" 594 __ERR=$? 595 if [ -n "$LUCI_HELPER" ]; then # no retry if called by LuCI helper script 596 return $__ERR 597 elif [ $__ERR -ne 0 -a $VERBOSE -gt 1 ]; then # VERBOSE > 1 then NO retry 598 write_log 4 "Verify DNS server '$1' failed - Verbose Mode: $VERBOSE - NO retry on error" 599 return $__ERR 600 elif [ $__ERR -ne 0 ]; then 601 __CNT=$(( $__CNT + 1 )) # increment error counter 602 # if error count > retry_count leave here 603 [ $retry_count -gt 0 -a $__CNT -gt $retry_count ] && \ 604 write_log 14 "Verify DNS server '$1' failed after $retry_count retries" 605 606 write_log 4 "Verify DNS server '$1' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds" 607 sleep $RETRY_SECONDS & 608 PID_SLEEP=$! 609 wait $PID_SLEEP # enable trap-handler 610 PID_SLEEP=0 611 fi 612 done 613 return 0 614 } 615 616 # analyze and verify given proxy string 617 # $1 Proxy-String to verify 618 verify_proxy() { 619 # complete entry user:password@host:port 620 # inside user and password NO '@' of ":" allowed 621 # host and port only host:port 622 # host only host ERROR unsupported 623 # IPv4 address instead of host 123.234.234.123 624 # IPv6 address instead of host [xxxx:....:xxxx] in square bracket 625 local __TMP __HOST __PORT 626 local __ERR=255 # last error buffer 627 local __CNT=0 # error counter 628 629 [ $# -ne 1 ] && write_log 12 "Error calling 'verify_proxy()' - wrong number of parameters" 630 write_log 7 "Verify Proxy server 'http://$1'" 631 632 # try to split user:password "@" host:port 633 __TMP=$(echo $1 | awk -F "@" '{print $2}') 634 # no "@" found - only host:port is given 635 [ -z "$__TMP" ] && __TMP="$1" 636 # now lets check for IPv6 address 637 __HOST=$(echo $__TMP | grep -m 1 -o "$IPV6_REGEX") 638 # IPv6 host address found read port 639 if [ -n "$__HOST" ]; then 640 # IPv6 split at "]:" 641 __PORT=$(echo $__TMP | awk -F "]:" '{print $2}') 642 else 643 __HOST=$(echo $__TMP | awk -F ":" '{print $1}') 644 __PORT=$(echo $__TMP | awk -F ":" '{print $2}') 645 fi 646 # No Port detected - EXITING 647 [ -z "$__PORT" ] && { 648 [ -n "$LUCI_HELPER" ] && return 5 649 write_log 14 "Invalid Proxy server Error '5' - proxy port missing" 650 } 651 652 while [ $__ERR -gt 0 ]; do 653 verify_host_port "$__HOST" "$__PORT" 654 __ERR=$? 655 if [ -n "$LUCI_HELPER" ]; then # no retry if called by LuCI helper script 656 return $__ERR 657 elif [ $__ERR -gt 0 -a $VERBOSE -gt 1 ]; then # VERBOSE > 1 then NO retry 658 write_log 4 "Verify Proxy server '$1' failed - Verbose Mode: $VERBOSE - NO retry on error" 659 return $__ERR 660 elif [ $__ERR -gt 0 ]; then 661 __CNT=$(( $__CNT + 1 )) # increment error counter 662 # if error count > retry_count leave here 663 [ $retry_count -gt 0 -a $__CNT -gt $retry_count ] && \ 664 write_log 14 "Verify Proxy server '$1' failed after $retry_count retries" 665 666 write_log 4 "Verify Proxy server '$1' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds" 667 sleep $RETRY_SECONDS & 668 PID_SLEEP=$! 669 wait $PID_SLEEP # enable trap-handler 670 PID_SLEEP=0 671 fi 672 done 673 return 0 674 } 675 676 do_transfer() { 677 # $1 # URL to use 678 local __URL="$1" 679 local __ERR=0 680 local __CNT=0 # error counter 681 local __PROG __RUNPROG 682 683 [ $# -ne 1 ] && write_log 12 "Error in 'do_transfer()' - wrong number of parameters" 684 685 # lets prefer GNU Wget because it does all for us - IPv4/IPv6/HTTPS/PROXY/force IP version 686 if [ -n "$WGET_SSL" -a $USE_CURL -eq 0 ]; then # except global option use_curl is set to "1" 687 __PROG="$WGET_SSL -nv -t 1 -O $DATFILE -o $ERRFILE" # non_verbose no_retry outfile errfile 688 # force network/ip to use for communication 689 if [ -n "$bind_network" ]; then 690 local __BINDIP 691 # set correct program to detect IP 692 [ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" || __RUNPROG="network_get_ipaddr6" 693 eval "$__RUNPROG __BINDIP $bind_network" || \ 694 write_log 13 "Can not detect local IP using '$__RUNPROG $bind_network' - Error: '$?'" 695 write_log 7 "Force communication via IP '$__BINDIP'" 696 __PROG="$__PROG --bind-address=$__BINDIP" 697 fi 698 # force ip version to use 699 if [ $force_ipversion -eq 1 ]; then 700 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" # force IPv4/IPv6 701 fi 702 # set certificate parameters 703 if [ $use_https -eq 1 ]; then 704 if [ "$cacert" = "IGNORE" ]; then # idea from Ticket #15327 to ignore server cert 705 __PROG="$__PROG --no-check-certificate" 706 elif [ -f "$cacert" ]; then 707 __PROG="$__PROG --ca-certificate=${cacert}" 708 elif [ -d "$cacert" ]; then 709 __PROG="$__PROG --ca-directory=${cacert}" 710 elif [ -n "$cacert" ]; then # it's not a file and not a directory but given 711 write_log 14 "No valid certificate(s) found at '$cacert' for HTTPS communication" 712 fi 713 fi 714 # disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set) 715 [ -z "$proxy" ] && __PROG="$__PROG --no-proxy" 716 717 __RUNPROG="$__PROG '$__URL'" # build final command 718 __PROG="GNU Wget" # reuse for error logging 719 720 # 2nd choice is cURL IPv4/IPv6/HTTPS 721 # libcurl might be compiled without Proxy or HTTPS Support 722 elif [ -n "$CURL" ]; then 723 __PROG="$CURL -RsS -o $DATFILE --stderr $ERRFILE" 724 # check HTTPS support 725 [ -z "$CURL_SSL" -a $use_https -eq 1 ] && \ 726 write_log 13 "cURL: libcurl compiled without https support" 727 # force network/interface-device to use for communication 728 if [ -n "$bind_network" ]; then 729 local __DEVICE 730 network_get_physdev __DEVICE $bind_network || \ 731 write_log 13 "Can not detect local device using 'network_get_physdev $bind_network' - Error: '$?'" 732 write_log 7 "Force communication via device '$__DEVICE'" 733 __PROG="$__PROG --interface $__DEVICE" 734 fi 735 # force ip version to use 736 if [ $force_ipversion -eq 1 ]; then 737 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" # force IPv4/IPv6 738 fi 739 # set certificate parameters 740 if [ $use_https -eq 1 ]; then 741 if [ "$cacert" = "IGNORE" ]; then # idea from Ticket #15327 to ignore server cert 742 __PROG="$__PROG --insecure" # but not empty better to use "IGNORE" 743 elif [ -f "$cacert" ]; then 744 __PROG="$__PROG --cacert $cacert" 745 elif [ -d "$cacert" ]; then 746 __PROG="$__PROG --capath $cacert" 747 elif [ -n "$cacert" ]; then # it's not a file and not a directory but given 748 write_log 14 "No valid certificate(s) found at '$cacert' for HTTPS communication" 749 fi 750 fi 751 # disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set) 752 # or check if libcurl compiled with proxy support 753 if [ -z "$proxy" ]; then 754 __PROG="$__PROG --noproxy '*'" 755 elif [ -z "$CURL_PROXY" ]; then 756 # if libcurl has no proxy support and proxy should be used then force ERROR 757 write_log 13 "cURL: libcurl compiled without Proxy support" 758 fi 759 760 __RUNPROG="$__PROG '$__URL'" # build final command 761 __PROG="cURL" # reuse for error logging 762 763 # uclient-fetch possibly with ssl support if /lib/libustream-ssl.so installed 764 elif [ -n "$UCLIENT_FETCH" ]; then 765 __PROG="$UCLIENT_FETCH -q -O $DATFILE" 766 # force network/ip not supported 767 [ -n "$__BINDIP" ] && \ 768 write_log 14 "uclient-fetch: FORCE binding to specific address not supported" 769 # force ip version to use 770 if [ $force_ipversion -eq 1 ]; then 771 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" # force IPv4/IPv6 772 fi 773 # https possibly not supported 774 [ $use_https -eq 1 -a -z "$UCLIENT_FETCH_SSL" ] && \ 775 write_log 14 "uclient-fetch: no HTTPS support! Additional install one of ustream-ssl packages" 776 # proxy support 777 [ -z "$proxy" ] && __PROG="$__PROG -Y off" || __PROG="$__PROG -Y on" 778 # https & certificates 779 if [ $use_https -eq 1 ]; then 780 if [ "$cacert" = "IGNORE" ]; then 781 __PROG="$__PROG --no-check-certificate" 782 elif [ -f "$cacert" ]; then 783 __PROG="$__PROG --ca-certificate=$cacert" 784 elif [ -n "$cacert" ]; then # it's not a file; nothing else supported 785 write_log 14 "No valid certificate file '$cacert' for HTTPS communication" 786 fi 787 fi 788 __RUNPROG="$__PROG '$__URL' 2>$ERRFILE" # build final command 789 __PROG="uclient-fetch" # reuse for error logging 790 791 # Busybox Wget or any other wget in search $PATH (did not support neither IPv6 nor HTTPS) 792 elif [ -n "$WGET" ]; then 793 __PROG="$WGET -q -O $DATFILE" 794 # force network/ip not supported 795 [ -n "$__BINDIP" ] && \ 796 write_log 14 "BusyBox Wget: FORCE binding to specific address not supported" 797 # force ip version not supported 798 [ $force_ipversion -eq 1 ] && \ 799 write_log 14 "BusyBox Wget: Force connecting to IPv4 or IPv6 addresses not supported" 800 # https not supported 801 [ $use_https -eq 1 ] && \ 802 write_log 14 "BusyBox Wget: no HTTPS support" 803 # disable proxy if no set (there might be .wgetrc or .curlrc or wrong environment set) 804 [ -z "$proxy" ] && __PROG="$__PROG -Y off" 805 806 __RUNPROG="$__PROG '$__URL' 2>$ERRFILE" # build final command 807 __PROG="Busybox Wget" # reuse for error logging 808 809 else 810 write_log 13 "Neither 'Wget' nor 'cURL' nor 'uclient-fetch' installed or executable" 811 fi 812 813 while : ; do 814 write_log 7 "#> $__RUNPROG" 815 eval $__RUNPROG # DO transfer 816 __ERR=$? # save error code 817 [ $__ERR -eq 0 ] && return 0 # no error leave 818 [ -n "$LUCI_HELPER" ] && return 1 # no retry if called by LuCI helper script 819 820 write_log 3 "$__PROG Error: '$__ERR'" 821 write_log 7 "$(cat $ERRFILE)" # report error 822 823 [ $VERBOSE -gt 1 ] && { 824 # VERBOSE > 1 then NO retry 825 write_log 4 "Transfer failed - Verbose Mode: $VERBOSE - NO retry on error" 826 return 1 827 } 828 829 __CNT=$(( $__CNT + 1 )) # increment error counter 830 # if error count > retry_count leave here 831 [ $retry_count -gt 0 -a $__CNT -gt $retry_count ] && \ 832 write_log 14 "Transfer failed after $retry_count retries" 833 834 write_log 4 "Transfer failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds" 835 sleep $RETRY_SECONDS & 836 PID_SLEEP=$! 837 wait $PID_SLEEP # enable trap-handler 838 PID_SLEEP=0 839 done 840 # we should never come here there must be a programming error 841 write_log 12 "Error in 'do_transfer()' - program coding error" 842 } 843 844 send_update() { 845 # $1 # IP to set at DDNS service provider 846 local __IP 847 848 [ $# -ne 1 ] && write_log 12 "Error calling 'send_update()' - wrong number of parameters" 849 850 if [ $upd_privateip -eq 0 ]; then 851 # verify given IP / no private IPv4's / no IPv6 addr starting with fxxx of with ":" 852 [ $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)") 853 [ $use_ipv6 -eq 1 ] && __IP=$(echo $1 | grep "^[0-9a-eA-E]") 854 else 855 __IP=$(echo $1 | grep -m 1 -o "$IPV4_REGEX") # valid IPv4 or 856 [ -z "$__IP" ] && __IP=$(echo $1 | grep -m 1 -o "$IPV6_REGEX") # IPv6 857 fi 858 [ -z "$__IP" ] && { 859 write_log 3 "No or private or invalid IP '$1' given! Please check your configuration" 860 return 127 861 } 862 863 if [ -n "$update_script" ]; then 864 write_log 7 "parsing script '$update_script'" 865 . $update_script 866 else 867 local __URL __ERR 868 869 # do replaces in URL 870 __URL=$(echo $update_url | sed -e "s#\[USERNAME\]#$URL_USER#g" -e "s#\[PASSWORD\]#$URL_PASS#g" \ 871 -e "s#\[PARAMENC\]#$URL_PENC#g" -e "s#\[PARAMOPT\]#$param_opt#g" \ 872 -e "s#\[DOMAIN\]#$domain#g" -e "s#\[IP\]#$__IP#g") 873 [ $use_https -ne 0 ] && __URL=$(echo $__URL | sed -e 's#^http:#https:#') 874 875 do_transfer "$__URL" || return 1 876 877 write_log 7 "DDNS Provider answered:\n$(cat $DATFILE)" 878 879 [ -z "$UPD_ANSWER" ] && return 0 # not set then ignore 880 881 grep -i -E "$UPD_ANSWER" $DATFILE >/dev/null 2>&1 882 return $? # "0" if found 883 fi 884 } 885 886 get_local_ip () { 887 # $1 Name of Variable to store local IP (LOCAL_IP) 888 local __CNT=0 # error counter 889 local __RUNPROG __DATA __URL __ERR 890 891 [ $# -ne 1 ] && write_log 12 "Error calling 'get_local_ip()' - wrong number of parameters" 892 write_log 7 "Detect local IP on '$ip_source'" 893 894 while : ; do 895 if [ -n "$ip_network" ]; then 896 # set correct program 897 [ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" \ 898 || __RUNPROG="network_get_ipaddr6" 899 eval "$__RUNPROG __DATA $ip_network" || \ 900 write_log 13 "Can not detect local IP using $__RUNPROG '$ip_network' - Error: '$?'" 901 [ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected on network '$ip_network'" 902 elif [ -n "$ip_interface" ]; then 903 local __DATA4=""; local __DATA6="" 904 if [ -n "$(which ip)" ]; then # ip program installed 905 write_log 7 "#> ip -o addr show dev $ip_interface scope global >$DATFILE 2>$ERRFILE" 906 ip -o addr show dev $ip_interface scope global >$DATFILE 2>$ERRFILE 907 __ERR=$? 908 if [ $__ERR -eq 0 ]; then 909 # DATFILE (sample) 910 # 10: l2tp-inet: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1456 qdisc fq_codel state UNKNOWN qlen 3\ link/ppp 911 # 10: l2tp-inet inet 95.30.176.51 peer 95.30.176.1/32 scope global l2tp-inet\ valid_lft forever preferred_lft forever 912 # 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 913 # 5: eth1 inet 172.27.10.128/24 brd 172.27.10.255 scope global eth1\ valid_lft forever preferred_lft forever 914 # 5: eth1 inet 172.55.55.155/24 brd 172.27.10.255 scope global eth1\ valid_lft 12345sec preferred_lft 12345sec 915 # 5: eth1 inet6 2002:b0c7:f326::806b:c629:b8b9:433/128 scope global dynamic \ valid_lft 8026sec preferred_lft 8026sec 916 # 5: eth1 inet6 fd43:5368:6f6d:6500:806b:c629:b8b9:433/128 scope global dynamic \ valid_lft 8026sec preferred_lft 8026sec 917 # 5: eth1 inet6 fd43:5368:6f6d:6500:a00:27ff:fed0:1032/64 scope global dynamic \ valid_lft 14352sec preferred_lft 14352sec 918 # 5: eth1 inet6 2002:b0c7:f326::a00:27ff:fed0:1032/64 scope global dynamic \ valid_lft 14352sec preferred_lft 14352sec 919 920 # remove remove remove replace replace 921 # link inet6 fxxx sec forever=>-1 / => ' ' to separate subnet from ip 922 sed "/link/d; /inet6 f/d; s/sec//g; s/forever/-1/g; s/\// /g" $DATFILE | \ 923 awk '{ print $3" "$4" "$NF }' > $ERRFILE # temp reuse ERRFILE 924 # we only need inet? IP prefered time 925 926 local __TIME4=0; local __TIME6=0 927 local __TYP __ADR __TIME 928 while read __TYP __ADR __TIME; do 929 __TIME=${__TIME:-0} # supress shell errors on last (empty) line of DATFILE 930 # IPversion no "-1" record stored - now "-1" record or new time > oldtime 931 [ "$__TYP" = "inet6" -a $__TIME6 -ge 0 -a \( $__TIME -lt 0 -o $__TIME -gt $__TIME6 \) ] && { 932 __DATA6="$__ADR" 933 __TIME6="$__TIME" 934 } 935 [ "$__TYP" = "inet" -a $__TIME4 -ge 0 -a \( $__TIME -lt 0 -o $__TIME -gt $__TIME4 \) ] && { 936 __DATA4="$__ADR" 937 __TIME4="$__TIME" 938 } 939 done < $ERRFILE 940 else 941 write_log 3 "ip Error: '$__ERR'" 942 write_log 7 "$(cat $ERRFILE)" # report error 943 fi 944 else # use deprecated ifconfig 945 write_log 7 "#> ifconfig $ip_interface >$DATFILE 2>$ERRFILE" 946 ifconfig $ip_interface >$DATFILE 2>$ERRFILE 947 __ERR=$? 948 if [ $__ERR -eq 0 ]; then 949 __DATA4=$(awk ' 950 /inet addr:/ { # Filter IPv4 951 # inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0 952 $1=""; # remove inet 953 $3=""; # remove Bcast: ... 954 $4=""; # remove Mask: ... 955 FS=":"; # separator ":" 956 $0=$0; # reread to activate separator 957 $1=""; # remove addr 958 FS=" "; # set back separator to default " " 959 $0=$0; # reread to activate separator (remove whitespaces) 960 print $1; # print IPv4 addr 961 }' $DATFILE 962 ) 963 __DATA6=$(awk ' 964 /inet6/ && /: [0-9a-eA-E]/ { # Filter IPv6 exclude fxxx 965 # inet6 addr: 2001:db8::xxxx:xxxx/32 Scope:Global 966 FS="/"; # separator "/" 967 $0=$0; # reread to activate separator 968 $2=""; # remove everything behind "/" 969 FS=" "; # set back separator to default " " 970 $0=$0; # reread to activate separator 971 print $3; # print IPv6 addr 972 }' $DATFILE 973 ) 974 else 975 write_log 3 "ifconfig Error: '$__ERR'" 976 write_log 7 "$(cat $ERRFILE)" # report error 977 fi 978 fi 979 [ $use_ipv6 -eq 0 ] && __DATA="$__DATA4" || __DATA="$__DATA6" 980 [ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected on interface '$ip_interface'" 981 elif [ -n "$ip_script" ]; then 982 write_log 7 "#> $ip_script >$DATFILE 2>$ERRFILE" 983 eval $ip_script >$DATFILE 2>$ERRFILE 984 __ERR=$? 985 if [ $__ERR -eq 0 ]; then 986 __DATA=$(cat $DATFILE) 987 [ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected via script '$ip_script'" 988 else 989 write_log 3 "$ip_script Error: '$__ERR'" 990 write_log 7 "$(cat $ERRFILE)" # report error 991 fi 992 elif [ -n "$ip_url" ]; then 993 do_transfer "$ip_url" 994 # use correct regular expression 995 [ $use_ipv6 -eq 0 ] \ 996 && __DATA=$(grep -m 1 -o "$IPV4_REGEX" $DATFILE) \ 997 || __DATA=$(grep -m 1 -o "$IPV6_REGEX" $DATFILE) 998 [ -n "$__DATA" ] && write_log 7 "Local IP '$__DATA' detected on web at '$ip_url'" 999 else 1000 write_log 12 "Error in 'get_local_ip()' - unhandled ip_source '$ip_source'" 1001 fi 1002 # valid data found return here 1003 [ -n "$__DATA" ] && { 1004 eval "$1=\"$__DATA\"" 1005 return 0 1006 } 1007 1008 [ -n "$LUCI_HELPER" ] && return 1 # no retry if called by LuCI helper script 1009 1010 write_log 7 "Data detected:" 1011 write_log 7 "$(cat $DATFILE)" 1012 1013 [ $VERBOSE -gt 1 ] && { 1014 # VERBOSE > 1 then NO retry 1015 write_log 4 "Get local IP via '$ip_source' failed - Verbose Mode: $VERBOSE - NO retry on error" 1016 return 1 1017 } 1018 1019 __CNT=$(( $__CNT + 1 )) # increment error counter 1020 # if error count > retry_count leave here 1021 [ $retry_count -gt 0 -a $__CNT -gt $retry_count ] && \ 1022 write_log 14 "Get local IP via '$ip_source' failed after $retry_count retries" 1023 write_log 4 "Get local IP via '$ip_source' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds" 1024 sleep $RETRY_SECONDS & 1025 PID_SLEEP=$! 1026 wait $PID_SLEEP # enable trap-handler 1027 PID_SLEEP=0 1028 done 1029 # we should never come here there must be a programming error 1030 write_log 12 "Error in 'get_local_ip()' - program coding error" 1031 } 1032 1033 get_registered_ip() { 1034 # $1 Name of Variable to store public IP (REGISTERED_IP) 1035 # $2 (optional) if set, do not retry on error 1036 local __CNT=0 # error counter 1037 local __ERR=255 1038 local __REGEX __PROG __RUNPROG __DATA __IP 1039 # return codes 1040 # 1 no IP detected 1041 1042 [ $# -lt 1 -o $# -gt 2 ] && write_log 12 "Error calling 'get_registered_ip()' - wrong number of parameters" 1043 [ $is_glue -eq 1 -a -z "$BIND_HOST" ] && write_log 14 "Lookup of glue records is only supported using BIND host" 1044 write_log 7 "Detect registered/public IP" 1045 1046 # set correct regular expression 1047 [ $use_ipv6 -eq 0 ] && __REGEX="$IPV4_REGEX" || __REGEX="$IPV6_REGEX" 1048 1049 if [ -n "$BIND_HOST" ]; then 1050 __PROG="$BIND_HOST" 1051 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -t A" || __PROG="$__PROG -t AAAA" 1052 if [ $force_ipversion -eq 1 ]; then # force IP version 1053 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" 1054 fi 1055 [ $force_dnstcp -eq 1 ] && __PROG="$__PROG -T" # force TCP 1056 [ $is_glue -eq 1 ] && __PROG="$__PROG -v" # use verbose output to get additional section 1057 1058 __RUNPROG="$__PROG $lookup_host $dns_server >$DATFILE 2>$ERRFILE" 1059 __PROG="BIND host" 1060 elif [ -n "$KNOT_HOST" ]; then 1061 __PROG="$KNOT_HOST" 1062 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -t A" || __PROG="$__PROG -t AAAA" 1063 if [ $force_ipversion -eq 1 ]; then # force IP version 1064 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" 1065 fi 1066 [ $force_dnstcp -eq 1 ] && __PROG="$__PROG -T" # force TCP 1067 1068 __RUNPROG="$__PROG $lookup_host $dns_server >$DATFILE 2>$ERRFILE" 1069 __PROG="Knot host" 1070 elif [ -n "$DRILL" ]; then 1071 __PROG="$DRILL -V0" # drill options name @server type 1072 if [ $force_ipversion -eq 1 ]; then # force IP version 1073 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" 1074 fi 1075 [ $force_dnstcp -eq 1 ] && __PROG="$__PROG -t" || __PROG="$__PROG -u" # force TCP 1076 __PROG="$__PROG $lookup_host" 1077 [ -n "$dns_server" ] && __PROG="$__PROG @$dns_server" 1078 [ $use_ipv6 -eq 0 ] && __PROG="$__PROG A" || __PROG="$__PROG AAAA" 1079 1080 __RUNPROG="$__PROG >$DATFILE 2>$ERRFILE" 1081 __PROG="drill" 1082 elif [ -n "$HOSTIP" ]; then # hostip package installed 1083 __PROG="$HOSTIP" 1084 [ $force_dnstcp -ne 0 ] && \ 1085 write_log 14 "hostip - no support for 'DNS over TCP'" 1086 1087 # is IP given as dns_server ? 1088 __IP=$(echo $dns_server | grep -m 1 -o "$IPV4_REGEX") 1089 [ -z "$__IP" ] && __IP=$(echo $dns_server | grep -m 1 -o "$IPV6_REGEX") 1090 1091 # we got NO ip for dns_server, so build command 1092 [ -z "$__IP" -a -n "$dns_server" ] && { 1093 __IP="\`$HOSTIP" 1094 [ $use_ipv6 -eq 1 -a $force_ipversion -eq 1 ] && __IP="$__IP -6" 1095 __IP="$__IP $dns_server | grep -m 1 -o" 1096 [ $use_ipv6 -eq 1 -a $force_ipversion -eq 1 ] \ 1097 && __IP="$__IP '$IPV6_REGEX'" \ 1098 || __IP="$__IP '$IPV4_REGEX'" 1099 __IP="$__IP \`" 1100 } 1101 1102 [ $use_ipv6 -eq 1 ] && __PROG="$__PROG -6" 1103 [ -n "$dns_server" ] && __PROG="$__PROG -r $__IP" 1104 __RUNPROG="$__PROG $lookup_host >$DATFILE 2>$ERRFILE" 1105 __PROG="hostip" 1106 elif [ -n "$NSLOOKUP" ]; then # last use BusyBox nslookup 1107 [ $force_dnstcp -ne 0 ] && \ 1108 write_log 14 "Busybox nslookup - no support for 'DNS over TCP'" 1109 [ -n "$NSLOOKUP_MUSL" -a -n "$dns_server" ] && \ 1110 write_log 14 "Busybox compiled with musl - nslookup don't support the use of DNS Server" 1111 [ $force_ipversion -ne 0 ] && \ 1112 write_log 5 "Busybox nslookup - no support to 'force IP Version' (ignored)" 1113 1114 __RUNPROG="$NSLOOKUP $lookup_host $dns_server >$DATFILE 2>$ERRFILE" 1115 __PROG="BusyBox nslookup" 1116 else # there must be an error 1117 write_log 12 "Error in 'get_registered_ip()' - no supported Name Server lookup software accessible" 1118 fi 1119 1120 while : ; do 1121 write_log 7 "#> $__RUNPROG" 1122 eval $__RUNPROG 1123 __ERR=$? 1124 if [ $__ERR -ne 0 ]; then 1125 write_log 3 "$__PROG error: '$__ERR'" 1126 write_log 7 "$(cat $ERRFILE)" 1127 else 1128 if [ -n "$BIND_HOST" -o -n "$KNOT_HOST" ]; then 1129 if [ $is_glue -eq 1 ]; then 1130 __DATA=$(cat $DATFILE | grep "^$lookup_host" | grep -om1 "$__REGEX" ) 1131 else 1132 __DATA=$(cat $DATFILE | awk -F "address " '/has/ {print $2; exit}' ) 1133 fi 1134 elif [ -n "$DRILL" ]; then 1135 __DATA=$(cat $DATFILE | awk '/^'"$lookup_host"'/ {print $5; exit}' ) 1136 elif [ -n "$HOSTIP" ]; then 1137 __DATA=$(cat $DATFILE | grep -om1 "$__REGEX") 1138 elif [ -n "$NSLOOKUP" ]; then 1139 __DATA=$(cat $DATFILE | sed -ne "/^Name:/,\$ { s/^Address[0-9 ]\{0,\}: \($__REGEX\).*$/\\1/p }" ) 1140 fi 1141 [ -n "$__DATA" ] && { 1142 write_log 7 "Registered IP '$__DATA' detected" 1143 eval "$1=\"$__DATA\"" # valid data found 1144 return 0 # leave here 1145 } 1146 write_log 4 "NO valid IP found" 1147 __ERR=127 1148 fi 1149 1150 [ -n "$LUCI_HELPER" ] && return $__ERR # no retry if called by LuCI helper script 1151 [ -n "$2" ] && return $__ERR # $2 is given -> no retry 1152 [ $VERBOSE -gt 1 ] && { 1153 # VERBOSE > 1 then NO retry 1154 write_log 4 "Get registered/public IP for '$lookup_host' failed - Verbose Mode: $VERBOSE - NO retry on error" 1155 return $__ERR 1156 } 1157 1158 __CNT=$(( $__CNT + 1 )) # increment error counter 1159 # if error count > retry_count leave here 1160 [ $retry_count -gt 0 -a $__CNT -gt $retry_count ] && \ 1161 write_log 14 "Get registered/public IP for '$lookup_host' failed after $retry_count retries" 1162 1163 write_log 4 "Get registered/public IP for '$lookup_host' failed - retry $__CNT/$retry_count in $RETRY_SECONDS seconds" 1164 sleep $RETRY_SECONDS & 1165 PID_SLEEP=$! 1166 wait $PID_SLEEP # enable trap-handler 1167 PID_SLEEP=0 1168 done 1169 # we should never come here there must be a programming error 1170 write_log 12 "Error in 'get_registered_ip()' - program coding error" 1171 } 1172 1173 get_uptime() { 1174 # $1 Variable to store result in 1175 [ $# -ne 1 ] && write_log 12 "Error calling 'verify_host_port()' - wrong number of parameters" 1176 local __UPTIME=$(cat /proc/uptime) 1177 eval "$1=\"${__UPTIME%%.*}\"" 1178 } 1179 1180 trap_handler() { 1181 # $1 trap signal 1182 # $2 optional (exit status) 1183 local __PIDS __PID 1184 local __ERR=${2:-0} 1185 local __OLD_IFS=$IFS 1186 local __NEWLINE_IFS=' 1187 ' # __NEWLINE_IFS 1188 1189 [ $PID_SLEEP -ne 0 ] && kill -$1 $PID_SLEEP 2>/dev/null # kill pending sleep if exist 1190 1191 case $1 in 1192 0) if [ $__ERR -eq 0 ]; then 1193 write_log 5 "PID '$$' exit normal at $(eval $DATE_PROG)\n" 1194 else 1195 write_log 4 "PID '$$' exit WITH ERROR '$__ERR' at $(eval $DATE_PROG)\n" 1196 fi ;; 1197 1) write_log 6 "PID '$$' received 'SIGHUP' at $(eval $DATE_PROG)" 1198 # reload config via starting the script again 1199 /usr/lib/ddns/dynamic_dns_updater.sh -v "0" -S "$__SECTIONID" -- start || true 1200 exit 0 ;; # and leave this one 1201 2) write_log 5 "PID '$$' terminated by 'SIGINT' at $(eval $DATE_PROG)\n";; 1202 3) write_log 5 "PID '$$' terminated by 'SIGQUIT' at $(eval $DATE_PROG)\n";; 1203 15) write_log 5 "PID '$$' terminated by 'SIGTERM' at $(eval $DATE_PROG)\n";; 1204 *) write_log 13 "Unhandled signal '$1' in 'trap_handler()'";; 1205 esac 1206 1207 __PIDS=$(pgrep -P $$) # get my childs (pgrep prints with "newline") 1208 IFS=$__NEWLINE_IFS 1209 for __PID in $__PIDS; do 1210 kill -$1 $__PID # terminate it 1211 done 1212 IFS=$__OLD_IFS 1213 1214 # remove out and err file 1215 [ -f $DATFILE ] && rm -f $DATFILE 1216 [ -f $ERRFILE ] && rm -f $ERRFILE 1217 1218 # exit with correct handling: 1219 # remove trap handling settings and send kill to myself 1220 trap - 0 1 2 3 15 1221 [ $1 -gt 0 ] && kill -$1 $$ 1222 } 1223 1224 split_FQDN() { 1225 # $1 FQDN to split 1226 # $2 name of variable to store TLD 1227 # $3 name of variable to store (reg)Domain 1228 # $4 name of variable to store Host/Subdomain 1229 1230 [ $# -ne 4 ] && write_log 12 "Error calling 'split_FQDN()' - wrong number of parameters" 1231 [ -z "$1" ] && write_log 12 "Error calling 'split_FQDN()' - missing FQDN to split" 1232 [ -f $TLDFILE ] || write_log 12 "Error calling 'split_FQDN()' - missing file '$TLDFILE'" 1233 1234 local _HOST _FDOM _CTLD _FTLD 1235 local _SET="$@" # save given function parameters 1236 1237 local _PAR=$(echo "$1" | tr [A-Z] [a-z] | tr "." " ") # to lower and replace DOT with SPACE 1238 set -- $_PAR # set new as function parameters 1239 _PAR="" # clear variable for later reuse 1240 while [ -n "$1" ] ; do # as long we have parameters 1241 _PAR="$1 $_PAR" # invert order of parameters 1242 shift 1243 done 1244 set -- $_PAR # use new as function parameters 1245 _PAR="" # clear variable 1246 1247 while [ -n "$1" ] ; do # as long we have parameters 1248 if [ -z "$_CTLD" ]; then # first loop 1249 _CTLD="$1" # CURRENT TLD to look at 1250 shift 1251 else 1252 _CTLD="$1.$_CTLD" # Next TLD to look at 1253 shift 1254 fi 1255 # check if TLD exact match in tld_names.dat, save TLD 1256 zcat $TLDFILE | grep -E "^$_CTLD$" >/dev/null 2>&1 && { 1257 _FTLD="$_CTLD" # save found 1258 _FDOM="$1" # save domain next step might be invalid 1259 continue 1260 } 1261 # check if match any "*" in tld_names.dat, 1262 zcat $TLDFILE | grep -E "^\*.$_CTLD$" >/dev/null 2>&1 && { 1263 [ -z "$1" ] && break # no more data break 1264 # check if next level TLD match excludes "!" in tld_names.dat 1265 if zcat $TLDFILE | grep -E "^!$1.$_CTLD$" >/dev/null 2>&1 ; then 1266 _FTLD="$_CTLD" # Yes 1267 else 1268 _FTLD="$1.$_CTLD" 1269 shift 1270 fi 1271 _FDOM="$1"; shift 1272 } 1273 [ -n "$_FTLD" ] && break # we have something valid, break 1274 done 1275 1276 # the leftover parameters are the HOST/SUBDOMAIN 1277 while [ -n "$1" ]; do 1278 _HOST="$1 $_HOST" # remember we need to invert 1279 shift 1280 done 1281 _HOST=$(echo $_HOST | tr " " ".") # insert DOT 1282 1283 set -- $_SET # set back parameters from function call 1284 [ -n "$_FTLD" ] && { 1285 eval "$2=$_FTLD" # set TLD 1286 eval "$3=$_FDOM" # set registrable domain 1287 eval "$4=$_HOST" # set HOST/SUBDOMAIN 1288 return 0 1289 } 1290 eval "$2=''" # clear TLD 1291 eval "$3=''" # clear registrable domain 1292 eval "$4=''" # clear HOST/SUBDOMAIN 1293 return 1 1294 } 1295 1296 expand_ipv6() { 1297 # Original written for bash by 1298 #.Author: Florian Streibelt <florian@f-streibelt.de> 1299 # Date: 08.04.2012 1300 # License: Public Domain, but please be fair and 1301 # attribute the original author(s) and provide 1302 # a link to the original source for corrections: 1303 #. https://github.com/mutax/IPv6-Address-checks 1304 1305 # $1 IPv6 to expand 1306 # $2 name of variable to store expanded IPv6 1307 [ $# -ne 2 ] && write_log 12 "Error calling 'expand_ipv6()' - wrong number of parameters" 1308 1309 INPUT="$(echo "$1" | tr 'A-F' 'a-f')" 1310 [ "$INPUT" = "::" ] && INPUT="::0" # special case :: 1311 1312 O="" 1313 1314 while [ "$O" != "$INPUT" ]; do 1315 O="$INPUT" 1316 1317 # fill all words with zeroes 1318 INPUT=$( echo "$INPUT" | sed -e 's|:\([0-9a-f]\{3\}\):|:0\1:|g' \ 1319 -e 's|:\([0-9a-f]\{3\}\)$|:0\1|g' \ 1320 -e 's|^\([0-9a-f]\{3\}\):|0\1:|g' \ 1321 -e 's|:\([0-9a-f]\{2\}\):|:00\1:|g' \ 1322 -e 's|:\([0-9a-f]\{2\}\)$|:00\1|g' \ 1323 -e 's|^\([0-9a-f]\{2\}\):|00\1:|g' \ 1324 -e 's|:\([0-9a-f]\):|:000\1:|g' \ 1325 -e 's|:\([0-9a-f]\)$|:000\1|g' \ 1326 -e 's|^\([0-9a-f]\):|000\1:|g' ) 1327 1328 done 1329 1330 # now expand the :: 1331 ZEROES="" 1332 1333 echo "$INPUT" | grep -qs "::" 1334 if [ "$?" -eq 0 ]; then 1335 GRPS="$( echo "$INPUT" | sed 's|[0-9a-f]||g' | wc -m )" 1336 GRPS=$(( GRPS-1 )) # remove carriage return 1337 MISSING=$(( 8-GRPS )) 1338 while [ $MISSING -gt 0 ]; do 1339 ZEROES="$ZEROES:0000" 1340 MISSING=$(( MISSING-1 )) 1341 done 1342 1343 # be careful where to place the : 1344 INPUT=$( echo "$INPUT" | sed -e 's|\(.\)::\(.\)|\1'$ZEROES':\2|g' \ 1345 -e 's|\(.\)::$|\1'$ZEROES':0000|g' \ 1346 -e 's|^::\(.\)|'$ZEROES':0000:\1|g;s|^:||g' ) 1347 fi 1348 1349 # an expanded address has 39 chars + CR 1350 if [ $(echo $INPUT | wc -m) != 40 ]; then 1351 write_log 4 "Error in 'expand_ipv6()' - invalid IPv6 found: '$1' expanded: '$INPUT'" 1352 eval "$2='invalid'" 1353 return 1 1354 fi 1355 1356 # echo the fully expanded version of the address 1357 eval "$2=$INPUT" 1358 return 0 1359 }