dynamic_dns_updater.sh (18049B)
1 #!/bin/sh 2 # /usr/lib/ddns/dynamic_dns_updater.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 # variables in small chars are read from /etc/config/ddns 12 # variables in big chars are defined inside these scripts as global vars 13 # variables in big chars beginning with "__" are local defined inside functions only 14 # set -vx #script debugger 15 16 . $(dirname $0)/dynamic_dns_functions.sh # global vars are also defined here 17 18 usage() { 19 cat << EOF 20 21 Usage: 22 $MYPROG [options] -- command 23 24 Commands: 25 start Start SECTION or NETWORK or all 26 stop Stop NETWORK or all 27 28 Parameters: 29 -n NETWORK Start/Stop sections in background monitoring NETWORK, force VERBOSE=0 30 -S SECTION SECTION to start 31 use either -N NETWORK or -S SECTION 32 33 -h show this help and exit 34 -V show version and exit 35 -v LEVEL VERBOSE=LEVEL (default 1) 36 '0' NO output to console 37 '1' output to console 38 '2' output to console AND logfile 39 + run once WITHOUT retry on error 40 '3' output to console AND logfile 41 + run once WITHOUT retry on error 42 + NOT sending update to DDNS service 43 44 EOF 45 } 46 47 usage_err() { 48 printf %s\\n "$MYPROG: $@" >&2 49 usage >&2 50 exit 1 51 } 52 53 while getopts ":hv:n:S:V" OPT; do 54 case "$OPT" in 55 h) usage; exit 0;; 56 v) VERBOSE=$OPTARG;; 57 n) NETWORK=$OPTARG;; 58 S) SECTION_ID=$OPTARG;; 59 V) printf %s\\n "ddns-scripts $VERSION"; exit 0;; 60 :) usage_err "option -$OPTARG missing argument";; 61 \?) usage_err "invalid option -$OPTARG";; 62 *) usage_err "unhandled option -$OPT $OPTARG";; 63 esac 64 done 65 shift $((OPTIND - 1 )) # OPTIND is 1 based 66 67 [ -n "$NETWORK" -a -n "$SECTION_ID" ] && usage_err "use either option '-N' or '-S' not both" 68 [ $# -eq 0 ] && usage_err "missing command" 69 [ $# -gt 1 ] && usage_err "to much commands" 70 71 case "$1" in 72 start) 73 if [ -n "$NETWORK" ]; then 74 start_daemon_for_all_ddns_sections "$NETWORK" 75 exit 0 76 fi 77 if [ -z "$SECTION_ID" ]; then 78 start_daemon_for_all_ddns_sections 79 exit 0 80 fi 81 ;; 82 stop) 83 if [ -n "$INTERFACE" ]; then 84 stop_daemon_for_all_ddns_sections "$NETWORK" 85 exit 0 86 else 87 stop_daemon_for_all_ddns_sections 88 exit 0 89 fi 90 exit 1 91 ;; 92 reload) 93 killall -1 dynamic_dns_updater.sh 2>/dev/null 94 exit $? 95 ;; 96 *) usage_err "unknown command - $1";; 97 esac 98 99 # set file names 100 PIDFILE="$ddns_rundir/$SECTION_ID.pid" # Process ID file 101 UPDFILE="$ddns_rundir/$SECTION_ID.update" # last update successful send (system uptime) 102 DATFILE="$ddns_rundir/$SECTION_ID.dat" # save stdout data of WGet and other extern programs called 103 ERRFILE="$ddns_rundir/$SECTION_ID.err" # save stderr output of WGet and other extern programs called 104 LOGFILE="$ddns_logdir/$SECTION_ID.log" # log file 105 106 # VERBOSE > 1 delete logfile if exist to create an empty one 107 # only with this data of this run for easier diagnostic 108 # new one created by write_log function 109 [ $VERBOSE -gt 1 -a -f $LOGFILE ] && rm -f $LOGFILE 110 111 # TRAP handler 112 trap "trap_handler 0 \$?" 0 # handle script exit with exit status 113 trap "trap_handler 1" 1 # SIGHUP Hangup / reload config 114 trap "trap_handler 2" 2 # SIGINT Terminal interrupt 115 trap "trap_handler 3" 3 # SIGQUIT Terminal quit 116 # trap "trap_handler 9" 9 # SIGKILL no chance to trap 117 trap "trap_handler 15" 15 # SIGTERM Termination 118 119 ################################################################################ 120 # Leave this comment here, to clearly document variable names that are expected/possible 121 # Use load_all_config_options to load config options, which is a much more flexible solution. 122 # 123 # config_load "ddns" 124 # config_get <variable> $SECTION_ID <option> 125 # 126 # defined options (also used as variable): 127 # 128 # enabled self-explanatory 129 # interface network interface used by hotplug.d i.e. 'wan' or 'wan6' 130 # 131 # service_name Which DDNS service do you use or "custom" 132 # update_url URL to use to update your "custom" DDNS service 133 # update_script SCRIPT to use to update your "custom" DDNS service 134 # 135 # lookup_host FQDN of ONE of your at DDNS service defined host / required to validate if IP update happen/necessary 136 # domain Nomally your DDNS hostname / replace [DOMAIN] in update_url 137 # username Username of your DDNS service account / urlenceded and replace [USERNAME] in update_url 138 # password Password of your DDNS service account / urlencoded and replace [PASSWORD] in update_url 139 # param_enc Optional parameter for (later) usage / urlencoded and replace [PARAMENC] in update_url 140 # param_opt Optional parameter for (later) usage / replace [PARAMOPT] in update_url 141 # 142 # use_https use HTTPS to update DDNS service 143 # cacert file or directory where HTTPS can find certificates to verify server; 'IGNORE' ignore check of server certificate 144 # 145 # use_syslog log activity to syslog 146 # 147 # ip_source source to detect current local IP ('network' or 'web' or 'script' or 'interface') 148 # ip_network local defined network to read IP from i.e. 'wan' or 'wan6' 149 # ip_url URL to read local address from i.e. http://checkip.dyndns.com/ or http://checkipv6.dyndns.com/ 150 # ip_script full path and name of your script to detect local IP 151 # ip_interface physical interface to use for detecting 152 # 153 # check_interval check for changes every !!! checks below 10 minutes make no sense because the Internet 154 # check_unit 'days' 'hours' 'minutes' !!! needs about 5-10 minutes to sync an IP-change for an DNS entry 155 # 156 # force_interval force to send an update to your service if no change was detected 157 # force_unit 'days' 'hours' 'minutes' !!! force_interval="0" runs this script once for use i.e. with cron 158 # 159 # retry_interval if error was detected retry in 160 # retry_unit 'days' 'hours' 'minutes' 'seconds' 161 # retry_count number of retries before scripts stops 162 # 163 # use_ipv6 detecting/sending IPv6 address 164 # force_ipversion force usage of IPv4 or IPv6 for the whole detection and update communication 165 # dns_server using a non default dns server to get Registered IP from Internet 166 # force_dnstcp force communication with DNS server via TCP instead of default UDP 167 # proxy using a proxy for communication !!! ALSO used to detect local IP via web => return proxy's IP !!! 168 # use_logfile self-explanatory "/var/log/ddns/$SECTION_ID.log" 169 # is_glue the record that should be updated is a glue record 170 # 171 # some functionality needs 172 # - GNU Wget or cURL installed for sending updates to DDNS service 173 # - BIND host installed to detect Registered IP 174 # 175 ################################################################################ 176 177 load_all_config_options "ddns" "$SECTION_ID" 178 ERR_LAST=$? # save return code - equal 0 if SECTION_ID found 179 180 # set defaults if not defined 181 [ -z "$enabled" ] && enabled=0 182 [ -z "$retry_count" ] && retry_count=0 # endless retry 183 [ -z "$use_syslog" ] && use_syslog=2 # syslog "Notice" 184 [ -z "$use_https" ] && use_https=0 # not use https 185 [ -z "$use_logfile" ] && use_logfile=1 # use logfile by default 186 [ -z "$use_ipv6" ] && use_ipv6=0 # use IPv4 by default 187 [ -z "$force_ipversion" ] && force_ipversion=0 # default let system decide 188 [ -z "$force_dnstcp" ] && force_dnstcp=0 # default UDP 189 [ -z "$ip_source" ] && ip_source="network" 190 [ -z "$is_glue" ] && is_glue=0 # default the ddns record is not a glue record 191 [ "$ip_source" = "network" -a -z "$ip_network" -a $use_ipv6 -eq 0 ] && ip_network="wan" # IPv4: default wan 192 [ "$ip_source" = "network" -a -z "$ip_network" -a $use_ipv6 -eq 1 ] && ip_network="wan6" # IPv6: default wan6 193 [ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 0 ] && ip_url="http://checkip.dyndns.com" 194 [ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 1 ] && ip_url="http://checkipv6.dyndns.com" 195 [ "$ip_source" = "interface" -a -z "$ip_interface" ] && ip_interface="eth1" 196 197 # SECTION_ID does not exists 198 [ $ERR_LAST -ne 0 ] && { 199 [ $VERBOSE -le 1 ] && VERBOSE=2 # force console out and logfile output 200 [ -f $LOGFILE ] && rm -f $LOGFILE # clear logfile before first entry 201 write_log 7 "************ ************** ************** **************" 202 write_log 5 "PID '$$' started at $(eval $DATE_PROG)" 203 write_log 7 "ddns version : $VERSION" 204 write_log 7 "uci configuration:\n$(uci -q show ddns | grep '=service' | sort)" 205 write_log 14 "Service section '$SECTION_ID' not defined" 206 } 207 208 write_log 7 "************ ************** ************** **************" 209 write_log 5 "PID '$$' started at $(eval $DATE_PROG)" 210 write_log 7 "ddns version : $VERSION" 211 write_log 7 "uci configuration:\n$(uci -q show ddns.$SECTION_ID | sort)" 212 # write_log 7 "ddns version : $(opkg list-installed ddns-scripts | cut -d ' ' -f 3)" 213 case $VERBOSE in 214 0) write_log 7 "verbose mode : 0 - run normal, NO console output";; 215 1) write_log 7 "verbose mode : 1 - run normal, console mode";; 216 2) write_log 7 "verbose mode : 2 - run once, NO retry on error";; 217 3) write_log 7 "verbose mode : 3 - run once, NO retry on error, NOT sending update";; 218 *) write_log 14 "error detecting VERBOSE '$VERBOSE'";; 219 esac 220 221 # check enabled state otherwise we don't need to continue 222 [ $enabled -eq 0 ] && write_log 14 "Service section disabled!" 223 224 # determine what update url we're using if a service_name is supplied 225 # otherwise update_url is set inside configuration (custom update url) 226 # or update_script is set inside configuration (custom update script) 227 [ -n "$service_name" ] && get_service_data update_url update_script UPD_ANSWER 228 [ -z "$update_url" -a -z "$update_script" ] && write_log 14 "No update_url found/defined or no update_script found/defined!" 229 [ -n "$update_script" -a ! -f "$update_script" ] && write_log 14 "Custom update_script not found!" 230 231 # temporary needed to convert existing uci settings 232 [ -z "$lookup_host" ] && { 233 uci -q set ddns.$SECTION_ID.lookup_host="$domain" 234 uci -q commit ddns 235 lookup_host="$domain" 236 } 237 # later versions only check if configured correctly 238 239 # without lookup host and possibly other required options we can do nothing for you 240 [ -z "$lookup_host" ] && write_log 14 "Service section not configured correctly! Missing 'lookup_host'" 241 242 [ -n "$update_url" ] && { 243 # only check if update_url is given, update_scripts have to check themselves 244 [ -z "$domain" ] && $(echo "$update_url" | grep "\[DOMAIN\]" >/dev/null 2>&1) && \ 245 write_log 14 "Service section not configured correctly! Missing 'domain'" 246 [ -z "$username" ] && $(echo "$update_url" | grep "\[USERNAME\]" >/dev/null 2>&1) && \ 247 write_log 14 "Service section not configured correctly! Missing 'username'" 248 [ -z "$password" ] && $(echo "$update_url" | grep "\[PASSWORD\]" >/dev/null 2>&1) && \ 249 write_log 14 "Service section not configured correctly! Missing 'password'" 250 [ -z "$param_enc" ] && $(echo "$update_url" | grep "\[PARAMENC\]" >/dev/null 2>&1) && \ 251 write_log 14 "Service section not configured correctly! Missing 'param_enc'" 252 [ -z "$param_opt" ] && $(echo "$update_url" | grep "\[PARAMOPT\]" >/dev/null 2>&1) && \ 253 write_log 14 "Service section not configured correctly! Missing 'param_opt'" 254 } 255 256 # url encode username (might be email or something like this) 257 # and password (might have special chars for security reason) 258 # and optional parameter "param_enc" 259 [ -n "$username" ] && urlencode URL_USER "$username" 260 [ -n "$password" ] && urlencode URL_PASS "$password" 261 [ -n "$param_enc" ] && urlencode URL_PENC "$param_enc" 262 263 # verify ip_source 'script' if script is configured and executable 264 if [ "$ip_source" = "script" ]; then 265 set -- $ip_script #handling script with parameters, we need a trick 266 [ -z "$1" ] && write_log 14 "No script defined to detect local IP!" 267 [ -x "$1" ] || write_log 14 "Script to detect local IP not executable!" 268 fi 269 270 # compute update interval in seconds 271 get_seconds CHECK_SECONDS ${check_interval:-10} ${check_unit:-"minutes"} # default 10 min 272 get_seconds FORCE_SECONDS ${force_interval:-72} ${force_unit:-"hours"} # default 3 days 273 get_seconds RETRY_SECONDS ${retry_interval:-60} ${retry_unit:-"seconds"} # default 60 sec 274 [ $CHECK_SECONDS -lt 300 ] && CHECK_SECONDS=300 # minimum 5 minutes 275 [ $FORCE_SECONDS -gt 0 -a $FORCE_SECONDS -lt $CHECK_SECONDS ] && FORCE_SECONDS=$CHECK_SECONDS # FORCE_SECONDS >= CHECK_SECONDS or 0 276 write_log 7 "check interval: $CHECK_SECONDS seconds" 277 write_log 7 "force interval: $FORCE_SECONDS seconds" 278 write_log 7 "retry interval: $RETRY_SECONDS seconds" 279 write_log 7 "retry counter : $retry_count times" 280 281 # kill old process if it exists & set new pid file 282 stop_section_processes "$SECTION_ID" 283 [ $? -gt 0 ] && write_log 7 "'SIGTERM' was send to old process" || write_log 7 "No old process" 284 echo $$ > $PIDFILE 285 286 # determine when the last update was 287 # the following lines should prevent multiple updates if hotplug fires multiple startups 288 # as described in Ticket #7820, but did not function if never an update take place 289 # i.e. after a reboot (/var is linked to /tmp) 290 # using uptime as reference because date might not be updated via NTP client 291 get_uptime CURR_TIME 292 [ -e "$UPDFILE" ] && { 293 LAST_TIME=$(cat $UPDFILE) 294 # check also LAST > CURR because link of /var/run to /tmp might be removed 295 # i.e. boxes with larger filesystems 296 [ -z "$LAST_TIME" ] && LAST_TIME=0 297 [ $LAST_TIME -gt $CURR_TIME ] && LAST_TIME=0 298 } 299 if [ $LAST_TIME -eq 0 ]; then 300 write_log 7 "last update: never" 301 else 302 EPOCH_TIME=$(( $(date +%s) - $CURR_TIME + $LAST_TIME )) 303 EPOCH_TIME="date -d @$EPOCH_TIME +'$ddns_dateformat'" 304 write_log 7 "last update: $(eval $EPOCH_TIME)" 305 fi 306 307 # verify DNS server 308 [ -n "$dns_server" ] && verify_dns "$dns_server" 309 310 # verify Proxy server and set environment 311 [ -n "$proxy" ] && { 312 verify_proxy "$proxy" && { 313 # everything ok set proxy 314 export HTTP_PROXY="http://$proxy" 315 export HTTPS_PROXY="http://$proxy" 316 export http_proxy="http://$proxy" 317 export https_proxy="http://$proxy" 318 } 319 } 320 321 # let's check if there is already an IP registered on the web 322 get_registered_ip REGISTERED_IP "NO_RETRY" 323 ERR_LAST=$? 324 # No error or No IP set otherwise retry 325 [ $ERR_LAST -eq 0 -o $ERR_LAST -eq 127 ] || get_registered_ip REGISTERED_IP 326 # on IPv6 we use expanded version to be shure when comparing 327 [ $use_ipv6 -eq 1 ] && expand_ipv6 "$REGISTERED_IP" REGISTERED_IP 328 329 # loop endlessly, checking ip every check_interval and forcing an updating once every force_interval 330 write_log 6 "Starting main loop at $(eval $DATE_PROG)" 331 while : ; do 332 333 get_local_ip LOCAL_IP # read local IP 334 [ $use_ipv6 -eq 1 ] && expand_ipv6 "$LOCAL_IP" LOCAL_IP # on IPv6 we use expanded version 335 336 # prepare update 337 # never updated or forced immediate then NEXT_TIME = 0 338 [ $FORCE_SECONDS -eq 0 -o $LAST_TIME -eq 0 ] \ 339 && NEXT_TIME=0 \ 340 || NEXT_TIME=$(( $LAST_TIME + $FORCE_SECONDS )) 341 342 get_uptime CURR_TIME # get current uptime 343 344 # send update when current time > next time or local ip different from registered ip 345 if [ $CURR_TIME -ge $NEXT_TIME -o "$LOCAL_IP" != "$REGISTERED_IP" ]; then 346 if [ $VERBOSE -gt 2 ]; then 347 write_log 7 "Verbose Mode: $VERBOSE - NO UPDATE send" 348 elif [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then 349 write_log 7 "Update needed - L: '$LOCAL_IP' <> R: '$REGISTERED_IP'" 350 else 351 write_log 7 "Forced Update - L: '$LOCAL_IP' == R: '$REGISTERED_IP'" 352 fi 353 354 ERR_LAST=0 355 [ $VERBOSE -lt 3 ] && { 356 # only send if VERBOSE < 3 357 send_update "$LOCAL_IP" 358 ERR_LAST=$? # save return value 359 } 360 361 # error sending local IP to provider 362 # we have no communication error (handled inside send_update/do_transfer) 363 # but update was not recognized 364 # do NOT retry after RETRY_SECONDS, do retry after CHECK_SECONDS 365 # to early retrys will block most DDNS provider 366 # providers answer is checked inside send_update() function 367 if [ $ERR_LAST -eq 0 ]; then 368 get_uptime LAST_TIME # we send update, so 369 echo $LAST_TIME > $UPDFILE # save LASTTIME to file 370 [ "$LOCAL_IP" != "$REGISTERED_IP" ] \ 371 && write_log 6 "Update successful - IP '$LOCAL_IP' send" \ 372 || write_log 6 "Forced update successful - IP: '$LOCAL_IP' send" 373 elif [ $ERR_LAST -eq 127 ]; then 374 write_log 3 "No update send to DDNS Provider" 375 else 376 write_log 3 "IP update not accepted by DDNS Provider" 377 fi 378 fi 379 380 # now we wait for check interval before testing if update was recognized 381 # only sleep if VERBOSE <= 2 because otherwise nothing was send 382 [ $VERBOSE -le 2 ] && { 383 write_log 7 "Waiting $CHECK_SECONDS seconds (Check Interval)" 384 sleep $CHECK_SECONDS & 385 PID_SLEEP=$! 386 wait $PID_SLEEP # enable trap-handler 387 PID_SLEEP=0 388 } || write_log 7 "Verbose Mode: $VERBOSE - NO Check Interval waiting" 389 390 REGISTERED_IP="" # clear variable 391 get_registered_ip REGISTERED_IP # get registered/public IP 392 [ $use_ipv6 -eq 1 ] && expand_ipv6 "$REGISTERED_IP" REGISTERED_IP # on IPv6 we use expanded version 393 394 # IP's are still different 395 if [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then 396 if [ $VERBOSE -le 1 ]; then # VERBOSE <=1 then retry 397 ERR_UPDATE=$(( $ERR_UPDATE + 1 )) 398 [ $retry_count -gt 0 -a $ERR_UPDATE -gt $retry_count ] && \ 399 write_log 14 "Updating IP at DDNS provider failed after $retry_count retries" 400 write_log 4 "Updating IP at DDNS provider failed - starting retry $ERR_UPDATE/$retry_count" 401 continue # loop to beginning 402 else 403 write_log 4 "Updating IP at DDNS provider failed" 404 write_log 7 "Verbose Mode: $VERBOSE - NO retry"; exit 1 405 fi 406 else 407 # we checked successful the last update 408 ERR_UPDATE=0 # reset error counter 409 fi 410 411 # force_update=0 or VERBOSE > 1 - leave here 412 [ $VERBOSE -gt 1 ] && write_log 7 "Verbose Mode: $VERBOSE - NO reloop" 413 [ $FORCE_SECONDS -eq 0 ] && write_log 6 "Configured to run once" 414 [ $VERBOSE -gt 1 -o $FORCE_SECONDS -eq 0 ] && exit 0 415 416 write_log 6 "Rerun IP check at $(eval $DATE_PROG)" 417 done 418 # we should never come here there must be a programming error 419 write_log 12 "Error in 'dynamic_dns_updater.sh - program coding error"