ddns-scripts: Update to Version 2.0.1-1
authorSteven Barth <steven@midlink.org>
Mon, 22 Sep 2014 05:59:19 +0000 (07:59 +0200)
committerSteven Barth <steven@midlink.org>
Mon, 22 Sep 2014 06:00:53 +0000 (08:00 +0200)
Squashed commit of the following:

commit fc1d42f069ff930180c5f067c2eb88c9e9df7003
Author: Christian Schoenebeck <christian.schoenebeck@gmail.com>
Date:   Sun Sep 21 18:01:43 2014 +0200

    [ddns-scripts] Update to Version 2.0.1-1

commit 731f9b4df00a8f29df2c17f102356c4d6980918a
Author: Christian Schoenebeck <christian.schoenebeck@gmail.com>
Date:   Sun Sep 21 17:59:25 2014 +0200

    [ddns-scripts] Update to Version 2.0.1-1

    personal helper script to create hashes for CA-Certificates for Wget and cURL using https protocol without errors.

    Sorry GitHub web interface only supports single commits that will be summarized into one pull request.
Signed-off-by: Christian Schoenebeck <christian.schoenebeck@gmail.com>
commit df8f6c9d5d31fde24fe1d673949d272d887505e1
Author: Christian Schoenebeck <christian.schoenebeck@gmail.com>
Date:   Sun Sep 21 17:56:05 2014 +0200

    [ddns-scripts] Update to Version 2.0.1-1

    rewritten

    Sorry GitHub web interface only supports single commits that will be summarized into one pull request.
Signed-off-by: Christian Schoenebeck <christian.schoenebeck@gmail.com>
commit 50cdf5acb9caecfd9b65ab79696c40fb2bc7037b
Author: Christian Schoenebeck <christian.schoenebeck@gmail.com>
Date:   Sun Sep 21 17:54:40 2014 +0200

    [ddns-scripts] Update to Version 2.0.1-1

    rewritten

    Sorry GitHub web interface only supports single commits that will be summarized into one pull request.
Signed-off-by: Christian Schoenebeck <christian.schoenebeck@gmail.com>
commit b1d650a345fb06402c1eac01138cbafcca123a8c
Author: Christian Schoenebeck <christian.schoenebeck@gmail.com>
Date:   Sun Sep 21 17:52:52 2014 +0200

    [ddns-scripts] Update to Version 2.0.1-1

    not needed in this version

    Sorry GitHub web interface only supports single commits that will be summarized into one pull request.
Signed-off-by: Christian Schoenebeck <christian.schoenebeck@gmail.com>
commit 9532114b03d428a3162b16e06706d3aa50e601bb
Author: Christian Schoenebeck <christian.schoenebeck@gmail.com>
Date:   Sun Sep 21 17:51:39 2014 +0200

    [ddns-scripts] Update to Version 2.0.1-1

    same function as existing services file but used for IPv6

    Sorry GitHub web interface only supports single commits that will be summarized into one pull request.
Signed-off-by: Christian Schoenebeck <christian.schoenebeck@gmail.com>
commit a636bc25c62e23694c009886c13253c9cecc548c
Author: Christian Schoenebeck <christian.schoenebeck@gmail.com>
Date:   Sun Sep 21 17:46:56 2014 +0200

    [ddns-scripts] Update to Version 2.0.1-1

    New file explaining availible parameters.

    Sorry GitHub web interface only supports single commits that will be summarized into one pull request.
Signed-off-by: Christian Schoenebeck <christian.schoenebeck@gmail.com>
commit 52332354fc245861e17c898aa6b806f6c174e9a5
Author: Christian Schoenebeck <christian.schoenebeck@gmail.com>
Date:   Sun Sep 21 17:44:45 2014 +0200

    [ddns-scripts] Update to Version 2.0.1-1

    Sorry GitHub web interface only supports single commits that will be summarized into one pull request.

Signed-off-by: Christian Schoenebeck <christian.schoenebeck@gmail.com>
net/ddns-scripts/Makefile
net/ddns-scripts/files/etc/config/ddns
net/ddns-scripts/files/etc/config/ddns.sample [new file with mode: 0644]
net/ddns-scripts/files/usr/lib/ddns/create_cert_hashes.sh [new file with mode: 0644]
net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_functions.sh
net/ddns-scripts/files/usr/lib/ddns/dynamic_dns_updater.sh
net/ddns-scripts/files/usr/lib/ddns/services_ipv6 [new file with mode: 0644]
net/ddns-scripts/files/usr/lib/ddns/url_escape.sed [deleted file]

index 5104c9d60c54609cfd3bfdfd3a31d91d1a23461d..7fcb2cc805f66c966b122c3133b3e3bb3b306125 100644 (file)
@@ -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 <christian.schoenebeck@gmail.com>
+    SECTION:=net
+    CATEGORY:=Network
+    SUBMENU:=IP Addresses and Names
+    TITLE:=Dynamic DNS Scripts (with IPv6 support)
+    PKGARCH:=all
+    MAINTAINER:=Christian Schoenebeck <christian.schoenebeck@gmail.com>
 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))
index e7337b5ff7dbc444babd2750a729e5b500a4c5ec..54ec42c125d7e380bafeab5c83a588932b299833 100644 (file)
@@ -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 (file)
index 0000000..c207728
--- /dev/null
@@ -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 (file)
index 0000000..ff788c3
--- /dev/null
@@ -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
index 6e498144811fc510594098dd9c9cf22b87d4f238..057fe9258d765aec8e34f0be2f8488d258ea56d4 100644 (file)
-# /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 >/dev/null 2>&1"
+               __ERRPROG="nc -vw 1 $__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'\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 >/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%%.*}'"
 }
index e6d298740d973de2624754a68749ff7eb23cfbd4..20e40b310de3f4db0ac7e50b61a72182cd56c495 100755 (executable)
 #!/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 <variable> $SECTION_ID <option]>
 #
-#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 (file)
index 0000000..bc64cd0
--- /dev/null
@@ -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 (file)
index eac4002..0000000
+++ /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