as the line terminator (some proxies are strict about this)
+
+- 2.2
+- added support (-static) for updating static DNS (thanks Marc Sira)
+- changed ddclient.cache format (old style is still read)
+- sample-etc_ppp_ip-up.local - detect improper calling sequences
+- sample-etc_ppp_ip-up.local - local ip address is $3 or $PPP_LOCAL (for debian)
+
+- 2.1.2
+- updated README
+
+- 2.1.1
+- make sure result code reflects any failures
+- optionally (-quiet) omit messages for unnecessary updates
+- update sample-etc_cron.d_ddclient to use -quiet
+
+- 2.1
+- avoid unnecessary updates by recording the last hosts updated in a
+ cache file (default /etc/ddclient.cache)
+
+- optionally (-force) force an update, even if it may be unnecessary.
+
+ This can be used to prevent dyndns.org from deleting a host that has not
+ required an update for a long period of time.
+
+- optionally (-refresh), reissue all host updates.
+
+ This can be used together with cron to periodically update DynDNS.
+ See sample-etc-cron.d-ddclient for details.
+
+- optionally (-retry) save failed updates for future processing.
+
+ This feature can be used to reissue updates that may have failed due to
+ network connectivity problems or a DynDNS server outage
+
+-------------------------------------------------------------------------------
+$Id$
diff --git a/svn/README b/svn/README
new file mode 100644
index 0000000..f70e0c9
--- /dev/null
+++ b/svn/README
@@ -0,0 +1,173 @@
+===============================================================================
+DDCLIENT v3.6.7
+
+ddclient is a Perl client used to update dynamic DNS entries for accounts
+on many dynamic DNS services.
+
+IMPORTANT: The format and options used by ddclient have CHANGED.
+ Please examine the sample configuration file.
+ It is highly recommended that you take advantage of the
+ new daemon mode of operation.
+
+IMPORTANT: The installation location for the ddclient script has changed from
+ /root/bin/
+ to
+ /usr/sbin/
+
+===============================================================================
+
+Dynamic DNS services currently supported include:
+
+DynDNS.org - See http://www.dyndns.org for details on obtaining a free account.
+Hammernode - See http://www.hn.org for details on obtaining a free account.
+Zoneedit - See http://www.zoneedit.com for details.
+EasyDNS - See http://www.easydns.com for details.
+NameCheap - See http://www.namecheap.com for details
+ConCont - See http://www.dydns.za.net for details
+DnsPark - See http://www.dnspark.com for details
+DslReports - See http://www.dslreports.com for details
+Sitelutions - see http://www.sitelutions.com for details
+
+DDclient now supports many of cable/dsl broadband routers.
+
+Comments, suggestions and requests: use the forums on
+ http://sourceforge.net/projects/ddclient/
+
+The code was originally written by Paul Burry and is now hosted and maintained
+through sf.net. Please check out http://ddclient.sf.net and the project page.
+
+-------------------------------------------------------------------------------
+REQUIREMENTS:
+
+- one or more accounts from one of the dynamic DNS services
+
+- Perl 5.004 or later
+ (you need the IO::Socket::SSL perl library for ssl-support)
+
+- Linux or probably any common Unix system
+
+-------------------------------------------------------------------------------
+INSTALLATION:
+
+ cp ddclient /usr/sbin/
+ mkdir /etc/ddclient
+ cp sample-etc_ddclient.conf /etc/ddclient/ddclient.conf
+ vi /etc/ddclient/ddclient.conf
+ -- and change hostnames, logins, and passwords appropriately
+
+ ## For those using Redhat style rc files and using daemon-mode:
+ cp sample-etc_rc.d_init.d_ddclient /etc/rc.d/init.d/ddclient
+ ## enable automatic startup when booting
+ /sbin/chkconfig --add ddclient
+ ## start the first time by hand
+ /etc/rc.d/init.d/ddclient start
+
+ ## If you are not using daemon-mode, configure cron and dhcp or ppp
+ ## as described below.
+
+-------------------------------------------------------------------------------
+TROUBLESHOOTING:
+
+ 1. enable debugging and verbose messages.
+ $ ddclient -daemon=0 -debug -verbose -noquiet
+
+ 2. Do you need to specify a proxy?
+ If so, just add a
+ proxy=your.isp.proxy
+ to the ddclient.conf file.
+
+ 3. Define the IP address of your router with fw=xxx.xxx.xxx.xxx in
+ /etc/ddclient/ddclient.conf and then try
+ $ ddclient -daemon=0 -query
+ to see if the router status web page can be understood.
+
+ 4. Need support for another router/firewall?
+ Define the router status page yourself with:
+ fw=url-to-your-router's-status-page
+ fw-skip=any-string-preceding-your-IP-address
+
+ ddclient does something like this to provide builtin support for
+ common routers.
+ For example, the Linksys routers could have been added with:
+ fw=192.168.1.1/Status.htm
+ fw-skip=WAN.*?IP Address
+
+ OR
+ Send me the output from:
+ $ ddclient -geturl {fw-ip-status-url} [-login login [-password password]]
+ and I'll add it to the next release!
+
+ ie. for my fw/router I used:
+ $ ddclient -geturl 192.168.1.254/status.htm
+
+ 5. Some broadband routers require the use of a password when ddclient
+ accesses its status page to determine the router's WAN IP address.
+ If this is the case for your router, add
+ fw-login=your-router-login
+ fw-password=your-router-password
+ to the beginning of your ddclient.conf file.
+ Note that some routers use either 'root' or 'admin' as their login
+ while some others accept anything.
+
+-------------------------------------------------------------------------------
+USING DDCLIENT WITH ppp
+
+If you are using a ppp connection, you can easily update your DynDNS
+entry with each connection, with:
+ ## configure pppd to update DynDNS with each connection
+ cp sample-etc_ppp_ip-up.local /etc/ppp/ip-up.local
+
+Alternatively, you may just configure ddclient to operate as a daemon
+and monitor your ppp interface.
+
+-------------------------------------------------------------------------------
+USING DDCLIENT WITH cron
+
+If you have not configured ddclient to use daemon-mode, you'll need to
+configure cron to force an update once a month so that the dns entry will
+not become stale.
+
+ ## configure cron to force an update twice a month
+ cp sample-etc_cron.d_ddclient /etc/cron.d/ddclient
+ vi /etc/cron.d/ddclient
+
+-------------------------------------------------------------------------------
+USING DDCLIENT WITH dhcpcd-1.3.17
+
+If you are using dhcpcd-1.3.17 or thereabouts, you can easily update
+your DynDNS entry automatically every time your lease is obtained
+or renewed by creating an executable file named:
+ /etc/dhcpc/dhcpcd-{your-interface}.exe
+ie.:
+ cp sample-etc_dhcpc_dhcpcd-eth0.exe /etc/dhcpc/dhcpcd-{your-interface}.exe
+
+In my case, it is named dhcpcd-eth0.exe and contains the lines:
+ #!/bin/sh
+ PATH=/usr/sbin:/root/bin:${PATH}
+ logger -t dhcpcd IP address changed to $1
+ ddclient -proxy fasthttp.sympatico.ca -wildcard -ip $1 | logger -t ddclient
+ exit 0
+
+Other DHCP clients may have another method of calling out to programs
+for updating DNS entries.
+
+Alternatively, you may just configure ddclient to operate as a daemon
+and monitor your ethernet interface.
+
+-------------------------------------------------------------------------------
+USING DDCLIENT WITH dhclient
+
+If you are using the ISC DHCP client (dhclient), you can update
+your DynDNS entry automatically every time your lease is obtained
+or renewed by creating an executable file named:
+ /etc/dhclient-exit-hooks
+ie.:
+ cp sample-etc_dhclient-exit-hooks /etc/dhclient-exit-hooks
+
+Edit /etc/dhclient-exit-hooks to change any options required.
+
+Alternatively, you may just configure ddclient to operate as a daemon
+and monitor your ethernet interface.
+
+-------------------------------------------------------------------------------
+$Id$
diff --git a/svn/README.cisco b/svn/README.cisco
new file mode 100644
index 0000000..2d7e337
--- /dev/null
+++ b/svn/README.cisco
@@ -0,0 +1,35 @@
+$Id$
+Method 1 ------------------------------------------------------
+
+The following config will allow the Linux machine (10.1.1.2) to read
+the IP address from the DHCP interface on the Cisco router (eth0) as
+user ddclient. Since ddclient is configured with a priv level of 1 it
+cannot do anything except look at the routers stats, ip addresses,
+etc. This should be pretty harmless even if ddclient's password were
+to be discovered.
+
+This has been tested with Cisco IOS 12.1(5)T5 running on a Cisco 2621
+router.
+
+Cisco Router Config (Assuming eth0 is DHCP interface)
+-----------------------------------------------------
+user ddclient password password
+user ddclient priv 1
+ip http auth local
+ip http access-class 99
+ip http port 1021
+ip http server
+access-list 99 permit host 10.1.1.2
+
+DDClient Config
+---------------------------------------------------
+use=cisco, fw=10.1.1.1, if=eth0, fw-login=ddclient, fw-password=password
+
+
+Method 2 ------------------------------------------------------
+
+use=fw
+fw=192.168.1.1/exec/show/interfaces/CR
+fw-skip=FastEthernet0/0
+fw-login=ddclient
+fw-password=xxxxxxxx
diff --git a/svn/README.ssl b/svn/README.ssl
new file mode 100644
index 0000000..70f9ca0
--- /dev/null
+++ b/svn/README.ssl
@@ -0,0 +1,10 @@
+$Id$
+
+Since 3.7.0, ddclient support ssl-updates
+To use ssl, put "ssl=yes" in your configuration and make sure
+you have IO::Socket::SSL.
+
+On debian, you need libio-socket-ssl-perl to have IO::Socket::SSL
+
+ssl support is tested on folowing dynamic dns providers:
+- dyndns.org
diff --git a/svn/ddclient b/svn/ddclient
new file mode 100755
index 0000000..a1996a8
--- /dev/null
+++ b/svn/ddclient
@@ -0,0 +1,3132 @@
+#!/usr/bin/perl -w
+#!/usr/local/bin/perl -w
+######################################################################
+# $Id$
+#
+# DDCLIENT - a Perl client for updating DynDNS information
+#
+# Author: Paul Burry (paul+ddclient@burry.ca)
+# ddclient-developers: see https://sourceforge.net/project/memberlist.php?group_id=116817
+######################################################################
+require 5.004;
+use strict;
+use Getopt::Long;
+use Sys::Hostname;
+use IO::Socket;
+
+my $version = "3.7.0-pre";
+my $programd = $0;
+$programd =~ s%^.*/%%;
+my $program = $programd;
+$program =~ s/d$//;
+my $now = time;
+my $hostname = hostname();
+my $etc = ($program =~ /test/i) ? './' : '/etc/ddclient/';
+my $savedir = ($program =~ /test/i) ? 'URL/' : '/tmp/';
+my $msgs = '';
+my $last_msgs = '';
+
+use vars qw($file $lineno);
+local $file = '';
+local $lineno = '';
+
+$ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:";
+
+sub T_ANY {'any'};
+sub T_STRING {'string'};
+sub T_EMAIL {'e-mail address'};
+sub T_NUMBER {'number'};
+sub T_DELAY {'time delay (ie. 1d, 1hour, 1m)'};
+sub T_LOGIN {'login'};
+sub T_PASSWD {'password'};
+sub T_BOOL {'boolean value'};
+sub T_FQDN {'fully qualified host name'};
+sub T_OFQDN {'optional fully qualified host name'};
+sub T_FILE {'file name'};
+sub T_FQDNP {'fully qualified host name and optional port number'};
+sub T_PROTO {'protocol'}
+sub T_USE {'ip strategy'}
+sub T_IF {'interface'}
+sub T_PROG {'program name'}
+sub T_IP {'ip'}
+sub T_POSTS {'postscript'};
+
+## strategies for obtaining an ip address.
+my %builtinweb = (
+ 'dyndns' => { 'url' => 'http://checkip.dyndns.org/', 'skip' => 'Current IP Address', },
+ 'dnspark' => { 'url' => 'http://ipdetect.dnspark.com/', 'skip' => 'Current Address:', },
+);
+my %builtinfw = (
+ 'watchguard-soho' => {
+ 'name' => 'Watchguard SOHO FW',
+ 'url' => '/pubnet.htm',
+ 'skip' => 'NAME=IPAddress VALUE=',
+ },
+ 'netopia-r910' => {
+ 'name' => 'Netopia R910 FW',
+ 'url' => '/WanEvtLog',
+ 'skip' => 'local:',
+ },
+ 'smc-barricade' => {
+ 'name' => 'SMC Barricade FW',
+ 'url' => '/status.htm',
+ 'skip' => 'IP Address',
+ },
+ 'netgear-rt3xx' => {
+ 'name' => 'Netgear FW',
+ 'url' => '/mtenSysStatus.html',
+ 'skip' => 'IP Address',
+ },
+ 'elsa-lancom-dsl10' => {
+ 'name' => 'ELSA LanCom DSL/10 DSL FW',
+ 'url' => '/config/1/6/8/3/',
+ 'skip' => 'IP.Address',
+ },
+ 'elsa-lancom-dsl10-ch01' => {
+ 'name' => 'ELSA LanCom DSL/10 DSL FW (isdn ch01)',
+ 'url' => '/config/1/6/8/3/',
+ 'skip' => 'IP.Address.*?CH01',
+ },
+ 'elsa-lancom-dsl10-ch02' => {
+ 'name' => 'ELSA LanCom DSL/10 DSL FW (isdn ch01)',
+ 'url' => '/config/1/6/8/3/',
+ 'skip' => 'IP.Address.*?CH02',
+ },
+ 'linksys' => {
+ 'name' => 'Linksys FW',
+ 'url' => '/Status.htm',
+ 'skip' => 'WAN.*?Address',
+ },
+ 'linksys-ver2' => {
+ 'name' => 'Linksys FW version 2',
+ 'url' => '/RouterStatus.htm',
+ 'skip' => 'WAN.*?Address',
+ },
+ 'linksys-ver3' => {
+ 'name' => 'Linksys FW version 3',
+ 'url' => '/Status_Router.htm',
+ 'skip' => 'WAN.*?Address',
+ },
+ 'linksys-wrt854g' => {
+ 'name' => 'Linksys WRT854G FW',
+ 'url' => '/Status_Router.asp',
+ 'skip' => 'IP Address:',
+ },
+ 'maxgate-ugate3x00' => {
+ 'name' => 'MaxGate UGATE-3x00 FW',
+ 'url' => '/Status.htm',
+ 'skip' => 'WAN.*?IP Address',
+ },
+ 'netcomm-nb3' => {
+ 'name' => 'NetComm NB3',
+ 'url' => '/MainPage?id=6',
+ 'skip' => 'ppp-0',
+ },
+ '3com-3c886a' => {
+ 'name' => '3com 3c886a 56k Lan Modem',
+ 'url' => '/stat3.htm',
+ 'skip' => 'IP address in use',
+ },
+ 'sohoware-nbg800' => {
+ 'name' => 'SOHOWare BroadGuard NBG800',
+ 'url' => '/status.htm',
+ 'skip' => 'Internet IP',
+ },
+ 'xsense-aero' => {
+ 'name' => 'Xsense Aero',
+ 'url' => '/A_SysInfo.htm',
+ 'skip' => 'WAN.*?IP Address',
+ },
+ 'alcatel-stp' => {
+ 'name' => 'Alcatel Speed Touch Pro',
+ 'url' => '/cgi/router/',
+ 'skip' => 'Brt',
+ },
+ 'alcatel-510' => {
+ 'name' => 'Alcatel Speed Touch 510',
+ 'url' => '/cgi/ip/',
+ 'skip' => 'ppp',
+ },
+ 'allnet-1298' => {
+ 'name' => 'Allnet 1298',
+ 'url' => '/cgi/router/',
+ 'skip' => 'WAN',
+ },
+ '3com-oc-remote812' => {
+ 'name' => '3com OfficeConnect Remote 812',
+ 'url' => '/callEvent',
+ 'skip' => '.*IP address in use',
+ },
+ 'e-tech' => {
+ 'name' => 'E-tech Router',
+ 'url' => '/Status.htm',
+ 'skip' => 'Public IP Address',
+ },
+ 'cayman-3220h' => {
+ 'name' => 'Cayman 3220-H DSL',
+ 'url' => '/shell/show+ip+interfaces',
+ 'skip' => '.*inet',
+ },
+ 'vigor-2200usb' => {
+ 'name' => 'Vigor 2200 USB',
+ 'url' => '/doc/online.sht',
+ 'skip' => 'PPPoA',
+# 'skip' => 'PPPoA ',
+ },
+ 'dlink-614' => {
+ 'name' => 'D-Link DI-614+',
+ 'url' => '/st_devic.html',
+ 'skip' => 'WAN',
+ },
+ 'dlink-604' => {
+ 'name' => 'D-Link DI-604',
+ 'url' => '/st_devic.html',
+ 'skip' => 'WAN.*?IP.*Address',
+ },
+ 'olitec-SX200' => {
+ 'name' => 'olitec-SX200',
+ 'url' => '/doc/wan.htm',
+ 'skip' => 'st_wan_ip[0] = "',
+ },
+ 'westell-6100' => {
+ 'name' => 'Westell C90-610015-06 DSL Router',
+ 'url' => '/advstat.htm',
+ 'skip' => 'IP.+?Address',
+ },
+ '2wire' => {
+ 'name' => '2Wire 1701HG Gateway',
+ 'url' => '/xslt?PAGE=B01',
+ 'skip' => 'Internet Address:',
+ },
+ 'linksys-rv042-wan1' => {
+ 'name' => 'Linksys RV042 Dual Homed Router WAN Port 2',
+ 'url' => '/home.htm',
+ 'skip' => 'WAN1 IP',
+ },
+ 'linksys-rv042-wan2' => {
+ 'name' => 'Linksys RV042 Dual Homed Router WAN Port 2',
+ 'url' => '/home.htm',
+ 'skip' => 'WAN2 IP',
+ },
+ 'netgear-rp614' => {
+ 'name' => 'Netgear RP614 FW',
+ 'url' => '/sysstatus.html',
+ 'skip' => 'IP Address',
+ },
+ 'watchguard-edge-x' => {
+ 'name' => 'Watchguard Edge X FW',
+ 'url' => '/netstat.htm',
+ 'skip' => 'inet addr:',
+ },
+ 'dlink-524' => {
+ 'name' => 'D-Link DI-524',
+ 'url' => '/st_device.html',
+ 'skip' => 'WAN.*?Addres',
+ },
+ 'rtp300' => {
+ 'name' => 'Linksys RTP300',
+ 'url' => '/cgi-bin/webcm?getpage=%2Fusr%2Fwww_safe%2Fhtml%2Fstatus%2FRouter.html',
+ 'skip' => 'Internet.*?IP Address',
+ },
+ 'netgear-wpn824' => {
+ 'name' => 'Netgear WPN824 FW',
+ 'url' => '/RST_status.htm',
+ 'skip' => 'IP Address',
+ },
+ 'linksys-wcg200' => {
+ 'name' => 'Linksys WCG200 FW',
+ 'url' => '/RgStatus.asp',
+ 'skip' => 'WAN.IP.*?Address',
+ },
+ 'netgear-dg834g' => {
+ 'name' => 'netgear-dg834g',
+ 'url' => '/setup.cgi?next_file=s_status.htm&todo=cfg_init',
+ 'skip' => '',
+ },
+ 'netgear-wgt624' => {
+ 'name' => 'Netgear WGT624',
+ 'url' => '/RST_st_dhcp.htm',
+ 'skip' => 'IP Address | ',
+ },
+ 'sveasoft' => {
+ 'name' => 'Sveasoft WRT54G/WRT54GS',
+ 'url' => '/Status_Router.asp',
+ 'skip' => 'var wan_ip',
+ },
+ 'smc-barricade-7004vbr' => {
+ 'name' => 'SMC Barricade FW (7004VBR model config)',
+ 'url' => '/status_main.stm',
+ 'skip' => 'var wan_ip=',
+ },
+ 'sitecom-dc202' => {
+ 'name' => 'Sitecom DC-202 FW',
+ 'url' => '/status.htm',
+ 'skip' => 'Internet IP Address',
+ },
+);
+my %ip_strategies = (
+ 'ip' => ": obtain IP from -ip {address}",
+ 'web' => ": obtain IP from an IP discovery page on the web",
+ 'fw' => ": obtain IP from the firewall specified by -fw {type|address}",
+ 'if' => ": obtain IP from the -if {interface}",
+ 'cmd' => ": obtain IP from the -cmd {external-command}",
+ 'cisco' => ": obtain IP from Cisco FW at the -fw {address}",
+ map { $_ => sprintf ": obtain IP from %s at the -fw {address}", $builtinfw{$_}->{'name'} } keys %builtinfw,
+);
+sub ip_strategies_usage {
+ return map { sprintf(" -use=%-22s %s.", $_, $ip_strategies{$_}) } sort keys %ip_strategies;
+}
+
+my %web_strategies = (
+ 'dyndns'=> 1,
+ 'dnspark'=> 1,
+);
+
+sub setv {
+ return {
+ 'type' => shift,
+ 'required' => shift,
+ 'cache' => shift,
+ 'config' => shift,
+ 'default' => shift,
+ 'minimum' => shift,
+ };
+};
+my %variables = (
+ 'global-defaults' => {
+ 'daemon' => setv(T_DELAY, 0, 0, 1, 0, interval('60s')),
+ 'file' => setv(T_FILE, 0, 0, 1, "$etc$program.conf", undef),
+ 'cache' => setv(T_FILE, 0, 0, 1, "$etc$program.cache", undef),
+ 'pid' => setv(T_FILE, 0, 0, 1, "", undef),
+ 'proxy' => setv(T_FQDNP, 0, 0, 1, '', undef),
+ 'protocol' => setv(T_PROTO, 0, 0, 1, 'dyndns2', undef),
+
+ 'use' => setv(T_USE, 0, 0, 1, 'ip', undef),
+ 'ip' => setv(T_IP, 0, 0, 1, undef, undef),
+ 'if' => setv(T_IF, 0, 0, 1, 'ppp0', undef),
+ 'if-skip' => setv(T_STRING,1, 0, 1, '', undef),
+ 'web' => setv(T_STRING,0, 0, 1, 'dyndns', undef),
+ 'web-skip' => setv(T_STRING,1, 0, 1, '', undef),
+ 'fw' => setv(T_ANY, 0, 0, 1, '', undef),
+ 'fw-skip' => setv(T_STRING,1, 0, 1, '', undef),
+ 'fw-login' => setv(T_LOGIN, 1, 0, 1, '', undef),
+ 'fw-password' => setv(T_PASSWD,1, 0, 1, '', undef),
+ 'cmd' => setv(T_PROG, 0, 0, 1, '', undef),
+ 'cmd-skip' => setv(T_STRING,1, 0, 1, '', undef),
+
+ 'timeout' => setv(T_DELAY, 0, 0, 1, 0, interval('120s')),
+ 'retry' => setv(T_BOOL, 0, 0, 0, 0, undef),
+ 'force' => setv(T_BOOL, 0, 0, 0, 0, undef),
+ 'ssl' => setv(T_BOOL, 0, 0, 0, 0, undef),
+
+ 'syslog' => setv(T_BOOL, 0, 0, 1, 0, undef),
+ 'facility' => setv(T_STRING,0, 0, 1, 'daemon', undef),
+ 'priority' => setv(T_STRING,0, 0, 1, 'notice', undef),
+ 'mail' => setv(T_EMAIL, 0, 0, 1, '', undef),
+ 'mail-failure' => setv(T_EMAIL, 0, 0, 1, '', undef),
+
+ 'exec' => setv(T_BOOL, 0, 0, 1, 1, undef),
+ 'debug' => setv(T_BOOL, 0, 0, 1, 0, undef),
+ 'verbose' => setv(T_BOOL, 0, 0, 1, 0, undef),
+ 'quiet' => setv(T_BOOL, 0, 0, 1, 0, undef),
+ 'help' => setv(T_BOOL, 0, 0, 1, 0, undef),
+ 'test' => setv(T_BOOL, 0, 0, 1, 0, undef),
+ 'geturl' => setv(T_STRING,0, 0, 0, '', undef),
+
+ 'postscript' => setv(T_POSTS, 0, 0, 1, '', undef),
+ },
+ 'service-common-defaults' => {
+ 'server' => setv(T_FQDNP, 1, 0, 1, 'members.dyndns.org', undef),
+ 'login' => setv(T_LOGIN, 1, 0, 1, '', undef),
+ 'password' => setv(T_PASSWD, 1, 0, 1, '', undef),
+ 'host' => setv(T_STRING, 1, 1, 1, '', undef),
+ 'ip' => setv(T_IP, 0, 1, 0, undef, undef),
+ 'wtime' => setv(T_DELAY, 0, 1, 1, 0, interval('30s')),
+ 'mtime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
+ 'atime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
+ 'status' => setv(T_ANY, 0, 1, 0, '', undef),
+ 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('30s'), 0),
+ 'max-interval' => setv(T_DELAY, 0, 0, 1, interval('15d'), 0),
+ 'min-error-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),
+
+ 'warned-min-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
+ 'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
+ },
+ 'dyndns-common-defaults' => {
+ 'static' => setv(T_BOOL, 0, 1, 1, 0, undef),
+ 'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef),
+ 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef),
+ 'backupmx' => setv(T_BOOL, 0, 1, 1, 0, undef),
+ },
+ 'dnspark-common-defaults' => {
+ 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef),
+ 'mxpri' => setv(T_NUMBER, 0, 0, 1, 5, undef),
+ },
+);
+my %services = (
+ 'dyndns1' => {
+ 'updateable' => \&nic_dyndns2_updateable,
+ 'update' => \&nic_dyndns1_update,
+ 'examples' => \&nic_dyndns1_examples,
+ 'variables' => merge(
+ $variables{'dyndns-common-defaults'},
+ $variables{'service-common-defaults'},
+ ),
+ },
+ 'dyndns2' => {
+ 'updateable' => \&nic_dyndns2_updateable,
+ 'update' => \&nic_dyndns2_update,
+ 'examples' => \&nic_dyndns2_examples,
+ 'variables' => merge(
+ { 'custom' => setv(T_BOOL, 0, 1, 1, 0, undef), },
+# { 'offline' => setv(T_BOOL, 0, 1, 1, 0, undef), },
+ $variables{'dyndns-common-defaults'},
+ $variables{'service-common-defaults'},
+ ),
+ },
+ 'concont' => {
+ 'updateable' => undef,
+ 'update' => \&nic_concont_update,
+ 'examples' => \&nic_concont_examples,
+ 'variables' => merge(
+ $variables{'service-common-defaults'},
+ { 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef), },
+ { 'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef), },
+ ),
+ },
+ 'dslreports1' => {
+ 'updateable' => undef,
+ 'update' => \&nic_dslreports1_update,
+ 'examples' => \&nic_dslreports1_examples,
+ 'variables' => merge(
+ { 'host' => setv(T_NUMBER, 1, 1, 1, 0, undef) },
+ $variables{'service-common-defaults'},
+ ),
+ },
+ 'hammernode1' => {
+ 'updateable' => undef,
+ 'update' => \&nic_hammernode1_update,
+ 'examples' => \&nic_hammernode1_examples,
+ 'variables' => merge(
+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'dup.hn.org', undef) },
+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
+ $variables{'service-common-defaults'},
+ ),
+ },
+ 'zoneedit1' => {
+ 'updateable' => undef,
+ 'update' => \&nic_zoneedit1_update,
+ 'examples' => \&nic_zoneedit1_examples,
+ 'variables' => merge(
+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'www.zoneedit.com', undef) },
+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
+ $variables{'service-common-defaults'},
+ ),
+ },
+ 'easydns' => {
+ 'updateable' => undef,
+ 'update' => \&nic_easydns_update,
+ 'examples' => \&nic_easydns_examples,
+ 'variables' => merge(
+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'members.easydns.com', undef) },
+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
+ $variables{'service-common-defaults'},
+ ),
+ },
+ 'dnspark' => {
+ 'updateable' => undef,
+ 'update' => \&nic_dnspark_update,
+ 'examples' => \&nic_dnspark_examples,
+ 'variables' => merge(
+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'www.dnspark.com', undef) },
+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
+ $variables{'dnspark-common-defaults'},
+ $variables{'service-common-defaults'},
+ ),
+ },
+ 'namecheap' => {
+ 'updateable' => undef,
+ 'update' => \&nic_namecheap_update,
+ 'examples' => \&nic_namecheap_examples,
+ 'variables' => merge(
+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'dynamicdns.park-your-domain.com', undef) },
+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, 0, interval('5m')),},
+ $variables{'service-common-defaults'},
+ ),
+ },
+ 'sitelutions' => {
+ 'updateable' => undef,
+ 'update' => \&nic_sitelutions_update,
+ 'examples' => \&nic_sitelutions_examples,
+ 'variables' => merge(
+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'www.sitelutions.com', undef) },
+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, 0, interval('5m')),},
+ $variables{'service-common-defaults'},
+ ),
+ },
+);
+$variables{'merged'} = merge($variables{'global-defaults'},
+ $variables{'service-common-defaults'},
+ $variables{'dyndns-common-defaults'},
+ map { $services{$_}{'variables'} } keys %services,
+);
+
+my @opt = (
+ "usage: ${program} [options]",
+ "options are:",
+ [ "daemon", "=s", "-daemon delay : run as a daemon" ],
+ [ "proxy", "=s", "-proxy host : use 'host' as the HTTP proxy" ],
+ [ "server", "=s", "-server host : update DNS information on 'host'" ],
+ [ "protocol", "=s", "-protocol type : update protocol used" ],
+ [ "file", "=s", "-file path : load configuration information from 'path'" ],
+ [ "cache", "=s", "-cache path : record address used in 'path'" ],
+ [ "pid", "=s", "-pid path : record process id in 'path'" ],
+ "",
+ [ "use", "=s", "-use which : how the should IP address be obtained." ],
+ &ip_strategies_usage(),
+ "",
+ [ "ip", "=s", "-ip address : set the IP address to 'address'" ],
+ "",
+ [ "if", "=s", "-if interface : obtain IP address from 'interface'" ],
+ [ "if-skip", "=s", "-if-skip pattern : skip any IP addresses before 'pattern' in the output of ifconfig {if}" ],
+ "",
+ [ "web", "=s", "-web provider|url : obtain IP address from provider's IP checking page" ],
+ [ "web-skip", "=s", "-web-skip pattern : skip any IP addresses before 'pattern' on the web provider|url" ],
+ "",
+ [ "fw", "=s", "-fw address|url : obtain IP address from firewall at 'address'" ],
+ [ "fw-skip", "=s", "-fw-skip pattern : skip any IP addresses before 'pattern' on the firewall address|url" ],
+ [ "fw-login", "=s", "-fw-login login : use 'login' when getting IP from fw" ],
+ [ "fw-password", "=s", "-fw-password secret : use password 'secret' when getting IP from fw" ],
+ "",
+ [ "cmd", "=s", "-cmd program : obtain IP address from by calling {program}" ],
+ [ "cmd-skip", "=s", "-cmd-skip pattern : skip any IP addresses before 'pattern' in the output of {cmd}" ],
+ "",
+ [ "login", "=s", "-login user : login as 'user'" ],
+ [ "password", "=s", "-password secret : use password 'secret'" ],
+ [ "host", "=s", "-host host : update DNS information for 'host'" ],
+ "",
+ [ "options", "=s", "-options opt,opt : optional per-service arguments (see below)" ],
+ "",
+ [ "ssl", "!", "-{no}ssl : do updates over encrypted SSL connection" ],
+ [ "retry", "!", "-{no}retry : retry failed updates." ],
+ [ "force", "!", "-{no}force : force an update even if the update may be unnecessary" ],
+ [ "timeout", "=i", "-timeout max : wait at most 'max' seconds for the host to respond" ],
+
+ [ "syslog", "!", "-{no}syslog : log messages to syslog" ],
+ [ "facility", "=s", "-facility {type} : log messages to syslog to facility {type}" ],
+ [ "priority", "=s", "-priority {pri} : log messages to syslog with priority {pri}" ],
+ [ "mail", "=s", "-mail address : e-mail messages to {address}" ],
+ [ "mail-failure","=s", "-mail-failure address : e-mail messages for failed updates to {address}" ],
+ [ "exec", "!", "-{no}exec : do {not} execute; just show what would be done" ],
+ [ "debug", "!", "-{no}debug : print {no} debugging information" ],
+ [ "verbose", "!", "-{no}verbose : print {no} verbose information" ],
+ [ "quiet", "!", "-{no}quiet : print {no} messages for unnecessary updates" ],
+ [ "help", "", "-help : this message" ],
+
+ [ "query", "!", "-{no}query : print {no} ip addresses and exit" ],
+ [ "test", "!", "" ], ## hidden
+ [ "geturl", "=s", "" ], ## hidden
+ "",
+ nic_examples(),
+ "$program version $version, ",
+ " originally written by Paul Burry, paul+ddclient\@burry.ca",
+ " maintained by WimPunk"
+);
+
+## process args
+my ($opt_usage, %opt) = process_args(@opt);
+my ($result, %config, %globals, %cache);
+my $saved_cache = '';
+my %saved_opt = %opt;
+$result = 'OK';
+
+test_geturl(opt('geturl')) if opt('geturl');
+
+## read config file because 'daemon' mode may be defined there.
+read_config(define($opt{'file'}, default('file')), \%config, \%globals);
+init_config();
+test_possible_ip() if opt('query');
+
+if (!opt('daemon') && $programd =~ /d$/) {
+ $opt{'daemon'} = minimum('daemon');
+}
+my $caught_hup = 0;
+my $caught_term = 0;
+$SIG{'HUP'} = sub { $caught_hup = 1; };
+$SIG{'TERM'} = sub { $caught_term = 1; };
+if (opt('daemon') && !opt('force')) {
+ $SIG{'CHLD'} = 'IGNORE';
+ my $pid = fork;
+ if ($pid < 0) {
+ print STDERR "${program}: can not fork ($!)\n";
+ exit -1;
+ } elsif ($pid) {
+ exit 0;
+ }
+ $SIG{'CHLD'} = 'DEFAULT';
+ $opt{'syslog'} = 1;
+ open(STDOUT, ">/dev/null");
+ open(STDERR, ">/dev/null");
+ open(STDIN, " 0) && !$caught_hup && !$caught_term) {
+ my $delay = $left > 10 ? 10 : $left;
+
+ $0 = sprintf("%s - sleeping for %s seconds", $program, $left);
+ $left -= sleep $delay;
+ }
+ $caught_hup = 0;
+ $result = 0;
+
+ } elsif (! scalar(%config)) {
+ warning("no hosts to update.") unless !opt('quiet') || opt('verbose') || !$daemon;
+ $result = 1;
+
+ } else {
+ $result = $result eq 'OK' ? 0 : 1;
+ }
+} while ($daemon && !$result && !$caught_term);
+
+warning("caught SIGTERM; exiting") if $caught_term;
+unlink_pid();
+sendmail();
+
+exit($result);
+
+######################################################################
+## runpostscript
+######################################################################
+
+sub runpostscript {
+ my ($ip) = @_;
+
+ if ( defined $globals{postscript} ) {
+ if ( -x $globals{postscript}) {
+ system ("$globals{postscript} $ip &");
+ } else {
+ warning ("Can not execute post script: %s", $globals{postscript});
+ }
+ }
+}
+
+######################################################################
+## update_nics
+######################################################################
+sub update_nics {
+ my %examined = ();
+
+ foreach my $s (sort keys %services) {
+ my @hosts = ();
+ my $updateable = $services{$s}{'updateable'};
+ my $update = $services{$s}{'update'};
+
+ foreach my $h (sort keys %config) {
+ next if $config{$h}{'protocol'} ne lc($s);
+ $examined{$h} = 1;
+ next if !nic_updateable($h, $updateable);
+ push @hosts, $h;
+ }
+ if (@hosts) {
+ $0 = sprintf("%s - updating %s", $program, join(',', @hosts));
+ &$update(@hosts);
+ runpostscript($ip);
+ }
+ }
+ foreach my $h (sort keys %config) {
+ if (!exists $examined{$h}) {
+ failed("%s was not updated because protocol %s is not supported.",
+ $h, define($config{$h}{'protocol'}, '')
+ );
+ }
+ }
+ write_cache(opt('cache'));
+}
+######################################################################
+## unlink_pid()
+######################################################################
+sub unlink_pid {
+ if (opt('pid') && opt('daemon')) {
+ unlink opt('pid');
+ }
+}
+
+######################################################################
+## write_pid()
+######################################################################
+sub write_pid {
+ my $file = opt('pid');
+
+ if ($file && opt('daemon')) {
+ local *FD;
+ if (! open(FD, "> $file")) {
+ warning("Cannot create file '%s'. ($!)", $file);
+
+ } else {
+ printf FD "$$\n";
+ close(FD);
+ }
+ }
+}
+
+######################################################################
+## write_cache($file)
+######################################################################
+sub write_cache {
+ my ($file) = @_;
+
+ ## merge the updated host entries into the cache.
+ foreach my $h (keys %config) {
+ if (! exists $cache{$h} || $config{$h}{'update'}) {
+ map {$cache{$h}{$_} = $config{$h}{$_} } @{$config{$h}{'cacheable'}};
+
+ } else {
+ map {$cache{$h}{$_} = $config{$h}{$_} } qw(atime wtime status);
+ }
+ }
+
+ ## construct the cache file.
+ my $cache = "";
+ foreach my $h (sort keys %cache) {
+ my $opt = join(',', map { "$_=".define($cache{$h}{$_},'') } sort keys %{$cache{$h}});
+
+ $cache .= sprintf "%s%s%s\n", $opt, ($opt ? ' ' : ''), $h;
+ }
+ $file = '' if defined($saved_cache) && $cache eq $saved_cache;
+
+ ## write the updates and other entries to the cache file.
+ if ($file) {
+ $saved_cache = undef;
+ local *FD;
+ if (! open(FD, "> $file")) {
+ fatal("Cannot create file '%s'. ($!)", $file);
+ }
+ printf FD "## $program-$version\n";
+ printf FD "## last updated at %s (%d)\n", prettytime($now), $now;
+ printf FD $cache;
+
+ close(FD);
+ }
+}
+######################################################################
+## read_cache($file) - called before reading the .conf
+######################################################################
+sub read_cache {
+ my $file = shift;
+ my $config = shift;
+ my $globals = {};
+
+ %{$config} = ();
+ ## read the cache file ignoring anything on the command-line.
+ if (-e $file) {
+ my %saved = %opt;
+ %opt = ();
+ $saved_cache = _read_config($config, $globals, "##\\s*$program-$version\\s*", $file);
+ %opt = %saved;
+
+ foreach my $h (keys %cache) {
+ if (exists $config{$h}) {
+ foreach (qw(atime mtime wtime ip status)) {
+ $config{$h}{$_} = $cache{$h}{$_} if exists $cache{$h}{$_};
+ }
+ }
+ }
+ }
+}
+######################################################################
+## parse_assignments(string) return (rest, %variables)
+## parse_assignment(string) return (name, value, rest)
+######################################################################
+sub parse_assignments {
+ my $rest = shift;
+ my @args = @_;
+ my %variables = ();
+ my ($name, $value);
+
+ while (1) {
+ $rest =~ s/^\s+//;
+ ($name, $value, $rest) = parse_assignment($rest, @args);
+ if (defined $name) {
+ $variables{$name} = $value;
+ } else {
+ last;
+ }
+ }
+ return ($rest, %variables);
+}
+sub parse_assignment {
+ my $rest = shift;
+ my $stop = @_ ? shift : '[\n\s,]';
+ my ($c, $name, $value);
+ my ($escape, $quote) = (0, '');
+
+ if ($rest =~ /^\s*([a-z][a-z_-]*)=(.*)/i) {
+ ($name, $rest, $value) = ($1, $2, '');
+
+ while (length($c = substr($rest,0,1))) {
+ $rest = substr($rest,1);
+ if ($escape) {
+ $value .= $c;
+ $escape = 0;
+ } elsif ($c eq "\\") {
+ $escape = 1;
+ } elsif ($quote && $c eq $quote) {
+ $quote = ''
+ } elsif (!$quote && $c =~ /[\'\"]/) {
+ $quote = $c;
+ } elsif (!$quote && $c =~ /^${stop}/) {
+ last;
+ } else {
+ $value .= $c;
+ }
+ }
+ }
+ warning("assignment ended with an open quote") if $quote;
+ return ($name, $value, $rest);
+}
+######################################################################
+## read_config
+######################################################################
+sub read_config {
+ my $file = shift;
+ my $config = shift;
+ my $globals = shift;
+ my %globals = ();
+
+ _read_config($config, $globals, '', $file, %globals);
+}
+sub _read_config {
+ my $config = shift;
+ my $globals = shift;
+ my $stamp = shift;
+ local $file = shift;
+ my %globals = @_;
+ my %config = ();
+ my $content = '';
+
+ local *FD;
+ if (! open(FD, "< $file")) {
+ # fatal("Cannot open file '%s'. ($!)", $file);
+ warning("Cannot open file '%s'. ($!)", $file);
+ }
+ # Check for only owner has any access to config file
+ my ($dev, $ino, $mode, @statrest) = stat(FD);
+ if ($mode & 077) {
+ if (-f FD && (chmod 0600, $file)) {
+ warning("file $file must be accessible only by its owner (fixed).");
+ } else {
+ # fatal("file $file must be accessible only by its owner.");
+ warning("file $file must be accessible only by its owner.");
+ }
+ }
+
+ local $lineno = 0;
+ my $continuation = '';
+ while () {
+ s/[\r\n]//g;
+
+ $lineno++;
+
+ ## check for the program version stamp
+ if (($. == 1) && $stamp && ($_ !~ /^$stamp$/i)) {
+ warning("program version mismatch; ignoring %s", $file);
+ last;
+ }
+ if (/\\\s+$/) {
+ warning("whitespace follows the \\ at the end-of-line.\nIf you meant to have a line continuation, remove the trailing whitespace.");
+ }
+
+ $content .= "$_\n" unless /^#/;
+ # lines contain passwords are a special case, we don't want to
+ # arbitrarily strip out '#'
+ if (/^(\S*password\S*)\s*=\s*(\S+)/i) { $_ = "$1=$2"; }
+ if (/^#/ || !(/password/i) || /#.*password/i) { s/#.*//; } # remove comments
+
+ ## handle continuation lines
+ $_ = "$continuation$_";
+ if (/\\$/) {
+ chop;
+ $continuation = $_;
+ next;
+ }
+ $continuation = '';
+
+ s/^\s+//; # remove leading white space
+ s/\s+$//; # remove trailing white space
+ s/\s+/ /g; # canonify
+ next if /^$/;
+
+ ## expected configuration line is:
+ ## [opt=value,opt=..] [host [login [password]]]
+ my %locals;
+ ($_, %locals) = parse_assignments($_);
+ s/\s*,\s*/,/g;
+ my @args = split;
+
+ ## verify that keywords are valid...and check the value
+ foreach my $k (keys %locals) {
+ if (!exists $variables{'merged'}{$k}) {
+ warning("unrecognized keyword '%s' (ignored)", $k);
+ delete $locals{$k};
+ } else {
+ my $def = $variables{'merged'}{$k};
+ my $value = check_value($locals{$k}, $def);
+ if (!defined($value)) {
+ warning("Invalid Value for keyword '%s' = '%s'", $k, $locals{$k});
+ delete $locals{$k};
+ } else { $locals{$k} = $value; }
+ }
+ }
+ if (exists($locals{'host'})) {
+ $args[0] = @args ? "$args[0],$locals{host}" : "$locals{host}";
+ }
+ ## accumulate globals
+ if ($#args < 0) {
+ map { $globals{$_} = $locals{$_} } keys %locals;
+ }
+
+ ## process this host definition
+ if (@args) {
+ my ($host, $login, $password) = @args;
+
+ ## add in any globals..
+ %locals = %{ merge(\%locals, \%globals) };
+
+ ## override login and password if specified the old way.
+ $locals{'login'} = $login if defined $login;
+ $locals{'password'} = $password if defined $password;
+
+ ## allow {host} to be a comma separated list of hosts
+ foreach my $h (split_by_comma($host)) {
+ ## save a copy of the current globals
+ $config{$h} = { %locals };
+ $config{$h}{'host'} = $h;
+ }
+ }
+ }
+ close(FD);
+
+ warning("file ends while expecting a continuation line.")
+ if $continuation;
+
+ %$globals = %globals;
+ %$config = %config;
+
+ return $content;
+}
+######################################################################
+## init_config -
+######################################################################
+sub init_config {
+ %opt = %saved_opt;
+
+ ##
+ $opt{'quiet'} = 0 if opt('verbose');
+
+ ## infer the IP strategy if possible
+ $opt{'use'} = 'ip' if !define($opt{'use'}) && defined($opt{'ip'});
+ $opt{'use'} = 'if' if !define($opt{'use'}) && defined($opt{'if'});
+ $opt{'use'} = 'web' if !define($opt{'use'}) && defined($opt{'web'});
+
+ ## sanity check
+ $opt{'max-interval'} = max(interval(opt('max-interval')), interval(default('max-interval')));
+ $opt{'min-interval'} = max(interval(opt('min-interval')), interval(default('min-interval')));
+ $opt{'min-error-interval'} = max(interval(opt('min-error-interval')), interval(default('min-error-interval')));
+
+ $opt{'timeout'} = 0 if opt('timeout') < 0;
+ $opt{'daemon'} = minimum('daemon')
+ if define($opt{'daemon'},$globals{'daemon'},0)
+ && define($opt{'daemon'},$globals{'daemon'}) < minimum('daemon');
+
+ ## define or modify host options specified on the command-line
+ if (exists $opt{'options'} && defined $opt{'options'}) {
+ ## collect cmdline configuration options.
+ my %options = ();
+ foreach my $opt (split_by_comma($opt{'options'})) {
+ my ($name,$var) = split /\s*=\s*/, $opt;
+ $options{$name} = $var;
+ }
+ ## determine hosts specified with -host
+ my @hosts = ();
+ if (exists $opt{'host'}) {
+ foreach my $h (split_by_comma($opt{'host'})) {
+ push @hosts, $h;
+ }
+ }
+ ## and those in -options=...
+ if (exists $options{'host'}) {
+ foreach my $h (split_by_comma($options{'host'})) {
+ push @hosts, $h;
+ }
+ delete $options{'host'};
+ }
+ ## merge options into host definitions or globals
+ if (@hosts) {
+ foreach my $h (@hosts) {
+ $config{$h} = %{ merge(\%options, $config{$h}) };
+ }
+ $opt{'host'} = join(',', @hosts);
+ } else {
+ %globals = %{ merge(\%options, \%globals) };
+ }
+ }
+
+ ## override global options with those on the command-line.
+ foreach my $o (keys %opt) {
+ if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) {
+ $globals{$o} = $opt{$o};
+ }
+ }
+
+ ## sanity check
+ if (defined $opt{'host'} && defined $opt{'retry'}) {
+ usage("options -retry and -host (or -option host=..) are mutually exclusive");
+ }
+
+ ## determine hosts to update (those on the cmd-line, config-file, or failed cached)
+ my @hosts = keys %config;
+ if (opt('host')) {
+ @hosts = split_by_comma($opt{'host'});
+ }
+ if (opt('retry')) {
+ @hosts = map { $_ if $cache{$_}{'status'} ne 'good' } keys %cache;
+ }
+
+ ## remove any other hosts
+ my %hosts;
+ map { $hosts{$_} = undef } @hosts;
+ map { delete $config{$_} unless exists $hosts{$_} } keys %config;
+
+ ## collect the cacheable variables.
+ foreach my $proto (keys %services) {
+ my @cacheable = ();
+ foreach my $k (keys %{$services{$proto}{'variables'}}) {
+ push @cacheable, $k if $services{$proto}{'variables'}{$k}{'cache'};
+ }
+ $services{$proto}{'cacheable'} = [ @cacheable ];
+ }
+
+ ## sanity check..
+ ## make sure config entries have all defaults and they meet minimums
+ ## first the globals...
+ foreach my $k (keys %globals) {
+ my $def = $variables{'merged'}{$k};
+ my $ovalue = define($globals{$k}, $def->{'default'});
+ my $value = check_value($ovalue, $def);
+ if ($def->{'required'} && !defined $value) {
+ $value = default($k);
+ warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value);
+ }
+ $globals{$k} = $value;
+ }
+
+ ## now the host definitions...
+ HOST:
+ foreach my $h (keys %config) {
+ my $proto;
+ $proto = $config{$h}{'protocol'};
+ $proto = opt('protocol') if !defined($proto);
+
+ if (!exists($services{$proto})) {
+ warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto);
+ delete $config{$h};
+
+ } else {
+ my $svars = $services{$proto}{'variables'};
+ my $conf = { 'protocol' => $proto };
+
+ foreach my $k (keys %$svars) {
+ my $def = $svars->{$k};
+ my $ovalue = define($config{$h}{$k}, $def->{'default'});
+ my $value = check_value($ovalue, $def);
+ if ($def->{'required'} && !defined $value) {
+ warning("skipping host: %s: '%s=%s' is an invalid %s.", $h, $k, $ovalue, $def->{'type'});
+ delete $config{$h};
+ next HOST;
+ }
+ $conf->{$k} = $value;
+
+ }
+ $config{$h} = $conf;
+ $config{$h}{'cacheable'} = [ @{$services{$proto}{'cacheable'}} ];
+ }
+ }
+ if (opt('help')) {
+ *STDERR = *STDOUT;
+ usage();
+ }
+}
+
+######################################################################
+## usage
+######################################################################
+sub usage {
+ my $msg = '';
+ if (@_) {
+ my $format = shift;
+ $msg .= sprintf $format, @_;
+ 1 while chomp($msg);
+ $msg .= "\n";
+ }
+ printf STDERR "%s%s\n", $msg, $opt_usage;
+ sendmail();
+ exit 1;
+}
+
+######################################################################
+## process_args -
+######################################################################
+sub process_args {
+ my @spec = ();
+ my $usage = "";
+ my %opts = ();
+
+ foreach (@_) {
+ if (ref $_) {
+ my ($key, $specifier, $arg_usage) = @$_;
+ my $value = default($key);
+
+ ## add a option specifier
+ push @spec, $key . $specifier;
+
+ ## define the default value which can be overwritten later
+ $opt{$key} = undef;
+
+ next unless $arg_usage;
+
+ ## add a line to the usage;
+ $usage .= " $arg_usage";
+ if (defined($value) && $value ne '') {
+ $usage .= " (default: ";
+ if ($specifier eq '!') {
+ $usage .= "no" if ($specifier eq '!') && !$value;
+ $usage .= $key;
+ } else {
+ $usage .= $value;
+ }
+ $usage .= ")";
+ }
+ $usage .= ".";
+ } else {
+ $usage .= $_;
+ }
+ $usage .= "\n";
+ }
+ ## process the arguments
+ if (! GetOptions(\%opt, @spec)) {
+ $opt{"help"} = 1;
+ }
+ return ($usage, %opt);
+}
+######################################################################
+## test_possible_ip - print possible IPs
+######################################################################
+sub test_possible_ip {
+ local $opt{'debug'} = 0;
+
+ printf "use=ip, ip=%s address is %s\n", opt('ip'), define(get_ip('ip'), 'NOT FOUND')
+ if defined opt('ip');
+
+ {
+ local $opt{'use'} = 'if';
+ foreach my $if (grep {/^[a-zA-Z]/} `ifconfig -a`) {
+ $if =~ s/:?\s.*//is;
+ local $opt{'if'} = $if;
+ printf "use=if, if=%s address is %s\n", opt('if'), define(get_ip('if'), 'NOT FOUND');
+ }
+ }
+ if (opt('fw')) {
+ if (opt('fw') !~ m%/%) {
+ foreach my $fw (sort keys %builtinfw) {
+ local $opt{'use'} = $fw;
+ printf "use=$fw address is %s\n", define(get_ip($fw), 'NOT FOUND');
+ }
+ }
+ local $opt{'use'} = 'fw';
+ printf "use=fw, fw=%s address is %s\n", opt('fw'), define(get_ip(opt('fw')), 'NOT FOUND')
+ if ! exists $builtinfw{opt('fw')};
+
+ }
+ {
+ local $opt{'use'} = 'web';
+ foreach my $web (sort keys %builtinweb) {
+ local $opt{'web'} = $web;
+ printf "use=web, web=$web address is %s\n", define(get_ip('web'), 'NOT FOUND');
+ }
+ printf "use=web, web=%s address is %s\n", opt('web'), define(get_ip('web'), 'NOT FOUND')
+ if ! exists $builtinweb{opt('web')};
+ }
+ if (opt('cmd')) {
+ local $opt{'use'} = 'cmd';
+ printf "use=cmd, cmd=%s address is %s\n", opt('cmd'), define(get_ip('cmd'), 'NOT FOUND');
+ }
+ exit 0 unless opt('debug');
+}
+######################################################################
+## test_geturl - print (and save if -test) result of fetching a URL
+######################################################################
+sub test_geturl {
+ my $url = shift;
+
+ my $reply = geturl(opt('proxy'), $url, opt('login'), opt('password'));
+ print "URL $url\n";;
+ print defined($reply) ? $reply : "\n";
+ exit;
+}
+######################################################################
+## load_file
+######################################################################
+sub load_file {
+ my $file = shift;
+ my $buffer = '';
+
+ if (exists($ENV{'TEST_CASE'})) {
+ my $try = "$file-$ENV{'TEST_CASE'}";
+ $file = $try if -f $try;
+ }
+
+ local *FD;
+ if (open(FD, "< $file")) {
+ read(FD, $buffer, -s FD);
+ close(FD);
+ debug("Loaded %d bytes from %s", length($buffer), $file);
+ } else {
+ debug("Load failed from %s ($!)", $file);
+ }
+ return $buffer
+}
+######################################################################
+## save_file
+######################################################################
+sub save_file {
+ my ($file, $buffer, $opt) = @_;
+
+ $file .= "-$ENV{'TEST_CASE'}" if exists $ENV{'TEST_CASE'};
+ if (defined $opt) {
+ my $i = 0;
+ while (-f "$file-$i") {
+ if ('unique' =~ /^$opt/i) {
+ my $a = join('\n', grep {!/^Date:/} split /\n/, $buffer);
+ my $b = join('\n', grep {!/^Date:/} split /\n/, load_file("$file-$i"));
+ last if $a eq $b;
+ }
+ $i++;
+ }
+ $file = "$file-$i";
+ }
+ debug("Saving to %s", $file);
+ local *FD;
+ open(FD, "> $file") or return;
+ print FD $buffer;
+ close(FD);
+ return $buffer;
+}
+######################################################################
+## print_opt
+## print_globals
+## print_config
+## print_cache
+## print_info
+######################################################################
+sub _print_hash {
+ my ($string, $ptr) = @_;
+ my $value = $ptr;
+
+ if (! defined($ptr)) {
+ $value = "";
+ } elsif (ref $ptr eq 'HASH') {
+ foreach my $key (sort keys %$ptr) {
+ _print_hash("${string}\{$key\}", $ptr->{$key});
+ }
+ return;
+ }
+ printf "%-36s : %s\n", $string, $value;
+}
+sub print_hash {
+ my ($string, $hash) = @_;
+ printf "=== %s ====\n", $string;
+ _print_hash($string, $hash);
+}
+sub print_opt { print_hash("opt", \%opt); }
+sub print_globals { print_hash("globals", \%globals); }
+sub print_config { print_hash("config", \%config); }
+sub print_cache { print_hash("cache", \%cache); }
+sub print_info {
+ print_opt();
+ print_globals();
+ print_config();
+ print_cache();
+}
+######################################################################
+## pipecmd - run an external command
+## logger
+## sendmail
+######################################################################
+sub pipecmd {
+ my $cmd = shift;
+ my $stdin = join("\n", @_);
+ my $ok = 0;
+
+ ## remove trailing newlines
+ 1 while chomp($stdin);
+
+ ## override when debugging.
+ $cmd = opt('exec') ? "| $cmd" : "> /dev/null";
+
+ ## execute the command.
+ local *FD;
+ if (! open(FD, $cmd)) {
+ printf STDERR "$program: cannot execute command %s.\n", $cmd;
+
+ } elsif ($stdin && (! print FD "$stdin\n")) {
+ printf STDERR "$program: failed writting to %s.\n", $cmd;
+ close(FD);
+
+ } elsif (! close(FD)) {
+ printf STDERR "$program: failed closing %s.($@)\n", $cmd;
+
+ } elsif (opt('exec') && $?) {
+ printf STDERR "$program: failed %s. ($@)\n", $cmd;
+
+ } else {
+ $ok = 1;
+ }
+ return $ok;
+}
+sub logger {
+ if (opt('syslog') && opt('facility') && opt('priority')) {
+ my $facility = opt('facility');
+ my $priority = opt('priority');
+ return pipecmd("logger -p$facility.$priority -t${program}\[$$\]", @_);
+ }
+ return 1;
+}
+sub sendmail {
+ my $recipients = opt('mail');
+
+ if (opt('mail-failure') && ($result ne 'OK')) {
+ $recipients = opt('mail-failure');
+ }
+ if ($msgs && $recipients && $msgs ne $last_msgs) {
+ pipecmd("sendmail -oi $recipients",
+ "To: $recipients",
+ "Subject: status report from $program\@$hostname",
+ "\r\n",
+ $msgs,
+ "",
+ "regards,",
+ " $program\@$hostname (version $version)"
+ );
+ }
+ $last_msgs = $msgs;
+ $msgs = '';
+}
+######################################################################
+## split_by_comma
+## merge
+## default
+## minimum
+## opt
+######################################################################
+sub split_by_comma {
+ my $string = shift;
+
+ return split /\s*[, ]\s*/, $string if defined $string;
+ return ();
+}
+sub merge {
+ my %merged = ();
+ foreach my $h (@_) {
+ foreach my $k (keys %$h) {
+ $merged{$k} = $h->{$k} unless exists $merged{$k};
+ }
+ }
+ return \%merged;
+}
+sub default {
+ my $v = shift;
+ return $variables{'merged'}{$v}{'default'};
+}
+sub minimum {
+ my $v = shift;
+ return $variables{'merged'}{$v}{'minimum'};
+}
+sub opt {
+ my $v = shift;
+ return $opt{$v} if defined $opt{$v};
+ return $globals{$v} if defined $globals{$v};
+ return default($v) if defined default($v);
+ return undef;
+}
+sub min {
+ my $min = shift;
+ foreach my $arg (@_) {
+ $min = $arg if $arg < $min;
+ }
+ return $min;
+}
+sub max {
+ my $max = shift;
+ foreach my $arg (@_) {
+ $max = $arg if $arg > $max;
+ }
+ return $max;
+}
+######################################################################
+## define
+######################################################################
+sub define {
+ foreach (@_) {
+ return $_ if defined $_;
+ }
+ return undef;
+}
+######################################################################
+## ynu
+######################################################################
+sub ynu {
+ my ($value, $yes, $no, $undef) = @_;
+
+ return $no if !defined($value) || !$value;
+ return $yes if $value eq '1';
+ foreach (qw(yes true)) {
+ return $yes if $_ =~ /^$value/i;
+ }
+ foreach (qw(no false)) {
+ return $no if $_ =~ /^$value/i;
+ }
+ return $undef;
+}
+######################################################################
+## msg
+## debug
+## warning
+## fatal
+######################################################################
+sub _msg {
+ my $log = shift;
+ my $prefix = shift;
+ my $format = shift;
+ my $buffer = sprintf $format, @_;
+ chomp($buffer);
+
+ $prefix = sprintf "%-9s ", $prefix if $prefix;
+ if ($file) {
+ $prefix .= "file $file";
+ $prefix .= ", line $lineno" if $lineno;
+ $prefix .= ": ";
+ }
+ if ($prefix) {
+ $buffer = "$prefix$buffer";
+ $buffer =~ s/\n/\n$prefix /g;
+ }
+ $buffer .= "\n";
+ print $buffer;
+
+ $msgs .= $buffer if $log;
+ logger($buffer) if $log;
+
+}
+sub msg { _msg(0, '', @_); }
+sub verbose { _msg(1, @_) if opt('verbose'); }
+sub info { _msg(1, 'INFO:', @_) if opt('verbose'); }
+sub debug { _msg(0, 'DEBUG:', @_) if opt('debug'); }
+sub debug2 { _msg(0, 'DEBUG:', @_) if opt('debug') && opt('verbose');}
+sub warning { _msg(1, 'WARNING:', @_); }
+sub fatal { _msg(1, 'FATAL:', @_); sendmail(); exit(1); }
+sub success { _msg(1, 'SUCCESS:', @_); }
+sub failed { _msg(1, 'FAILED:', @_); $result = 'FAILED'; }
+sub prettytime { return scalar(localtime(shift)); }
+
+sub prettyinterval {
+ my $interval = shift;
+ use integer;
+ my $s = $interval % 60; $interval /= 60;
+ my $m = $interval % 60; $interval /= 60;
+ my $h = $interval % 24; $interval /= 24;
+ my $d = $interval;
+
+ my $string = "";
+ $string .= "$d day" if $d;
+ $string .= "s" if $d > 1;
+ $string .= ", " if $string && $h;
+ $string .= "$h hour" if $h;
+ $string .= "s" if $h > 1;
+ $string .= ", " if $string && $m;
+ $string .= "$m minute" if $m;
+ $string .= "s" if $m > 1;
+ $string .= ", " if $string && $s;
+ $string .= "$s second" if $s;
+ $string .= "s" if $s > 1;
+ return $string;
+}
+sub interval {
+ my $value = shift;
+ if ($value =~ /^(\d+)(seconds|s)/i) {
+ $value = $1;
+ } elsif ($value =~ /^(\d+)(minutes|m)/i) {
+ $value = $1 * 60;
+ } elsif ($value =~ /^(\d+)(hours|h)/i) {
+ $value = $1 * 60*60;
+ } elsif ($value =~ /^(\d+)(days|d)/i) {
+ $value = $1 * 60*60*24;
+ } elsif ($value !~ /^\d+$/) {
+ $value = undef;
+ }
+ return $value;
+}
+sub interval_expired {
+ my ($host, $time, $interval) = @_;
+
+ return 1 if !exists $cache{$host};
+ return 1 if !exists $cache{$host}{$time} || !$cache{$host}{$time};
+ return 1 if !exists $config{$host}{$interval} || !$config{$host}{$interval};
+
+ return $now > ($cache{$host}{$time} + $config{$host}{$interval});
+}
+
+
+
+######################################################################
+## check_value
+######################################################################
+sub check_value {
+ my ($value, $def) = @_;
+ my $type = $def->{'type'};
+ my $min = $def->{'minimum'};
+ my $required = $def->{'required'};
+
+ if (!defined $value && !$required) {
+ ;
+
+ } elsif ($type eq T_DELAY) {
+ $value = interval($value);
+ $value = $min if defined($value) && defined($min) && $value < $min;
+
+ } elsif ($type eq T_NUMBER) {
+ return undef if $value !~ /^\d+$/;
+ $value = $min if defined($min) && $value < $min;
+
+ } elsif ($type eq T_BOOL) {
+ if ($value =~ /^y(es)?$|^t(true)?$|^1$/i) {
+ $value = 1;
+ } elsif ($value =~ /^n(o)?$|^f(alse)?$|^0$/i) {
+ $value = 0;
+ } else {
+ return undef;
+ }
+ } elsif ($type eq T_FQDN || $type eq T_OFQDN && $value ne '') {
+ $value = lc $value;
+ return undef if $value !~ /[^.]\.[^.]/;
+
+ } elsif ($type eq T_FQDNP) {
+ $value = lc $value;
+ return undef if $value !~ /[^.]\.[^.].*(:\d+)?$/;
+
+ } elsif ($type eq T_PROTO) {
+ $value = lc $value;
+ return undef if ! exists $services{$value};
+
+ } elsif ($type eq T_USE) {
+ $value = lc $value;
+ return undef if ! exists $ip_strategies{$value};
+
+ } elsif ($type eq T_FILE) {
+ return undef if $value eq "";
+
+ } elsif ($type eq T_IF) {
+ return undef if $value !~ /^[a-z0-9:_-]+$/;
+
+ } elsif ($type eq T_PROG) {
+ return undef if $value eq "";
+
+ } elsif ($type eq T_LOGIN) {
+ return undef if $value eq "";
+
+# } elsif ($type eq T_PASSWD) {
+# return undef if $value =~ /:/;
+
+ } elsif ($type eq T_IP) {
+ return undef if $value !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
+ }
+ return $value;
+}
+######################################################################
+## encode_base64 - from MIME::Base64
+######################################################################
+sub encode_base64 ($;$) {
+ my $res = '';
+ my $eol = $_[1];
+ $eol = "\n" unless defined $eol;
+ pos($_[0]) = 0; # ensure start at the beginning
+ while ($_[0] =~ /(.{1,45})/gs) {
+ $res .= substr(pack('u', $1), 1);
+ chop($res);
+ }
+ $res =~ tr|` -_|AA-Za-z0-9+/|; # `# help emacs
+
+ # fix padding at the end
+ my $padding = (3 - length($_[0]) % 3) % 3;
+ $res =~ s/.{$padding}$/'=' x $padding/e if $padding;
+
+ # break encoded string into lines of no more than 76 characters each
+ if (length $eol) {
+ $res =~ s/(.{1,76})/$1$eol/g;
+ }
+ $res;
+}
+######################################################################
+## geturl
+######################################################################
+sub geturl {
+ my $proxy = shift || '';
+ my $url = shift || '';
+ my $login = shift || '';
+ my $password = shift || '';
+ my ($peer, $server, $port, $default_port, $use_ssl);
+ my ($sd, $rq, $request, $reply);
+
+ debug("proxy = $proxy");
+ debug("url = $url");
+ ## canonify proxy and url
+ $proxy =~ s%^https?://%%i;
+ $url =~ s%^https?://%%i;
+ $server = $url;
+ $server =~ s%/.*%%;
+ $url = "/" unless $url =~ m%/%;
+ $url =~ s%^[^/]*/%%;
+
+ debug("server = $server");
+ debug("opt(fw = ",opt('fw'));
+ debug("glo fw = $globals{'fw'}");
+ #if ( $globals{'ssl'} and $server ne $globals{'fw'} ) {
+ ## always omit SSL for connections to local router
+ if ( $globals{'ssl'} and (caller(1))[3] ne 'main::get_ip' ) {
+ $use_ssl = 1;
+ $default_port = 443;
+ require IO::Socket::SSL;
+ import IO::Socket::SSL;
+ { no warnings; $IO::Socket::SSL::DEBUG = 0; }
+ } else {
+ $use_ssl = 0;
+ $default_port = 80;
+ }
+
+ ## determine peer and port to use.
+ $peer = $proxy || $server;
+ $peer =~ s%/.*%%;
+ $port = $peer;
+ $port =~ s%^.*:%%;
+ $port = $default_port unless $port =~ /^\d+$/;
+ $peer =~ s%:.*$%%;
+
+ my $to = sprintf "%s%s", $server, $proxy ? " via proxy $peer:$port" : "";
+ verbose("CONNECT:", "%s", $to);
+
+ $request = "GET ";
+ $request .= "http://$server" if $proxy;
+ $request .= "/$url HTTP/1.0\n";
+ $request .= "Host: $server\n";
+
+ my $auth = encode_base64("${login}:${password}");
+ $request .= "Authorization: Basic $auth" if $login || $password;
+ $request .= "User-Agent: ${program}/${version}\n";
+ $request .= "Connection: close\n";
+ $request .= "\n";
+
+ ## make sure newlines are for some pedantic proxy servers
+ ($rq = $request) =~ s/\n/\r\n/g;
+
+ # local $^W = 0;
+ $0 = sprintf("%s - connecting to %s port %s", $program, $peer, $port);
+ if (! opt('exec')) {
+ debug("skipped network connection");
+ verbose("SENDING:", $request);
+ } elsif ($use_ssl) {
+ $sd = IO::Socket::SSL->new(
+ PeerAddr => $peer,
+ PeerPort => $port,
+ Proto => 'tcp',
+ MultiHomed => 1,
+ Timeout => opt('timeout'),
+ );
+ defined $sd or warning("cannot connect to $peer:$port socket: $@ " . IO::Socket::SSL::errstr());
+ } else {
+ $sd = IO::Socket::INET->new(
+ PeerAddr => $peer,
+ PeerPort => $port,
+ Proto => 'tcp',
+ MultiHomed => 1,
+ Timeout => opt('timeout'),
+ );
+ defined $sd or warning("cannot connect to $peer:$port socket: $@");
+ }
+
+ if (defined $sd) {
+ ## send the request to the http server
+ verbose("CONNECTED: ", $use_ssl ? 'using SSL' : 'using HTTP');
+ verbose("SENDING:", $request);
+
+ $0 = sprintf("%s - sending to %s port %s", $program, $peer, $port);
+ my $result = syswrite $sd, $rq;
+ if ($result != length($rq)) {
+ warning("cannot send to $peer:$port ($!).");
+
+ } else {
+ my $timeout = 0;
+ local $SIG{'ALRM'} = sub { $timeout = 1; };
+
+ $0 = sprintf("%s - reading from %s port %s", $program, $peer, $port);
+ alarm(opt('timeout')) if opt('timeout') > 0;
+ while (!$timeout && ($_ = <$sd>)) {
+ $0 = sprintf("%s - read from %s port %s", $program, $peer, $port);
+ verbose("RECEIVE:", "%s", define($_, ""));
+ $reply .= $_ if defined $_;
+ }
+ if (opt('timeout') > 0) {
+ alarm(0);
+ }
+ close($sd);
+ if ($timeout) {
+ warning("TIMEOUT: %s after %s seconds", $to, opt('timeout'));
+ $reply = '';
+ }
+ $reply = '' if !defined $reply;
+ }
+ }
+ $0 = sprintf("%s - closed %s port %s", $program, $peer, $port);
+
+ ## during testing simulate reading the URL
+ if (opt('test')) {
+ my $filename = "$server/$url";
+ $filename =~ s|/|%2F|g;
+ if (opt('exec')) {
+ $reply = save_file("${savedir}$filename", $reply, 'unique');
+ } else {
+ $reply = load_file("${savedir}$filename");
+ }
+ }
+
+ $reply =~ s/\r//g if defined $reply;
+ return $reply;
+}
+######################################################################
+## get_ip
+######################################################################
+sub get_ip {
+ my $use = lc shift;
+ my ($ip, $arg, $reply, $url, $skip) = (undef, opt($use), '');
+ $arg = '' unless $arg;
+
+ if ($use eq 'ip') {
+ $ip = opt('ip');
+ $arg = 'ip';
+
+ } elsif ($use eq 'if') {
+ $skip = opt('if-skip') || '';
+ $reply = `ifconfig $arg 2> /dev/null`;
+ $reply = '' if $?;
+
+ } elsif ($use eq 'cmd') {
+ if ($arg) {
+ $skip = opt('cmd-skip') || '';
+ $reply = `$arg`;
+ $reply = '' if $?;
+ }
+
+ } elsif ($use eq 'web') {
+ $url = opt('web') || '';
+ $skip = opt('web-skip') || '';
+
+ if (exists $builtinweb{$url}) {
+ $skip = $builtinweb{$url}->{'skip'} unless $skip;
+ $url = $builtinweb{$url}->{'url'};
+ }
+ $arg = $url;
+
+ if ($url) {
+ $reply = geturl(opt('proxy'), $url) || '';
+ }
+
+ } elsif (($use eq 'cisco')) {
+ # Stuff added to support Cisco router ip http daemon
+ # User fw-login should only have level 1 access to prevent
+ # password theft. This is pretty harmless.
+ my $queryif = opt('if');
+ $skip = opt('fw-skip') || '';
+
+ # Convert slashes to protected value "\/"
+ $queryif =~ s%\/%\\\/%g;
+
+ # Protect special HTML characters (like '?')
+ $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge;
+
+ $url = "http://${arg}/level/1/exec/show/ip/interface/brief/${queryif}/CR";
+ $reply = geturl('', $url, opt('fw-login'), opt('fw-password')) || '';
+ $arg = $url;
+
+ } else {
+ $url = opt('fw') || '';
+ $skip = opt('fw-skip') || '';
+
+ if (exists $builtinfw{$use}) {
+ $skip = $builtinfw{$use}->{'skip'} unless $skip;
+ $url = "http://${url}" . $builtinfw{$use}->{'url'} unless $url =~ /\//;
+ }
+ $arg = $url;
+
+ if ($url) {
+ $reply = geturl('', $url, opt('fw-login'), opt('fw-password')) || '';
+ }
+ }
+ if (!defined $reply) {
+ $reply = '';
+ }
+ if ($skip) {
+ $skip =~ s/ /\\s/is;
+ $reply =~ s/^.*?${skip}//is;
+ }
+ if ($reply =~ /^.*?\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b.*/is) {
+ $ip = $1;
+ }
+ if (($use ne 'ip') && (define($ip,'') eq '0.0.0.0')) {
+ $ip = undef;
+ }
+
+ debug("get_ip: using %s, %s reports %s", $use, $arg, define($ip, ""));
+ return $ip;
+}
+
+######################################################################
+## group_hosts_by
+######################################################################
+sub group_hosts_by {
+ my ($hosts, $attributes) = @_;
+
+ my %groups = ();
+ foreach my $h (@$hosts) {
+ my @keys = @$attributes;
+ map { $config{$h}{$_} = '' unless exists $config{$h}{$_} } @keys;
+ my $sig = join(',', map { "$_=$config{$h}{$_}" } @keys);
+
+ push @{$groups{$sig}}, $h;
+ }
+ return %groups;
+}
+######################################################################
+## nic_examples
+######################################################################
+sub nic_examples {
+ my $examples = "";
+ my $separator = "";
+ foreach my $s (sort keys %services) {
+ my $subr = $services{$s}{'examples'};
+ my $example;
+
+ if (defined($subr) && ($example = &$subr())) {
+ chomp($example);
+ $examples .= $example;
+ $examples .= "\n\n$separator";
+ $separator = "\n";
+ }
+ }
+ my $intro = < $now) {
+ warning("cannot update %s from %s to %s until after %s.",
+ $host,
+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), $ip,
+ prettytime($cache{$host}{'wtime'})
+ );
+
+ } elsif ($cache{$host}{'mtime'} && interval_expired($host, 'mtime', 'max-interval')) {
+ warning("forcing update of %s from %s to %s; %s since last update on %s.",
+ $host,
+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''), $ip,
+ prettyinterval($config{$host}{'max-interval'}),
+ prettytime($cache{$host}{'mtime'})
+ );
+ $update = 1;
+
+ } elsif ($cache{$host}{'ip'} ne $ip) {
+ if (($cache{$host}{'status'} eq 'good') && !interval_expired($host, 'mtime', 'min-interval')) {
+
+ warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.",
+ $host,
+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''),
+ $ip,
+ ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''),
+ prettyinterval($config{$host}{'min-interval'})
+ )
+ if opt('verbose') || !define($cache{$host}{'warned-min-interval'}, 0);
+
+ $cache{$host}{'warned-min-interval'} = $now;
+
+ } elsif (($cache{$host}{'status'} ne 'good') && !interval_expired($host, 'atime', 'min-error-interval')) {
+
+ warning("skipping update of %s from %s to %s.\nlast updated %s but last attempt on %s failed.\nWait at least %s between update attempts.",
+ $host,
+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : ''),
+ $ip,
+ ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : ''),
+ ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : ''),
+ prettyinterval($config{$host}{'min-error-interval'})
+ )
+ if opt('verbose') || !define($cache{$host}{'warned-min-error-interval'}, 0);
+
+ $cache{$host}{'warned-min-error-interval'} = $now;
+
+ } else {
+ $update = 1;
+ }
+
+ } elsif (defined($sub) && &$sub($host)) {
+ $update = 1;
+
+ } else {
+ success("%s: skipped: IP address was already set to %s.", $host, $ip)
+ if opt('verbose');
+ }
+ $config{$host}{'status'} = define($cache{$host}{'status'},'');
+ $config{$host}{'update'} = $update;
+ if ($update) {
+ $config{$host}{'status'} = 'noconnect';
+ $config{$host}{'atime'} = $now;
+ $config{$host}{'wtime'} = 0;
+ $config{$host}{'warned-min-interval'} = 0;
+ $config{$host}{'warned-min-error-interval'} = 0;
+
+ delete $cache{$host}{'warned-min-interval'};
+ delete $cache{$host}{'warned-min-error-interval'};
+ }
+
+ return $update;
+}
+######################################################################
+## header_ok
+######################################################################
+sub header_ok {
+ my ($host, $line) = @_;
+ my $ok = 0;
+
+ if ($line =~ m%^s*HTTP/1.*\s+(\d+)%i) {
+ my $result = $1;
+
+ if ($result eq '200') {
+ $ok = 1;
+
+ } elsif ($result eq '401') {
+ failed("updating %s: authorization failed (%s)", $host, $line);
+ }
+
+ } else {
+ failed("updating %s: unexpected line (%s)", $host, $line);
+ }
+ return $ok;
+}
+######################################################################
+## nic_dyndns1_examples
+######################################################################
+sub nic_dyndns1_examples {
+ return <\s*(.*)\s*%i;
+ $return_code = $1 if $line =~ m%^return\s+code\s*:\s*(.*)\s*$%i;
+ $error_code = $1 if $line =~ m%^error\s+code\s*:\s*(.*)\s*$%i;
+ }
+
+ if ($return_code ne 'NOERROR' || $error_code ne 'NOERROR' || !$title) {
+ $config{$h}{'status'} = 'failed';
+ $title = "incomplete response from $config{$h}{server}" unless $title;
+ warning("SENT: %s", $url) unless opt('verbose');
+ warning("REPLIED: %s", $reply);
+ failed("updating %s: %s", $h, $title);
+
+ } else {
+ $config{$h}{'ip'} = $ip;
+ $config{$h}{'mtime'} = $now;
+ $config{$h}{'status'} = 'good';
+ success("updating %s: %s: IP address set to %s (%s)", $h, $return_code, $ip, $title);
+ }
+ }
+}
+######################################################################
+## nic_dyndns2_updateable
+######################################################################
+sub nic_dyndns2_updateable {
+ my $host = shift;
+ my $update = 0;
+
+ if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
+ info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
+ $update = 1;
+
+ } elsif ($config{$host}{'mx'} && (ynu($config{$host}{'backupmx'},1,2,3) ne ynu($config{$host}{'backupmx'},1,2,3))) {
+ info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'},"YES","NO","NO"));
+ $update = 1;
+
+ } elsif ($config{$host}{'static'} ne $cache{$host}{'static'}) {
+
+ info("forcing updating %s because 'static' has changed to %s.", $host, ynu($config{$host}{'static'},"YES","NO","NO"));
+ $update = 1;
+
+ }
+ return $update;
+}
+######################################################################
+## nic_dyndns2_examples
+######################################################################
+sub nic_dyndns2_examples {
+ return < 'Bad authorization (username or password)',
+ 'badsys' => 'The system parameter given was not valid',
+
+ 'notfqdn' => 'A Fully-Qualified Domain Name was not provided',
+ 'nohost' => 'The hostname specified does not exist in the database',
+ '!yours' => 'The hostname specified exists, but not under the username currently being used',
+ '!donator' => 'The offline setting was set, when the user is not a donator',
+ '!active' => 'The hostname specified is in a Custom DNS domain which has not yet been activated.',
+ 'abuse', => 'The hostname specified is blocked for abuse; contact support@dyndns.org to be unblocked',
+
+ 'numhost' => 'System error: Too many or too few hosts found. Contact support@dyndns.org',
+ 'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org',
+
+ 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive',
+ );
+
+ ## update each set of hosts that had similar configurations
+ foreach my $sig (keys %groups) {
+ my @hosts = @{$groups{$sig}};
+ my $hosts = join(',', @hosts);
+ my $h = $hosts[0];
+
+ info("setting IP address to %s for %s", $ip, $hosts);
+ verbose("UPDATE:","updating %s", $hosts);
+
+ ## Select the DynDNS system to update
+ my $url = "http://$config{$h}{'server'}/nic/update?system=";
+ if ($config{$h}{'custom'}) {
+ warning("updating %s: 'custom' and 'static' may not be used together. ('static' ignored)", $hosts)
+ if $config{$h}{'static'};
+# warning("updating %s: 'custom' and 'offline' may not be used together. ('offline' ignored)", $hosts)
+# if $config{$h}{'offline'};
+ $url .= 'custom';
+
+ } elsif ($config{$h}{'static'}) {
+# warning("updating %s: 'static' and 'offline' may not be used together. ('offline' ignored)", $hosts)
+# if $config{$h}{'offline'};
+ $url .= 'statdns';
+
+ } else {
+ $url .= 'dyndns';
+ }
+
+ $url .= "&hostname=$hosts";
+ $url .= "&myip=";
+ $url .= $ip if $ip;
+
+ ## some args are not valid for a custom domain.
+ $url .= "&wildcard=ON" if ynu($config{$h}{'wildcard'}, 1, 0, 0);
+ if ($config{$h}{'mx'}) {
+ $url .= "&mx=$config{$h}{'mx'}";
+ $url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
+ }
+
+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
+ if (!defined($reply) || !$reply) {
+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
+ last;
+ }
+ last if !header_ok($hosts, $reply);
+
+ my @reply = split /\n/, $reply;
+ my $state = 'header';
+ foreach my $line (@reply) {
+ if ($state eq 'header') {
+ $state = 'body';
+
+ } elsif ($state eq 'body') {
+ $state = 'results' if $line eq '';
+
+ } elsif ($state =~ /^results/) {
+ $state = 'results2';
+
+ my ($status, $ip) = split / /, lc $line;
+ my $h = shift @hosts;
+
+ $config{$h}{'status'} = $status;
+ if ($status eq 'good') {
+ $config{$h}{'ip'} = $ip;
+ $config{$h}{'mtime'} = $now;
+ success("updating %s: %s: IP address set to %s", $h, $status, $ip);
+
+ } elsif (exists $errors{$status}) {
+ if ($status eq 'nochg') {
+ warning("updating %s: %s: %s", $h, $status, $errors{$status});
+ $config{$h}{'ip'} = $ip;
+ $config{$h}{'mtime'} = $now;
+ $config{$h}{'status'} = 'good';
+
+ } else {
+ failed("updating %s: %s: %s", $h, $status, $errors{$status});
+ }
+
+ } elsif ($status =~ /w(\d+)(.)/) {
+ my ($wait, $units) = ($1, lc $2);
+ my ($sec, $scale) = ($wait, 1);
+
+ ($scale, $units) = (1, 'seconds') if $units eq 's';
+ ($scale, $units) = (60, 'minutes') if $units eq 'm';
+ ($scale, $units) = (60*60, 'hours') if $units eq 'h';
+
+ $sec = $wait * $scale;
+ $config{$h}{'wtime'} = $now + $sec;
+ warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
+
+ } else {
+ failed("updating %s: %s: unexpected status (%s)", $h, $line);
+ }
+ }
+ }
+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
+ if $state ne 'results2';
+ }
+}
+######################################################################
+## nic_concont_examples
+######################################################################
+sub nic_concont_examples {
+ return </i, @reply) {
+ $config{$h}{'ip'} = $ip;
+ $config{$h}{'mtime'} = $now;
+ $config{$h}{'status'} = 'good';
+ success("updating %s: good: IP address set to %s", $h, $ip);
+ } else {
+ $config{$h}{'status'} = 'failed';
+ warning("SENT: %s", $url) unless opt('verbose');
+ warning("REPLIED: %s", $reply);
+ failed("updating %s: Invalid reply.", $h);
+ }
+ }
+}
+######################################################################
+## nic_zoneedit1_examples
+######################################################################
+sub nic_zoneedit1_examples {
+ return <
+#
+#
+######################################################################
+sub nic_zoneedit1_update {
+ debug("\nnic_zoneedit1_update -------------------");
+
+ ## group hosts with identical attributes together
+ my %groups = group_hosts_by([ @_ ], [ qw(login password server) ]);
+
+ ## update each set of hosts that had similar configurations
+ foreach my $sig (keys %groups) {
+ my @hosts = @{$groups{$sig}};
+ my $hosts = join(',', @hosts);
+ my $h = $hosts[0];
+
+ info("setting IP address to %s for %s", $ip, $hosts);
+ verbose("UPDATE:","updating %s", $hosts);
+
+ my $url = '';
+ $url .= "http://$config{$h}{'server'}/auth/dynamic.html";
+ $url .= "?host=$hosts";
+ $url .= "&dnsto=$ip" if $ip;
+
+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
+ if (!defined($reply) || !$reply) {
+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
+ last;
+ }
+ last if !header_ok($hosts, $reply);
+
+ my @reply = split /\n/, $reply;
+ foreach my $line (@reply) {
+ if ($line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) {
+ my ($status, $assignments, $rest) = ($1, $2, $3);
+ my ($left, %var) = parse_assignments($assignments);
+
+ if (keys %var) {
+ my ($status_code, $status_text, $status_ip) = ('999', '', $ip);
+ $status_code = $var{'CODE'} if exists $var{'CODE'};
+ $status_text = $var{'TEXT'} if exists $var{'TEXT'};
+ $status_ip = $var{'IP'} if exists $var{'IP'};
+
+ if ($status eq 'SUCCESS' || ($status eq 'ERROR' && $var{'CODE'} eq '707')) {
+ $config{$h}{'ip'} = $status_ip;
+ $config{$h}{'mtime'} = $now;
+ $config{$h}{'status'} = 'good';
+
+ success("updating %s: IP address set to %s (%s: %s)", $h, $ip, $status_code, $status_text);
+
+ } else {
+ $config{$h}{'status'} = 'failed';
+ failed("updating %s: %s: %s", $h, $status_code, $status_text);
+ }
+ shift @hosts;
+ $h = $hosts[0];
+ $hosts = join(',', @hosts);
+ }
+ $line = $rest;
+ redo if $line;
+ }
+ }
+ failed("updating %s: no response from %s", $hosts, $config{$h}{'server'})
+ if @hosts;
+ }
+}
+######################################################################
+## nic_easydns_updateable
+######################################################################
+sub nic_easydns_updateable {
+ my $host = shift;
+ my $update = 0;
+
+ if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
+ info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
+ $update = 1;
+
+ } elsif ($config{$host}{'mx'} && (ynu($config{$host}{'backupmx'},1,2,3) ne ynu($config{$host}{'backupmx'},1,2,3))) {
+ info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'},"YES","NO","NO"));
+ $update = 1;
+
+ } elsif ($config{$host}{'static'} ne $cache{$host}{'static'}) {
+
+ info("forcing updating %s because 'static' has changed to %s.", $host, ynu($config{$host}{'static'},"YES","NO","NO"));
+ $update = 1;
+
+ }
+ return $update;
+}
+######################################################################
+## nic_easydns_examples
+######################################################################
+sub nic_easydns_examples {
+ return < [ $_ ] } @_;
+
+ my %errors = (
+ 'NOACCESS' => 'Authentication failed. This happens if the username/password OR host or domain are wrong.',
+ 'NOSERVICE'=> 'Dynamic DNS is not turned on for this domain.',
+ 'ILLEGAL' => 'Client sent data that is not allowed in a dynamic DNS update.',
+ 'TOOSOON' => 'Update frequency is too short.',
+ );
+
+ ## update each set of hosts that had similar configurations
+ foreach my $sig (keys %groups) {
+ my @hosts = @{$groups{$sig}};
+ my $hosts = join(',', @hosts);
+ my $h = $hosts[0];
+ info("setting IP address to %s for %s", $ip, $hosts);
+ verbose("UPDATE:","updating %s", $hosts);
+
+ #'http://members.easydns.com:80/dyn/ez-ipupdate.php?action=edit&myip=10.20.30.40&host_id=test.burry.ca&'
+
+ my $url;
+ $url = "http://$config{$h}{'server'}/dyn/ez-ipupdate.php?action=edit";
+ $url .= "&host_id=$hosts";
+ $url .= "&myip=";
+ $url .= $ip if $ip;
+ $url .= "&wildcard=" . ynu($config{$h}{'wildcard'}, 'YES', 'NO', 'NO') if defined $config{$h}{'wildcard'};
+
+ if ($config{$h}{'mx'}) {
+ $url .= "&mx=$config{$h}{'mx'}";
+ $url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
+ }
+
+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
+ if (!defined($reply) || !$reply) {
+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
+ last;
+ }
+ last if !header_ok($hosts, $reply);
+
+ my @reply = split /\n/, $reply;
+ my $state = 'header';
+ foreach my $line (@reply) {
+ if ($state eq 'header') {
+ $state = 'body';
+
+ } elsif ($state eq 'body') {
+ $state = 'results' if $line eq '';
+
+ } elsif ($state =~ /^results/) {
+ $state = 'results2';
+
+ my ($status) = $line =~ /^(\S*)\b.*/;
+ my $h = shift @hosts;
+
+ $config{$h}{'status'} = $status;
+ if ($status eq 'NOERROR') {
+ $config{$h}{'ip'} = $ip;
+ $config{$h}{'mtime'} = $now;
+ success("updating %s: %s: IP address set to %s", $h, $status, $ip);
+
+ } elsif ($status =~ /TOOSOON/) {
+ ## make sure we wait at least a little
+ my ($wait, $units) = (5, 'm');
+ my ($sec, $scale) = ($wait, 1);
+
+ ($scale, $units) = (1, 'seconds') if $units eq 's';
+ ($scale, $units) = (60, 'minutes') if $units eq 'm';
+ ($scale, $units) = (60*60, 'hours') if $units eq 'h';
+ $config{$h}{'wtime'} = $now + $sec;
+ warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
+
+ } elsif (exists $errors{$status}) {
+ failed("updating %s: %s: %s", $h, $line, $errors{$status});
+
+ } else {
+ failed("updating %s: %s: unexpected status (%s)", $h, $line);
+ }
+ last;
+ }
+ }
+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
+ if $state ne 'results2';
+ }
+}
+######################################################################
+
+######################################################################
+## nic_dnspark_updateable
+######################################################################
+sub nic_dnspark_updateable {
+ my $host = shift;
+ my $update = 0;
+
+ if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
+ info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
+ $update = 1;
+
+ } elsif ($config{$host}{'mx'} && ($config{$host}{'mxpri'} ne $cache{$host}{'mxpri'})) {
+ info("forcing updating %s because 'mxpri' has changed to %s.", $host, $config{$host}{'mxpri'});
+ $update = 1;
+ }
+ return $update;
+}
+######################################################################
+## nic_dnspark_examples
+######################################################################
+sub nic_dnspark_examples {
+ return < [ $_ ] } @_;
+
+ my %errors = (
+ 'nochange' => 'No changes made to the hostname(s). Continual updates with no changes lead to blocked clients.',
+ 'nofqdn' => 'No valid FQDN (fully qualified domain name) was specified',
+ 'nohost'=> 'An invalid hostname was specified. This due to the fact the hostname has not been created in the system. Creating new host names via clients is not supported.',
+ 'abuse' => 'The hostname specified has been blocked for abuse.',
+ 'unauth' => 'The username specified is not authorized to update this hostname and domain.',
+ 'blocked' => 'The dynamic update client (specified by the user-agent) has been blocked from the system.',
+ 'notdyn' => 'The hostname specified has not been marked as a dynamic host. Hosts must be marked as dynamic in the system in order to be updated via clients. This prevents unwanted or accidental updates.',
+ );
+
+ ## update each set of hosts that had similar configurations
+ foreach my $sig (keys %groups) {
+ my @hosts = @{$groups{$sig}};
+ my $hosts = join(',', @hosts);
+ my $h = $hosts[0];
+ info("setting IP address to %s for %s", $ip, $hosts);
+ verbose("UPDATE:","updating %s", $hosts);
+
+ #'http://www.dnspark.com:80/visitors/update.html?myip=10.20.30.40&hostname=test.burry.ca'
+
+ my $url;
+ $url = "http://$config{$h}{'server'}/visitors/update.html";
+ $url .= "?hostname=$hosts";
+ $url .= "&myip=";
+ $url .= $ip if $ip;
+
+ if ($config{$h}{'mx'}) {
+ $url .= "&mx=$config{$h}{'mx'}";
+ $url .= "&mxpri=" . $config{$h}{'mxpri'};
+ }
+
+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
+ if (!defined($reply) || !$reply) {
+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
+ last;
+ }
+ last if !header_ok($hosts, $reply);
+
+ my @reply = split /\n/, $reply;
+ my $state = 'header';
+ foreach my $line (@reply) {
+ if ($state eq 'header') {
+ $state = 'body';
+
+ } elsif ($state eq 'body') {
+ $state = 'results' if $line eq '';
+
+ } elsif ($state =~ /^results/) {
+ $state = 'results2';
+
+ my ($status) = $line =~ /^(\S*)\b.*/;
+ my $h = pop @hosts;
+
+ $config{$h}{'status'} = $status;
+ if ($status eq 'ok') {
+ $config{$h}{'ip'} = $ip;
+ $config{$h}{'mtime'} = $now;
+ success("updating %s: %s: IP address set to %s", $h, $status, $ip);
+
+ } elsif ($status =~ /TOOSOON/) {
+ ## make sure we wait at least a little
+ my ($wait, $units) = (5, 'm');
+ my ($sec, $scale) = ($wait, 1);
+
+ ($scale, $units) = (1, 'seconds') if $units eq 's';
+ ($scale, $units) = (60, 'minutes') if $units eq 'm';
+ ($scale, $units) = (60*60, 'hours') if $units eq 'h';
+ $config{$h}{'wtime'} = $now + $sec;
+ warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
+
+ } elsif (exists $errors{$status}) {
+ failed("updating %s: %s: %s", $h, $line, $errors{$status});
+
+ } else {
+ failed("updating %s: %s: unexpected status (%s)", $h, $line);
+ }
+ last;
+ }
+ }
+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
+ if $state ne 'results2';
+ }
+}
+
+######################################################################
+
+######################################################################
+## nic_namecheap_examples
+######################################################################
+sub nic_namecheap_examples {
+ return <0/i, @reply) {
+ $config{$h}{'ip'} = $ip;
+ $config{$h}{'mtime'} = $now;
+ $config{$h}{'status'} = 'good';
+ success("updating %s: good: IP address set to %s", $h, $ip);
+ } else {
+ $config{$h}{'status'} = 'failed';
+ warning("SENT: %s", $url) unless opt('verbose');
+ warning("REPLIED: %s", $reply);
+ failed("updating %s: Invalid reply.", $h);
+ }
+ }
+}
+
+######################################################################
+
+
+######################################################################
+
+######################################################################
+## nic_sitelutions_examples
+######################################################################
+sub nic_sitelutions_examples {
+ return </dev/null 2>&1
+ ;;
+esac
diff --git a/svn/sample-etc_dhcpc_dhcpcd-eth0.exe b/svn/sample-etc_dhcpc_dhcpcd-eth0.exe
new file mode 100644
index 0000000..437e92e
--- /dev/null
+++ b/svn/sample-etc_dhcpc_dhcpcd-eth0.exe
@@ -0,0 +1,20 @@
+#!/bin/sh
+######################################################################
+## $Id$
+######################################################################
+PATH=/usr/sbin:${PATH}
+
+## update the DNS server unless the IP address is a private address
+## that may be used as an internal LAN address. This may be true if
+## other interfaces are assigned private addresses from internal
+## DHCP server.
+
+case "$1" in
+10.*) ;;
+172.1[6-9].* | 172.2[0-9].* | 172.3[0-1].*) ;;
+192.168.*) ;;
+*)
+ logger -t dhcpcd IP address changed to $1
+ ddclient -daemon=0 -syslog -use=ip -ip=$1 >/dev/null 2>&1
+ ;;
+esac
diff --git a/svn/sample-etc_ppp_ip-up.local b/svn/sample-etc_ppp_ip-up.local
new file mode 100644
index 0000000..b7a9025
--- /dev/null
+++ b/svn/sample-etc_ppp_ip-up.local
@@ -0,0 +1,40 @@
+#!/bin/sh
+######################################################################
+## $Id$
+######################################################################
+##
+## On my host, pppd invokes this script with args:
+## /etc/ppp/ip-up.local ppp0 /dev/pts/1 115200 192.168.2.1 192.168.2.3
+##
+## From the manual page for my pppd, these aguments are:
+## scriptname interface-name tty-device speed local-IP-address remote-IP-address ipparam
+##
+## Some people have reported that their pppd returns their
+## local-IP-address as $3. If that's also the case for you,
+## you may need to change the $4 below to $3. This may not
+## be necessary if your pppd also passes the local-ip-address
+## in the environment as either PPP_LOCAL or IPLOCAL.
+##
+######################################################################
+PATH=/usr/sbin:${PATH}
+IP=
+IP=${IP:-$PPP_LOCAL}
+IP=${IP:-$IPLOCAL}
+IP=${IP:-$4}
+
+## update the DNS server unless the IP address is a private address
+## that may be used as an internal LAN address (or PPtP tunnel).
+
+logger -t ddclient $0 $*
+case "$IP" in
+10.*) ;;
+172.1[6-9].* | 172.2[0-9].* | 172.3[0-1].*) ;;
+192.168.*) ;;
+"") logger -t ddclient No local IP given so cannot update
+ ;;
+*) (
+ sleep 5
+ ddclient -daemon=0 -syslog -use=if -if=$1 >/dev/null 2>&1
+ ) &
+ ;;
+esac
diff --git a/svn/sample-etc_rc.d_init.d_ddclient b/svn/sample-etc_rc.d_init.d_ddclient
new file mode 100755
index 0000000..ab6faf6
--- /dev/null
+++ b/svn/sample-etc_rc.d_init.d_ddclient
@@ -0,0 +1,91 @@
+#!/bin/bash
+#
+# ddclient This shell script takes care of starting and stopping
+# ddclient.
+#
+# chkconfig: 2345 65 35
+# description: ddclient provides support for updating dynamic DNS services.
+
+CONF=/etc/ddclient/ddclient.conf
+program=ddclient
+
+[ -f $CONF ] || exit 0
+
+system=unknown
+if [ -f /etc/fedora-release ]; then
+ system=fedora
+elif [ -f /etc/redhat-release ]; then
+ system=redhat
+elif [ -f /etc/debian_version ]; then
+ system=debian
+fi
+
+PID=''
+if [ "$system" = "fedora" ] || [ "$system" = "redhat" ]; then
+ . /etc/init.d/functions
+ PID=`pidofproc $program`
+else
+ PID=`ps -aef | grep "$program - sleep" | grep -v grep | awk '{print $2}'`
+fi
+
+PATH=/usr/sbin:/usr/local/sbin:${PATH}
+export PATH
+
+# See how we were called.
+case "$1" in
+ start)
+ # Start daemon.
+ DELAY=`grep -v '^\s*#' $CONF | grep -i -m 1 "daemon" | awk -F '=' '{print $2}'`
+ if [ -z "$DELAY" ] ; then
+ DELAY="-daemon 300"
+ else
+ DELAY=''
+ fi
+ echo -n "Starting ddclient: "
+ if [ "$system" = "fedora" ] || [ "$system" = "redhat" ]; then
+ daemon $program $DELAY
+ else
+ ddclient $DELAY
+ fi
+ echo
+ ;;
+ stop)
+ # Stop daemon.
+ echo -n "Shutting down ddclient: "
+ if [ -n "$PID" ] ; then
+ if [ "$system" = "fedora" ] || [ "$system" = "redhat" ]; then
+ killproc $program
+ else
+ kill $PID
+ fi
+ else
+ echo "ddclient is not running"
+ fi
+ echo
+ ;;
+ restart)
+ $0 stop
+ $0 start
+ ;;
+ status)
+ if [ "$system" = "fedora" ] || [ "$system" = "redhat" ]; then
+ status $program
+ else
+ if test "$PID"
+ then
+ for p in $PID
+ do
+ echo "$program (pid $p) is running"
+ done
+ else
+ echo "$program is stopped"
+ fi
+ fi
+ ;;
+ *)
+ echo "Usage: ddclient {start|stop|restart|status}"
+ exit 1
+esac
+
+exit 0
+
diff --git a/svn/sample-etc_rc.d_init.d_ddclient.lsb b/svn/sample-etc_rc.d_init.d_ddclient.lsb
new file mode 100755
index 0000000..01f06e1
--- /dev/null
+++ b/svn/sample-etc_rc.d_init.d_ddclient.lsb
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# ddclient This shell script takes care of starting and stopping
+# ddclient.
+#
+# chkconfig: 2345 65 35
+# description: ddclient provides support for updating dynamic DNS services.
+#
+# Above is for RedHat and now the LSB part
+### BEGIN INIT INFO
+# Provides: ddclient
+# Required-Start: $syslog $remote_fs
+# Should-Start: $time ypbind sendmail
+# Required-Stop: $syslog $remote_fs
+# Should-Stop: $time ypbind sendmail
+# Default-Start: 3 5
+# Default-Stop: 0 1 2 6
+# Short-Description: ddclient provides support for updating dynamic DNS services
+# Description: ddclient is a Perl client used to update dynamic DNS
+# entries for accounts on many dynamic DNS services and
+# can be used on many types of firewalls
+### END INIT INFO
+#
+# $Id$
+#
+###
+
+[ -f /etc/ddclient/ddclient.conf ] || exit 0
+
+DDCLIENT_BIN=/usr/sbin/ddclient
+
+#
+# LSB Standard (SuSE,RedHat,...)
+#
+if [ -f /lib/lsb/init-functions ] ; then
+ . /lib/lsb/init-functions
+fi
+
+# See how we were called.
+case "$1" in
+ start)
+ echo -n "Starting ddclient "
+ start_daemon $DDCLIENT_BIN -daemon 300
+ rc_status -v
+ ;;
+ stop)
+ echo -n "Shutting down ddclient "
+ killproc -TERM `basename $DDCLIENT_BIN`
+ rc_status -v
+ ;;
+ restart)
+ $0 stop
+ $0 start
+ rc_status
+ ;;
+ status)
+ echo -n "Checking for service ddclient "
+ checkproc `basename $DDCLIENT_BIN`w
+ rc_status -v
+ ;;
+ *)
+ echo "Usage: ddclient {start|stop|restart|status}"
+ exit 1
+esac
+
+exit 0
diff --git a/svn/sample-etc_rc.d_init.d_ddclient.redhat b/svn/sample-etc_rc.d_init.d_ddclient.redhat
new file mode 100755
index 0000000..a81b8cb
--- /dev/null
+++ b/svn/sample-etc_rc.d_init.d_ddclient.redhat
@@ -0,0 +1,41 @@
+#!/bin/sh
+# $Id$
+# ddclient This shell script takes care of starting and stopping
+# ddclient.
+#
+# chkconfig: 2345 65 35
+# description: ddclient provides support for updating dynamic DNS services.
+
+[ -f /etc/ddclient/ddclient.conf ] || exit 0
+
+. /etc/rc.d/init.d/functions
+
+# See how we were called.
+case "$1" in
+ start)
+ # Start daemon.
+ echo -n "Starting ddclient: "
+ touch /var/lock/subsys/ddclient
+ daemon ddclient -daemon 300
+ echo
+ ;;
+ stop)
+ # Stop daemon.
+ echo -n "Shutting down ddclient: "
+ killproc ddclient
+ echo
+ rm -f /var/lock/subsys/ddclient
+ ;;
+ restart)
+ $0 stop
+ $0 start
+ ;;
+ status)
+ status ddclient
+ ;;
+ *)
+ echo "Usage: ddclient {start|stop|restart|status}"
+ exit 1
+esac
+
+exit 0
|