diff --git a/net/ddns-scripts/Makefile b/net/ddns-scripts/Makefile index 5104c9d60..7fcb2cc80 100644 --- a/net/ddns-scripts/Makefile +++ b/net/ddns-scripts/Makefile @@ -1,8 +1,8 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ddns-scripts -PKG_VERSION:=1.0.0 -PKG_RELEASE:=23 +PKG_VERSION:=2.0.1 +PKG_RELEASE:=1 PKG_LICENSE:=GPL-2.0 PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) @@ -10,17 +10,25 @@ PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) include $(INCLUDE_DIR)/package.mk define Package/ddns-scripts - SECTION:=net - CATEGORY:=Network - SUBMENU:=IP Addresses and Names - TITLE:=Dynamic DNS Scripts - PKGARCH:=all - MAINTAINER:=Christian Schoenebeck + SECTION:=net + CATEGORY:=Network + SUBMENU:=IP Addresses and Names + TITLE:=Dynamic DNS Scripts (with IPv6 support) + PKGARCH:=all + MAINTAINER:=Christian Schoenebeck endef define Package/ddns-scripts/description - A highly configurable set of scripts for doing - dynamic dns updates + A highly configurable set of scripts for doing dynamic dns updates. + NEW in this version: + - IPv6 support + - force communication to IPv4 or IPv6 only + - DNS server support + - using BIND host if installed + - DNS requests via TCP + - Proxy server support + - log file support + - support to run once endef define Build/Prepare @@ -37,10 +45,50 @@ define Package/ddns-scripts/conffiles endef define Package/ddns-scripts/install - $(INSTALL_DIR) $(1) - $(CP) ./files/* $(1)/ + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/etc/config/* $(1)/etc/config + + $(INSTALL_DIR) $(1)/etc/hotplug.d/iface + $(INSTALL_BIN) ./files/etc/hotplug.d/iface/* $(1)/etc/hotplug.d/iface + $(INSTALL_DIR) $(1)/etc/init.d - $(INSTALL_BIN) ./files/etc/init.d/ddns $(1)/etc/init.d/ + $(INSTALL_BIN) ./files/etc/init.d/* $(1)/etc/init.d/ + + $(INSTALL_DIR) $(1)/usr/lib/ddns + $(INSTALL_DATA) ./files/usr/lib/ddns/service* $(1)/usr/lib/ddns + $(INSTALL_BIN) ./files/usr/lib/ddns/*.sh $(1)/usr/lib/ddns +endef + +define Package/ddns-scripts/postinst + #!/bin/sh + # if run within buildroot exit + [ -n "${IPKG_INSTROOT}" ] && exit 0 + + # add new section "ddns" "global" if not exists + uci -q get ddns.global > /dev/null || uci -q set ddns.global='ddns' + uci -q get ddns.global.date_format > /dev/null || uci -q set ddns.global.date_format='%F %R' + uci -q get ddns.global.log_lines > /dev/null || uci -q set ddns.global.log_lines='250' + uci -q commit ddns + + # clear LuCI indexcache + rm -f /tmp/luci-indexcache >/dev/null 2>&1 + + exit 0 +endef + +define Package/ddns-scripts/prerm + #!/bin/sh + # if run within buildroot exit + [ -n "${IPKG_INSTROOT}" ] && exit 0 + + # stop running scripts + /etc/init.d/ddns disable + /etc/init.d/ddns stop + + # clear LuCI indexcache + rm -f /tmp/luci-indexcache >/dev/null 2>&1 + + exit 0 endef $(eval $(call BuildPackage,ddns-scripts)) diff --git a/net/ddns-scripts/files/etc/config/ddns b/net/ddns-scripts/files/etc/config/ddns index e7337b5ff..54ec42c12 100644 --- a/net/ddns-scripts/files/etc/config/ddns +++ b/net/ddns-scripts/files/etc/config/ddns @@ -1,97 +1,29 @@ -################################################################# -# In order to enable dynamic dns you need at least one section, -# and in that seciton the "enabled" option must be set to one -# -# Each section represents an update to a different service # -# You specify your domain name, your username and your password -# with the optins "domain", "username" and "password" respectively +# Please read ddns.sample # -# Next you need to specify the name of the service you are -# connecting to "eg. dyndns.org". The format of the update -# urls for several different dynamic dns services is specified -# in the /usr/lib/ddns/services file. This list is hardly complete -# as there are many, many different dynamic dns services. If your -# service is on the list you can merely specify it with the -# "service_name" option. Otherwise you will need to determine -# the format of the url to update with. You can either add an -# entry to the /usr/lib/ddns/services file or specify this with -# the "update_url" option. -# -# We also need to specify the source of the ip address to associate with -# your domain. The "ip_source" option can be "network", "interface" -# or "web", with "network" as the default. -# -# If "ip_source" is "network" you specify a network section in your -# /etc/network config file (e.g. "wan", which is the default) with -# the "ip_network" option. If you specify "wan", you will update -# with whatever the ip for your wan is. -# -# If "ip_source" is "interface" you specify a hardware interface -# (e.g. "eth1") and whatever the current ip of this interface is -# will be associated with the domain when an update is performed. -# -# If "ip_source" is "script" you specify a script to obtain ip address. -# The "ip_script" option should contain path to your script. -# -# The last possibility is that "ip_source" is "web", which means -# that in order to obtain our ip address we will connect to a -# website, and the first valid ip address listed on that page -# will be assumed to be ours. If you are behind another firewall -# this is the best option since none of the local networks or -# interfaces will have the external ip. The website to connect -# to is specified by the "ip_url" option. You may specify multiple -# urls in the option, separated by whitespace. -# -# Finally we need to specify how often to check whether we need -# to check whether the ip address has changed (and if so update -# it) and how often we need to force an update ( many services -# will expire your domain if you don't connect and do an update -# every so often). Use the "check_interval" to specify how -# often to check whether an update is necessary, the "retry_interval" -# to specify how often to retry in case the update has failed, and -# the "force_interval" option to specify how often to force an -# update. Specify the units for these values with the "check_unit", -# the "retry_init" and the "force_unit" options. Units can be -# "days", "hours", "minutes" or "seconds". The default force_unit -# is hours, the default retry_unit is seconds and the default -# check_unit is seconds. The default check_interval is 600 seconds, -# or ten minutes. The default retry_interval is 60 seconds, or one -# minute. The default force_interval is 72 hours or 3 days. -# -# -######################################################### +config ddns "global" + option date_format "%F %R" +# option run_dir "/var/run/ddns" +# option log_dir "/var/log/ddns" + option log_lines "250" -config service "myddns" - option enabled "0" - option interface "wan" - option use_syslog "1" +config service "myddns_ipv4" option service_name "dyndns.org" - option domain "mypersonaldomain.dyndns.org" - option username "myusername" - option password "mypassword" - option use_https "0" - - option force_interval "72" - option force_unit "hours" - option check_interval "10" - option check_unit "minutes" - option retry_interval "60" - option retry_unit "seconds" - - #option ip_source "network" - #option ip_network "wan" - - #option ip_source "interface" - #option ip_interface "eth0.1" - - #option ip_source "script" - #option ip_script "path to your scrip" - - option ip_source "web" - option ip_url "http://checkip.dyndns.com/" - - #option update_url "http://[USERNAME]:[PASSWORD]@members.dyndns.org/nic/update?hostname=[DOMAIN]&myip=[IP]" - - + option domain "yourhost.dyndns.org" + option username "your_username" + option password "your_password" + option interface "wan" + option ip_source "network" + option ip_network "wan" + +config service "myddns_ipv6" + option update_url "http://[USERNAME]:[PASSWORD]@your.provider.net/nic/update?hostname=[DOMAIN]&myip=[IP]" + option domain "yourhost.dyndns.org" + option username "your_username" + option password "your_password" + option use_ipv6 "1" + option interface "wan6" + option ip_source "network" + option ip_network "wan6" + diff --git a/net/ddns-scripts/files/etc/config/ddns.sample b/net/ddns-scripts/files/etc/config/ddns.sample new file mode 100644 index 000000000..c20772879 --- /dev/null +++ b/net/ddns-scripts/files/etc/config/ddns.sample @@ -0,0 +1,272 @@ +# +# +# Here you find a description on every parameter supported +# and used by ddns-scripts and corresponding LuCI application +# +# Inside your ddns configuration file (/etc/config/ddns) +# you might not find some of below described options. +# This is because you don't need to define options +# if using there defaults. The LuCI application will delete +# options that configured to there default values. +# +# If you have a working ddns configuration from old ddns-scripts (Version 1.x) +# everything will function the same with new scripts +# without any changes to the configuration. +# +# If you like to use this file for your configuration then +# use a copy, because the used software to modify the +# configuration files will throw away all empty lines +# and those starting with # (comments). +# + +##################################################################### +# Global application settings +# +config ddns "global" + + ########### + # set date format to use for display date in logfiles + # and LuCI web application. + # For codes see man pages of date command. + # default: "%F %R" (ISO 8601 format) +# option date_format "%F %R" + + ########### + # set run directory to use for .pid and .update files + # there will be a separate file for every running service section + # default: "/var/run/ddns" +# option run_dir "/var/run/ddns" + + ########### + # set log directory to use for .log files + # there will be a separate file for every running service section + # default: "/var/log/ddns" +# option log_dir "/var/log/ddns" + + ########### + # set number of lines stored in .log file before auto truncated + # default: "250" lines +# option log_lines "250" + + +##################################################################### +# DDNS service settings +# +# for each service you want to serve you need a separate configuration +# if you need IPv4 and IPv6 you need to setup 2 separate configurations +# with different names. (i.e. "myddns_ipv4" and "myddns_ipv6") +# do not use white-spaces or dashes "-" or "@" ":" "!" or +# other special characters inside name. +config service "myddns" + + ########### Basic settings ######################## + + ########### + # enable/disable this service section + # default: "0" disabled + option enabled "0" + + ########### + # detecting/sending IPv4 or IPv6 address to the DDNS provider + # set to "1" if you want to use IPv6 + # default: "0" use IPv4 + option use_ipv6 "0" + + ########### + # defines the network as defined in /etc/config/network + # to be monitored for up/down events to start via hotplug + default: "wan" for IPv4 + default: "wan6" for IPv6 + option interface "wan" + + ########### + # Next you need to specify the name of the service you are + # connecting to "eg. dyndns.org". The format of the update + # urls for several different dynamic dns services is specified + # in the "/usr/lib/ddns/services" file for IPv4 and in + # "/usr/lib/ddns/service_ipv6" file. This list is hardly complete + # as there are many, many different dynamic dns services. + # If your service is on the list you can merely specify it with the + # "service_name" option. Otherwise you will need to determine + # the format of the url to update with. You can either add an + # entry to the "/usr/lib/ddns/services" or "services_ipv6" file + # or specify this with the "update_url" option. + # default: none + option service_name "dyndns.org" + + # sample: + # "http://[USERNAME]:[PASSWORD]@members.dyndns.org/nic/update?hostname=[DOMAIN]&myip=[IP]" +# option update_url "" + + ########### + # You must specify your domain/host name, your username and your password + # as you get from you DDNS provider. Keep an eye on providers help pages. + # + # Your DNS name / replace [DOMAIN] in update_url + # default: none + option domain "" + + # Username of your DDNS service account / replace [USERNAME] in update_url + # default: none + option username "" + + # Password of your DDNS service account / replace [PASSWORD] in update_url + # default: none + option password "" + + ########### + # use HTTPS for secure communication with you DDNS provider + # personally found some providers having problems when not sending + # updates via HTTPS. Yyou must not specify "https://" in update_url. + # It's modified by the scripts themselves + # Needs GNU Wget (with SSL support) or cURL to be installed. + # default: "0" do not use HTTPS + option use_https "0" + + # if using HTTPS (see above) the transfer program tries to verify + # the providers server certificate. For verification there needs to be + # the counterpart on this machine. Specify the path or path/file where + # the transfer program can find them. (might need package CA-certificates) + # if you don't want to verify servers certificate (insecure) you should + # this parameter to "IGNORE" (in capital letters) + # default: "/etc/cacert" path where CA-certificate package is installed + option cacert "/etc/cacert" + + ########### + # for logging and control if everything work fine you can get information inside + # system log . Critical Errors are always send to system log. + # You can define which information you like to log + # 1 == info, notice, warning, errors + # 2 == notice, warning, errors + # 3 == warning, errors + # 4 == errors + # default: "0" off + option use_syslog "0" + + ########### + # for logging and control if everything work fine you can get information inside + # log file. You find the file per default in /var/log/ddns/[sectionname].log + # The path can be modified for all log files in ddns.global section (see above) + # default: "1" on + option use_logfile "1" + + ########### Advanced settings ##################### + + ########### + # you need to specify how ddns-scripts should detect you current local ip. + # the ip_source could be set to "network", "web", "interface" or "script" + # the parameters below specifying the additional information needed for + # the corresponding ip_spource configuration + # default: "network" + + # ip_source "network" additional uses option ip_network and detects the + # current local ip on network as defined in /etc/config/network + # default: "wan" using IPv4 + # default: "wan6" using IPv6 + option ip_source "network" + option ip_network "wan" + + # ip_source "web" additional uses option ip_url and detects the current + # local ip from special web sides that response with the ip address of + # calling host. If you are behind a firewall/NAT this is the best option + # since none of the local networks or interfaces will have the external ip. + # default: "http://checkip.dyndns.com" using IPv4 + # default: "http://checkipv6.dyndns.com" using IPv6 +# option ip_source "web" +# option ip_url "http://checkip.dyndns.com" + + # ip_source "interface" additional uses option ip_interface + # ip_source "interface" uses one of the locally installed physical interfaces + # to detect independent from network they configured to. + # default: none +# option ip_source "interface" +# option ip_interface "eth1" + + # ip_source "script" additional uses option ip_script + # it's useful if you want to write your own script to detect the + # current local ip. put full path into ip_script option. + # The script must be executable. + # default: none +# option ip_source "script" +# option ip_script "" + + ########### + # force_ipversion option will set the "-4" respectively "-6" parameter + # on command line of transfer and DNS lookup program. + # So the whole communication uses the selected IP version between both ends. + # needs GNU Wget or cURL installed for transfer and + # BIND's host for DNS lookup. + # default: "0" disabled + option force_ipversion "0" + + ########### + # normally the current (in the internet) registered ip is detected using the + # local defined name lookup policies (i.e. /etc/resolve.conf etc.) + # Specify here a DNS server to use instead of the defaults. + # you can use hostname or ip address + # IPv6 address must be in squared brackets "[...]" + # i.e. "google-public-dns-a.google.com" + # default: none +# option dns_server "google-public-dns-a.google.com" + + # By default every DNS call is made via UDP protocol + # Some internet provider offer modems that cache UDP DNS requests. + # They also redirect calls to external servers to local. + # To force the usage of TCP for DNS requests enable this option + # Needs BIND's host program be installed + # default: "0" disabled +# option force_dnstcp "0" + + ########### + # If a Proxy is need to access HTTP/HTTPS pages on the WEB + # it can be configured here also for sending updates to the + # DDNS provider. If you configured use_https='1' above, you + # need to setup your HTTPS proxy here, otherwise your + # HTTP proxy. !!! You should not detect your current IP + # ip_source='web' (see above) because this request is also + # send via the configured proxy !!! + # Syntax: [user:password@]proxy:port !port is required ! + # default: none +# option proxy '' + + ########### Timer settings ######################## + + ########### + # defines the time interval to check if local IP has changed + # After the first start and first update send, the system will + # wait this time before verify if update was successful send. + # !!! checks below 5 minutes make no sense because the Internet + # needs about 5-10 minutes to sync an IP-change to all DNS servers !!! + # accepted unit entry’s: 'seconds' 'minutes' 'hours' 'days' + # minimum 5 minutes == 300 seconds + # default 10 minutes + option check_interval '10' + option check_unit 'minutes' + + ########### + # force to send an update to service provider, if no change was detected. + # consult DDNS providers documentation if your DDNS entry might timeout. + # accepted unit entry’s: 'seconds' 'minutes' 'hours' 'days' + # minimum needs to be greater or equal check interval (see above) + # A special setting of '0' is allowed, which forces the script to run once. + # It sends an update, verify if update was accepted by DNS + # (retry if not) and finish. Useful if you want to start by your own (i.e. cron) + # default 3 days == 72 hours + option force_interval '72' + option force_unit 'hours' + + ########### + # if error happen on detecting, sending or updating the + # script will retry the relevant action for retry_count times + # before stopping script execution. + # default: 5 + option retry_count '5' + + ########### + # if error happen on detecting, sending or updating the + # script will retry the relevant action. + # here you define the time to wait before retry is started + # accepted unit entry’s: 'seconds' 'minutes' 'hours' 'days' + # default: 60 seconds + option retry_interval '60' + option retry_unit 'seconds' diff --git a/net/ddns-scripts/files/usr/lib/ddns/create_cert_hashes.sh b/net/ddns-scripts/files/usr/lib/ddns/create_cert_hashes.sh new file mode 100644 index 000000000..ff788c375 --- /dev/null +++ b/net/ddns-scripts/files/usr/lib/ddns/create_cert_hashes.sh @@ -0,0 +1,34 @@ +#!/bin/sh +# +#set -vx + +[ -d /etc/ssl/certs ] || { + echo "CA-Certificates not istalled - please install first" + exit 1 +} + +NUMCERT=$(find /etc/ssl/certs -name *.crt 2>/dev/null | wc -l) +NUMLINK=$(find /etc/ssl/certs -type l 2>/dev/null | wc -l) + +[ $NUMLINK -gt 0 ] && { + echo "File-Links already exist. Exiting" + exit 0 +} + +[ -f /usr/bin/openssl ] && OPENSSL="EXIST" +[ -z "$OPENSSL" ] && { + opkg update || exit 1 + opkg install openssl-util 2>/dev/null +} + +for CERTFILE in `ls -1 $(1)/etc/ssl/certs`; do \ + HASH=`openssl x509 -hash -noout -in /etc/ssl/certs/$CERTFILE` + SUFFIX=0 + while [ -h "/etc/ssl/certs/$HASH.$SUFFIX" ]; do + let "SUFFIX += 1" + done + ln -s "$CERTFILE" "/etc/ssl/certs/$HASH.$SUFFIX" + echo "link $HASH.$SUFFIX created for $CERTFILE" +done + +[ -z "$OPENSSL" ] && opkg remove --force-remove --autoremove openssl-util 2>/dev/null diff --git a/net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_functions.sh b/net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_functions.sh index 6e4981448..057fe9258 100644 --- a/net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_functions.sh +++ b/net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_functions.sh @@ -1,144 +1,715 @@ -# /usr/lib/dynamic_dns/dynamic_dns_functions.sh +#!/bin/sh +# /usr/lib/ddns/dynamic_dns_functions.sh # -# Written by Eric Paul Bishop, Janary 2008 +# Original written by Eric Paul Bishop, January 2008 # Distributed under the terms of the GNU General Public License (GPL) version 2.0 -# -# This script is (loosely) based on the one posted by exobyte in the forums here: +# (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 by Christian Schoenebeck in August 2014 to support: +# - IPv6 DDNS services +# - setting DNS Server to retrieve current IP including TCP transport +# - Proxy Server to send out updates or retrieving WEB based IP detection +# - force_interval=0 to run once (usefull for cron jobs etc.) +# - the usage of BIND's host instead of BusyBox's nslookup if installed (DNS via TCP) +# - extended Verbose Mode and log file support for better error detection +# +# 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 # +SECTION_ID="" # hold config's section name +VERBOSE_MODE=1 # default mode is log to console, but easily changed with parameter +LUCI_HELPER="" # set by dynamic_dns_lucihelper.sh, if filled supress all error logging + +PIDFILE="" # pid file +UPDFILE="" # store UPTIME of last update + +# directory to store run information to. +RUNDIR=$(uci -q get ddns.global.run_dir) || RUNDIR="/var/run/ddns" +# NEW # directory to store log files +LOGDIR=$(uci -q get ddns.global.log_dir) || LOGDIR="/var/log/ddns" +LOGFILE="" # NEW # logfile can be enabled as new option +# number of lines to before rotate logfile +LOGLINES=$(uci -q get ddns.global.log_lines) || LOGLINES=250 + +CHECK_SECONDS=0 # calculated seconds out of given +FORCE_SECONDS=0 # interval and unit +RETRY_SECONDS=0 # in configuration + +OLD_PID=0 # Holds the PID of already running process for the same config section + +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 + +ERR_LAST=0 # used to save $? return code of program and function calls +ERR_LOCAL_IP=0 # error counter on getting local ip +ERR_REG_IP=0 # error counter on getting DNS registered ip +ERR_SEND=0 # error counter on sending update to DNS provider +ERR_UPDATE=0 # error counter on different local and registered ip + +# format to show date information in log and luci-app-ddns default ISO 8601 format +DATE_FORMAT=$(uci -q get ddns.global.date_format) || DATE_FORMAT="%F %R" +DATE_PROG="date +'$DATE_FORMAT'" + +# 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,\}\)" -#loads all options for a given package and section -#also, sets all_option_variables to a list of the variable names +# 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() { - pkg_name="$1" - section_id="$2" - - ALL_OPTION_VARIABLES="" - # this callback loads all the variables - # in the section_id 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 + 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" = ."$section_id" ]; then + if [ ."$2" = ."$__SECTIONID" ]; then option_cb() { - ALL_OPTION_VARIABLES="$ALL_OPTION_VARIABLES $1" + __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 - config_load "$pkg_name" - for var in $ALL_OPTION_VARIABLES + for __VAR in $__ALL_OPTION_VARIABLES do - config_get "$var" "$section_id" "$var" + config_get "$__VAR" "$__SECTIONID" "$__VAR" done + return 0 } - -get_current_ip() +# 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) +start_daemon_for_all_ddns_sections() { + local __EVENTIF="$1" + local __SECTIONS="" + local __SECTIONID="" + local __IFACE="" + + config_cb() + { + # only look for section type "service", ignore everything else + [ "$1" == "service" ] && __SECTIONS="$__SECTIONS $2" + } + config_load "ddns" - #if ip source is not defined, assume we want to get ip from wan - if [ "$ip_source" != "interface" ] && [ "$ip_source" != "web" ] && [ "$ip_source" != "script" ] - then - ip_source="network" + for __SECTIONID in $__SECTIONS + do + config_get __IFACE "$__SECTIONID" interface "wan" + [ -z "$__EVENTIF" -o "$__IFACE" = "$__EVENTIF" ] || continue + /usr/lib/ddns/dynamic_dns_updater.sh $__SECTIONID 0 > /dev/null 2>&1 & + done +} + +verbose_echo() +{ + [ -n "$LUCI_HELPER" ] && return # nothing to report when used by LuCI helper script + [ $VERBOSE_MODE -gt 0 ] && echo -e " $*" + if [ ${use_logfile:-0} -eq 1 -o $VERBOSE_MODE -gt 1 ]; then + [ -d $LOGDIR ] || mkdir -p -m 755 $LOGDIR + echo -e " $*" >> $LOGFILE + # VERBOSE_MODE > 1 then NO loop so NO truncate log to $LOGLINES lines + [ $VERBOSE_MODE -gt 1 ] || sed -i -e :a -e '$q;N;'$LOGLINES',$D;ba' $LOGFILE fi + return +} - if [ "$ip_source" = "network" ] - then - if [ -z "$ip_network" ] - then - ip_network="wan" +syslog_info(){ + [ $use_syslog -eq 1 ] && logger -p user.info -t ddns-scripts[$$] "$SECTION_ID: $*" + return +} +syslog_notice(){ + [ $use_syslog -ge 1 -a $use_syslog -le 2 ] && logger -p user.notice -t ddns-scripts[$$] "$SECTION_ID: $*" + return +} +syslog_warn(){ + [ $use_syslog -ge 1 -a $use_syslog -le 3 ] && logger -p user.warn -t ddns-scripts[$$] "$SECTION_ID: $*" + return +} +syslog_err(){ + [ $use_syslog -ge 1 ] && logger -p user.err -t ddns-scripts[$$] "$SECTION_ID: $*" + return +} + +critical_error() { + [ -n "$LUCI_HELPER" ] && return # nothing to report when used by LuCI helper script + verbose_echo "\n CRITICAL ERROR =: $* - EXITING\n" + [ $VERBOSE_MODE -eq 0 ] && echo -e "\n$SECTION_ID: CRITICAL ERROR - $* - EXITING\n" + logger -t ddns-scripts[$$] -p user.crit "$SECTION_ID: CRITICAL ERROR - $* - EXITING" + exit 1 # critical error -> leave here +} + +# extract update_url for given DDNS Provider from +# file /usr/lib/ddns/services for IPv4 or from +# file /usr/lib/ddns/services_ipv6 for IPv6 +get_service_url() { + # $1 Name of Variable to store url to + local __LINE __FILE __NAME __URL __SERVICES + local __OLD_IFS=$IFS + local __NEWLINE_IFS=' +' #__NEWLINE_IFS + + __FILE="/usr/lib/ddns/services" # IPv4 + [ $use_ipv6 -ne 0 ] && __FILE="/usr/lib/ddns/services_ipv6" # IPv6 + + #remove any lines not containing data, and then make sure fields are enclosed in double quotes + __SERVICES=$(cat $__FILE | grep "^[\t ]*[^#]" | \ + awk ' gsub("\x27", "\"") { if ($1~/^[^\"]*$/) $1="\""$1"\"" }; { if ( $NF~/^[^\"]*$/) $NF="\""$NF"\"" }; { print $0 }') + + IFS=$__NEWLINE_IFS + for __LINE in $__SERVICES + do + #grep out proper parts of data and use echo to remove quotes + __NAME=$(echo $__LINE | grep -o "^[\t ]*\"[^\"]*\"" | xargs -r -n1 echo) + __URL=$(echo $__LINE | grep -o "\"[^\"]*\"[\t ]*$" | xargs -r -n1 echo) + + if [ "$__NAME" = "$service_name" ]; then + break # found so leave for loop fi + done + IFS=$__OLD_IFS + + eval "$1='$__URL'" + return 0 +} + +get_seconds() { + # $1 Name of Variable to store result in + # $2 Number and + # $3 Unit of time interval + 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 folloing changes + # - commented out "#!/bin/bash" and usage section + # - replace exit by return for usage as function + # - some reformating + # + # timeout [-SIG] time [--] command args... + # + # Run the given command until completion, but kill it if it runs too long. + # Specifically designed to exit immediatally (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 backgrouds 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 +} + +__verify_host_port() { + # $1 Host/IP to verify + # $2 Port to verify + local __HOST=$1 + local __PORT=$2 + local __TMP __IP __IPV4 __IPV6 __RUNPROG __ERRPROG __ERR + # return codes + # 1 system specific error + # 2 nslookup error + # 3 nc (netcat) error + # 4 unmatched IP version + + __RUNPROG="nslookup $__HOST 2>/dev/null" + __ERRPROG="nslookup $__HOST 2>&1" + verbose_echo " resolver prog =: '$__RUNPROG'" + __TMP=$(eval $__RUNPROG) # test if nslookup runs without errors + __ERR=$? + # command error + [ $__ERR -gt 0 ] && { + verbose_echo "\n!!!!!!!!! ERROR =: BusyBox nslookup Error '$__ERR'\n$(eval $__ERRPROG)\n" + syslog_err "DNS Resolver Error - BusyBox nslookup Error: '$__ERR'" + return 2 + } || { + # we need to run twice because multi-line output needs to be directly piped to grep because + # pipe returns return code of last prog in pipe but we need errors from nslookup command + __IPV4=$(eval $__RUNPROG | sed '1,2d' | grep -o "Name:\|Address.*" | grep -m 1 -o "$IPV4_REGEX") + __IPV6=$(eval $__RUNPROG | sed '1,2d' | grep -o "Name:\|Address.*" | grep -m 1 -o "$IPV6_REGEX") + } + + # check IP version if forced + if [ $force_ipversion -ne 0 ]; then + [ $use_ipv6 -eq 0 -a -z "$__IPV4" ] && return 4 + [ $use_ipv6 -eq 1 -a -z "$__IPV6" ] && return 4 fi - current_ip=''; - if [ "$ip_source" = "network" ] - then - network_get_ipaddr current_ip "$ip_network" || return - elif [ "$ip_source" = "interface" ] - then - current_ip=$(ifconfig $ip_interface | grep -o 'inet addr:[0-9.]*' | grep -o "$ip_regex") - elif [ "$ip_source" = "script" ] - then - # get ip from script - current_ip=$($ip_script) - else - # get ip from web - # we check each url in order in ip_url variable, and if no ips are found we use dyndns ip checker - # ip is set to FIRST expression in page that matches the ip_regex regular expression - for addr in $ip_url - do - if [ -z "$current_ip" ] - then - current_ip=$(echo $( wget -O - $addr 2>/dev/null) | grep -o "$ip_regex") - fi - done + # verify nc command + # busybox nc compiled without -l option "NO OPT l!" -> critical error + nc --help 2>&1 | grep -iq "NO OPT l!" && \ + critical_error "Busybox nc: netcat compiled with errors" + # busybox nc compiled with extensions + nc --help 2>&1 | grep -q "\-w" && __NCEXT="TRUE" - #here we hard-code the dyndns checkip url in case no url was specified - if [ -z "$current_ip" ] - then - current_ip=$(echo $( wget -O - http://checkip.dyndns.org 2>/dev/null) | grep -o "$ip_regex") - fi + # 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 an IPv6 availible for host + # not worring if there is an IPv6 wan address + # so if not "forced_ipversion" to use ipv6 then connect test via ipv4 if availible + [ $force_ipversion -ne 0 -a $use_ipv6 -ne 0 -o -z "$__IPV4" ] && { + # force IPv6 + __IP=$__IPV6 + } || __IP=$__IPV4 + + if [ -n "$__NCEXT" ]; then # nc compiled with extensions (timeout support) + __RUNPROG="nc -w 1 $__IP $__PORT /dev/null 2>&1" + __ERRPROG="nc -vw 1 $__IP $__PORT &1" + verbose_echo " connect prog =: '$__RUNPROG'" + eval $__RUNPROG + __ERR=$? + [ $__ERR -eq 0 ] && return 0 + verbose_echo "\n!!!!!!!!! ERROR =: BusyBox nc Error '$__ERR'\n$(eval $__ERRPROG)\n" + syslog_err "host verify Error - BusyBox nc Error: '$__ERR'" + return 3 + else # nc compiled without extensions (no timeout support) + __RUNPROG="__timeout 2 -- nc $__IP $__PORT /dev/null 2>&1" + verbose_echo " connect prog =: '$__RUNPROG'" + eval $__RUNPROG + __ERR=$? + [ $__ERR -eq 0 ] && return 0 + verbose_echo "\n!!!!!!!!! ERROR =: BusyBox nc Error '$__ERR' (timeout)" + syslog_err "host verify Error - BusyBox nc Error: '$__ERR' (timeout)" + return 3 fi +} - echo "$current_ip" +verify_dns() { + # $1 DNS server to verify + # we need DNS server to verify otherwise exit with ERROR 1 + [ -z "$1" ] && return 1 + + # DNS uses port 53 + __verify_host_port "$1" "53" } +verify_proxy() { + # $1 Proxy-String to verify + # complete entry user:password@host:port + # host and port only host:port + # host only host unsupported + # IPv4 address instead of host 123.234.234.123 + # IPv6 address instead of host [xxxx:....:xxxx] in square bracket + local __TMP __HOST __PORT -verbose_echo() -{ - if [ "$verbose_mode" = 1 ] - then - echo $1 + # we need Proxy-Sting to verify otherwise exit with ERROR 1 + [ -z "$1" ] && return 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 ERROR 5 + [ -z "$__PORT" ] && return 5 + + __verify_host_port "$__HOST" "$__PORT" } -syslog_echo() -{ - if [ "$use_syslog" = 1 ] - then - echo $1|logger -t ddns-scripts-$service_id +__do_transfer() { + # $1 # Variable to store Answer of transfer + # $2 # URL to use + local __URL="$2" + local __ERR=0 + local __PROG __RUNPROG __ERRPROG __DATA + + # lets prefer GNU Wget because it does all for us - IPv4/IPv6/HTTPS/PROXY/force IP version + if /usr/bin/wget --version 2>&1 | grep -q "\+ssl"; then + __PROG="/usr/bin/wget -t 2 -O -" # standard output only 2 retrys on error + # 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}" + else # exit here because it makes no sense to start loop + critical_error "Wget: No valid certificate(s) found for running HTTPS" + 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 -q '$__URL' 2>/dev/null" # do transfer with "-q" to suppress not needed output + __ERRPROG="$__PROG -d '$__URL' 2>&1" # do transfer with "-d" for debug mode + verbose_echo " transfer prog =: $__RUNPROG" + __DATA=$(eval $__RUNPROG) + __ERR=$? + [ $__ERR -gt 0 ] && { + verbose_echo "\n!!!!!!!!! ERROR =: GNU Wget Error '$__ERR'\n$(eval $__ERRPROG)\n" + syslog_err "Communication Error - GNU Wget Error: '$__ERR'" + return 1 + } + + # 2nd choice is cURL IPv4/IPv6/HTTPS + # libcurl might be compiled without Proxy Support (default in trunk) + elif [ -x /usr/bin/curl ]; then + __PROG="/usr/bin/curl" + # 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" + else # exit here because it makes no sense to start loop + critical_error "cURL: No valid certificate(s) found for running HTTPS" + 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 '*'" + else + # if libcurl has no proxy support and proxy should be used then force ERROR + # libcurl currently no proxy support by default + grep -iq all_proxy /usr/lib/libcurl.so* || \ + critical_error "cURL: libcurl compiled without Proxy support" + fi + + __RUNPROG="$__PROG -q '$__URL' 2>/dev/null" # do transfer with "-s" to suppress not needed output + __ERRPROG="$__PROG -v '$__URL' 2>&1" # do transfer with "-v" for verbose mode + verbose_echo " transfer prog =: $__RUNPROG" + __DATA=$(eval $__RUNPROG) + __ERR=$? + [ $__ERR -gt 0 ] && { + verbose_echo "\n!!!!!!!!! ERROR =: cURL Error '$__ERR'\n$(eval $__ERRPROG)\n" + syslog_err "Communication Error - cURL Error: '$__ERR'" + return 1 + } + + # busybox Wget (did not support neither IPv6 nor HTTPS) + elif [ -x /usr/bin/wget ]; then + __PROG="/usr/bin/wget -O -" + # force ip version not supported + [ $force_ipversion -eq 1 ] && \ + critical_error "BusyBox Wget: can not force IP version to use" + # https not supported + [ $use_https -eq 1 ] && \ + critical_error "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 -q '$__URL' 2>/dev/null" # do transfer with "-q" to suppress not needed output + __ERRPROG="$__PROG '$__URL' 2>&1" + verbose_echo " transfer prog =: $__RUNPROG" + __DATA=$(eval $__RUNPROG) + __ERR=$? + [ $__ERR -gt 0 ] && { + verbose_echo "\n!!!!!!!!! ERROR =: BusyBox Wget Error '$__ERR'\n$(eval $__ERRPROG)\n" + syslog_err "Communication Error - BusyBox Wget Error: '$__ERR'" + return 1 + } + + else + critical_error "Program not found - Neither 'Wget' nor 'cURL' installed or executable" fi + + eval "$1='$__DATA'" + return 0 } -start_daemon_for_all_ddns_sections() -{ - local event_interface="$1" +send_update() { + # $1 # IP to set at DDNS service provider + local __IP __URL __ANSWER __ERR - SECTIONS="" - config_cb() - { - SECTIONS="$SECTIONS $2" + # verify given IP + [ $use_ipv6 -eq 0 ] && __IP=$(echo $1 | grep -v -E "(^0|^10|^127|^172|^192)") # no private IPv4's + [ $use_ipv6 -eq 1 ] && __IP=$(echo $1 | grep "^[0-9a-eA-E]") # no IPv6 addr starting with fxxx of with ":" + [ -z "$__IP" ] && critical_error "Invalid or no IP '$1' given" + + # do replaces in URL + __URL=$(echo $update_url | sed -e "s#\[USERNAME\]#$username#g" -e "s#\[PASSWORD\]#$password#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 __ANSWER "$__URL" + __ERR=$? + [ $__ERR -gt 0 ] && { + verbose_echo "\n!!!!!!!!! ERROR =: Error sending update to DDNS Provider\n" + return 1 } - config_load "ddns" - for section in $SECTIONS - do - local iface - config_get iface "$section" interface "wan" - [ -z "$event_interface" -o "$iface" = "$event_interface" ] || continue - /usr/lib/ddns/dynamic_dns_updater.sh $section 0 > /dev/null 2>&1 & - done + verbose_echo " update send =: DDNS Provider answered\n$__ANSWER" + + return 0 } -monotonic_time() -{ - local uptime - read uptime < /proc/uptime - echo "${uptime%%.*}" +get_local_ip () { + # $1 Name of Variable to store local IP (LOCAL_IP) + local __RUNPROG __IP __URL __ANSWER + + case $ip_source in + network ) + # set correct program + [ $use_ipv6 -eq 0 ] && __RUNPROG="network_get_ipaddr" \ + || __RUNPROG="network_get_ipaddr6" + $__RUNPROG __IP "$ip_network" + verbose_echo " local ip =: '$__IP' detected on network '$ip_network'" + ;; + interface ) + if [ $use_ipv6 -eq 0 ]; then + __IP=$(ifconfig $ip_interface | awk ' + /Bcast.*Mask/ { # 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 + }' + ) + else + __IP=$(ifconfig $ip_interface | awk ' + /inet6/ && /: [0-9a-eA-E]/ && !/\/128/ { # Filter IPv6 exclude fxxx and /128 prefix + # 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 + }' + ) + fi + verbose_echo " local ip =: '$__IP' detected on interface '$ip_interface'" + ;; + script ) + # get ip from script + __IP=$($ip_script) + verbose_echo " local ip =: '$__IP' detected via script '$ip_script'" + ;; + * ) + for __URL in $ip_url; do + __do_transfer __ANSWER "$__URL" + [ -n "$__IP" ] && break # Answer detected, leave for loop + done + # use correct regular expression + [ $use_ipv6 -eq 0 ] \ + && __IP=$(echo "$__ANSWER" | grep -m 1 -o "$IPV4_REGEX") \ + || __IP=$(echo "$__ANSWER" | grep -m 1 -o "$IPV6_REGEX") + verbose_echo " local ip =: '$__IP' detected via web at '$__URL'" + ;; + esac + + # if NO IP was found + [ -z "$__IP" ] && return 1 + + eval "$1='$__IP'" + return 0 +} + +get_registered_ip() { + # $1 Name of Variable to store public IP (REGISTERED_IP) + local __IP __REGEX __PROG __RUNPROG __ERRPROG __ERR + # return codes + # 1 no IP detected + + # set correct regular expression + [ $use_ipv6 -eq 0 ] && __REGEX="$IPV4_REGEX" || __REGEX="$IPV6_REGEX" + + if [ -x /usr/bin/host ]; then # otherwise try to use BIND host + __PROG="/usr/bin/host" + [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -t A" || __PROG="$__PROG -t AAAA" + if [ $force_ipversion -eq 1 ]; then # force IP version + [ $use_ipv6 -eq 0 ] && __PROG="$__PROG -4" || __PROG="$__PROG -6" + fi + [ $force_dnstcp -eq 1 ] && __PROG="$__PROG -T" # force TCP + + __RUNPROG="$__PROG $domain $dns_server 2>/dev/null" + __ERRPROG="$__PROG -v $domain $dns_server 2>&1" + verbose_echo " resolver prog =: $__RUNPROG" + __IP=$(eval $__RUNPROG) + __ERR=$? + # command error + [ $__ERR -gt 0 ] && { + verbose_echo "\n!!!!!!!!! ERROR =: BIND host Error '$__ERR'\n$(eval $__ERRPROG)\n" + syslog_err "DNS Resolver Error - BIND host Error: '$__ERR'" + return 1 + } || { + # we need to run twice because multi-line output needs to be directly piped to grep because + # pipe returns return code of last prog in pipe but we need errors from host command + __IP=$(eval $__RUNPROG | grep "^$domain" | grep -m 1 -o "$__REGEX") + } + + elif [ -x /usr/bin/nslookup ]; then # last use BusyBox nslookup + [ $force_ipversion -ne 0 -o $force_dnstcp -ne 0 ] && \ + critical_error "nslookup - no support to 'force IP Version' or 'DNS over TCP'" + + __RUNPROG="nslookup $domain $dns_server 2>/dev/null" + __ERRPROG="nslookup $domain $dns_server 2>&1" + verbose_echo " resolver prog =: $__RUNPROG" + __IP=$(eval $__RUNPROG) + __ERR=$? + # command error + [ $__ERR -gt 0 ] && { + verbose_echo "\n!!!!!!!!! ERROR =: BusyBox nslookup Error '$__ERR'\n$(eval $__ERRPROG)\n" + syslog_err "DNS Resolver Error - BusyBox nslookup Error: '$__ERR'" + return 1 + } || { + # we need to run twice because multi-line output needs to be directly piped to grep because + # pipe returns return code of last prog in pipe but we need errors from nslookup command + __IP=$(eval $__RUNPROG | sed '1,2d' | grep -o "Name:\|Address.*" | grep -m 1 -o "$__REGEX") + } + + else # there must be an error + critical_error "No program found to request public registered IP" + fi + + verbose_echo " resolved ip =: '$__IP'" + + # if NO IP was found + [ -z "$__IP" ] && return 1 + + eval "$1='$__IP'" + return 0 +} + +get_uptime() { + # $1 Variable to store result in + local __UPTIME=$(cat /proc/uptime) + eval "$1='${__UPTIME%%.*}'" } diff --git a/net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_updater.sh b/net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_updater.sh index e6d298740..20e40b310 100755 --- a/net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_updater.sh +++ b/net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_updater.sh @@ -1,360 +1,399 @@ #!/bin/sh -# /usr/lib/dynamic_dns/dynamic_dns_updater.sh +# /usr/lib/ddns/dynamic_dns_updater.sh # -# Written by Eric Paul Bishop, Janary 2008 +# Original written by Eric Paul Bishop, January 2008 # Distributed under the terms of the GNU General Public License (GPL) version 2.0 -# -# This script is (loosely) based on the one posted by exobyte in the forums here: +# (Loosely) based on the script on the one posted by exobyte in the forums here: # http://forum.openwrt.org/viewtopic.php?id=14040 # - -. /usr/lib/ddns/dynamic_dns_functions.sh - - -service_id=$1 -if [ -z "$service_id" ] -then - echo "ERRROR: You must specify a service id (the section name in the /etc/config/ddns file) to initialize dynamic DNS." - return 1 -fi - -#default mode is verbose_mode, but easily turned off with second parameter -verbose_mode="1" -if [ -n "$2" ] -then - verbose_mode="$2" -fi - -############################################################### -# Leave this comment here, to clearly document variable names -# that are expected/possible +# extended and partial rewritten by Christian Schoenebeck in August 2014 to support: +# - IPv6 DDNS services +# - DNS Server to retrieve registered IP including TCP transport +# - Proxy Server to send out updates +# - force_interval=0 to run once +# - the usage of BIND's host command instead of BusyBox's nslookup if installed +# - extended Verbose Mode and log file support for better error detection # -# Now use load_all_config_options to load config -# options, which is a much more flexible solution. +# 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 + +[ $# -lt 1 -o -n "${2//[0-3]/}" -o ${#2} -gt 1 ] && { + echo -e "\n USAGE:" + echo -e " $0 [SECTION] [VERBOSE_MODE]\n" + echo " [SECTION] - service section as defined in /etc/config/ddns" + echo " [VERBOSE_MODE] - '0' NO output to console" + echo " '1' output to console" + echo " '2' output to console AND logfile" + echo " + run once WITHOUT retry on error" + echo " '3' output to console AND logfile" + echo " + run once WITHOUT retry on error" + echo -e " + NOT sending update to DDNS service\n" + exit 1 +} + +. /usr/lib/ddns/dynamic_dns_functions.sh # global vars are also defined here + +SECTION_ID="$1" +VERBOSE_MODE=${2:-1} #default mode is log to console + +# set file names +PIDFILE="$RUNDIR/$SECTION_ID.pid" # Process ID file +UPDFILE="$RUNDIR/$SECTION_ID.update" # last update successful send (system uptime) +LOGFILE="$LOGDIR/$SECTION_ID.log" # log file + +# VERBOSE_MODE > 1 delete logfile if exist to create an empty one +# only with this data of this run for easier diagnostic +# new one created by verbose_echo function +[ $VERBOSE_MODE -gt 1 -a -f $LOGFILE ] && rm -f $LOGFILE + +################################################################################ +# Leave this comment here, to clearly document variable names that are expected/possible +# Use load_all_config_options to load config options, which is a much more flexible solution. # +# config_load "ddns" +# config_get $SECTION_ID # -#config_load "ddns" +# defined options (also used as variable): +# +# enable self-explanatory +# interface network interface used by hotplug.d i.e. 'wan' or 'wan6' # -#config_get enabled $service_id enabled -#config_get service_name $service_id service_name -#config_get update_url $service_id update_url +# service_name Which DDNS service do you use or "custom" +# update_url URL to use to update your "custom" DDNS service # +# domain Your DNS name / replace [DOMAIN] in update_url +# username Username of your DDNS service account / replace [USERNAME] in update_url +# password Password of your DDNS service account / replace [PASSWORD] in update_url # -#config_get username $service_id username -#config_get password $service_id password -#config_get domain $service_id domain +# use_https use HTTPS to update DDNS service +# cacert file or directory where HTTPS can find certificates to verify server; 'IGNORE' ignore check of server certificate # +# use_syslog log activity to syslog # -#config_get use_https $service_id use_https -#config_get use_syslog $service_id use_syslog -#config_get cacert $service_id cacert +# ip_source source to detect current local IP ('network' or 'web' or 'script' or 'interface') +# ip_network local defined network to read IP from i.e. 'wan' or 'wan6' +# ip_url URL to read local address from i.e. http://checkip.dyndns.com/ or http://checkipv6.dyndns.com/ +# ip_script full path and name of your script to detect local IP +# ip_interface physical interface to use for detecting # -#config_get ip_source $service_id ip_source -#config_get ip_interface $service_id ip_interface -#config_get ip_network $service_id ip_network -#config_get ip_url $service_id ip_url +# check_interval check for changes every !!! checks below 10 minutes make no sense because the Internet +# check_unit 'days' 'hours' 'minutes' !!! needs about 5-10 minutes to sync an IP-change for an DNS entry # -#config_get force_interval $service_id force_interval -#config_get force_unit $service_id force_unit +# force_interval force to send an update to your service if no change was detected +# force_unit 'days' 'hours' 'minutes' !!! force_interval="0" runs this script once for use i.e. with cron # -#config_get check_interval $service_id check_interval -#config_get check_unit $service_id check_unit -######################################################### -load_all_config_options "ddns" "$service_id" - - -#some defaults -if [ -z "$check_interval" ] -then - check_interval=600 -fi - -if [ -z "$retry_interval" ] -then - retry_interval=60 -fi - -if [ -z "$check_unit" ] -then - check_unit="seconds" -fi - -if [ -z "$force_interval" ] -then - force_interval=72 -fi - -if [ -z "$force_unit" ] -then - force_unit="hours" -fi - -if [ -z $use_syslog ] -then - use_syslog=0 -fi - -if [ -z "$use_https" ] -then - use_https=0 -fi - - -#some constants - -retrieve_prog="/usr/bin/wget -O - "; -if [ "x$use_https" = "x1" ] -then - /usr/bin/wget --version 2>&1 |grep -q "\+ssl" - if [ $? -eq 0 ] - then - if [ -f "$cacert" ] - then - retrieve_prog="${retrieve_prog}--ca-certificate=${cacert} " - elif [ -d "$cacert" ] - then - retrieve_prog="${retrieve_prog}--ca-directory=${cacert} " - fi - else - retrieve_prog="/usr/bin/curl " - if [ -f "$cacert" ] - then - retrieve_prog="${retrieve_prog}--cacert $cacert " - elif [ -d "$cacert" ] - then - retrieve_prog="${retrieve_prog}--capath $cacert " - fi - fi -fi - - -service_file="/usr/lib/ddns/services" - -ip_regex="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" - -NEWLINE_IFS=' -' - -#determine what update url we're using if the service_name is supplied -if [ -n "$service_name" ] -then - #remove any lines not containing data, and then make sure fields are enclosed in double quotes - quoted_services=$(cat $service_file | grep "^[\t ]*[^#]" | awk ' gsub("\x27", "\"") { if ($1~/^[^\"]*$/) $1="\""$1"\"" }; { if ( $NF~/^[^\"]*$/) $NF="\""$NF"\"" }; { print $0 }' ) - - - #echo "quoted_services = $quoted_services" - OLD_IFS=$IFS - IFS=$NEWLINE_IFS - for service_line in $quoted_services - do - #grep out proper parts of data and use echo to remove quotes - next_name=$(echo $service_line | grep -o "^[\t ]*\"[^\"]*\"" | xargs -r -n1 echo) - next_url=$(echo $service_line | grep -o "\"[^\"]*\"[\t ]*$" | xargs -r -n1 echo) - - if [ "$next_name" = "$service_name" ] - then - update_url=$next_url - fi - done - IFS=$OLD_IFS -fi - -if [ "x$use_https" = x1 ] -then - update_url=$(echo $update_url | sed -e 's/^http:/https:/') -fi - -verbose_echo "update_url=$update_url" - -#if this service isn't enabled then quit -if [ "$enabled" != "1" ] -then - return 0 -fi - -#compute update interval in seconds -case "$force_unit" in - "days" ) - force_interval_seconds=$(($force_interval*60*60*24)) - ;; - "hours" ) - force_interval_seconds=$(($force_interval*60*60)) - ;; - "minutes" ) - force_interval_seconds=$(($force_interval*60)) - ;; - "seconds" ) - force_interval_seconds=$force_interval - ;; - * ) - #default is hours - force_interval_seconds=$(($force_interval*60*60)) - ;; -esac - - -#compute check interval in seconds -case "$check_unit" in - "days" ) - check_interval_seconds=$(($check_interval*60*60*24)) - ;; - "hours" ) - check_interval_seconds=$(($check_interval*60*60)) - ;; - "minutes" ) - check_interval_seconds=$(($check_interval*60)) - ;; - "seconds" ) - check_interval_seconds=$check_interval - ;; - * ) - #default is seconds - check_interval_seconds=$check_interval - ;; -esac - - -#compute retry interval in seconds -case "$retry_unit" in - "days" ) - retry_interval_seconds=$(($retry_interval*60*60*24)) - ;; - "hours" ) - retry_interval_seconds=$(($retry_interval*60*60)) - ;; - "minutes" ) - retry_interval_seconds=$(($retry_interval*60)) - ;; - "seconds" ) - retry_interval_seconds=$retry_interval - ;; - * ) - #default is seconds - retry_interval_seconds=$retry_interval - ;; +# retry_interval if error was detected retry in +# retry_unit 'days' 'hours' 'minutes' 'seconds' +# retry_count #NEW# number of retries before scripts stops +# +# use_ipv6 #NEW# detecting/sending IPv6 address +# force_ipversion #NEW# force usage of IPv4 or IPv6 for the whole detection and update communication +# dns_server #NEW# using a non default dns server to get Registered IP from Internet +# force_dnstcp #NEW# force communication with DNS server via TCP instead of default UDP +# proxy #NEW# using a proxy for communication !!! ALSO used to detect local IP via web => return proxy's IP !!! +# use_logfile #NEW# self-explanatory "/var/log/ddns/$SECTION_ID.log" +# +# some functionality needs +# - GNU Wget or cURL installed for sending updates to DDNS service +# - BIND host installed to detect Registered IP +# +################################################################################ + +# verify and load SECTION_ID is exists +[ "$(uci_get ddns $SECTION_ID)" != "service" ] && { + [ $VERBOSE_MODE -le 1 ] && VERBOSE_MODE=2 # force console out and logfile output + [ -f $LOGFILE ] && rm -f $LOGFILE # clear logfile before first entry + verbose_echo "\n ************** =: ************** ************** **************" + verbose_echo " STARTED =: PID '$$' at $(eval $DATE_PROG)" + verbose_echo " UCI CONFIG =:\n$(uci -q show ddns | grep '=service' | sort)" + critical_error "Service '$SECTION_ID' not defined" +} +load_all_config_options "ddns" "$SECTION_ID" + +verbose_echo "\n ************** =: ************** ************** **************" +verbose_echo " STARTED =: PID '$$' at $(eval $DATE_PROG)" +syslog_info "Started" +case $VERBOSE_MODE in + 0) verbose_echo " verbose mode =: '0' - run normal, NO console output";; + 1) verbose_echo " verbose mode =: '1' - run normal, console mode";; + 2) verbose_echo " verbose mode =: '2' - run once, NO retry on error";; + 3) verbose_echo " verbose mode =: '3' - run once, NO retry on error, NOT sending update";; + *) critical_error "ERROR detecting VERBOSE_MODE '$VERBOSE_MODE'" esac +verbose_echo " UCI CONFIG =:\n$(uci -q show ddns.$SECTION_ID | sort)" + +# set defaults if not defined +[ -z "$enabled" ] && enabled=0 +[ -z "$retry_count" ] && retry_count=5 +[ -z "$use_syslog" ] && use_syslog=0 # not use syslog +[ -z "$use_https" ] && use_https=0 # not use https +[ -z "$use_logfile" ] && use_logfile=1 # NEW - use logfile by default +[ -z "$use_ipv6" ] && use_ipv6=0 # NEW - use IPv4 by default +[ -z "$force_ipversion" ] && force_ipversion=0 # NEW - default let system decide +[ -z "$force_dnstcp" ] && force_dnstcp=0 # NEW - default UDP +[ -z "$ip_source" ] && ip_source="network" +[ "$ip_source" = "network" -a -z "$ip_network" -a $use_ipv6 -eq 0 ] && ip_network="wan" # IPv4: default wan +[ "$ip_source" = "network" -a -z "$ip_network" -a $use_ipv6 -eq 1 ] && ip_network="wan6" # IPv6: default wan6 +[ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 0 ] && ip_url="http://checkip.dyndns.com" +[ "$ip_source" = "web" -a -z "$ip_url" -a $use_ipv6 -eq 1 ] && ip_url="http://checkipv6.dyndns.com" +[ "$ip_source" = "interface" -a -z "$ip_interface" ] && ip_interface="eth1" + +# check configuration and enabled state +[ -z "$domain" -o -z "$username" -o -z "$password" ] && critical_error "Service Configuration not correctly configured" +[ $enabled -eq 0 ] && critical_error "Service Configuration is disabled" + +# verify script if configured and executable +if [ "$ip_source" = "script" ]; then + [ -z "$ip_script" ] && critical_error "No script defined to detect local IP" + [ -x "$ip_script" ] || critical_error "Script to detect local IP not found or not executable" +fi +# compute update interval in seconds +get_seconds CHECK_SECONDS ${check_interval:-10} ${check_unit:-"minutes"} # default 10 min +get_seconds FORCE_SECONDS ${force_interval:-72} ${force_unit:-"hours"} # default 3 days +get_seconds RETRY_SECONDS ${retry_interval:-60} ${retry_unit:-"seconds"} # default 60 sec +verbose_echo "check interval =: $CHECK_SECONDS seconds" +verbose_echo "force interval =: $FORCE_SECONDS seconds" +verbose_echo "retry interval =: $RETRY_SECONDS seconds" +verbose_echo " retry counter =: $retry_count times" -verbose_echo "force seconds = $force_interval_seconds" -verbose_echo "check seconds = $check_interval_seconds" +# determine what update url we're using if a service_name is supplied +# otherwise update_url is set inside configuration (custom service) +[ -n "$service_name" ] && get_service_url update_url +[ -z "$update_url" ] && critical_error "no update url found/defined" #kill old process if it exists & set new pid file -if [ -d /var/run/dynamic_dns ] -then +if [ -d $RUNDIR ]; then #if process is already running, stop it - if [ -e "/var/run/dynamic_dns/$service_id.pid" ] - then - old_pid=$(cat /var/run/dynamic_dns/$service_id.pid) - test_match=$(ps | grep "^[\t ]*$old_pid") - verbose_echo "old process id (if it exists) = \"$test_match\"" - if [ -n "$test_match" ] - then - kill $old_pid - fi + if [ -e "$PIDFILE" ]; then + OLD_PID=$(cat $PIDFILE) + ps | grep -q "^[\t ]*$OLD_PID" && { + verbose_echo " old process =: PID '$OLD_PID'" + kill $OLD_PID + } || verbose_echo "old process id =: PID 'none'" + else + verbose_echo "old process id =: PID 'none'" fi - else #make dir since it doesn't exist - mkdir /var/run/dynamic_dns + mkdir -p $RUNDIR + verbose_echo "old process id =: PID 'none'" fi -echo $$ > /var/run/dynamic_dns/$service_id.pid - - - - -#determine when the last update was -current_time=$(monotonic_time) -last_update=$(( $current_time - (2*$force_interval_seconds) )) -if [ -e "/var/run/dynamic_dns/$service_id.update" ] -then - last_update=$(cat /var/run/dynamic_dns/$service_id.update) +echo $$ > $PIDFILE + +# determine when the last update was +# the following lines should prevent multiple updates if hotplug fires multiple startups +# as described in Ticket #7820, but did not function if never an update take place +# i.e. after a reboot (/var is linked to /tmp) +# using uptime as reference because date might not be updated via NTP client +get_uptime CURR_TIME +[ -e "$UPDFILE" ] && { + LAST_TIME=$(cat $UPDFILE) + # check also LAST > CURR because link of /var/run to /tmp might be removed + # i.e. boxes with larger filesystems + [ -z "$LAST_TIME" ] && LAST_TIME=0 + [ $LAST_TIME -gt $CURR_TIME ] && LAST_TIME=0 +} +if [ $LAST_TIME -eq 0 ]; then + verbose_echo " last update =: never" +else + EPOCH_TIME=$(( $(date +%s) - CURR_TIME + LAST_TIME )) + EPOCH_TIME="date -d @$EPOCH_TIME +'$DATE_FORMAT'" + verbose_echo " last update =: $(eval $EPOCH_TIME)" fi -time_since_update=$(($current_time - $last_update)) - - -human_time_since_update=$(( $time_since_update / ( 60 * 60 ) )) -verbose_echo "time_since_update = $human_time_since_update hours" - - -#do update and then loop endlessly, checking ip every check_interval and forcing an updating once every force_interval - -while [ true ] -do - registered_ip=$(echo $(nslookup "$domain" 2>/dev/null) | grep -o "Name:.*" | grep -o "$ip_regex") - current_ip=$(get_current_ip) - - - current_time=$(monotonic_time) - time_since_update=$(($current_time - $last_update)) - - syslog_echo "Running IP check ..." - verbose_echo "Running IP check..." - verbose_echo "current system ip = $current_ip" - verbose_echo "registered domain ip = $registered_ip" - - - if [ "$current_ip" != "$registered_ip" ] || [ $force_interval_seconds -lt $time_since_update ] - then - verbose_echo "update necessary, performing update ..." - - #do replacement - final_url=$update_url - for option_var in $ALL_OPTION_VARIABLES - do - if [ "$option_var" != "update_url" ] - then - replace_name=$(echo "\[$option_var\]" | tr 'a-z' 'A-Z') - replace_value=$(eval echo "\$$option_var") - replace_value=$(echo $replace_value | sed -f /usr/lib/ddns/url_escape.sed) - final_url=$(echo $final_url | sed s^"$replace_name"^"$replace_value"^g ) +# we need time here because hotplug.d is fired by netifd +# but IP addresses are not set by DHCP/DHCPv6 etc. +verbose_echo " waiting =: 10 seconds for interfaces to fully come up" +sleep 10 + +# verify DNS server +[ -n "$dns_server" ] && { + verbose_echo "******* VERIFY =: DNS server '$dns_server'" + verify_dns "$dns_server" + case $? in + 0) ;; # everything OK + 2) critical_error "Invalid DNS server Error: '2' - nslookup can not resolve host";; + 3) critical_error "Invalid DNS server Error: '3' - nc (netcat) can not connect";; + 4) critical_error "Invalid DNS server Error: '4' - Forced IP Version don't matched";; + *) critical_error "Invalid DNS server Error: '1' - unspecific error";; + esac +} + +# verify Proxy server and set environment +[ -n "$proxy" ] && { + verbose_echo "******* VERIFY =: Proxy server 'http://$proxy'" + verify_proxy "$proxy" + case $? in + 0) # everything OK + export HTTP_PROXY="http://$proxy" + export HTTPS_PROXY="http://$proxy" + export http_proxy="http://$proxy" + export https_proxy="http://$proxy" + ;; + 2) critical_error "Invalid Proxy server Error: '2' - nslookup can not resolve host";; + 3) critical_error "Invalid Proxy server Error: '3' - nc (netcat) can not connect";; + 4) critical_error "Invalid Proxy server Error: '4' - Forced IP Version don't matched";; + 5) critical_error "Invalid Proxy server Error: '5' - proxy port missing";; + *) critical_error "Invalid Proxy server Error: '1' - unspecific error";; + esac +} + +# let's check if there is already an IP registered at the web +# but ignore errors if not +verbose_echo "******* DETECT =: Registered IP" +get_registered_ip REGISTERED_IP + +# loop endlessly, checking ip every check_interval and forcing an updating once every force_interval +# NEW: ### Luci Ticket 538 +# a "force_interval" of "0" will run this script only once +# the update is only done once when an interface goes up +# or you run /etc/init.d/ddns start or you can use a cron job +# it will force an update without check when lastupdate happen +# but it will verify after "check_interval" if update is seen in the web +# and retries on error retry_count times +# CHANGES: ### Ticket 16363 +# modified nslookup / sed / grep to detect registered ip +# NEW: ### Ticket 7820 +# modified nslookup to support non standard dns_server (needs to be defined in /etc/config/ddns) +# support for BIND host command. +# Wait for interface to fully come up, before the first update is done +verbose_echo "*** START LOOP =: $(eval $DATE_PROG)" +# we run NOT once +[ $FORCE_SECONDS -gt 0 -o $VERBOSE_MODE -le 1 ] && syslog_info "Starting main loop" + +while : ; do + + # read local IP + verbose_echo "******* DETECT =: Local IP" + get_local_ip LOCAL_IP + ERR_LAST=$? # save return value + # Error in function + [ $ERR_LAST -gt 0 ] && { + if [ $VERBOSE_MODE -le 1 ]; then # VERBOSE_MODE <= 1 then retry + # we can't read local IP + ERR_LOCAL_IP=$(( $ERR_LOCAL_IP + 1 )) + [ $ERR_LOCAL_IP -gt $retry_count ] && critical_error "Can not detect local IP" + verbose_echo "\n!!!!!!!!! ERROR =: detecting local IP - retry $ERR_LOCAL_IP/$retry_count in $RETRY_SECONDS seconds\n" + syslog_err "Error detecting local IP - retry $ERR_LOCAL_IP/$retry_count in $RETRY_SECONDS seconds" + sleep $RETRY_SECONDS + continue # jump back to the beginning of while loop + else + verbose_echo "\n!!!!!!!!! ERROR =: detecting local IP - NO retry\n" + fi + } + ERR_LOCAL_IP=0 # reset err counter + + # prepare update + # never updated or forced immediate then NEXT_TIME = 0 + [ $FORCE_SECONDS -eq 0 -o $LAST_TIME -eq 0 ] \ + && NEXT_TIME=0 \ + || NEXT_TIME=$(( $LAST_TIME + $FORCE_SECONDS )) + # get current uptime + get_uptime CURR_TIME + + # send update when current time > next time or local ip different from registered ip (as loop on error) + ERR_SEND=0 + while [ $CURR_TIME -ge $NEXT_TIME -o "$LOCAL_IP" != "$REGISTERED_IP" ]; do + if [ $VERBOSE_MODE -gt 2 ]; then + verbose_echo " VERBOSE MODE =: NO UPDATE send to DDNS provider" + elif [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then + verbose_echo "******* UPDATE =: LOCAL: '$LOCAL_IP' <=> REGISTERED: '$REGISTERED_IP'" + else + verbose_echo "******* FORCED =: LOCAL: '$LOCAL_IP' == REGISTERED: '$REGISTERED_IP'" + fi + # only send if VERBOSE_MODE < 3 + ERR_LAST=0 + [ $VERBOSE_MODE -lt 3 ] && { + send_update "$LOCAL_IP" + ERR_LAST=$? # save return value + } + + # Error in function + if [ $ERR_LAST -gt 0 ]; then + if [ $VERBOSE_MODE -le 1 ]; then # VERBOSE_MODE <=1 then retry + # error sending local IP + ERR_SEND=$(( $ERR_SEND + 1 )) + [ $ERR_SEND -gt $retry_count ] && critical_error "can not send update to DDNS Provider" + verbose_echo "\n!!!!!!!!! ERROR =: sending update - retry $ERR_SEND/$retry_count in $RETRY_SECONDS seconds\n" + syslog_err "Error sending update - retry $ERR_SEND/$retry_count in $RETRY_SECONDS seconds" + sleep $RETRY_SECONDS + continue # re-loop + else + verbose_echo "\n!!!!!!!!! ERROR =: sending update to DDNS service - NO retry\n" + break fi - done - final_url=$(echo $final_url | sed s^"\[HTTPAUTH\]"^"${username//^/\\^}${password:+:${password//^/\\^}}"^g ) - final_url=$(echo $final_url | sed s/"\[IP\]"/"$current_ip"/g ) - - - verbose_echo "updating with url=\"$final_url\"" - - #here we actually connect, and perform the update - update_output=$( $retrieve_prog "$final_url" ) - if [ $? -gt 0 ] - then - syslog_echo "update failed, retrying in $retry_interval_seconds seconds" - verbose_echo "update failed" - sleep $retry_interval_seconds - continue + else + # we send data so save "last time" + get_uptime LAST_TIME + echo $LAST_TIME > $UPDFILE # save LASTTIME to file + [ "$LOCAL_IP" != "$REGISTERED_IP" ] \ + && syslog_notice "Changed IP: '$LOCAL_IP' successfully send" \ + || syslog_notice "Forced Update: IP: '$LOCAL_IP' successfully send" + break # leave while fi - syslog_echo "Update successful" - verbose_echo "Update Output:" - verbose_echo "$update_output" - verbose_echo "" - - #save the time of the update - current_time=$(monotonic_time) - last_update=$current_time - time_since_update='0' - registered_ip=$current_ip - - human_time=$(date) - verbose_echo "update complete, time is: $human_time" + done - echo "$last_update" > "/var/run/dynamic_dns/$service_id.update" - else - human_time=$(date) - human_time_since_update=$(( $time_since_update / ( 60 * 60 ) )) - verbose_echo "update unnecessary" - verbose_echo "time since last update = $human_time_since_update hours" - verbose_echo "the time is now $human_time" - fi + # now we wait for check interval before testing if update was recognized + # only sleep if VERBOSE_MODE <= 2 because nothing send so do not wait + [ $VERBOSE_MODE -le 2 ] && { + verbose_echo "****** WAITING =: $CHECK_SECONDS seconds (Check Interval) before continue" + sleep $CHECK_SECONDS + } || verbose_echo " VERBOSE MODE =: NO WAITING for Check Interval\n" + + # read at DDNS service registered IP (in loop on error) + REGISTERED_IP="" + ERR_REG_IP=0 + while : ; do + verbose_echo "******* DETECT =: Registered IP" + get_registered_ip REGISTERED_IP + ERR_LAST=$? # save return value + + # No Error in function we leave while loop + [ $ERR_LAST -eq 0 ] && break + + # we can't read Registered IP + if [ $VERBOSE_MODE -le 1 ]; then # VERBOSE_MODE <=1 then retry + ERR_REG_IP=$(( $ERR_REG_IP + 1 )) + [ $ERR_REG_IP -gt $retry_count ] && critical_error "can not detect registered local IP" + verbose_echo "\n!!!!!!!!! ERROR =: detecting Registered IP - retry $ERR_REG_IP/$retry_count in $RETRY_SECONDS seconds\n" + syslog_err "Error detecting Registered IP - retry $ERR_REG_IP/$retry_count in $RETRY_SECONDS seconds" + sleep $RETRY_SECONDS + else + verbose_echo "\n!!!!!!!!! ERROR =: detecting Registered IP - NO retry\n" + break # leave while loop + fi + done - #sleep for 10 minutes, then re-check ip && time since last update - sleep $check_interval_seconds + # IP's are still different + if [ "$LOCAL_IP" != "$REGISTERED_IP" ]; then + if [ $VERBOSE_MODE -le 1 ]; then # VERBOSE_MODE <=1 then retry + ERR_UPDATE=$(( $ERR_UPDATE + 1 )) + [ $ERR_UPDATE -gt $retry_count ] && critical_error "Registered IP <> Local IP - LocalIP: '$LOCAL_IP' - RegisteredIP: '$REGISTERED_IP'" + verbose_echo "\n!!!!!!!!! ERROR =: Registered IP <> Local IP - starting retry $ERR_UPDATE/$retry_count\n" + syslog_warn "Warning: Registered IP <> Local IP - starting retry $ERR_UPDATE/$retry_count" + continue # loop to beginning + else + verbose_echo "\n!!!!!!!!! ERROR =: Registered IP <> Local IP - LocalIP: '$LOCAL_IP' - RegisteredIP: '$REGISTERED_IP' - NO retry\n" + fi + fi + + # we checked successful the last update + ERR_UPDATE=0 # reset error counter + + # force_update=0 or VERBOSE_MODE > 1 - leave the main loop + [ $FORCE_SECONDS -eq 0 -o $VERBOSE_MODE -gt 1 ] && { + verbose_echo "****** LEAVING =: $(eval $DATE_PROG)" + syslog_info "Leaving" + break + } + verbose_echo "********* LOOP =: $(eval $DATE_PROG)" + syslog_info "Rerun IP check" done -#should never get here since we're a daemon, but I'll throw it in anyway -return 0 - - - +verbose_echo "****** STOPPED =: PID '$$' at $(eval $DATE_PROG)\n" +syslog_info "Done" +exit 0 diff --git a/net/ddns-scripts/files/usr/lib/ddns/services_ipv6 b/net/ddns-scripts/files/usr/lib/ddns/services_ipv6 new file mode 100644 index 000000000..bc64cd0cb --- /dev/null +++ b/net/ddns-scripts/files/usr/lib/ddns/services_ipv6 @@ -0,0 +1,29 @@ +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# !!!!! IPv6 Version of original services file !!!!! +# !!!!! funtionally and syntax is the same !!!!! +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# This file contains the update urls for various dynamic dns services. +# Column one contains the service name, column two contains the update url. +# within the update url there are 4 variables you can use: [USERNAME], +# [PASSWORD], [DOMAIN] and [IP]. These are substituted for the username, +# password, and domain name specified in the /etc/config/ddns file when an +# update is performed. The IP is substituted for the current ip address of the +# router. These variables are case sensitive, while urls generally are not, so +# if you need to enter the same text in the url (which seems very unlikely) put +# that text in lowercase, while the variables should remain in uppercase + +# tested with + +# Securepoint Dynamic-DNS-Service +"spdns.de" "http://[USERNAME]:[PASSWORD]@update.spdns.de/nic/update?hostname=[DOMAIN]&myip=[IP]" + +# Hurricane Electric Dynamic DNS +"he.net" "http://[DOMAIN]:[PASSWORD]@dyn.dns.he.net/nic/update?hostname=[DOMAIN]&myip=[IP]" + +#### ADD YOURS HERE! ###################################################################################### +# # +# There are TONS of dynamic dns services out there. There's a huge list of them at: # +# http://www.dmoz.org/Computers/Software/Internet/Servers/Address_Management/Dynamic_DNS_Services/ # +# If anyone has time they could update this file to be compatible with a bunch of them # +# # +########################################################################################################### diff --git a/net/ddns-scripts/files/usr/lib/ddns/url_escape.sed b/net/ddns-scripts/files/usr/lib/ddns/url_escape.sed deleted file mode 100644 index eac400265..000000000 --- a/net/ddns-scripts/files/usr/lib/ddns/url_escape.sed +++ /dev/null @@ -1,25 +0,0 @@ -# sed url escaping -s:%:%25:g -s: :%20:g -s:<:%3C:g -s:>:%3E:g -s:#:%23:g -s:{:%7B:g -s:}:%7D:g -s:|:%7C:g -s:\\:%5C:g -s:\^:%5E:g -s:~:%7E:g -s:\[:%5B:g -s:\]:%5D:g -s:`:%60:g -s:;:%3B:g -s:/:%2F:g -s:?:%3F:g -s^:^%3A^g -s:@:%40:g -s:=:%3D:g -s:&:%26:g -s:\$:%24:g -s:\!:%21:g -s:\*:%2A:g