Merge lp:~swt-techie/ubuntu/trusty/ddclient/bug-1068884 into lp:ubuntu/trusty/ddclient

Proposed by Scott Talbert
Status: Approved
Approved by: Brian Murray
Approved revision: 39
Proposed branch: lp:~swt-techie/ubuntu/trusty/ddclient/bug-1068884
Merge into: lp:ubuntu/trusty/ddclient
Diff against target: 3799 lines (+3730/-5)
7 files modified
.pc/applied-patches (+1/-0)
.pc/fix_digest_sha_freedns.diff/ddclient (+3683/-0)
ddclient (+9/-4)
debian/changelog (+7/-0)
debian/control (+2/-1)
debian/patches/fix_digest_sha_freedns.diff (+27/-0)
debian/patches/series (+1/-0)
To merge this branch: bzr merge lp:~swt-techie/ubuntu/trusty/ddclient/bug-1068884
Reviewer Review Type Date Requested Status
Brian Murray Approve
Review via email: mp+244252@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Brian Murray (brian-murray) wrote :

This looks okay, but you neglected to change the release and package version number for trusty. I've changed it to the following:

ddclient (3.8.1-1ubuntu2.14.04.1) trusty; urgency=low

You can find guidelines for package versioning here:

https://wiki.ubuntu.com/SecurityTeam/UpdatePreparation#Update_the_packaging

review: Approve

Unmerged revisions

39. By Scott Talbert

Add patch from upstream which enables ddclient to find Digest::SHA to
fix FreeDNS support (LP: #1068884)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.pc/applied-patches'
2--- .pc/applied-patches 2012-09-18 13:30:33 +0000
3+++ .pc/applied-patches 2014-12-10 04:48:25 +0000
4@@ -2,3 +2,4 @@
5 config_path.diff
6 maxinterval.diff
7 sample_ubuntu.diff
8+fix_digest_sha_freedns.diff
9
10=== added file '.pc/config_path.diff/.timestamp'
11=== added directory '.pc/fix_digest_sha_freedns.diff'
12=== added file '.pc/fix_digest_sha_freedns.diff/.timestamp'
13=== added file '.pc/fix_digest_sha_freedns.diff/ddclient'
14--- .pc/fix_digest_sha_freedns.diff/ddclient 1970-01-01 00:00:00 +0000
15+++ .pc/fix_digest_sha_freedns.diff/ddclient 2014-12-10 04:48:25 +0000
16@@ -0,0 +1,3683 @@
17+#!/usr/bin/perl -w
18+#!/usr/local/bin/perl -w
19+######################################################################
20+# $Id: ddclient 130 2011-07-11 21:02:07Z wimpunk $
21+#
22+# DDCLIENT - a Perl client for updating DynDNS information
23+#
24+# Author: Paul Burry (paul+ddclient@burry.ca)
25+# ddclient-developers: see https://sourceforge.net/project/memberlist.php?group_id=116817
26+#
27+# website: http://ddclient.sf.net
28+#
29+# Support for multiple IP numbers added by
30+# Astaro AG, Ingo Schwarze <ischwarze-OOs/4mkCeqbQT0dZR+AlfA@public.gmane.org> September 16, 2008
31+#
32+######################################################################
33+require 5.004;
34+use strict;
35+use Getopt::Long;
36+use Sys::Hostname;
37+use IO::Socket;
38+
39+my ($VERSION) = q$Revision: 130 $ =~ /(\d+)/;
40+
41+my $version = "3.8.1";
42+my $programd = $0;
43+$programd =~ s%^.*/%%;
44+my $program = $programd;
45+$program =~ s/d$//;
46+my $now = time;
47+my $hostname = hostname();
48+my $etc = ($program =~ /test/i) ? './' : '/etc/';
49+my $cachedir = ($program =~ /test/i) ? './' : '/var/cache/ddclient/';
50+my $savedir = ($program =~ /test/i) ? 'URL/' : '/tmp/';
51+my $msgs = '';
52+my $last_msgs = '';
53+
54+use vars qw($file $lineno);
55+local $file = '';
56+local $lineno = '';
57+
58+$ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:";
59+
60+sub T_ANY {'any'};
61+sub T_STRING {'string'};
62+sub T_EMAIL {'e-mail address'};
63+sub T_NUMBER {'number'};
64+sub T_DELAY {'time delay (ie. 1d, 1hour, 1m)'};
65+sub T_LOGIN {'login'};
66+sub T_PASSWD {'password'};
67+sub T_BOOL {'boolean value'};
68+sub T_FQDN {'fully qualified host name'};
69+sub T_OFQDN {'optional fully qualified host name'};
70+sub T_FILE {'file name'};
71+sub T_FQDNP {'fully qualified host name and optional port number'};
72+sub T_PROTO {'protocol'}
73+sub T_USE {'ip strategy'}
74+sub T_IF {'interface'}
75+sub T_PROG {'program name'}
76+sub T_IP {'ip'}
77+sub T_POSTS {'postscript'};
78+
79+## strategies for obtaining an ip address.
80+my %builtinweb = (
81+ 'dyndns' => { 'url' => 'http://checkip.dyndns.org/', 'skip' =>
82+ 'Current IP Address:', },
83+ 'dnspark' => { 'url' => 'http://ipdetect.dnspark.com/', 'skip' => 'Current Address:', },
84+ 'loopia' => { 'url' => 'http://dns.loopia.se/checkip/checkip.php', 'skip' => 'Current Address:', },
85+);
86+my %builtinfw = (
87+ 'watchguard-soho' => {
88+ 'name' => 'Watchguard SOHO FW',
89+ 'url' => '/pubnet.htm',
90+ 'skip' => 'NAME=IPAddress VALUE=',
91+ },
92+ 'netopia-r910' => {
93+ 'name' => 'Netopia R910 FW',
94+ 'url' => '/WanEvtLog',
95+ 'skip' => 'local:',
96+ },
97+ 'smc-barricade' => {
98+ 'name' => 'SMC Barricade FW',
99+ 'url' => '/status.htm',
100+ 'skip' => 'IP Address',
101+ },
102+ 'smc-barricade-alt' => {
103+ 'name' => 'SMC Barricade FW (alternate config)',
104+ 'url' => '/status.HTM',
105+ 'skip' => 'WAN IP',
106+ },
107+ 'smc-barricade-alt' => {
108+ 'name' => 'SMC Barricade FW (alternate config)',
109+ 'url' => '/status.HTM',
110+ 'skip' => 'WAN IP',
111+ },
112+ 'smc-barricade-7401bra' => {
113+ 'name' => 'SMC Barricade 7401BRA FW',
114+ 'url' => '/admin/wan1.htm',
115+ 'skip' => 'IP Address',
116+ },
117+ 'netgear-rt3xx' => {
118+ 'name' => 'Netgear FW',
119+ 'url' => '/mtenSysStatus.html',
120+ 'skip' => 'IP Address',
121+ },
122+ 'elsa-lancom-dsl10' => {
123+ 'name' => 'ELSA LanCom DSL/10 DSL FW',
124+ 'url' => '/config/1/6/8/3/',
125+ 'skip' => 'IP.Address',
126+ },
127+ 'elsa-lancom-dsl10-ch01' => {
128+ 'name' => 'ELSA LanCom DSL/10 DSL FW (isdn ch01)',
129+ 'url' => '/config/1/6/8/3/',
130+ 'skip' => 'IP.Address.*?CH01',
131+ },
132+ 'elsa-lancom-dsl10-ch02' => {
133+ 'name' => 'ELSA LanCom DSL/10 DSL FW (isdn ch01)',
134+ 'url' => '/config/1/6/8/3/',
135+ 'skip' => 'IP.Address.*?CH02',
136+ },
137+ 'linksys' => {
138+ 'name' => 'Linksys FW',
139+ 'url' => '/Status.htm',
140+ 'skip' => 'WAN.*?Address',
141+ },
142+ 'linksys-ver2' => {
143+ 'name' => 'Linksys FW version 2',
144+ 'url' => '/RouterStatus.htm',
145+ 'skip' => 'WAN.*?Address',
146+ },
147+ 'linksys-ver3' => {
148+ 'name' => 'Linksys FW version 3',
149+ 'url' => '/Status_Router.htm',
150+ 'skip' => 'WAN.*?Address',
151+ },
152+ 'linksys-wrt854g' => {
153+ 'name' => 'Linksys WRT854G FW',
154+ 'url' => '/Status_Router.asp',
155+ 'skip' => 'IP Address:',
156+ },
157+ 'maxgate-ugate3x00' => {
158+ 'name' => 'MaxGate UGATE-3x00 FW',
159+ 'url' => '/Status.htm',
160+ 'skip' => 'WAN.*?IP Address',
161+ },
162+ 'netcomm-nb3' => {
163+ 'name' => 'NetComm NB3',
164+ 'url' => '/MainPage?id=6',
165+ 'skip' => 'ppp-0',
166+ },
167+ '3com-3c886a' => {
168+ 'name' => '3com 3c886a 56k Lan Modem',
169+ 'url' => '/stat3.htm',
170+ 'skip' => 'IP address in use',
171+ },
172+ 'sohoware-nbg800' => {
173+ 'name' => 'SOHOWare BroadGuard NBG800',
174+ 'url' => '/status.htm',
175+ 'skip' => 'Internet IP',
176+ },
177+ 'xsense-aero' => {
178+ 'name' => 'Xsense Aero',
179+ 'url' => '/A_SysInfo.htm',
180+ 'skip' => 'WAN.*?IP Address',
181+ },
182+ 'alcatel-stp' => {
183+ 'name' => 'Alcatel Speed Touch Pro',
184+ 'url' => '/cgi/router/',
185+ 'skip' => 'Brt',
186+ },
187+ 'alcatel-510' => {
188+ 'name' => 'Alcatel Speed Touch 510',
189+ 'url' => '/cgi/ip/',
190+ 'skip' => 'ppp',
191+ },
192+ 'allnet-1298' => {
193+ 'name' => 'Allnet 1298',
194+ 'url' => '/cgi/router/',
195+ 'skip' => 'WAN',
196+ },
197+ '3com-oc-remote812' => {
198+ 'name' => '3com OfficeConnect Remote 812',
199+ 'url' => '/callEvent',
200+ 'skip' => '.*LOCAL',
201+ },
202+ 'e-tech' => {
203+ 'name' => 'E-tech Router',
204+ 'url' => '/Status.htm',
205+ 'skip' => 'Public IP Address',
206+ },
207+ 'cayman-3220h' => {
208+ 'name' => 'Cayman 3220-H DSL',
209+ 'url' => '/shell/show+ip+interfaces',
210+ 'skip' => '.*inet',
211+ },
212+ 'vigor-2200usb' => {
213+ 'name' => 'Vigor 2200 USB',
214+ 'url' => '/doc/online.sht',
215+ 'skip' => 'PPPoA',
216+ },
217+ 'dlink-614' => {
218+ 'name' => 'D-Link DI-614+',
219+ 'url' => '/st_devic.html',
220+ 'skip' => 'WAN',
221+ },
222+ 'dlink-604' => {
223+ 'name' => 'D-Link DI-604',
224+ 'url' => '/st_devic.html',
225+ 'skip' => 'WAN.*?IP.*Address',
226+ },
227+ 'olitec-SX200' => {
228+ 'name' => 'olitec-SX200',
229+ 'url' => '/doc/wan.htm',
230+ 'skip' => 'st_wan_ip[0] = "',
231+ },
232+ 'westell-6100' => {
233+ 'name' => 'Westell C90-610015-06 DSL Router',
234+ 'url' => '/advstat.htm',
235+ 'skip' => 'IP.+?Address',
236+ },
237+ '2wire' => {
238+ 'name' => '2Wire 1701HG Gateway',
239+ 'url' => '/xslt?PAGE=B01',
240+ 'skip' => 'Internet Address:',
241+ },
242+ 'linksys-rv042-wan1' => {
243+ 'name' => 'Linksys RV042 Dual Homed Router WAN Port 2',
244+ 'url' => '/home.htm',
245+ 'skip' => 'WAN1 IP',
246+ },
247+ 'linksys-rv042-wan2' => {
248+ 'name' => 'Linksys RV042 Dual Homed Router WAN Port 2',
249+ 'url' => '/home.htm',
250+ 'skip' => 'WAN2 IP',
251+ },
252+ 'netgear-rp614' => {
253+ 'name' => 'Netgear RP614 FW',
254+ 'url' => '/sysstatus.html',
255+ 'skip' => 'IP Address',
256+ },
257+ 'watchguard-edge-x' => {
258+ 'name' => 'Watchguard Edge X FW',
259+ 'url' => '/netstat.htm',
260+ 'skip' => 'inet addr:',
261+ },
262+ 'dlink-524' => {
263+ 'name' => 'D-Link DI-524',
264+ 'url' => '/st_device.html',
265+ 'skip' => 'WAN.*?Addres',
266+ },
267+ 'rtp300' => {
268+ 'name' => 'Linksys RTP300',
269+ 'url' => '/cgi-bin/webcm?getpage=%2Fusr%2Fwww_safe%2Fhtml%2Fstatus%2FRouter.html',
270+ 'skip' => 'Internet.*?IP Address',
271+ },
272+ 'netgear-wpn824' => {
273+ 'name' => 'Netgear WPN824 FW',
274+ 'url' => '/RST_status.htm',
275+ 'skip' => 'IP Address',
276+ },
277+ 'linksys-wcg200' => {
278+ 'name' => 'Linksys WCG200 FW',
279+ 'url' => '/RgStatus.asp',
280+ 'skip' => 'WAN.IP.*?Address',
281+ },
282+ 'netgear-dg834g' => {
283+ 'name' => 'netgear-dg834g',
284+ 'url' => '/setup.cgi?next_file=s_status.htm&todo=cfg_init',
285+ 'skip' => '',
286+ },
287+ 'netgear-wgt624' => {
288+ 'name' => 'Netgear WGT624',
289+ 'url' => '/RST_st_dhcp.htm',
290+ 'skip' => 'IP Address</B></td><TD NOWRAP width="50%">',
291+ },
292+ 'sveasoft' => {
293+ 'name' => 'Sveasoft WRT54G/WRT54GS',
294+ 'url' => '/Status_Router.asp',
295+ 'skip' => 'var wan_ip',
296+ },
297+ 'smc-barricade-7004vbr' => {
298+ 'name' => 'SMC Barricade FW (7004VBR model config)',
299+ 'url' => '/status_main.stm',
300+ 'skip' => 'var wan_ip=',
301+ },
302+ 'sitecom-dc202' => {
303+ 'name' => 'Sitecom DC-202 FW',
304+ 'url' => '/status.htm',
305+ 'skip' => 'Internet IP Address',
306+ },
307+);
308+my %ip_strategies = (
309+ 'ip' => ": obtain IP from -ip {address}",
310+ 'web' => ": obtain IP from an IP discovery page on the web",
311+ 'fw' => ": obtain IP from the firewall specified by -fw {type|address}",
312+ 'if' => ": obtain IP from the -if {interface}",
313+ 'cmd' => ": obtain IP from the -cmd {external-command}",
314+ 'cisco' => ": obtain IP from Cisco FW at the -fw {address}",
315+ 'cisco-asa' => ": obtain IP from Cisco ASA at the -fw {address}",
316+ map { $_ => sprintf ": obtain IP from %s at the -fw {address}", $builtinfw{$_}->{'name'} } keys %builtinfw,
317+);
318+sub ip_strategies_usage {
319+ return map { sprintf(" -use=%-22s %s.", $_, $ip_strategies{$_}) } sort keys %ip_strategies;
320+}
321+
322+my %web_strategies = (
323+ 'dyndns'=> 1,
324+ 'dnspark'=> 1,
325+ 'loopia'=> 1,
326+);
327+
328+sub setv {
329+ return {
330+ 'type' => shift,
331+ 'required' => shift,
332+ 'cache' => shift,
333+ 'config' => shift,
334+ 'default' => shift,
335+ 'minimum' => shift,
336+ };
337+};
338+my %variables = (
339+ 'global-defaults' => {
340+ 'daemon' => setv(T_DELAY, 0, 0, 1, 0, interval('60s')),
341+ 'foreground' => setv(T_BOOL, 0, 0, 1, 0, undef),
342+ 'file' => setv(T_FILE, 0, 0, 1, "$etc$program.conf", undef),
343+ 'cache' => setv(T_FILE, 0, 0, 1, "$cachedir$program.cache", undef),
344+ 'pid' => setv(T_FILE, 0, 0, 1, "", undef),
345+ 'proxy' => setv(T_FQDNP, 0, 0, 1, '', undef),
346+ 'protocol' => setv(T_PROTO, 0, 0, 1, 'dyndns2', undef),
347+
348+ 'use' => setv(T_USE, 0, 0, 1, 'ip', undef),
349+ 'ip' => setv(T_IP, 0, 0, 1, undef, undef),
350+ 'if' => setv(T_IF, 0, 0, 1, 'ppp0', undef),
351+ 'if-skip' => setv(T_STRING,1, 0, 1, '', undef),
352+ 'web' => setv(T_STRING,0, 0, 1, 'dyndns', undef),
353+ 'web-skip' => setv(T_STRING,1, 0, 1, '', undef),
354+ 'fw' => setv(T_ANY, 0, 0, 1, '', undef),
355+ 'fw-skip' => setv(T_STRING,1, 0, 1, '', undef),
356+ 'fw-login' => setv(T_LOGIN, 1, 0, 1, '', undef),
357+ 'fw-password' => setv(T_PASSWD,1, 0, 1, '', undef),
358+ 'cmd' => setv(T_PROG, 0, 0, 1, '', undef),
359+ 'cmd-skip' => setv(T_STRING,1, 0, 1, '', undef),
360+
361+ 'timeout' => setv(T_DELAY, 0, 0, 1, interval('120s'), interval('120s')),
362+ 'retry' => setv(T_BOOL, 0, 0, 0, 0, undef),
363+ 'force' => setv(T_BOOL, 0, 0, 0, 0, undef),
364+ 'ssl' => setv(T_BOOL, 0, 0, 0, 0, undef),
365+
366+ 'syslog' => setv(T_BOOL, 0, 0, 1, 0, undef),
367+ 'facility' => setv(T_STRING,0, 0, 1, 'daemon', undef),
368+ 'priority' => setv(T_STRING,0, 0, 1, 'notice', undef),
369+ 'mail' => setv(T_EMAIL, 0, 0, 1, '', undef),
370+ 'mail-failure' => setv(T_EMAIL, 0, 0, 1, '', undef),
371+
372+ 'exec' => setv(T_BOOL, 0, 0, 1, 1, undef),
373+ 'debug' => setv(T_BOOL, 0, 0, 1, 0, undef),
374+ 'verbose' => setv(T_BOOL, 0, 0, 1, 0, undef),
375+ 'quiet' => setv(T_BOOL, 0, 0, 1, 0, undef),
376+ 'help' => setv(T_BOOL, 0, 0, 1, 0, undef),
377+ 'test' => setv(T_BOOL, 0, 0, 1, 0, undef),
378+ 'geturl' => setv(T_STRING,0, 0, 0, '', undef),
379+
380+ 'postscript' => setv(T_POSTS, 0, 0, 1, '', undef),
381+ },
382+ 'service-common-defaults' => {
383+ 'server' => setv(T_FQDNP, 1, 0, 1, 'members.dyndns.org', undef),
384+ 'login' => setv(T_LOGIN, 1, 0, 1, '', undef),
385+ 'password' => setv(T_PASSWD, 1, 0, 1, '', undef),
386+ 'host' => setv(T_STRING, 1, 1, 1, '', undef),
387+
388+ 'use' => setv(T_USE, 0, 0, 1, 'ip', undef),
389+ 'if' => setv(T_IF, 0, 0, 1, 'ppp0', undef),
390+ 'if-skip' => setv(T_STRING,0, 0, 1, '', undef),
391+ 'web' => setv(T_STRING,0, 0, 1, 'dyndns', undef),
392+ 'web-skip' => setv(T_STRING,0, 0, 1, '', undef),
393+ 'fw' => setv(T_ANY, 0, 0, 1, '', undef),
394+ 'fw-skip' => setv(T_STRING,0, 0, 1, '', undef),
395+ 'fw-login' => setv(T_LOGIN, 0, 0, 1, '', undef),
396+ 'fw-password' => setv(T_PASSWD,0, 0, 1, '', undef),
397+ 'cmd' => setv(T_PROG, 0, 0, 1, '', undef),
398+ 'cmd-skip' => setv(T_STRING,0, 0, 1, '', undef),
399+
400+ 'ip' => setv(T_IP, 0, 1, 0, undef, undef),
401+ 'wtime' => setv(T_DELAY, 0, 1, 1, 0, interval('30s')),
402+ 'mtime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
403+ 'atime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
404+ 'status' => setv(T_ANY, 0, 1, 0, '', undef),
405+ 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('30s'), 0),
406+ 'max-interval' => setv(T_DELAY, 0, 0, 1, interval('30d'), 0),
407+ 'min-error-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),
408+
409+ 'warned-min-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
410+ 'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
411+ },
412+ 'dyndns-common-defaults' => {
413+ 'static' => setv(T_BOOL, 0, 1, 1, 0, undef),
414+ 'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef),
415+ 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef),
416+ 'backupmx' => setv(T_BOOL, 0, 1, 1, 0, undef),
417+ },
418+ 'easydns-common-defaults' => {
419+ 'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef),
420+ 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef),
421+ 'backupmx' => setv(T_BOOL, 0, 1, 1, 0, undef),
422+ },
423+ 'dnspark-common-defaults' => {
424+ 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef),
425+ 'mxpri' => setv(T_NUMBER, 0, 0, 1, 5, undef),
426+ },
427+ 'noip-common-defaults' => {
428+ 'static' => setv(T_BOOL, 0, 1, 1, 0, undef),
429+ },
430+ 'noip-service-common-defaults' => {
431+ 'server' => setv(T_FQDNP, 1, 0, 1, 'dynupdate.no-ip.com', undef),
432+ 'login' => setv(T_LOGIN, 1, 0, 1, '', undef),
433+ 'password' => setv(T_PASSWD, 1, 0, 1, '', undef),
434+ 'host' => setv(T_STRING, 1, 1, 1, '', undef),
435+ 'ip' => setv(T_IP, 0, 1, 0, undef, undef),
436+ 'wtime' => setv(T_DELAY, 0, 1, 1, 0, interval('30s')),
437+ 'mtime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
438+ 'atime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
439+ 'status' => setv(T_ANY, 0, 1, 0, '', undef),
440+ 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('30s'), 0),
441+ 'max-interval' => setv(T_DELAY, 0, 0, 1, interval('25d'), 0),
442+ 'min-error-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),
443+ 'warned-min-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
444+ 'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
445+ },
446+ 'zoneedit-service-common-defaults' => {
447+ 'zone' => setv(T_OFQDN, 0, 0, 1, undef, undef),
448+ },
449+ 'dtdns-common-defaults' => {
450+ 'login' => setv(T_LOGIN, 0, 0, 0, 'unused', undef),
451+ 'client' => setv(T_STRING, 0, 1, 1, $program, undef),
452+ },
453+);
454+my %services = (
455+ 'dyndns1' => {
456+ 'updateable' => \&nic_dyndns2_updateable,
457+ 'update' => \&nic_dyndns1_update,
458+ 'examples' => \&nic_dyndns1_examples,
459+ 'variables' => merge(
460+ $variables{'dyndns-common-defaults'},
461+ $variables{'service-common-defaults'},
462+ ),
463+ },
464+ 'dyndns2' => {
465+ 'updateable' => \&nic_dyndns2_updateable,
466+ 'update' => \&nic_dyndns2_update,
467+ 'examples' => \&nic_dyndns2_examples,
468+ 'variables' => merge(
469+ { 'custom' => setv(T_BOOL, 0, 1, 1, 0, undef), },
470+ { 'script' => setv(T_STRING, 1, 1, 1, '/nic/update', undef), },
471+# { 'offline' => setv(T_BOOL, 0, 1, 1, 0, undef), },
472+ $variables{'dyndns-common-defaults'},
473+ $variables{'service-common-defaults'},
474+ ),
475+ },
476+ 'noip' => {
477+ 'updateable' => undef,
478+ 'update' => \&nic_noip_update,
479+ 'examples' => \&nic_noip_examples,
480+ 'variables' => merge(
481+ { 'custom' => setv(T_BOOL, 0, 1, 1, 0, undef), },
482+ $variables{'noip-common-defaults'},
483+ $variables{'noip-service-common-defaults'},
484+ ),
485+ },
486+ 'concont' => {
487+ 'updateable' => undef,
488+ 'update' => \&nic_concont_update,
489+ 'examples' => \&nic_concont_examples,
490+ 'variables' => merge(
491+ $variables{'service-common-defaults'},
492+ { 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef), },
493+ { 'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef), },
494+ ),
495+ },
496+ 'dslreports1' => {
497+ 'updateable' => undef,
498+ 'update' => \&nic_dslreports1_update,
499+ 'examples' => \&nic_dslreports1_examples,
500+ 'variables' => merge(
501+ { 'host' => setv(T_NUMBER, 1, 1, 1, 0, undef) },
502+ $variables{'service-common-defaults'},
503+ ),
504+ },
505+ 'hammernode1' => {
506+ 'updateable' => undef,
507+ 'update' => \&nic_hammernode1_update,
508+ 'examples' => \&nic_hammernode1_examples,
509+ 'variables' => merge(
510+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'dup.hn.org', undef) },
511+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
512+ $variables{'service-common-defaults'},
513+ ),
514+ },
515+ 'zoneedit1' => {
516+ 'updateable' => undef,
517+ 'update' => \&nic_zoneedit1_update,
518+ 'examples' => \&nic_zoneedit1_examples,
519+ 'variables' => merge(
520+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'dynamic.zoneedit.com', undef) },
521+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
522+ $variables{'service-common-defaults'},
523+ $variables{'zoneedit-service-common-defaults'},
524+ ),
525+ },
526+ 'easydns' => {
527+ 'updateable' => undef,
528+ 'update' => \&nic_easydns_update,
529+ 'examples' => \&nic_easydns_examples,
530+ 'variables' => merge(
531+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'members.easydns.com', undef) },
532+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
533+ $variables{'easydns-common-defaults'},
534+ $variables{'service-common-defaults'},
535+ ),
536+ },
537+ 'dnspark' => {
538+ 'updateable' => undef,
539+ 'update' => \&nic_dnspark_update,
540+ 'examples' => \&nic_dnspark_examples,
541+ 'variables' => merge(
542+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'www.dnspark.com', undef) },
543+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
544+ $variables{'dnspark-common-defaults'},
545+ $variables{'service-common-defaults'},
546+ ),
547+ },
548+ 'namecheap' => {
549+ 'updateable' => undef,
550+ 'update' => \&nic_namecheap_update,
551+ 'examples' => \&nic_namecheap_examples,
552+ 'variables' => merge(
553+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'dynamicdns.park-your-domain.com', undef) },
554+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, 0, interval('5m')),},
555+ $variables{'service-common-defaults'},
556+ ),
557+ },
558+ 'sitelutions' => {
559+ 'updateable' => undef,
560+ 'update' => \&nic_sitelutions_update,
561+ 'examples' => \&nic_sitelutions_examples,
562+ 'variables' => merge(
563+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'www.sitelutions.com', undef) },
564+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, 0, interval('5m')),},
565+ $variables{'service-common-defaults'},
566+ ),
567+ },
568+ 'freedns' => {
569+ 'updateable' => undef,
570+ 'update' => \&nic_freedns_update,
571+ 'examples' => \&nic_freedns_examples,
572+ 'variables' => merge(
573+ { 'server' => setv(T_FQDNP, 1, 0, 1, 'freedns.afraid.org', undef) },
574+ { 'min-interval' => setv(T_DELAY, 0, 0, 1, 0, interval('5m')),},
575+ $variables{'service-common-defaults'},
576+ ),
577+ },
578+ 'dtdns' => {
579+ 'updateable' => undef,
580+ 'update' => \&nic_dtdns_update,
581+ 'examples' => \&nic_dtdns_examples,
582+ 'variables' => merge(
583+ $variables{'dtdns-common-defaults'},
584+ $variables{'service-common-defaults'},
585+ ),
586+ },
587+);
588+$variables{'merged'} = merge($variables{'global-defaults'},
589+ $variables{'service-common-defaults'},
590+ $variables{'dyndns-common-defaults'},
591+ map { $services{$_}{'variables'} } keys %services,
592+);
593+
594+my @opt = (
595+ "usage: ${program} [options]",
596+ "options are:",
597+ [ "daemon", "=s", "-daemon delay : run as a daemon, specify delay as an interval." ],
598++ [ "foreground", "!", "-foreground : do not fork" ],
599+ [ "proxy", "=s", "-proxy host : use 'host' as the HTTP proxy" ],
600+ [ "server", "=s", "-server host : update DNS information on 'host'" ],
601+ [ "protocol", "=s", "-protocol type : update protocol used" ],
602+ [ "file", "=s", "-file path : load configuration information from 'path'" ],
603+ [ "cache", "=s", "-cache path : record address used in 'path'" ],
604+ [ "pid", "=s", "-pid path : record process id in 'path'" ],
605+ "",
606+ [ "use", "=s", "-use which : how the should IP address be obtained." ],
607+ &ip_strategies_usage(),
608+ "",
609+ [ "ip", "=s", "-ip address : set the IP address to 'address'" ],
610+ "",
611+ [ "if", "=s", "-if interface : obtain IP address from 'interface'" ],
612+ [ "if-skip", "=s", "-if-skip pattern : skip any IP addresses before 'pattern' in the output of ifconfig {if}" ],
613+ "",
614+ [ "web", "=s", "-web provider|url : obtain IP address from provider's IP checking page" ],
615+ [ "web-skip", "=s", "-web-skip pattern : skip any IP addresses before 'pattern' on the web provider|url" ],
616+ "",
617+ [ "fw", "=s", "-fw address|url : obtain IP address from firewall at 'address'" ],
618+ [ "fw-skip", "=s", "-fw-skip pattern : skip any IP addresses before 'pattern' on the firewall address|url" ],
619+ [ "fw-login", "=s", "-fw-login login : use 'login' when getting IP from fw" ],
620+ [ "fw-password", "=s", "-fw-password secret : use password 'secret' when getting IP from fw" ],
621+ "",
622+ [ "cmd", "=s", "-cmd program : obtain IP address from by calling {program}" ],
623+ [ "cmd-skip", "=s", "-cmd-skip pattern : skip any IP addresses before 'pattern' in the output of {cmd}" ],
624+ "",
625+ [ "login", "=s", "-login user : login as 'user'" ],
626+ [ "password", "=s", "-password secret : use password 'secret'" ],
627+ [ "host", "=s", "-host host : update DNS information for 'host'" ],
628+ "",
629+ [ "options", "=s", "-options opt,opt : optional per-service arguments (see below)" ],
630+ "",
631+ [ "ssl", "!", "-{no}ssl : do updates over encrypted SSL connection" ],
632+ [ "retry", "!", "-{no}retry : retry failed updates." ],
633+ [ "force", "!", "-{no}force : force an update even if the update may be unnecessary" ],
634+ [ "timeout", "=i", "-timeout max : wait at most 'max' seconds for the host to respond" ],
635+
636+ [ "syslog", "!", "-{no}syslog : log messages to syslog" ],
637+ [ "facility", "=s", "-facility {type} : log messages to syslog to facility {type}" ],
638+ [ "priority", "=s", "-priority {pri} : log messages to syslog with priority {pri}" ],
639+ [ "mail", "=s", "-mail address : e-mail messages to {address}" ],
640+ [ "mail-failure","=s", "-mail-failure address : e-mail messages for failed updates to {address}" ],
641+ [ "exec", "!", "-{no}exec : do {not} execute; just show what would be done" ],
642+ [ "debug", "!", "-{no}debug : print {no} debugging information" ],
643+ [ "verbose", "!", "-{no}verbose : print {no} verbose information" ],
644+ [ "quiet", "!", "-{no}quiet : print {no} messages for unnecessary updates" ],
645+ [ "help", "", "-help : this message" ],
646+ [ "postscript", "", "-postscript : script to run after updating ddclient, has new IP as param" ],
647+
648+ [ "query", "!", "-{no}query : print {no} ip addresses and exit" ],
649+ [ "test", "!", "" ], ## hidden
650+ [ "geturl", "=s", "" ], ## hidden
651+ "",
652+ nic_examples(),
653+ "$program version $version, ",
654+ " originally written by Paul Burry, paul+ddclient\@burry.ca",
655+ " project now maintained on http://ddclient.sourceforge.net"
656+);
657+
658+## process args
659+my ($opt_usage, %opt) = process_args(@opt);
660+my ($result, %config, %globals, %cache);
661+my $saved_cache = '';
662+my %saved_opt = %opt;
663+$result = 'OK';
664+
665+test_geturl(opt('geturl')) if opt('geturl');
666+
667+## process help option
668+if (opt('help')) {
669+ *STDERR = *STDOUT;
670+ usage(0);
671+}
672+
673+## read config file because 'daemon' mode may be defined there.
674+read_config(define($opt{'file'}, default('file')), \%config, \%globals);
675+init_config();
676+test_possible_ip() if opt('query');
677+
678+if (!opt('daemon') && $programd =~ /d$/) {
679+ $opt{'daemon'} = minimum('daemon');
680+}
681+my $caught_hup = 0;
682+my $caught_term = 0;
683+my $caught_kill = 0;
684+$SIG{'HUP'} = sub { $caught_hup = 1; };
685+$SIG{'TERM'} = sub { $caught_term = 1; };
686+$SIG{'KILL'} = sub { $caught_kill = 1; };
687+# don't fork() if foreground or force is on
688+if (opt('foreground') || opt('force')) {
689+ ;
690+} elsif (opt('daemon')) {
691+ $SIG{'CHLD'} = 'IGNORE';
692+ my $pid = fork;
693+ if ($pid < 0) {
694+ print STDERR "${program}: can not fork ($!)\n";
695+ exit -1;
696+ } elsif ($pid) {
697+ exit 0;
698+ }
699+ $SIG{'CHLD'} = 'DEFAULT';
700+ open(STDOUT, ">/dev/null");
701+ open(STDERR, ">/dev/null");
702+ open(STDIN, "</dev/null");
703+}
704+
705+# write out the pid file if we're daemon'ized
706+if(opt('daemon')) {
707+ write_pid();
708+ $opt{'syslog'} = 1;
709+}
710+
711+umask 077;
712+my $daemon;
713+do {
714+ $now = time;
715+ $result = 'OK';
716+ %opt = %saved_opt;
717+ if (opt('help')) {
718+ *STDERR = *STDOUT;
719+ printf("Help found");
720+ # usage();
721+ }
722+
723+ read_config(define($opt{'file'}, default('file')), \%config, \%globals);
724+ init_config();
725+ read_cache(opt('cache'), \%cache);
726+ print_info() if opt('debug') && opt('verbose');
727+
728+# usage("invalid argument '-use %s'; possible values are:\n\t%s", $opt{'use'}, join("\n\t,",sort keys %ip_strategies))
729+ usage("invalid argument '-use %s'; possible values are:\n%s", $opt{'use'}, join("\n",ip_strategies_usage()))
730+ unless exists $ip_strategies{lc opt('use')};
731+
732+ $daemon = $opt{'daemon'};
733+ $daemon = 0 if opt('force');
734+
735+ update_nics();
736+
737+ if ($daemon) {
738+ debug("sleep %s", $daemon);
739+ sendmail();
740+
741+ my $left = $daemon;
742+ while (($left > 0) && !$caught_hup && !$caught_term && !$caught_kill) {
743+ my $delay = $left > 10 ? 10 : $left;
744+
745+ $0 = sprintf("%s - sleeping for %s seconds", $program, $left);
746+ $left -= sleep $delay;
747+ }
748+ $caught_hup = 0;
749+ $result = 0;
750+
751+ } elsif (! scalar(%config)) {
752+ warning("no hosts to update.") unless !opt('quiet') || opt('verbose') || !$daemon;
753+ $result = 1;
754+
755+ } else {
756+ $result = $result eq 'OK' ? 0 : 1;
757+ }
758+} while ($daemon && !$result && !$caught_term && !$caught_kill);
759+
760+warning("caught SIGKILL; exiting") if $caught_kill;
761+unlink_pid();
762+sendmail();
763+
764+exit($result);
765+
766+######################################################################
767+## runpostscript
768+######################################################################
769+
770+sub runpostscript {
771+ my ($ip) = @_;
772+
773+ if ( defined $globals{postscript} ) {
774+ if ( -x $globals{postscript}) {
775+ system ("$globals{postscript} $ip &");
776+ } else {
777+ warning ("Can not execute post script: %s", $globals{postscript});
778+ }
779+ }
780+}
781+
782+######################################################################
783+## update_nics
784+######################################################################
785+sub update_nics {
786+ my %examined = ();
787+ my %iplist = ();
788+
789+ foreach my $s (sort keys %services) {
790+ my (@hosts, %ips) = ();
791+ my $updateable = $services{$s}{'updateable'};
792+ my $update = $services{$s}{'update'};
793+
794+ foreach my $h (sort keys %config) {
795+ next if $config{$h}{'protocol'} ne lc($s);
796+ $examined{$h} = 1;
797+ my $use = $config{$h}{'use'} || opt('use');
798+ local $opt{$use} = $config{$h}{$use} if $config{$h}{$use};
799+ # bug #13: we should only do this once
800+ # use isn't enough, we have to save the origin to.
801+ # this will break the multiple ip stuff if use has
802+ # been used twice for the same device.
803+ my $ip = "";
804+ if (defined $iplist{$use}) {
805+ $ip = $iplist{$use};
806+ } else {
807+ $ip = get_ip($use, $h);
808+ if (!defined $ip || !$ip) {
809+ warning("unable to determine IP address")
810+ if !$daemon || opt('verbose');
811+ next;
812+ }
813+ if ($ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
814+ warning("malformed IP address (%s)", $ip);
815+ next;
816+ }
817+ $iplist{$use} = $ip;
818+ }
819+ $config{$h}{'wantip'} = $ip;
820+ next if !nic_updateable($h, $updateable);
821+ push @hosts, $h;
822+ $ips{$ip} = $h;
823+ }
824+ if (@hosts) {
825+ $0 = sprintf("%s - updating %s", $program, join(',', @hosts));
826+ &$update(@hosts);
827+ runpostscript(join ' ', keys %ips);
828+ }
829+ }
830+ foreach my $h (sort keys %config) {
831+ if (!exists $examined{$h}) {
832+ failed("%s was not updated because protocol %s is not supported.",
833+ $h, define($config{$h}{'protocol'}, '<undefined>')
834+ );
835+ }
836+ }
837+ write_cache(opt('cache'));
838+}
839+######################################################################
840+## unlink_pid()
841+######################################################################
842+sub unlink_pid {
843+ if (opt('pid') && opt('daemon')) {
844+ unlink opt('pid');
845+ }
846+}
847+
848+######################################################################
849+## write_pid()
850+######################################################################
851+sub write_pid {
852+ my $file = opt('pid');
853+
854+ if ($file && opt('daemon')) {
855+ local *FD;
856+ if (! open(FD, "> $file")) {
857+ warning("Cannot create file '%s'. ($!)", $file);
858+
859+ } else {
860+ printf FD "$$\n";
861+ close(FD);
862+ }
863+ }
864+}
865+
866+######################################################################
867+## write_cache($file)
868+######################################################################
869+sub write_cache {
870+ my ($file) = @_;
871+
872+ ## merge the updated host entries into the cache.
873+ foreach my $h (keys %config) {
874+ if (! exists $cache{$h} || $config{$h}{'update'}) {
875+ map {$cache{$h}{$_} = $config{$h}{$_} } @{$config{$h}{'cacheable'}};
876+
877+ } else {
878+ map {$cache{$h}{$_} = $config{$h}{$_} } qw(atime wtime status);
879+ }
880+ }
881+
882+ ## construct the cache file.
883+ my $cache = "";
884+ foreach my $h (sort keys %cache) {
885+ my $opt = join(',', map { "$_=".define($cache{$h}{$_},'') } sort keys %{$cache{$h}});
886+
887+ $cache .= sprintf "%s%s%s\n", $opt, ($opt ? ' ' : ''), $h;
888+ }
889+ $file = '' if defined($saved_cache) && $cache eq $saved_cache;
890+
891+ ## write the updates and other entries to the cache file.
892+ if ($file) {
893+ $saved_cache = undef;
894+ local *FD;
895+ if (! open(FD, "> $file")) {
896+ fatal("Cannot create file '%s'. ($!)", $file);
897+ }
898+ printf FD "## $program-$version\n";
899+ printf FD "## last updated at %s (%d)\n", prettytime($now), $now;
900+ printf FD $cache;
901+
902+ close(FD);
903+ }
904+}
905+######################################################################
906+## read_cache($file) - called before reading the .conf
907+######################################################################
908+sub read_cache {
909+ my $file = shift;
910+ my $config = shift;
911+ my $globals = {};
912+
913+ %{$config} = ();
914+ ## read the cache file ignoring anything on the command-line.
915+ if (-e $file) {
916+ my %saved = %opt;
917+ %opt = ();
918+ $saved_cache = _read_config($config, $globals, "##\\s*$program-$version\\s*", $file);
919+ %opt = %saved;
920+
921+ foreach my $h (keys %cache) {
922+ if (exists $config->{$h}) {
923+ foreach (qw(atime mtime wtime ip status)) {
924+ $config->{$h}{$_} = $cache{$h}{$_} if exists $cache{$h}{$_};
925+ }
926+ }
927+ }
928+ }
929+}
930+######################################################################
931+## parse_assignments(string) return (rest, %variables)
932+## parse_assignment(string) return (name, value, rest)
933+######################################################################
934+sub parse_assignments {
935+ my $rest = shift;
936+ my @args = @_;
937+ my %variables = ();
938+ my ($name, $value);
939+
940+ while (1) {
941+ $rest =~ s/^\s+//;
942+ ($name, $value, $rest) = parse_assignment($rest, @args);
943+ if (defined $name) {
944+ $variables{$name} = $value;
945+ } else {
946+ last;
947+ }
948+ }
949+ return ($rest, %variables);
950+}
951+sub parse_assignment {
952+ my $rest = shift;
953+ my $stop = @_ ? shift : '[\n\s,]';
954+ my ($c, $name, $value);
955+ my ($escape, $quote) = (0, '');
956+
957+ if ($rest =~ /^\s*([a-z][a-z_-]*)=(.*)/i) {
958+ ($name, $rest, $value) = ($1, $2, '');
959+
960+ while (length($c = substr($rest,0,1))) {
961+ $rest = substr($rest,1);
962+ if ($escape) {
963+ $value .= $c;
964+ $escape = 0;
965+ } elsif ($c eq "\\") {
966+ $escape = 1;
967+ } elsif ($quote && $c eq $quote) {
968+ $quote = ''
969+ } elsif (!$quote && $c =~ /[\'\"]/) {
970+ $quote = $c;
971+ } elsif (!$quote && $c =~ /^${stop}/) {
972+ last;
973+ } else {
974+ $value .= $c;
975+ }
976+ }
977+ }
978+ warning("assignment ended with an open quote") if $quote;
979+ return ($name, $value, $rest);
980+}
981+######################################################################
982+## read_config
983+######################################################################
984+sub read_config {
985+ my $file = shift;
986+ my $config = shift;
987+ my $globals = shift;
988+ my %globals = ();
989+
990+ _read_config($config, $globals, '', $file, %globals);
991+}
992+sub _read_config {
993+ my $config = shift;
994+ my $globals = shift;
995+ my $stamp = shift;
996+ local $file = shift;
997+ my %globals = @_;
998+ my %config = ();
999+ my $content = '';
1000+
1001+ local *FD;
1002+ if (! open(FD, "< $file")) {
1003+ # fatal("Cannot open file '%s'. ($!)", $file);
1004+ warning("Cannot open file '%s'. ($!)", $file);
1005+ }
1006+ # Check for only owner has any access to config file
1007+ my ($dev, $ino, $mode, @statrest) = stat(FD);
1008+ if ($mode & 077) {
1009+ if (-f FD && (chmod 0600, $file)) {
1010+ warning("file $file must be accessible only by its owner (fixed).");
1011+ } else {
1012+ # fatal("file $file must be accessible only by its owner.");
1013+ warning("file $file must be accessible only by its owner.");
1014+ }
1015+ }
1016+
1017+ local $lineno = 0;
1018+ my $continuation = '';
1019+ my %passwords = ();
1020+ while (<FD>) {
1021+ s/[\r\n]//g;
1022+
1023+ $lineno++;
1024+
1025+ ## check for the program version stamp
1026+ if (($. == 1) && $stamp && ($_ !~ /^$stamp$/i)) {
1027+ warning("program version mismatch; ignoring %s", $file);
1028+ last;
1029+ }
1030+ if (/\\\s+$/) {
1031+ warning("whitespace follows the \\ at the end-of-line.\nIf you meant to have a line continuation, remove the trailing whitespace.");
1032+ }
1033+
1034+ $content .= "$_\n" unless /^#/;
1035+
1036+ ## parsing passwords is special
1037+ if (/^([^#]*\s)?([^#]*?password\S*?)\s*=\s*('.*'|[^']\S*)(.*)/) {
1038+ my ($head, $key, $value, $tail) = ($1 || '', $2, $3, $4);
1039+ $value = $1 if $value =~ /^'(.*)'$/;
1040+ $passwords{$key} = $value;
1041+ $_ = "${head}${key}=dummy${tail}";
1042+ }
1043+
1044+ ## remove comments
1045+ s/#.*//;
1046+
1047+ ## handle continuation lines
1048+ $_ = "$continuation$_";
1049+ if (/\\$/) {
1050+ chop;
1051+ $continuation = $_;
1052+ next;
1053+ }
1054+ $continuation = '';
1055+
1056+ s/^\s+//; # remove leading white space
1057+ s/\s+$//; # remove trailing white space
1058+ s/\s+/ /g; # canonify
1059+ next if /^$/;
1060+
1061+ ## expected configuration line is:
1062+ ## [opt=value,opt=..] [host [login [password]]]
1063+ my %locals;
1064+ ($_, %locals) = parse_assignments($_);
1065+ s/\s*,\s*/,/g;
1066+ my @args = split;
1067+
1068+ ## verify that keywords are valid...and check the value
1069+ foreach my $k (keys %locals) {
1070+ $locals{$k} = $passwords{$k} if defined $passwords{$k};
1071+ if (!exists $variables{'merged'}{$k}) {
1072+ warning("unrecognized keyword '%s' (ignored)", $k);
1073+ delete $locals{$k};
1074+ } else {
1075+ my $def = $variables{'merged'}{$k};
1076+ my $value = check_value($locals{$k}, $def);
1077+ if (!defined($value)) {
1078+ warning("Invalid Value for keyword '%s' = '%s'", $k, $locals{$k});
1079+ delete $locals{$k};
1080+ } else { $locals{$k} = $value; }
1081+ }
1082+ }
1083+ if (exists($locals{'host'})) {
1084+ $args[0] = @args ? "$args[0],$locals{host}" : "$locals{host}";
1085+ }
1086+ ## accumulate globals
1087+ if ($#args < 0) {
1088+ map { $globals{$_} = $locals{$_} } keys %locals;
1089+ }
1090+
1091+ ## process this host definition
1092+ if (@args) {
1093+ my ($host, $login, $password) = @args;
1094+
1095+ ## add in any globals..
1096+ %locals = %{ merge(\%locals, \%globals) };
1097+
1098+ ## override login and password if specified the old way.
1099+ $locals{'login'} = $login if defined $login;
1100+ $locals{'password'} = $password if defined $password;
1101+
1102+ ## allow {host} to be a comma separated list of hosts
1103+ foreach my $h (split_by_comma($host)) {
1104+ ## save a copy of the current globals
1105+ $config{$h} = { %locals };
1106+ $config{$h}{'host'} = $h;
1107+ }
1108+ }
1109+ %passwords = ();
1110+ }
1111+ close(FD);
1112+
1113+ warning("file ends while expecting a continuation line.")
1114+ if $continuation;
1115+
1116+ %$globals = %globals;
1117+ %$config = %config;
1118+
1119+ return $content;
1120+}
1121+######################################################################
1122+## init_config -
1123+######################################################################
1124+sub init_config {
1125+ %opt = %saved_opt;
1126+
1127+ ##
1128+ $opt{'quiet'} = 0 if opt('verbose');
1129+
1130+ ## infer the IP strategy if possible
1131+ $opt{'use'} = 'ip' if !define($opt{'use'}) && defined($opt{'ip'});
1132+ $opt{'use'} = 'if' if !define($opt{'use'}) && defined($opt{'if'});
1133+ $opt{'use'} = 'web' if !define($opt{'use'}) && defined($opt{'web'});
1134+
1135+ ## sanity check
1136+ $opt{'max-interval'} = min(interval(opt('max-interval')), interval(default('max-interval')));
1137+ $opt{'min-interval'} = max(interval(opt('min-interval')), interval(default('min-interval')));
1138+ $opt{'min-error-interval'} = max(interval(opt('min-error-interval')), interval(default('min-error-interval')));
1139+
1140+ $opt{'timeout'} = 0 if opt('timeout') < 0;
1141+
1142+ ## only set $opt{'daemon'} if it has been explicitly passed in
1143+ if (define($opt{'daemon'},$globals{'daemon'},0)) {
1144+ $opt{'daemon'} = interval(opt('daemon'));
1145+ $opt{'daemon'} = minimum('daemon')
1146+ if ($opt{'daemon'} < minimum('daemon'));
1147+ }
1148+
1149+ ## define or modify host options specified on the command-line
1150+ if (exists $opt{'options'} && defined $opt{'options'}) {
1151+ ## collect cmdline configuration options.
1152+ my %options = ();
1153+ foreach my $opt (split_by_comma($opt{'options'})) {
1154+ my ($name,$var) = split /\s*=\s*/, $opt;
1155+ $options{$name} = $var;
1156+ }
1157+ ## determine hosts specified with -host
1158+ my @hosts = ();
1159+ if (exists $opt{'host'}) {
1160+ foreach my $h (split_by_comma($opt{'host'})) {
1161+ push @hosts, $h;
1162+ }
1163+ }
1164+ ## and those in -options=...
1165+ if (exists $options{'host'}) {
1166+ foreach my $h (split_by_comma($options{'host'})) {
1167+ push @hosts, $h;
1168+ }
1169+ delete $options{'host'};
1170+ }
1171+ ## merge options into host definitions or globals
1172+ if (@hosts) {
1173+ foreach my $h (@hosts) {
1174+ $config{$h} = merge(\%options, $config{$h});
1175+ }
1176+ $opt{'host'} = join(',', @hosts);
1177+ } else {
1178+ %globals = %{ merge(\%options, \%globals) };
1179+ }
1180+ }
1181+
1182+ ## override global options with those on the command-line.
1183+ foreach my $o (keys %opt) {
1184+ if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) {
1185+ $globals{$o} = $opt{$o};
1186+ }
1187+ }
1188+
1189+ ## sanity check
1190+ if (defined $opt{'host'} && defined $opt{'retry'}) {
1191+ usage("options -retry and -host (or -option host=..) are mutually exclusive");
1192+ }
1193+
1194+ ## determine hosts to update (those on the cmd-line, config-file, or failed cached)
1195+ my @hosts = keys %config;
1196+ if (opt('host')) {
1197+ @hosts = split_by_comma($opt{'host'});
1198+ }
1199+ if (opt('retry')) {
1200+ @hosts = map { $_ if $cache{$_}{'status'} ne 'good' } keys %cache;
1201+ }
1202+
1203+ ## remove any other hosts
1204+ my %hosts;
1205+ map { $hosts{$_} = undef } @hosts;
1206+ map { delete $config{$_} unless exists $hosts{$_} } keys %config;
1207+
1208+ ## collect the cacheable variables.
1209+ foreach my $proto (keys %services) {
1210+ my @cacheable = ();
1211+ foreach my $k (keys %{$services{$proto}{'variables'}}) {
1212+ push @cacheable, $k if $services{$proto}{'variables'}{$k}{'cache'};
1213+ }
1214+ $services{$proto}{'cacheable'} = [ @cacheable ];
1215+ }
1216+
1217+ ## sanity check..
1218+ ## make sure config entries have all defaults and they meet minimums
1219+ ## first the globals...
1220+ foreach my $k (keys %globals) {
1221+ my $def = $variables{'merged'}{$k};
1222+ my $ovalue = define($globals{$k}, $def->{'default'});
1223+ my $value = check_value($ovalue, $def);
1224+ if ($def->{'required'} && !defined $value) {
1225+ $value = default($k);
1226+ warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value);
1227+ }
1228+ $globals{$k} = $value;
1229+ }
1230+
1231+ ## now the host definitions...
1232+ HOST:
1233+ foreach my $h (keys %config) {
1234+ my $proto;
1235+ $proto = $config{$h}{'protocol'};
1236+ $proto = opt('protocol') if !defined($proto);
1237+
1238+ load_sha1_support() if ($proto eq "freedns");
1239+
1240+ if (!exists($services{$proto})) {
1241+ warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto);
1242+ delete $config{$h};
1243+
1244+ } else {
1245+ my $svars = $services{$proto}{'variables'};
1246+ my $conf = { 'protocol' => $proto };
1247+
1248+ foreach my $k (keys %$svars) {
1249+ my $def = $svars->{$k};
1250+ my $ovalue = define($config{$h}{$k}, $def->{'default'});
1251+ my $value = check_value($ovalue, $def);
1252+ if ($def->{'required'} && !defined $value) {
1253+ warning("skipping host: %s: '%s=%s' is an invalid %s.", $h, $k, $ovalue, $def->{'type'});
1254+ delete $config{$h};
1255+ next HOST;
1256+ }
1257+ $conf->{$k} = $value;
1258+
1259+ }
1260+ $config{$h} = $conf;
1261+ $config{$h}{'cacheable'} = [ @{$services{$proto}{'cacheable'}} ];
1262+ }
1263+ }
1264+}
1265+
1266+######################################################################
1267+## usage
1268+######################################################################
1269+sub usage {
1270+ my $exitcode = 1;
1271+ $exitcode = shift if @_ != 0; # use first arg if given
1272+ my $msg = '';
1273+ if (@_) {
1274+ my $format = shift;
1275+ $msg .= sprintf $format, @_;
1276+ 1 while chomp($msg);
1277+ $msg .= "\n";
1278+ }
1279+ printf STDERR "%s%s\n", $msg, $opt_usage;
1280+ sendmail();
1281+ exit $exitcode;
1282+}
1283+
1284+######################################################################
1285+## process_args -
1286+######################################################################
1287+sub process_args {
1288+ my @spec = ();
1289+ my $usage = "";
1290+ my %opts = ();
1291+
1292+ foreach (@_) {
1293+ if (ref $_) {
1294+ my ($key, $specifier, $arg_usage) = @$_;
1295+ my $value = default($key);
1296+
1297+ ## add a option specifier
1298+ push @spec, $key . $specifier;
1299+
1300+ ## define the default value which can be overwritten later
1301+ $opt{$key} = undef;
1302+
1303+ next unless $arg_usage;
1304+
1305+ ## add a line to the usage;
1306+ $usage .= " $arg_usage";
1307+ if (defined($value) && $value ne '') {
1308+ $usage .= " (default: ";
1309+ if ($specifier eq '!') {
1310+ $usage .= "no" if ($specifier eq '!') && !$value;
1311+ $usage .= $key;
1312+ } else {
1313+ $usage .= $value;
1314+ }
1315+ $usage .= ")";
1316+ }
1317+ $usage .= ".";
1318+ } else {
1319+ $usage .= $_;
1320+ }
1321+ $usage .= "\n";
1322+ }
1323+ ## process the arguments
1324+ if (! GetOptions(\%opt, @spec)) {
1325+ $opt{"help"} = 1;
1326+ }
1327+ return ($usage, %opt);
1328+}
1329+######################################################################
1330+## test_possible_ip - print possible IPs
1331+######################################################################
1332+sub test_possible_ip {
1333+ local $opt{'debug'} = 0;
1334+
1335+ printf "use=ip, ip=%s address is %s\n", opt('ip'), define(get_ip('ip'), 'NOT FOUND')
1336+ if defined opt('ip');
1337+
1338+ {
1339+ local $opt{'use'} = 'if';
1340+ foreach my $if (grep {/^[a-zA-Z]/} `ifconfig -a`) {
1341+ $if =~ s/:?\s.*//is;
1342+ local $opt{'if'} = $if;
1343+ printf "use=if, if=%s address is %s\n", opt('if'), define(get_ip('if'), 'NOT FOUND');
1344+ }
1345+ }
1346+ if (opt('fw')) {
1347+ if (opt('fw') !~ m%/%) {
1348+ foreach my $fw (sort keys %builtinfw) {
1349+ local $opt{'use'} = $fw;
1350+ printf "use=$fw address is %s\n", define(get_ip($fw), 'NOT FOUND');
1351+ }
1352+ }
1353+ local $opt{'use'} = 'fw';
1354+ printf "use=fw, fw=%s address is %s\n", opt('fw'), define(get_ip(opt('fw')), 'NOT FOUND')
1355+ if ! exists $builtinfw{opt('fw')};
1356+
1357+ }
1358+ {
1359+ local $opt{'use'} = 'web';
1360+ foreach my $web (sort keys %builtinweb) {
1361+ local $opt{'web'} = $web;
1362+ printf "use=web, web=$web address is %s\n", define(get_ip('web'), 'NOT FOUND');
1363+ }
1364+ printf "use=web, web=%s address is %s\n", opt('web'), define(get_ip('web'), 'NOT FOUND')
1365+ if ! exists $builtinweb{opt('web')};
1366+ }
1367+ if (opt('cmd')) {
1368+ local $opt{'use'} = 'cmd';
1369+ printf "use=cmd, cmd=%s address is %s\n", opt('cmd'), define(get_ip('cmd'), 'NOT FOUND');
1370+ }
1371+ exit 0 unless opt('debug');
1372+}
1373+######################################################################
1374+## test_geturl - print (and save if -test) result of fetching a URL
1375+######################################################################
1376+sub test_geturl {
1377+ my $url = shift;
1378+
1379+ my $reply = geturl(opt('proxy'), $url, opt('login'), opt('password'));
1380+ print "URL $url\n";;
1381+ print defined($reply) ? $reply : "<undefined>\n";
1382+ exit;
1383+}
1384+######################################################################
1385+## load_file
1386+######################################################################
1387+sub load_file {
1388+ my $file = shift;
1389+ my $buffer = '';
1390+
1391+ if (exists($ENV{'TEST_CASE'})) {
1392+ my $try = "$file-$ENV{'TEST_CASE'}";
1393+ $file = $try if -f $try;
1394+ }
1395+
1396+ local *FD;
1397+ if (open(FD, "< $file")) {
1398+ read(FD, $buffer, -s FD);
1399+ close(FD);
1400+ debug("Loaded %d bytes from %s", length($buffer), $file);
1401+ } else {
1402+ debug("Load failed from %s ($!)", $file);
1403+ }
1404+ return $buffer
1405+}
1406+######################################################################
1407+## save_file
1408+######################################################################
1409+sub save_file {
1410+ my ($file, $buffer, $opt) = @_;
1411+
1412+ $file .= "-$ENV{'TEST_CASE'}" if exists $ENV{'TEST_CASE'};
1413+ if (defined $opt) {
1414+ my $i = 0;
1415+ while (-f "$file-$i") {
1416+ if ('unique' =~ /^$opt/i) {
1417+ my $a = join('\n', grep {!/^Date:/} split /\n/, $buffer);
1418+ my $b = join('\n', grep {!/^Date:/} split /\n/, load_file("$file-$i"));
1419+ last if $a eq $b;
1420+ }
1421+ $i++;
1422+ }
1423+ $file = "$file-$i";
1424+ }
1425+ debug("Saving to %s", $file);
1426+ local *FD;
1427+ open(FD, "> $file") or return;
1428+ print FD $buffer;
1429+ close(FD);
1430+ return $buffer;
1431+}
1432+######################################################################
1433+## print_opt
1434+## print_globals
1435+## print_config
1436+## print_cache
1437+## print_info
1438+######################################################################
1439+sub _print_hash {
1440+ my ($string, $ptr) = @_;
1441+ my $value = $ptr;
1442+
1443+ if (! defined($ptr)) {
1444+ $value = "<undefined>";
1445+ } elsif (ref $ptr eq 'HASH') {
1446+ foreach my $key (sort keys %$ptr) {
1447+ _print_hash("${string}\{$key\}", $ptr->{$key});
1448+ }
1449+ return;
1450+ }
1451+ printf "%-36s : %s\n", $string, $value;
1452+}
1453+sub print_hash {
1454+ my ($string, $hash) = @_;
1455+ printf "=== %s ====\n", $string;
1456+ _print_hash($string, $hash);
1457+}
1458+sub print_opt { print_hash("opt", \%opt); }
1459+sub print_globals { print_hash("globals", \%globals); }
1460+sub print_config { print_hash("config", \%config); }
1461+sub print_cache { print_hash("cache", \%cache); }
1462+sub print_info {
1463+ print_opt();
1464+ print_globals();
1465+ print_config();
1466+ print_cache();
1467+}
1468+######################################################################
1469+## pipecmd - run an external command
1470+## logger
1471+## sendmail
1472+######################################################################
1473+sub pipecmd {
1474+ my $cmd = shift;
1475+ my $stdin = join("\n", @_);
1476+ my $ok = 0;
1477+
1478+ ## remove trailing newlines
1479+ 1 while chomp($stdin);
1480+
1481+ ## override when debugging.
1482+ $cmd = opt('exec') ? "| $cmd" : "> /dev/null";
1483+
1484+ ## execute the command.
1485+ local *FD;
1486+ if (! open(FD, $cmd)) {
1487+ printf STDERR "$program: cannot execute command %s.\n", $cmd;
1488+
1489+ } elsif ($stdin && (! print FD "$stdin\n")) {
1490+ printf STDERR "$program: failed writting to %s.\n", $cmd;
1491+ close(FD);
1492+
1493+ } elsif (! close(FD)) {
1494+ printf STDERR "$program: failed closing %s.($@)\n", $cmd;
1495+
1496+ } elsif (opt('exec') && $?) {
1497+ printf STDERR "$program: failed %s. ($@)\n", $cmd;
1498+
1499+ } else {
1500+ $ok = 1;
1501+ }
1502+ return $ok;
1503+}
1504+sub logger {
1505+ if (opt('syslog') && opt('facility') && opt('priority')) {
1506+ my $facility = opt('facility');
1507+ my $priority = opt('priority');
1508+ return pipecmd("logger -p$facility.$priority -t${program}\[$$\]", @_);
1509+ }
1510+ return 1;
1511+}
1512+sub sendmail {
1513+ my $recipients = opt('mail');
1514+
1515+ if (opt('mail-failure') && ($result ne 'OK' && $result ne '0')) {
1516+ $recipients = opt('mail-failure');
1517+ }
1518+ if ($msgs && $recipients && $msgs ne $last_msgs) {
1519+ pipecmd("sendmail -oi $recipients",
1520+ "To: $recipients",
1521+ "Subject: status report from $program\@$hostname",
1522+ "\r\n",
1523+ $msgs,
1524+ "",
1525+ "regards,",
1526+ " $program\@$hostname (version $version)"
1527+ );
1528+ }
1529+ $last_msgs = $msgs;
1530+ $msgs = '';
1531+}
1532+######################################################################
1533+## split_by_comma
1534+## merge
1535+## default
1536+## minimum
1537+## opt
1538+######################################################################
1539+sub split_by_comma {
1540+ my $string = shift;
1541+
1542+ return split /\s*[, ]\s*/, $string if defined $string;
1543+ return ();
1544+}
1545+sub merge {
1546+ my %merged = ();
1547+ foreach my $h (@_) {
1548+ foreach my $k (keys %$h) {
1549+ $merged{$k} = $h->{$k} unless exists $merged{$k};
1550+ }
1551+ }
1552+ return \%merged;
1553+}
1554+sub default {
1555+ my $v = shift;
1556+ return $variables{'merged'}{$v}{'default'};
1557+}
1558+sub minimum {
1559+ my $v = shift;
1560+ return $variables{'merged'}{$v}{'minimum'};
1561+}
1562+sub opt {
1563+ my $v = shift;
1564+ my $h = shift;
1565+ return $config{$h}{$v} if defined($h && $config{$h}{$v});
1566+ return $opt{$v} if defined $opt{$v};
1567+ return $globals{$v} if defined $globals{$v};
1568+ return default($v) if defined default($v);
1569+ return undef;
1570+}
1571+sub min {
1572+ my $min = shift;
1573+ foreach my $arg (@_) {
1574+ $min = $arg if $arg < $min;
1575+ }
1576+ return $min;
1577+}
1578+sub max {
1579+ my $max = shift;
1580+ foreach my $arg (@_) {
1581+ $max = $arg if $arg > $max;
1582+ }
1583+ return $max;
1584+}
1585+######################################################################
1586+## define
1587+######################################################################
1588+sub define {
1589+ foreach (@_) {
1590+ return $_ if defined $_;
1591+ }
1592+ return undef;
1593+}
1594+######################################################################
1595+## ynu
1596+######################################################################
1597+sub ynu {
1598+ my ($value, $yes, $no, $undef) = @_;
1599+
1600+ return $no if !defined($value) || !$value;
1601+ return $yes if $value eq '1';
1602+ foreach (qw(yes true)) {
1603+ return $yes if $_ =~ /^$value/i;
1604+ }
1605+ foreach (qw(no false)) {
1606+ return $no if $_ =~ /^$value/i;
1607+ }
1608+ return $undef;
1609+}
1610+######################################################################
1611+## msg
1612+## debug
1613+## warning
1614+## fatal
1615+######################################################################
1616+sub _msg {
1617+ my $log = shift;
1618+ my $prefix = shift;
1619+ my $format = shift;
1620+ my $buffer = sprintf $format, @_;
1621+ chomp($buffer);
1622+
1623+ $prefix = sprintf "%-9s ", $prefix if $prefix;
1624+ if ($file) {
1625+ $prefix .= "file $file";
1626+ $prefix .= ", line $lineno" if $lineno;
1627+ $prefix .= ": ";
1628+ }
1629+ if ($prefix) {
1630+ $buffer = "$prefix$buffer";
1631+ $buffer =~ s/\n/\n$prefix /g;
1632+ }
1633+ $buffer .= "\n";
1634+ print $buffer;
1635+
1636+ $msgs .= $buffer if $log;
1637+ logger($buffer) if $log;
1638+
1639+}
1640+sub msg { _msg(0, '', @_); }
1641+sub verbose { _msg(1, @_) if opt('verbose'); }
1642+sub info { _msg(1, 'INFO:', @_) if opt('verbose'); }
1643+sub debug { _msg(0, 'DEBUG:', @_) if opt('debug'); }
1644+sub debug2 { _msg(0, 'DEBUG:', @_) if opt('debug') && opt('verbose');}
1645+sub warning { _msg(1, 'WARNING:', @_); }
1646+sub fatal { _msg(1, 'FATAL:', @_); sendmail(); exit(1); }
1647+sub success { _msg(1, 'SUCCESS:', @_); }
1648+sub failed { _msg(1, 'FAILED:', @_); $result = 'FAILED'; }
1649+sub prettytime { return scalar(localtime(shift)); }
1650+
1651+sub prettyinterval {
1652+ my $interval = shift;
1653+ use integer;
1654+ my $s = $interval % 60; $interval /= 60;
1655+ my $m = $interval % 60; $interval /= 60;
1656+ my $h = $interval % 24; $interval /= 24;
1657+ my $d = $interval;
1658+
1659+ my $string = "";
1660+ $string .= "$d day" if $d;
1661+ $string .= "s" if $d > 1;
1662+ $string .= ", " if $string && $h;
1663+ $string .= "$h hour" if $h;
1664+ $string .= "s" if $h > 1;
1665+ $string .= ", " if $string && $m;
1666+ $string .= "$m minute" if $m;
1667+ $string .= "s" if $m > 1;
1668+ $string .= ", " if $string && $s;
1669+ $string .= "$s second" if $s;
1670+ $string .= "s" if $s > 1;
1671+ return $string;
1672+}
1673+sub interval {
1674+ my $value = shift;
1675+ if ($value =~ /^(\d+)(seconds|s)/i) {
1676+ $value = $1;
1677+ } elsif ($value =~ /^(\d+)(minutes|m)/i) {
1678+ $value = $1 * 60;
1679+ } elsif ($value =~ /^(\d+)(hours|h)/i) {
1680+ $value = $1 * 60*60;
1681+ } elsif ($value =~ /^(\d+)(days|d)/i) {
1682+ $value = $1 * 60*60*24;
1683+ } elsif ($value !~ /^\d+$/) {
1684+ $value = undef;
1685+ }
1686+ return $value;
1687+}
1688+sub interval_expired {
1689+ my ($host, $time, $interval) = @_;
1690+
1691+ return 1 if !exists $cache{$host};
1692+ return 1 if !exists $cache{$host}{$time} || !$cache{$host}{$time};
1693+ return 1 if !exists $config{$host}{$interval} || !$config{$host}{$interval};
1694+
1695+ return $now > ($cache{$host}{$time} + $config{$host}{$interval});
1696+}
1697+
1698+
1699+
1700+######################################################################
1701+## check_value
1702+######################################################################
1703+sub check_value {
1704+ my ($value, $def) = @_;
1705+ my $type = $def->{'type'};
1706+ my $min = $def->{'minimum'};
1707+ my $required = $def->{'required'};
1708+
1709+ if (!defined $value && !$required) {
1710+ ;
1711+
1712+ } elsif ($type eq T_DELAY) {
1713+ $value = interval($value);
1714+ $value = $min if defined($value) && defined($min) && $value < $min;
1715+
1716+ } elsif ($type eq T_NUMBER) {
1717+ return undef if $value !~ /^\d+$/;
1718+ $value = $min if defined($min) && $value < $min;
1719+
1720+ } elsif ($type eq T_BOOL) {
1721+ if ($value =~ /^y(es)?$|^t(true)?$|^1$/i) {
1722+ $value = 1;
1723+ } elsif ($value =~ /^n(o)?$|^f(alse)?$|^0$/i) {
1724+ $value = 0;
1725+ } else {
1726+ return undef;
1727+ }
1728+ } elsif ($type eq T_FQDN || $type eq T_OFQDN && $value ne '') {
1729+ $value = lc $value;
1730+ return undef if $value !~ /[^.]\.[^.]/;
1731+
1732+ } elsif ($type eq T_FQDNP) {
1733+ $value = lc $value;
1734+ return undef if $value !~ /[^.]\.[^.].*(:\d+)?$/;
1735+
1736+ } elsif ($type eq T_PROTO) {
1737+ $value = lc $value;
1738+ return undef if ! exists $services{$value};
1739+
1740+ } elsif ($type eq T_USE) {
1741+ $value = lc $value;
1742+ return undef if ! exists $ip_strategies{$value};
1743+
1744+ } elsif ($type eq T_FILE) {
1745+ return undef if $value eq "";
1746+
1747+ } elsif ($type eq T_IF) {
1748+ return undef if $value !~ /^[a-z0-9:._-]+$/;
1749+
1750+ } elsif ($type eq T_PROG) {
1751+ return undef if $value eq "";
1752+
1753+ } elsif ($type eq T_LOGIN) {
1754+ return undef if $value eq "";
1755+
1756+# } elsif ($type eq T_PASSWD) {
1757+# return undef if $value =~ /:/;
1758+
1759+ } elsif ($type eq T_IP) {
1760+ return undef if $value !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
1761+ }
1762+ return $value;
1763+}
1764+######################################################################
1765+## encode_base64 - from MIME::Base64
1766+######################################################################
1767+sub encode_base64 ($;$) {
1768+ my $res = '';
1769+ my $eol = $_[1];
1770+ $eol = "\n" unless defined $eol;
1771+ pos($_[0]) = 0; # ensure start at the beginning
1772+ while ($_[0] =~ /(.{1,45})/gs) {
1773+ $res .= substr(pack('u', $1), 1);
1774+ chop($res);
1775+ }
1776+ $res =~ tr|` -_|AA-Za-z0-9+/|; # `# help emacs
1777+
1778+ # fix padding at the end
1779+ my $padding = (3 - length($_[0]) % 3) % 3;
1780+ $res =~ s/.{$padding}$/'=' x $padding/e if $padding;
1781+ $res;
1782+}
1783+######################################################################
1784+## load_ssl_support
1785+######################################################################
1786+sub load_ssl_support {
1787+ my $ssl_loaded = eval {require IO::Socket::SSL};
1788+ unless ($ssl_loaded) {
1789+ fatal(<<"EOM");
1790+Error loading the Perl module IO::Socket::SSL needed for SSL connect.
1791+On Debian, the package libio-socket-ssl-perl must be installed.
1792+On Red Hat, the package perl-IO-Socket-SSL must be installed.
1793+EOM
1794+ }
1795+ import IO::Socket::SSL;
1796+ { no warnings; $IO::Socket::SSL::DEBUG = 0; }
1797+}
1798+######################################################################
1799+## load_sha1_support
1800+######################################################################
1801+sub load_sha1_support {
1802+ my $sha1_loaded = eval {require Digest::SHA1};
1803+ unless ($sha1_loaded) {
1804+ fatal(<<"EOM");
1805+Error loading the Perl module Digest::SHA1 needed for freedns update.
1806+On Debian, the package libdigest-sha1-perl must be installed.
1807+EOM
1808+ }
1809+ import Digest::SHA1 (qw/sha1_hex/);
1810+}
1811+######################################################################
1812+## geturl
1813+######################################################################
1814+sub geturl {
1815+ my $proxy = shift || '';
1816+ my $url = shift || '';
1817+ my $login = shift || '';
1818+ my $password = shift || '';
1819+ my ($peer, $server, $port, $default_port, $use_ssl);
1820+ my ($sd, $rq, $request, $reply);
1821+
1822+ debug("proxy = $proxy");
1823+ debug("url = %s", $url);
1824+ ## canonify proxy and url
1825+ my $force_ssl;
1826+ $force_ssl = 1 if ($url =~ /^https:/);
1827+ $proxy =~ s%^https?://%%i;
1828+ $url =~ s%^https?://%%i;
1829+ $server = $url;
1830+ $server =~ s%/.*%%;
1831+ $url = "/" unless $url =~ m%/%;
1832+ $url =~ s%^[^/]*/%%;
1833+
1834+ debug("server = $server");
1835+ opt('fw') && debug("opt(fw = ",opt('fw'),")");
1836+ $globals{'fw'} && debug("glo fw = $globals{'fw'}");
1837+ #if ( $globals{'ssl'} and $server ne $globals{'fw'} ) {
1838+ ## always omit SSL for connections to local router
1839+ if ( $force_ssl || ($globals{'ssl'} and (caller(1))[3] ne 'main::get_ip') ) {
1840+ $use_ssl = 1;
1841+ $default_port = 443;
1842+ load_ssl_support;
1843+ } else {
1844+ $use_ssl = 0;
1845+ $default_port = 80;
1846+ }
1847+
1848+ ## determine peer and port to use.
1849+ $peer = $proxy || $server;
1850+ $peer =~ s%/.*%%;
1851+ $port = $peer;
1852+ $port =~ s%^.*:%%;
1853+ $port = $default_port unless $port =~ /^\d+$/;
1854+ $peer =~ s%:.*$%%;
1855+
1856+ my $to = sprintf "%s%s", $server, $proxy ? " via proxy $peer:$port" : "";
1857+ verbose("CONNECT:", "%s", $to);
1858+
1859+ $request = "GET ";
1860+ $request .= "http://$server" if $proxy;
1861+ $request .= "/$url HTTP/1.0\n";
1862+ $request .= "Host: $server\n";
1863+
1864+ my $auth = encode_base64("${login}:${password}");
1865+ $request .= "Authorization: Basic $auth\n" if $login || $password;
1866+ $request .= "User-Agent: ${program}/${version}\n";
1867+ $request .= "Connection: close\n";
1868+ $request .= "\n";
1869+
1870+ ## make sure newlines are <cr><lf> for some pedantic proxy servers
1871+ ($rq = $request) =~ s/\n/\r\n/g;
1872+
1873+ # local $^W = 0;
1874+ $0 = sprintf("%s - connecting to %s port %s", $program, $peer, $port);
1875+ if (! opt('exec')) {
1876+ debug("skipped network connection");
1877+ verbose("SENDING:", "%s", $request);
1878+ } elsif ($use_ssl) {
1879+ $sd = IO::Socket::SSL->new(
1880+ PeerAddr => $peer,
1881+ PeerPort => $port,
1882+ Proto => 'tcp',
1883+ MultiHomed => 1,
1884+ Timeout => opt('timeout'),
1885+ );
1886+ defined $sd or warning("cannot connect to $peer:$port socket: $@ " . IO::Socket::SSL::errstr());
1887+ } else {
1888+ $sd = IO::Socket::INET->new(
1889+ PeerAddr => $peer,
1890+ PeerPort => $port,
1891+ Proto => 'tcp',
1892+ MultiHomed => 1,
1893+ Timeout => opt('timeout'),
1894+ );
1895+ defined $sd or warning("cannot connect to $peer:$port socket: $@");
1896+ }
1897+
1898+ if (defined $sd) {
1899+ ## send the request to the http server
1900+ verbose("CONNECTED: ", $use_ssl ? 'using SSL' : 'using HTTP');
1901+ verbose("SENDING:", "%s", $request);
1902+
1903+ $0 = sprintf("%s - sending to %s port %s", $program, $peer, $port);
1904+ my $result = syswrite $sd, $rq;
1905+ if ($result != length($rq)) {
1906+ warning("cannot send to $peer:$port ($!).");
1907+ } else {
1908+ $0 = sprintf("%s - reading from %s port %s", $program, $peer, $port);
1909+ eval {
1910+ local $SIG{'ALRM'} = sub { die "timeout";};
1911+ alarm(opt('timeout')) if opt('timeout') > 0;
1912+ while ($_ = <$sd>) {
1913+ $0 = sprintf("%s - read from %s port %s", $program, $peer, $port);
1914+ verbose("RECEIVE:", "%s", define($_, "<undefined>"));
1915+ $reply .= $_ if defined $_;
1916+ }
1917+ if (opt('timeout') > 0) {
1918+ alarm(0);
1919+ }
1920+ };
1921+ close($sd);
1922+
1923+ if ($@ and $@ =~ /timeout/) {
1924+ warning("TIMEOUT: %s after %s seconds", $to, opt('timeout'));
1925+ $reply = '';
1926+ }
1927+ $reply = '' if !defined $reply;
1928+ }
1929+ }
1930+ $0 = sprintf("%s - closed %s port %s", $program, $peer, $port);
1931+
1932+ ## during testing simulate reading the URL
1933+ if (opt('test')) {
1934+ my $filename = "$server/$url";
1935+ $filename =~ s|/|%2F|g;
1936+ if (opt('exec')) {
1937+ $reply = save_file("${savedir}$filename", $reply, 'unique');
1938+ } else {
1939+ $reply = load_file("${savedir}$filename");
1940+ }
1941+ }
1942+
1943+ $reply =~ s/\r//g if defined $reply;
1944+ return $reply;
1945+}
1946+######################################################################
1947+## get_ip
1948+######################################################################
1949+sub get_ip {
1950+ my $use = lc shift;
1951+ my $h = shift;
1952+ my ($ip, $arg, $reply, $url, $skip) = (undef, opt($use), '');
1953+ $arg = '' unless $arg;
1954+
1955+ if ($use eq 'ip') {
1956+ $ip = opt('ip', $h);
1957+ $arg = 'ip';
1958+
1959+ } elsif ($use eq 'if') {
1960+ $skip = opt('if-skip', $h) || '';
1961+ $reply = `ifconfig $arg 2> /dev/null`;
1962+ $reply = '' if $?;
1963+
1964+ } elsif ($use eq 'cmd') {
1965+ if ($arg) {
1966+ $skip = opt('cmd-skip', $h) || '';
1967+ $reply = `$arg`;
1968+ $reply = '' if $?;
1969+ }
1970+
1971+ } elsif ($use eq 'web') {
1972+ $url = opt('web', $h) || '';
1973+ $skip = opt('web-skip', $h) || '';
1974+
1975+ if (exists $builtinweb{$url}) {
1976+ $skip = $builtinweb{$url}->{'skip'} unless $skip;
1977+ $url = $builtinweb{$url}->{'url'};
1978+ }
1979+ $arg = $url;
1980+
1981+ if ($url) {
1982+ $reply = geturl(opt('proxy', $h), $url) || '';
1983+ }
1984+
1985+ } elsif (($use eq 'cisco')) {
1986+ # Stuff added to support Cisco router ip http daemon
1987+ # User fw-login should only have level 1 access to prevent
1988+ # password theft. This is pretty harmless.
1989+ my $queryif = opt('if', $h);
1990+ $skip = opt('fw-skip', $h) || '';
1991+
1992+ # Convert slashes to protected value "\/"
1993+ $queryif =~ s%\/%\\\/%g;
1994+
1995+ # Protect special HTML characters (like '?')
1996+ $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge;
1997+
1998+ $url = "http://".opt('fw', $h)."/level/1/exec/show/ip/interface/brief/${queryif}/CR";
1999+ $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || '';
2000+ $arg = $url;
2001+
2002+ } elsif (($use eq 'cisco-asa')) {
2003+ # Stuff added to support Cisco ASA ip https daemon
2004+ # User fw-login should only have level 1 access to prevent
2005+ # password theft. This is pretty harmless.
2006+ my $queryif = opt('if', $h);
2007+ $skip = opt('fw-skip', $h) || '';
2008+
2009+ # Convert slashes to protected value "\/"
2010+ $queryif =~ s%\/%\\\/%g;
2011+
2012+ # Protect special HTML characters (like '?')
2013+ $queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge;
2014+
2015+ $url = "https://".opt('fw', $h)."/exec/show%20interface%20${queryif}";
2016+ $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || '';
2017+ $arg = $url;
2018+
2019+ } else {
2020+ $url = opt('fw', $h) || '';
2021+ $skip = opt('fw-skip', $h) || '';
2022+
2023+ if (exists $builtinfw{$use}) {
2024+ $skip = $builtinfw{$use}->{'skip'} unless $skip;
2025+ $url = "http://${url}" . $builtinfw{$use}->{'url'} unless $url =~ /\//;
2026+ }
2027+ $arg = $url;
2028+
2029+ if ($url) {
2030+ $reply = geturl('', $url, opt('fw-login', $h), opt('fw-password', $h)) || '';
2031+ }
2032+ }
2033+ if (!defined $reply) {
2034+ $reply = '';
2035+ }
2036+ if ($skip) {
2037+ $skip =~ s/ /\\s/is;
2038+ $reply =~ s/^.*?${skip}//is;
2039+ }
2040+ if ($reply =~ /^.*?\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b.*/is) {
2041+ $ip = $1;
2042+ }
2043+ if (($use ne 'ip') && (define($ip,'') eq '0.0.0.0')) {
2044+ $ip = undef;
2045+ }
2046+
2047+ debug("get_ip: using %s, %s reports %s", $use, $arg, define($ip, "<undefined>"));
2048+ return $ip;
2049+}
2050+
2051+######################################################################
2052+## group_hosts_by
2053+######################################################################
2054+sub group_hosts_by {
2055+ my ($hosts, $attributes) = @_;
2056+
2057+ my %groups = ();
2058+ foreach my $h (@$hosts) {
2059+ my @keys = (@$attributes, 'wantip');
2060+ map { $config{$h}{$_} = '' unless exists $config{$h}{$_} } @keys;
2061+ my $sig = join(',', map { "$_=$config{$h}{$_}" } @keys);
2062+
2063+ push @{$groups{$sig}}, $h;
2064+ }
2065+ return %groups;
2066+}
2067+######################################################################
2068+## nic_examples
2069+######################################################################
2070+sub nic_examples {
2071+ my $examples = "";
2072+ my $separator = "";
2073+ foreach my $s (sort keys %services) {
2074+ my $subr = $services{$s}{'examples'};
2075+ my $example;
2076+
2077+ if (defined($subr) && ($example = &$subr())) {
2078+ chomp($example);
2079+ $examples .= $example;
2080+ $examples .= "\n\n$separator";
2081+ $separator = "\n";
2082+ }
2083+ }
2084+ my $intro = <<EoEXAMPLE;
2085+== CONFIGURING ${program}
2086+
2087+The configuration file, ${program}.conf, can be used to define the
2088+default behaviour and operation of ${program}. The file consists of
2089+sequences of global variable definitions and host definitions.
2090+
2091+Global definitions look like:
2092+ name=value [,name=value]*
2093+
2094+For example:
2095+ daemon=5m
2096+ use=if, if=eth0
2097+ proxy=proxy.myisp.com
2098+ protocol=dyndns2
2099+
2100+specifies that ${program} should operate as a daemon, checking the
2101+eth0 interface for an IP address change every 5 minutes and use the
2102+'dyndns2' protocol by default. The daemon interval can be specified
2103+as seconds (600s), minutes (5m), hours (1h) or days (1d).
2104+
2105+Host definitions look like:
2106+ [name=value [,name=value]*]* a.host.domain [,b.host.domain] [login] [password]
2107+
2108+For example:
2109+ protocol=hammernode1, \\
2110+ login=my-hn-login, password=my-hn-password myhost.hn.org
2111+ login=my-login, password=my-password myhost.dyndns.org,my2nd.dyndns.org
2112+
2113+specifies two host definitions.
2114+
2115+The first definition will use the hammernode1 protocol,
2116+my-hn-login and my-hn-password to update the ip-address of
2117+myhost.hn.org and my2ndhost.hn.org.
2118+
2119+The second host definition will use the current default protocol
2120+('dyndns2'), my-login and my-password to update the ip-address of
2121+myhost.dyndns.org and my2ndhost.dyndns.org.
2122+
2123+The order of this sequence is significant because the values of any
2124+global variable definitions are bound to a host definition when the
2125+host definition is encountered.
2126+
2127+See the sample-${program}.conf file for further examples.
2128+EoEXAMPLE
2129+ $intro .= "\n== NIC specific variables and examples:\n$examples" if $examples;
2130+ return $intro;
2131+}
2132+######################################################################
2133+## nic_updateable
2134+######################################################################
2135+sub nic_updateable {
2136+ my $host = shift;
2137+ my $sub = shift;
2138+ my $update = 0;
2139+ my $ip = $config{$host}{'wantip'};
2140+
2141+ if ($config{$host}{'login'} eq '') {
2142+ warning("null login name specified for host %s.", $host);
2143+
2144+ } elsif ($config{$host}{'password'} eq '') {
2145+ warning("null password specified for host %s.", $host);
2146+
2147+ } elsif ($opt{'force'}) {
2148+ info("forcing update of %s.", $host);
2149+ $update = 1;
2150+
2151+ } elsif (!exists($cache{$host})) {
2152+ info("forcing updating %s because no cached entry exists.", $host);
2153+ $update = 1;
2154+
2155+ } elsif ($cache{$host}{'wtime'} && $cache{$host}{'wtime'} > $now) {
2156+ warning("cannot update %s from %s to %s until after %s.",
2157+ $host,
2158+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : '<nothing>'), $ip,
2159+ prettytime($cache{$host}{'wtime'})
2160+ );
2161+
2162+ } elsif ($cache{$host}{'mtime'} && interval_expired($host, 'mtime', 'max-interval')) {
2163+ warning("forcing update of %s from %s to %s; %s since last update on %s.",
2164+ $host,
2165+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : '<nothing>'), $ip,
2166+ prettyinterval($config{$host}{'max-interval'}),
2167+ prettytime($cache{$host}{'mtime'})
2168+ );
2169+ $update = 1;
2170+
2171+ } elsif ((!exists($cache{$host}{'ip'})) ||
2172+ ("$cache{$host}{'ip'}" ne "$ip")) {
2173+ if (($cache{$host}{'status'} eq 'good') &&
2174+ !interval_expired($host, 'mtime', 'min-interval')) {
2175+
2176+ warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.",
2177+ $host,
2178+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : '<nothing>'),
2179+ $ip,
2180+ ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : '<never>'),
2181+ prettyinterval($config{$host}{'min-interval'})
2182+ )
2183+ if opt('verbose') || !define($cache{$host}{'warned-min-interval'}, 0);
2184+
2185+ $cache{$host}{'warned-min-interval'} = $now;
2186+
2187+ } elsif (($cache{$host}{'status'} ne 'good') && !interval_expired($host, 'atime', 'min-error-interval')) {
2188+
2189+ 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.",
2190+ $host,
2191+ ($cache{$host}{'ip'} ? $cache{$host}{'ip'} : '<nothing>'),
2192+ $ip,
2193+ ($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : '<never>'),
2194+ ($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : '<never>'),
2195+ prettyinterval($config{$host}{'min-error-interval'})
2196+ )
2197+ if opt('verbose') || !define($cache{$host}{'warned-min-error-interval'}, 0);
2198+
2199+ $cache{$host}{'warned-min-error-interval'} = $now;
2200+
2201+ } else {
2202+ $update = 1;
2203+ }
2204+
2205+ } elsif (defined($sub) && &$sub($host)) {
2206+ $update = 1;
2207+ } elsif ((defined($cache{$host}{'static'}) && defined($config{$host}{'static'}) &&
2208+ ($cache{$host}{'static'} ne $config{$host}{'static'})) ||
2209+ (defined($cache{$host}{'wildcard'}) && defined($config{$host}{'wildcard'}) &&
2210+ ($cache{$host}{'wildcard'} ne $config{$host}{'wildcard'})) ||
2211+ (defined($cache{$host}{'mx'}) && defined($config{$host}{'mx'}) &&
2212+ ($cache{$host}{'mx'} ne $config{$host}{'mx'})) ||
2213+ (defined($cache{$host}{'backupmx'}) && defined($config{$host}{'backupmx'}) &&
2214+ ($cache{$host}{'backupmx'} ne $config{$host}{'backupmx'})) ) {
2215+ info("updating %s because host settings have been changed.", $host);
2216+ $update = 1;
2217+
2218+ } else {
2219+ success("%s: skipped: IP address was already set to %s.", $host, $ip)
2220+ if opt('verbose');
2221+ }
2222+ $config{$host}{'status'} = define($cache{$host}{'status'},'');
2223+ $config{$host}{'update'} = $update;
2224+ if ($update) {
2225+ $config{$host}{'status'} = 'noconnect';
2226+ $config{$host}{'atime'} = $now;
2227+ $config{$host}{'wtime'} = 0;
2228+ $config{$host}{'warned-min-interval'} = 0;
2229+ $config{$host}{'warned-min-error-interval'} = 0;
2230+
2231+ delete $cache{$host}{'warned-min-interval'};
2232+ delete $cache{$host}{'warned-min-error-interval'};
2233+ }
2234+
2235+ return $update;
2236+}
2237+######################################################################
2238+## header_ok
2239+######################################################################
2240+sub header_ok {
2241+ my ($host, $line) = @_;
2242+ my $ok = 0;
2243+
2244+ if ($line =~ m%^s*HTTP/1.*\s+(\d+)%i) {
2245+ my $result = $1;
2246+
2247+ if ($result eq '200') {
2248+ $ok = 1;
2249+
2250+ } elsif ($result eq '401') {
2251+ failed("updating %s: authorization failed (%s)", $host, $line);
2252+ }
2253+
2254+ } else {
2255+ failed("updating %s: unexpected line (%s)", $host, $line);
2256+ }
2257+ return $ok;
2258+}
2259+######################################################################
2260+## nic_dyndns1_examples
2261+######################################################################
2262+sub nic_dyndns1_examples {
2263+ return <<EoEXAMPLE;
2264+o 'dyndns1'
2265+
2266+The 'dyndns1' protocol is a deprecated protocol used by the free dynamic
2267+DNS service offered by www.dyndns.org. The 'dyndns2' should be used to
2268+update the www.dyndns.org service. However, other services are also
2269+using this protocol so support is still provided by ${program}.
2270+
2271+Configuration variables applicable to the 'dyndns1' protocol are:
2272+ protocol=dyndns1 ##
2273+ server=fqdn.of.service ## defaults to members.dyndns.org
2274+ backupmx=no|yes ## indicates that this host is the primary MX for the domain.
2275+ mx=any.host.domain ## a host MX'ing for this host definition.
2276+ wildcard=no|yes ## add a DNS wildcard CNAME record that points to {host}
2277+ login=service-login ## login name and password registered with the service
2278+ password=service-password ##
2279+ fully.qualified.host ## the host registered with the service.
2280+
2281+Example ${program}.conf file entries:
2282+ ## single host update
2283+ protocol=dyndns1, \\
2284+ login=my-dyndns.org-login, \\
2285+ password=my-dyndns.org-password \\
2286+ myhost.dyndns.org
2287+
2288+ ## multiple host update with wildcard'ing mx, and backupmx
2289+ protocol=dyndns1, \\
2290+ login=my-dyndns.org-login, \\
2291+ password=my-dyndns.org-password, \\
2292+ mx=a.host.willing.to.mx.for.me,backupmx=yes,wildcard=yes \\
2293+ myhost.dyndns.org,my2ndhost.dyndns.org
2294+EoEXAMPLE
2295+}
2296+######################################################################
2297+## nic_dyndns1_update
2298+######################################################################
2299+sub nic_dyndns1_update {
2300+ debug("\nnic_dyndns1_update -------------------");
2301+ ## update each configured host
2302+ foreach my $h (@_) {
2303+ my $ip = delete $config{$h}{'wantip'};
2304+ info("setting IP address to %s for %s", $ip, $h);
2305+ verbose("UPDATE:","updating %s", $h);
2306+
2307+ my $url;
2308+ $url = "http://$config{$h}{'server'}/nic/";
2309+ $url .= ynu($config{$h}{'static'}, 'statdns', 'dyndns', 'dyndns');
2310+ $url .= "?action=edit&started=1&hostname=YES&host_id=$h";
2311+ $url .= "&myip=";
2312+ $url .= $ip if $ip;
2313+ $url .= "&wildcard=ON" if ynu($config{$h}{'wildcard'}, 1, 0, 0);
2314+ if ($config{$h}{'mx'}) {
2315+ $url .= "&mx=$config{$h}{'mx'}";
2316+ $url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
2317+ }
2318+
2319+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2320+ if (!defined($reply) || !$reply) {
2321+ failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
2322+ next;
2323+ }
2324+ last if !header_ok($h, $reply);
2325+
2326+ my @reply = split /\n/, $reply;
2327+ my ($title, $return_code, $error_code) = ('','','');
2328+ foreach my $line (@reply) {
2329+ $title = $1 if $line =~ m%<TITLE>\s*(.*)\s*</TITLE>%i;
2330+ $return_code = $1 if $line =~ m%^return\s+code\s*:\s*(.*)\s*$%i;
2331+ $error_code = $1 if $line =~ m%^error\s+code\s*:\s*(.*)\s*$%i;
2332+ }
2333+
2334+ if ($return_code ne 'NOERROR' || $error_code ne 'NOERROR' || !$title) {
2335+ $config{$h}{'status'} = 'failed';
2336+ $title = "incomplete response from $config{$h}{server}" unless $title;
2337+ warning("SENT: %s", $url) unless opt('verbose');
2338+ warning("REPLIED: %s", $reply);
2339+ failed("updating %s: %s", $h, $title);
2340+
2341+ } else {
2342+ $config{$h}{'ip'} = $ip;
2343+ $config{$h}{'mtime'} = $now;
2344+ $config{$h}{'status'} = 'good';
2345+ success("updating %s: %s: IP address set to %s (%s)", $h, $return_code, $ip, $title);
2346+ }
2347+ }
2348+}
2349+######################################################################
2350+## nic_dyndns2_updateable
2351+######################################################################
2352+sub nic_dyndns2_updateable {
2353+ my $host = shift;
2354+ my $update = 0;
2355+
2356+ if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
2357+ info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
2358+ $update = 1;
2359+
2360+ } elsif ($config{$host}{'mx'} && (ynu($config{$host}{'backupmx'},1,2,3) ne ynu($config{$host}{'backupmx'},1,2,3))) {
2361+ info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'},"YES","NO","NO"));
2362+ $update = 1;
2363+
2364+ } elsif ($config{$host}{'static'} ne $cache{$host}{'static'}) {
2365+
2366+ info("forcing updating %s because 'static' has changed to %s.", $host, ynu($config{$host}{'static'},"YES","NO","NO"));
2367+ $update = 1;
2368+
2369+ }
2370+ return $update;
2371+}
2372+######################################################################
2373+## nic_dyndns2_examples
2374+######################################################################
2375+sub nic_dyndns2_examples {
2376+ return <<EoEXAMPLE;
2377+o 'dyndns2'
2378+
2379+The 'dyndns2' protocol is a newer low-bandwidth protocol used by a
2380+free dynamic DNS service offered by www.dyndns.org. It supports
2381+features of the older 'dyndns1' in addition to others. [These will be
2382+supported in a future version of ${program}.]
2383+
2384+Configuration variables applicable to the 'dyndns2' protocol are:
2385+ protocol=dyndns2 ##
2386+ server=fqdn.of.service ## defaults to members.dyndns.org
2387+ script=/path/to/script ## defaults to /nic/update
2388+ backupmx=no|yes ## indicates that this host is the primary MX for the domain.
2389+ static=no|yes ## indicates that this host has a static IP address.
2390+ custom=no|yes ## indicates that this host is a 'custom' top-level domain name.
2391+ mx=any.host.domain ## a host MX'ing for this host definition.
2392+ wildcard=no|yes ## add a DNS wildcard CNAME record that points to {host}
2393+ login=service-login ## login name and password registered with the service
2394+ password=service-password ##
2395+ fully.qualified.host ## the host registered with the service.
2396+
2397+Example ${program}.conf file entries:
2398+ ## single host update
2399+ protocol=dyndns2, \\
2400+ login=my-dyndns.org-login, \\
2401+ password=my-dyndns.org-password \\
2402+ myhost.dyndns.org
2403+
2404+ ## multiple host update with wildcard'ing mx, and backupmx
2405+ protocol=dyndns2, \\
2406+ login=my-dyndns.org-login, \\
2407+ password=my-dyndns.org-password, \\
2408+ mx=a.host.willing.to.mx.for.me,backupmx=yes,wildcard=yes \\
2409+ myhost.dyndns.org,my2ndhost.dyndns.org
2410+
2411+ ## multiple host update to the custom DNS service
2412+ protocol=dyndns2, \\
2413+ login=my-dyndns.org-login, \\
2414+ password=my-dyndns.org-password \\
2415+ my-toplevel-domain.com,my-other-domain.com
2416+EoEXAMPLE
2417+}
2418+######################################################################
2419+## nic_dyndns2_update
2420+######################################################################
2421+sub nic_dyndns2_update {
2422+ debug("\nnic_dyndns2_update -------------------");
2423+
2424+ ## group hosts with identical attributes together
2425+ my %groups = group_hosts_by([ @_ ], [ qw(login password server static custom wildcard mx backupmx) ]);
2426+
2427+ my %errors = (
2428+ 'badauth' => 'Bad authorization (username or password)',
2429+ 'badsys' => 'The system parameter given was not valid',
2430+
2431+ 'notfqdn' => 'A Fully-Qualified Domain Name was not provided',
2432+ 'nohost' => 'The hostname specified does not exist in the database',
2433+ '!yours' => 'The hostname specified exists, but not under the username currently being used',
2434+ '!donator' => 'The offline setting was set, when the user is not a donator',
2435+ '!active' => 'The hostname specified is in a Custom DNS domain which has not yet been activated.',
2436+ 'abuse', => 'The hostname specified is blocked for abuse; you should receive an email notification ' .
2437+ 'which provides an unblock request link. More info can be found on ' .
2438+ 'https://www.dyndns.com/support/abuse.html',
2439+
2440+ 'numhost' => 'System error: Too many or too few hosts found. Contact support@dyndns.org',
2441+ 'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org',
2442+
2443+ 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive',
2444+ );
2445+
2446+ ## update each set of hosts that had similar configurations
2447+ foreach my $sig (keys %groups) {
2448+ my @hosts = @{$groups{$sig}};
2449+ my $hosts = join(',', @hosts);
2450+ my $h = $hosts[0];
2451+ my $ip = $config{$h}{'wantip'};
2452+ delete $config{$_}{'wantip'} foreach @hosts;
2453+
2454+ info("setting IP address to %s for %s", $ip, $hosts);
2455+ verbose("UPDATE:","updating %s", $hosts);
2456+
2457+ ## Select the DynDNS system to update
2458+ my $url = "http://$config{$h}{'server'}$config{$h}{'script'}?system=";
2459+ if ($config{$h}{'custom'}) {
2460+ warning("updating %s: 'custom' and 'static' may not be used together. ('static' ignored)", $hosts)
2461+ if $config{$h}{'static'};
2462+# warning("updating %s: 'custom' and 'offline' may not be used together. ('offline' ignored)", $hosts)
2463+# if $config{$h}{'offline'};
2464+ $url .= 'custom';
2465+
2466+ } elsif ($config{$h}{'static'}) {
2467+# warning("updating %s: 'static' and 'offline' may not be used together. ('offline' ignored)", $hosts)
2468+# if $config{$h}{'offline'};
2469+ $url .= 'statdns';
2470+
2471+ } else {
2472+ $url .= 'dyndns';
2473+ }
2474+
2475+ $url .= "&hostname=$hosts";
2476+ $url .= "&myip=";
2477+ $url .= $ip if $ip;
2478+
2479+ ## some args are not valid for a custom domain.
2480+ $url .= "&wildcard=ON" if ynu($config{$h}{'wildcard'}, 1, 0, 0);
2481+ if ($config{$h}{'mx'}) {
2482+ $url .= "&mx=$config{$h}{'mx'}";
2483+ $url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
2484+ }
2485+
2486+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2487+ if (!defined($reply) || !$reply) {
2488+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
2489+ last;
2490+ }
2491+ last if !header_ok($hosts, $reply);
2492+
2493+ my @reply = split /\n/, $reply;
2494+ my $state = 'header';
2495+ my $returnedip = $ip;
2496+
2497+ foreach my $line (@reply) {
2498+ if ($state eq 'header') {
2499+ $state = 'body';
2500+
2501+ } elsif ($state eq 'body') {
2502+ $state = 'results' if $line eq '';
2503+
2504+ } elsif ($state =~ /^results/) {
2505+ $state = 'results2';
2506+
2507+ # bug #10: some dyndns providers does not return the IP so
2508+ # we can't use the returned IP
2509+ my ($status, $returnedip) = split / /, lc $line;
2510+ $ip = $returnedip if (not $ip);
2511+ my $h = shift @hosts;
2512+
2513+ $config{$h}{'status'} = $status;
2514+ if ($status eq 'good') {
2515+ $config{$h}{'ip'} = $ip;
2516+ $config{$h}{'mtime'} = $now;
2517+ success("updating %s: %s: IP address set to %s", $h, $status, $ip);
2518+
2519+ } elsif (exists $errors{$status}) {
2520+ if ($status eq 'nochg') {
2521+ warning("updating %s: %s: %s", $h, $status, $errors{$status});
2522+ $config{$h}{'ip'} = $ip;
2523+ $config{$h}{'mtime'} = $now;
2524+ $config{$h}{'status'} = 'good';
2525+
2526+ } else {
2527+ failed("updating %s: %s: %s", $h, $status, $errors{$status});
2528+ }
2529+
2530+ } elsif ($status =~ /w(\d+)(.)/) {
2531+ my ($wait, $units) = ($1, lc $2);
2532+ my ($sec, $scale) = ($wait, 1);
2533+
2534+ ($scale, $units) = (1, 'seconds') if $units eq 's';
2535+ ($scale, $units) = (60, 'minutes') if $units eq 'm';
2536+ ($scale, $units) = (60*60, 'hours') if $units eq 'h';
2537+
2538+ $sec = $wait * $scale;
2539+ $config{$h}{'wtime'} = $now + $sec;
2540+ warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
2541+
2542+ } else {
2543+ failed("updating %s: %s: unexpected status (%s)", $h, $line);
2544+ }
2545+ }
2546+ }
2547+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
2548+ if $state ne 'results2';
2549+ }
2550+}
2551+
2552+
2553+######################################################################
2554+## nic_noip_update
2555+## Note: uses same features as nic_dyndns2_update, less return codes
2556+######################################################################
2557+sub nic_noip_update {
2558+ debug("\nnic_noip_update -------------------");
2559+
2560+ ## group hosts with identical attributes together
2561+ my %groups = group_hosts_by([ @_ ], [ qw(login password server static custom wildcard mx backupmx) ]);
2562+
2563+ my %errors = (
2564+ 'badauth' => 'Invalid username or password',
2565+ 'badagent' => 'Invalid user agent',
2566+ 'nohost' => 'The hostname specified does not exist in the database',
2567+ '!donator' => 'The offline setting was set, when the user is not a donator',
2568+ 'abuse', => 'The hostname specified is blocked for abuse; open a trouble ticket at http://www.no-ip.com',
2569+ 'numhost' => 'System error: Too many or too few hosts found. open a trouble ticket at http://www.no-ip.com',
2570+ 'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org',
2571+ 'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive',
2572+ );
2573+
2574+ ## update each set of hosts that had similar configurations
2575+ foreach my $sig (keys %groups) {
2576+ my @hosts = @{$groups{$sig}};
2577+ my $hosts = join(',', @hosts);
2578+ my $h = $hosts[0];
2579+ my $ip = $config{$h}{'wantip'};
2580+ delete $config{$_}{'wantip'} foreach @hosts;
2581+
2582+ info("setting IP address to %s for %s", $ip, $hosts);
2583+ verbose("UPDATE:","updating %s", $hosts);
2584+
2585+ my $url = "http://$config{$h}{'server'}/nic/update?system=";
2586+ $url .= 'noip';
2587+ $url .= "&hostname=$hosts";
2588+ $url .= "&myip=";
2589+ $url .= $ip if $ip;
2590+
2591+
2592+ print "here..." . $config{$h}{'login'} . " --> " . $config{$h}{'password'} . "\n";
2593+
2594+
2595+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2596+ if (!defined($reply) || !$reply) {
2597+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
2598+ last;
2599+ }
2600+ last if !header_ok($hosts, $reply);
2601+
2602+ my @reply = split /\n/, $reply;
2603+ my $state = 'header';
2604+ foreach my $line (@reply) {
2605+ if ($state eq 'header') {
2606+ $state = 'body';
2607+
2608+ } elsif ($state eq 'body') {
2609+ $state = 'results' if $line eq '';
2610+
2611+ } elsif ($state =~ /^results/) {
2612+ $state = 'results2';
2613+
2614+ my ($status, $ip) = split / /, lc $line;
2615+ my $h = shift @hosts;
2616+
2617+ $config{$h}{'status'} = $status;
2618+ if ($status eq 'good') {
2619+ $config{$h}{'ip'} = $ip;
2620+ $config{$h}{'mtime'} = $now;
2621+ success("updating %s: %s: IP address set to %s", $h, $status, $ip);
2622+
2623+ } elsif (exists $errors{$status}) {
2624+ if ($status eq 'nochg') {
2625+ warning("updating %s: %s: %s", $h, $status, $errors{$status});
2626+ $config{$h}{'ip'} = $ip;
2627+ $config{$h}{'mtime'} = $now;
2628+ $config{$h}{'status'} = 'good';
2629+
2630+ } else {
2631+ failed("updating %s: %s: %s", $h, $status, $errors{$status});
2632+ }
2633+
2634+ } elsif ($status =~ /w(\d+)(.)/) {
2635+ my ($wait, $units) = ($1, lc $2);
2636+ my ($sec, $scale) = ($wait, 1);
2637+
2638+ ($scale, $units) = (1, 'seconds') if $units eq 's';
2639+ ($scale, $units) = (60, 'minutes') if $units eq 'm';
2640+ ($scale, $units) = (60*60, 'hours') if $units eq 'h';
2641+
2642+ $sec = $wait * $scale;
2643+ $config{$h}{'wtime'} = $now + $sec;
2644+ warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
2645+
2646+ } else {
2647+ failed("updating %s: %s: unexpected status (%s)", $h, $line);
2648+ }
2649+ }
2650+ }
2651+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
2652+ if $state ne 'results2';
2653+ }
2654+}
2655+######################################################################
2656+## nic_noip_examples
2657+######################################################################
2658+sub nic_noip_examples {
2659+ return <<EoEXAMPLE;
2660+o 'noip'
2661+
2662+The 'No-IP Compatible' protocol is used to make dynamic dns updates
2663+over an http request. Details of the protocol are outlined at:
2664+http://www.no-ip.com/integrate/
2665+
2666+Configuration variables applicable to the 'noip' protocol are:
2667+ protocol=noip ##
2668+ server=fqdn.of.service ## defaults to dynupdate.no-ip.com
2669+ login=service-login ## login name and password registered with the service
2670+ password=service-password ##
2671+ fully.qualified.host ## the host registered with the service.
2672+
2673+Example ${program}.conf file entries:
2674+ ## single host update
2675+ protocol=noip, \\
2676+ login=userlogin\@domain.com, \\
2677+ password=noip-password \\
2678+ myhost.no-ip.biz
2679+
2680+
2681+EoEXAMPLE
2682+}
2683+
2684+######################################################################
2685+## nic_concont_examples
2686+######################################################################
2687+sub nic_concont_examples {
2688+ return <<EoEXAMPLE;
2689+o 'concont'
2690+
2691+The 'concont' protocol is the protocol used by the content management
2692+system ConCont's dydns module. This is currently used by the free
2693+dynamic DNS service offered by Tyrmida at www.dydns.za.net
2694+
2695+Configuration variables applicable to the 'concont' protocol are:
2696+ protocol=concont ##
2697+ server=www.fqdn.of.service ## for example www.dydns.za.net (for most add a www)
2698+ login=service-login ## login registered with the service
2699+ password=service-password ## password registered with the service
2700+ mx=mail.server.fqdn ## fqdn of the server handling domain\'s mail (leave out for none)
2701+ wildcard=yes|no ## set yes for wild (*.host.domain) support
2702+ fully.qualified.host ## the host registered with the service.
2703+
2704+Example ${program}.conf file entries:
2705+ ## single host update
2706+ protocol=concont, \\
2707+ login=dydns.za.net, \\
2708+ password=my-dydns.za.net-password, \\
2709+ mx=mailserver.fqdn, \\
2710+ wildcard=yes \\
2711+ myhost.hn.org
2712+
2713+EoEXAMPLE
2714+}
2715+######################################################################
2716+## nic_concont_update
2717+######################################################################
2718+sub nic_concont_update {
2719+ debug("\nnic_concont_update -------------------");
2720+
2721+ ## update each configured host
2722+ foreach my $h (@_) {
2723+ my $ip = delete $config{$h}{'wantip'};
2724+ info("setting IP address to %s for %s", $ip, $h);
2725+ verbose("UPDATE:","updating %s", $h);
2726+
2727+ # Set the URL that we're going to to update
2728+ my $url;
2729+ $url = "http://$config{$h}{'server'}/modules/dydns/update.php";
2730+ $url .= "?username=";
2731+ $url .= $config{$h}{'login'};
2732+ $url .= "&password=";
2733+ $url .= $config{$h}{'password'};
2734+ $url .= "&wildcard=";
2735+ $url .= $config{$h}{'wildcard'};
2736+ $url .= "&mx=";
2737+ $url .= $config{$h}{'mx'};
2738+ $url .= "&host=";
2739+ $url .= $h;
2740+ $url .= "&ip=";
2741+ $url .= $ip;
2742+
2743+ # Try to get URL
2744+ my $reply = geturl(opt('proxy'), $url);
2745+
2746+ # No response, declare as failed
2747+ if (!defined($reply) || !$reply) {
2748+ failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
2749+ last;
2750+ }
2751+ last if !header_ok($h, $reply);
2752+
2753+ # Response found, just declare as success (this is ugly, we need more error checking)
2754+ if ($reply =~ /SUCCESS/)
2755+ {
2756+ $config{$h}{'ip'} = $ip;
2757+ $config{$h}{'mtime'} = $now;
2758+ $config{$h}{'status'} = 'good';
2759+ success("updating %s: good: IP address set to %s", $h, $ip);
2760+ }
2761+ else
2762+ {
2763+ my @reply = split /\n/, $reply;
2764+ my $returned = pop(@reply);
2765+ $config{$h}{'status'} = 'failed';
2766+ failed("updating %s: Server said: '$returned'", $h);
2767+ }
2768+ }
2769+}
2770+######################################################################
2771+## nic_dslreports1_examples
2772+######################################################################
2773+sub nic_dslreports1_examples {
2774+ return <<EoEXAMPLE;
2775+o 'dslreports1'
2776+
2777+The 'dslreports1' protocol is used by a free DSL monitoring service
2778+offered by www.dslreports.com.
2779+
2780+Configuration variables applicable to the 'dslreports1' protocol are:
2781+ protocol=dslreports1 ##
2782+ server=fqdn.of.service ## defaults to www.dslreports.com
2783+ login=service-login ## login name and password registered with the service
2784+ password=service-password ##
2785+ unique-number ## the host registered with the service.
2786+
2787+Example ${program}.conf file entries:
2788+ ## single host update
2789+ protocol=dslreports1, \\
2790+ server=www.dslreports.com, \\
2791+ login=my-dslreports-login, \\
2792+ password=my-dslreports-password \\
2793+ 123456
2794+
2795+Note: DSL Reports uses a unique number as the host name. This number
2796+can be found on the Monitor Control web page.
2797+EoEXAMPLE
2798+}
2799+######################################################################
2800+## nic_dslreports1_update
2801+######################################################################
2802+sub nic_dslreports1_update {
2803+ debug("\nnic_dslreports1_update -------------------");
2804+ ## update each configured host
2805+ foreach my $h (@_) {
2806+ my $ip = delete $config{$h}{'wantip'};
2807+ info("setting IP address to %s for %s", $ip, $h);
2808+ verbose("UPDATE:","updating %s", $h);
2809+
2810+ my $url;
2811+ $url = "http://$config{$h}{'server'}/nic/";
2812+ $url .= ynu($config{$h}{'static'}, 'statdns', 'dyndns', 'dyndns');
2813+ $url .= "?action=edit&started=1&hostname=YES&host_id=$h";
2814+ $url .= "&myip=";
2815+ $url .= $ip if $ip;
2816+
2817+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2818+ if (!defined($reply) || !$reply) {
2819+ failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
2820+ next;
2821+ }
2822+
2823+ my @reply = split /\n/, $reply;
2824+ my $return_code = '';
2825+ foreach my $line (@reply) {
2826+ $return_code = $1 if $line =~ m%^return\s+code\s*:\s*(.*)\s*$%i;
2827+ }
2828+
2829+ if ($return_code !~ /NOERROR/) {
2830+ $config{$h}{'status'} = 'failed';
2831+ warning("SENT: %s", $url) unless opt('verbose');
2832+ warning("REPLIED: %s", $reply);
2833+ failed("updating %s", $h);
2834+
2835+ } else {
2836+ $config{$h}{'ip'} = $ip;
2837+ $config{$h}{'mtime'} = $now;
2838+ $config{$h}{'status'} = 'good';
2839+ success("updating %s: %s: IP address set to %s", $h, $return_code, $ip);
2840+ }
2841+ }
2842+}
2843+######################################################################
2844+## nic_hammernode1_examples
2845+######################################################################
2846+sub nic_hammernode1_examples {
2847+ return <<EoEXAMPLE;
2848+o 'hammernode1'
2849+
2850+The 'hammernode1' protocol is the protocol used by the free dynamic
2851+DNS service offered by Hammernode at www.hn.org
2852+
2853+Configuration variables applicable to the 'hammernode1' protocol are:
2854+ protocol=hammernode1 ##
2855+ server=fqdn.of.service ## defaults to members.dyndns.org
2856+ login=service-login ## login name and password registered with the service
2857+ password=service-password ##
2858+ fully.qualified.host ## the host registered with the service.
2859+
2860+Example ${program}.conf file entries:
2861+ ## single host update
2862+ protocol=hammernode1, \\
2863+ login=my-hn.org-login, \\
2864+ password=my-hn.org-password \\
2865+ myhost.hn.org
2866+
2867+ ## multiple host update
2868+ protocol=hammernode1, \\
2869+ login=my-hn.org-login, \\
2870+ password=my-hn.org-password, \\
2871+ myhost.hn.org,my2ndhost.hn.org
2872+EoEXAMPLE
2873+}
2874+######################################################################
2875+## nic_hammernode1_update
2876+######################################################################
2877+sub nic_hammernode1_update {
2878+ debug("\nnic_hammernode1_update -------------------");
2879+
2880+ ## update each configured host
2881+ foreach my $h (@_) {
2882+ my $ip = delete $config{$h}{'wantip'};
2883+ info("setting IP address to %s for %s", $ip, $h);
2884+ verbose("UPDATE:","updating %s", $h);
2885+
2886+ my $url;
2887+ $url = "http://$config{$h}{'server'}/vanity/update";
2888+ $url .= "?ver=1";
2889+ $url .= "&ip=";
2890+ $url .= $ip if $ip;
2891+
2892+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2893+ if (!defined($reply) || !$reply) {
2894+ failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
2895+ last;
2896+ }
2897+ last if !header_ok($h, $reply);
2898+
2899+ my @reply = split /\n/, $reply;
2900+ if (grep /<!--\s+DDNS_Response_Code=101\s+-->/i, @reply) {
2901+ $config{$h}{'ip'} = $ip;
2902+ $config{$h}{'mtime'} = $now;
2903+ $config{$h}{'status'} = 'good';
2904+ success("updating %s: good: IP address set to %s", $h, $ip);
2905+ } else {
2906+ $config{$h}{'status'} = 'failed';
2907+ warning("SENT: %s", $url) unless opt('verbose');
2908+ warning("REPLIED: %s", $reply);
2909+ failed("updating %s: Invalid reply.", $h);
2910+ }
2911+ }
2912+}
2913+######################################################################
2914+## nic_zoneedit1_examples
2915+######################################################################
2916+sub nic_zoneedit1_examples {
2917+ return <<EoEXAMPLE;
2918+o 'zoneedit1'
2919+
2920+The 'zoneedit1' protocol is used by a DNS service offered by
2921+www.zoneedit.com.
2922+
2923+Configuration variables applicable to the 'zoneedit1' protocol are:
2924+ protocol=zoneedit1 ##
2925+ server=fqdn.of.service ## defaults to www.zoneedit.com
2926+ zone=zone-where-domains-are ## only needed if 1 or more subdomains are deeper
2927+ ## than 1 level in relation to the zone where it
2928+ ## is defined. For example, b.foo.com in a zone
2929+ ## foo.com doesn't need this, but a.b.foo.com in
2930+ ## the same zone needs zone=foo.com
2931+ login=service-login ## login name and password registered with the service
2932+ password=service-password ##
2933+ your.domain.name ## the host registered with the service.
2934+
2935+Example ${program}.conf file entries:
2936+ ## single host update
2937+ protocol=zoneedit1, \\
2938+ server=dynamic.zoneedit.com, \\
2939+ zone=zone-where-domains-are, \\
2940+ login=my-zoneedit-login, \\
2941+ password=my-zoneedit-password \\
2942+ my.domain.name
2943+EoEXAMPLE
2944+}
2945+
2946+######################################################################
2947+## nic_zoneedit1_updateable
2948+######################################################################
2949+sub nic_zoneedit1_updateable {
2950+ return 0;
2951+}
2952+
2953+######################################################################
2954+## nic_zoneedit1_update
2955+# <SUCCESS CODE="200" TEXT="Update succeeded." ZONE="trialdomain.com" IP="127.0.0.12">
2956+# <SUCCESS CODE="201" TEXT="No records need updating." ZONE="bannedware.com">
2957+# <ERROR CODE="701" TEXT="Zone is not set up in this account." ZONE="bad.com">
2958+######################################################################
2959+sub nic_zoneedit1_update {
2960+ debug("\nnic_zoneedit1_update -------------------");
2961+
2962+ ## group hosts with identical attributes together
2963+ my %groups = group_hosts_by([ @_ ], [ qw(login password server zone) ]);
2964+
2965+ ## update each set of hosts that had similar configurations
2966+ foreach my $sig (keys %groups) {
2967+ my @hosts = @{$groups{$sig}};
2968+ my $hosts = join(',', @hosts);
2969+ my $h = $hosts[0];
2970+ my $ip = $config{$h}{'wantip'};
2971+ delete $config{$_}{'wantip'} foreach @hosts;
2972+
2973+ info("setting IP address to %s for %s", $ip, $hosts);
2974+ verbose("UPDATE:","updating %s", $hosts);
2975+
2976+ my $url = '';
2977+ $url .= "http://$config{$h}{'server'}/auth/dynamic.html";
2978+ $url .= "?host=$hosts";
2979+ $url .= "&dnsto=$ip" if $ip;
2980+ $url .= "&zone=$config{$h}{'zone'}" if defined $config{$h}{'zone'};
2981+
2982+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2983+ if (!defined($reply) || !$reply) {
2984+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
2985+ last;
2986+ }
2987+ last if !header_ok($hosts, $reply);
2988+
2989+ my @reply = split /\n/, $reply;
2990+ foreach my $line (@reply) {
2991+ if ($line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) {
2992+ my ($status, $assignments, $rest) = ($1, $2, $3);
2993+ my ($left, %var) = parse_assignments($assignments);
2994+
2995+ if (keys %var) {
2996+ my ($status_code, $status_text, $status_ip) = ('999', '', $ip);
2997+ $status_code = $var{'CODE'} if exists $var{'CODE'};
2998+ $status_text = $var{'TEXT'} if exists $var{'TEXT'};
2999+ $status_ip = $var{'IP'} if exists $var{'IP'};
3000+
3001+ if ($status eq 'SUCCESS' || ($status eq 'ERROR' && $var{'CODE'} eq '707')) {
3002+ $config{$h}{'ip'} = $status_ip;
3003+ $config{$h}{'mtime'} = $now;
3004+ $config{$h}{'status'} = 'good';
3005+
3006+ success("updating %s: IP address set to %s (%s: %s)", $h, $ip, $status_code, $status_text);
3007+
3008+ } else {
3009+ $config{$h}{'status'} = 'failed';
3010+ failed("updating %s: %s: %s", $h, $status_code, $status_text);
3011+ }
3012+ shift @hosts;
3013+ $h = $hosts[0];
3014+ $hosts = join(',', @hosts);
3015+ }
3016+ $line = $rest;
3017+ redo if $line;
3018+ }
3019+ }
3020+ failed("updating %s: no response from %s", $hosts, $config{$h}{'server'})
3021+ if @hosts;
3022+ }
3023+}
3024+######################################################################
3025+## nic_easydns_updateable
3026+######################################################################
3027+sub nic_easydns_updateable {
3028+ my $host = shift;
3029+ my $update = 0;
3030+
3031+ if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
3032+ info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
3033+ $update = 1;
3034+
3035+ } elsif ($config{$host}{'mx'} && (ynu($config{$host}{'backupmx'},1,2,3) ne ynu($config{$host}{'backupmx'},1,2,3))) {
3036+ info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'},"YES","NO","NO"));
3037+ $update = 1;
3038+
3039+ } elsif ($config{$host}{'static'} ne $cache{$host}{'static'}) {
3040+
3041+ info("forcing updating %s because 'static' has changed to %s.", $host, ynu($config{$host}{'static'},"YES","NO","NO"));
3042+ $update = 1;
3043+
3044+ }
3045+ return $update;
3046+}
3047+######################################################################
3048+## nic_easydns_examples
3049+######################################################################
3050+sub nic_easydns_examples {
3051+ return <<EoEXAMPLE;
3052+o 'easydns'
3053+
3054+The 'easydns' protocol is used by the for fee DNS service offered
3055+by www.easydns.com.
3056+
3057+Configuration variables applicable to the 'easydns' protocol are:
3058+ protocol=easydns ##
3059+ server=fqdn.of.service ## defaults to members.easydns.com
3060+ backupmx=no|yes ## indicates that EasyDNS should be the secondary MX
3061+ ## for this domain or host.
3062+ mx=any.host.domain ## a host MX'ing for this host or domain.
3063+ wildcard=no|yes ## add a DNS wildcard CNAME record that points to {host}
3064+ login=service-login ## login name and password registered with the service
3065+ password=service-password ##
3066+ fully.qualified.host ## the host registered with the service.
3067+
3068+Example ${program}.conf file entries:
3069+ ## single host update
3070+ protocol=easydns, \\
3071+ login=my-easydns.com-login, \\
3072+ password=my-easydns.com-password \\
3073+ myhost.easydns.com
3074+
3075+ ## multiple host update with wildcard'ing mx, and backupmx
3076+ protocol=easydns, \\
3077+ login=my-easydns.com-login, \\
3078+ password=my-easydns.com-password, \\
3079+ mx=a.host.willing.to.mx.for.me, \\
3080+ backupmx=yes, \\
3081+ wildcard=yes \\
3082+ my-toplevel-domain.com,my-other-domain.com
3083+
3084+ ## multiple host update to the custom DNS service
3085+ protocol=easydns, \\
3086+ login=my-easydns.com-login, \\
3087+ password=my-easydns.com-password \\
3088+ my-toplevel-domain.com,my-other-domain.com
3089+EoEXAMPLE
3090+}
3091+######################################################################
3092+## nic_easydns_update
3093+######################################################################
3094+sub nic_easydns_update {
3095+ debug("\nnic_easydns_update -------------------");
3096+
3097+ ## group hosts with identical attributes together
3098+ ## my %groups = group_hosts_by([ @_ ], [ qw(login password server wildcard mx backupmx) ]);
3099+
3100+ ## each host is in a group by itself
3101+ my %groups = map { $_ => [ $_ ] } @_;
3102+
3103+ my %errors = (
3104+ 'NOACCESS' => 'Authentication failed. This happens if the username/password OR host or domain are wrong.',
3105+ 'NOSERVICE'=> 'Dynamic DNS is not turned on for this domain.',
3106+ 'ILLEGAL' => 'Client sent data that is not allowed in a dynamic DNS update.',
3107+ 'TOOSOON' => 'Update frequency is too short.',
3108+ );
3109+
3110+ ## update each set of hosts that had similar configurations
3111+ foreach my $sig (keys %groups) {
3112+ my @hosts = @{$groups{$sig}};
3113+ my $hosts = join(',', @hosts);
3114+ my $h = $hosts[0];
3115+ my $ip = $config{$h}{'wantip'};
3116+ delete $config{$_}{'wantip'} foreach @hosts;
3117+
3118+ info("setting IP address to %s for %s", $ip, $hosts);
3119+ verbose("UPDATE:","updating %s", $hosts);
3120+
3121+ #'http://members.easydns.com/dyn/dyndns.php?hostname=test.burry.ca&myip=10.20.30.40&wildcard=ON'
3122+
3123+ my $url;
3124+ $url = "http://$config{$h}{'server'}/dyn/dyndns.php?";
3125+ $url .= "hostname=$hosts";
3126+ $url .= "&myip=";
3127+ $url .= $ip if $ip;
3128+ $url .= "&wildcard=" . ynu($config{$h}{'wildcard'}, 'ON', 'OFF', 'OFF') if defined $config{$h}{'wildcard'};
3129+
3130+ if ($config{$h}{'mx'}) {
3131+ $url .= "&mx=$config{$h}{'mx'}";
3132+ $url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
3133+ }
3134+
3135+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
3136+ if (!defined($reply) || !$reply) {
3137+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
3138+ last;
3139+ }
3140+ last if !header_ok($hosts, $reply);
3141+
3142+ my @reply = split /\n/, $reply;
3143+ my $state = 'header';
3144+ foreach my $line (@reply) {
3145+ if ($state eq 'header') {
3146+ $state = 'body';
3147+
3148+ } elsif ($state eq 'body') {
3149+ $state = 'results' if $line eq '';
3150+
3151+ } elsif ($state =~ /^results/) {
3152+ $state = 'results2';
3153+
3154+ my ($status) = $line =~ /^(\S*)\b.*/;
3155+ my $h = shift @hosts;
3156+
3157+ $config{$h}{'status'} = $status;
3158+ if ($status eq 'NOERROR') {
3159+ $config{$h}{'ip'} = $ip;
3160+ $config{$h}{'mtime'} = $now;
3161+ success("updating %s: %s: IP address set to %s", $h, $status, $ip);
3162+
3163+ } elsif ($status =~ /TOOSOON/) {
3164+ ## make sure we wait at least a little
3165+ my ($wait, $units) = (5, 'm');
3166+ my ($sec, $scale) = ($wait, 1);
3167+
3168+ ($scale, $units) = (1, 'seconds') if $units eq 's';
3169+ ($scale, $units) = (60, 'minutes') if $units eq 'm';
3170+ ($scale, $units) = (60*60, 'hours') if $units eq 'h';
3171+ $config{$h}{'wtime'} = $now + $sec;
3172+ warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
3173+
3174+ } elsif (exists $errors{$status}) {
3175+ failed("updating %s: %s: %s", $h, $line, $errors{$status});
3176+
3177+ } else {
3178+ failed("updating %s: %s: unexpected status (%s)", $h, $line);
3179+ }
3180+ last;
3181+ }
3182+ }
3183+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
3184+ if $state ne 'results2';
3185+ }
3186+}
3187+######################################################################
3188+
3189+######################################################################
3190+## nic_dnspark_updateable
3191+######################################################################
3192+sub nic_dnspark_updateable {
3193+ my $host = shift;
3194+ my $update = 0;
3195+
3196+ if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
3197+ info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
3198+ $update = 1;
3199+
3200+ } elsif ($config{$host}{'mx'} && ($config{$host}{'mxpri'} ne $cache{$host}{'mxpri'})) {
3201+ info("forcing updating %s because 'mxpri' has changed to %s.", $host, $config{$host}{'mxpri'});
3202+ $update = 1;
3203+ }
3204+ return $update;
3205+}
3206+######################################################################
3207+## nic_dnspark_examples
3208+######################################################################
3209+sub nic_dnspark_examples {
3210+ return <<EoEXAMPLE;
3211+o 'dnspark'
3212+
3213+The 'dnspark' protocol is used by DNS service offered by www.dnspark.com.
3214+
3215+Configuration variables applicable to the 'dnspark' protocol are:
3216+ protocol=dnspark ##
3217+ server=fqdn.of.service ## defaults to www.dnspark.com
3218+ backupmx=no|yes ## indicates that DNSPark should be the secondary MX
3219+ ## for this domain or host.
3220+ mx=any.host.domain ## a host MX'ing for this host or domain.
3221+ mxpri=priority ## MX priority.
3222+ login=service-login ## login name and password registered with the service
3223+ password=service-password ##
3224+ fully.qualified.host ## the host registered with the service.
3225+
3226+Example ${program}.conf file entries:
3227+ ## single host update
3228+ protocol=dnspark, \\
3229+ login=my-dnspark.com-login, \\
3230+ password=my-dnspark.com-password \\
3231+ myhost.dnspark.com
3232+
3233+ ## multiple host update with wildcard'ing mx, and backupmx
3234+ protocol=dnspark, \\
3235+ login=my-dnspark.com-login, \\
3236+ password=my-dnspark.com-password, \\
3237+ mx=a.host.willing.to.mx.for.me, \\
3238+ mxpri=10, \\
3239+ my-toplevel-domain.com,my-other-domain.com
3240+
3241+ ## multiple host update to the custom DNS service
3242+ protocol=dnspark, \\
3243+ login=my-dnspark.com-login, \\
3244+ password=my-dnspark.com-password \\
3245+ my-toplevel-domain.com,my-other-domain.com
3246+EoEXAMPLE
3247+}
3248+######################################################################
3249+## nic_dnspark_update
3250+######################################################################
3251+sub nic_dnspark_update {
3252+ debug("\nnic_dnspark_update -------------------");
3253+
3254+ ## group hosts with identical attributes together
3255+ ## my %groups = group_hosts_by([ @_ ], [ qw(login password server wildcard mx backupmx) ]);
3256+
3257+ ## each host is in a group by itself
3258+ my %groups = map { $_ => [ $_ ] } @_;
3259+
3260+ my %errors = (
3261+ 'nochange' => 'No changes made to the hostname(s). Continual updates with no changes lead to blocked clients.',
3262+ 'nofqdn' => 'No valid FQDN (fully qualified domain name) was specified',
3263+ '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.',
3264+ 'abuse' => 'The hostname specified has been blocked for abuse.',
3265+ 'unauth' => 'The username specified is not authorized to update this hostname and domain.',
3266+ 'blocked' => 'The dynamic update client (specified by the user-agent) has been blocked from the system.',
3267+ '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.',
3268+ );
3269+
3270+ ## update each set of hosts that had similar configurations
3271+ foreach my $sig (keys %groups) {
3272+ my @hosts = @{$groups{$sig}};
3273+ my $hosts = join(',', @hosts);
3274+ my $h = $hosts[0];
3275+ my $ip = $config{$h}{'wantip'};
3276+ delete $config{$_}{'wantip'} foreach @hosts;
3277+
3278+ info("setting IP address to %s for %s", $ip, $hosts);
3279+ verbose("UPDATE:","updating %s", $hosts);
3280+
3281+ #'http://www.dnspark.com:80/visitors/update.html?myip=10.20.30.40&hostname=test.burry.ca'
3282+
3283+ my $url;
3284+ $url = "http://$config{$h}{'server'}/visitors/update.html";
3285+ $url .= "?hostname=$hosts";
3286+ $url .= "&myip=";
3287+ $url .= $ip if $ip;
3288+
3289+ if ($config{$h}{'mx'}) {
3290+ $url .= "&mx=$config{$h}{'mx'}";
3291+ $url .= "&mxpri=" . $config{$h}{'mxpri'};
3292+ }
3293+
3294+ my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
3295+ if (!defined($reply) || !$reply) {
3296+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
3297+ last;
3298+ }
3299+ last if !header_ok($hosts, $reply);
3300+
3301+ my @reply = split /\n/, $reply;
3302+ my $state = 'header';
3303+ foreach my $line (@reply) {
3304+ if ($state eq 'header') {
3305+ $state = 'body';
3306+
3307+ } elsif ($state eq 'body') {
3308+ $state = 'results' if $line eq '';
3309+
3310+ } elsif ($state =~ /^results/) {
3311+ $state = 'results2';
3312+
3313+ my ($status) = $line =~ /^(\S*)\b.*/;
3314+ my $h = pop @hosts;
3315+
3316+ $config{$h}{'status'} = $status;
3317+ if ($status eq 'ok') {
3318+ $config{$h}{'ip'} = $ip;
3319+ $config{$h}{'mtime'} = $now;
3320+ success("updating %s: %s: IP address set to %s", $h, $status, $ip);
3321+
3322+ } elsif ($status =~ /TOOSOON/) {
3323+ ## make sure we wait at least a little
3324+ my ($wait, $units) = (5, 'm');
3325+ my ($sec, $scale) = ($wait, 1);
3326+
3327+ ($scale, $units) = (1, 'seconds') if $units eq 's';
3328+ ($scale, $units) = (60, 'minutes') if $units eq 'm';
3329+ ($scale, $units) = (60*60, 'hours') if $units eq 'h';
3330+ $config{$h}{'wtime'} = $now + $sec;
3331+ warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
3332+
3333+ } elsif (exists $errors{$status}) {
3334+ failed("updating %s: %s: %s", $h, $line, $errors{$status});
3335+
3336+ } else {
3337+ failed("updating %s: %s: unexpected status (%s)", $h, $line);
3338+ }
3339+ last;
3340+ }
3341+ }
3342+ failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
3343+ if $state ne 'results2';
3344+ }
3345+}
3346+
3347+######################################################################
3348+
3349+######################################################################
3350+## nic_namecheap_examples
3351+######################################################################
3352+sub nic_namecheap_examples {
3353+ return <<EoEXAMPLE;
3354+
3355+o 'namecheap'
3356+
3357+The 'namecheap' protocol is used by DNS service offered by www.namecheap.com.
3358+
3359+Configuration variables applicable to the 'namecheap' protocol are:
3360+ protocol=namecheap ##
3361+ server=fqdn.of.service ## defaults to dynamicdns.park-your-domain.com
3362+ login=service-login ## login name and password registered with the service
3363+ password=service-password ##
3364+ fully.qualified.host ## the host registered with the service.
3365+
3366+Example ${program}.conf file entries:
3367+ ## single host update
3368+ protocol=namecheap, \\
3369+ login=my-namecheap.com-login, \\
3370+ password=my-namecheap.com-password \\
3371+ myhost.namecheap.com
3372+
3373+EoEXAMPLE
3374+}
3375+######################################################################
3376+## nic_namecheap_update
3377+##
3378+## written by Dan Boardman
3379+##
3380+## based on http://www.namecheap.com/resources/help/index.asp?t=dynamicdns
3381+## needs this url to update:
3382+## http://dynamicdns.park-your-domain.com/update?host=host_name&
3383+## domain=domain.com&password=domain_password[&ip=your_ip]
3384+##
3385+######################################################################
3386+sub nic_namecheap_update {
3387+
3388+
3389+ debug("\nnic_namecheap1_update -------------------");
3390+
3391+ ## update each configured host
3392+ foreach my $h (@_) {
3393+ my $ip = delete $config{$h}{'wantip'};
3394+ info("setting IP address to %s for %s", $ip, $h);
3395+ verbose("UPDATE:","updating %s", $h);
3396+
3397+ my $url;
3398+ $url = "http://$config{$h}{'server'}/update";
3399+ $url .= "?host=$h";
3400+ $url .= "&domain=$config{$h}{'login'}";
3401+ $url .= "&password=$config{$h}{'password'}";
3402+ $url .= "&ip=";
3403+ $url .= $ip if $ip;
3404+
3405+ my $reply = geturl(opt('proxy'), $url);
3406+ if (!defined($reply) || !$reply) {
3407+ failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
3408+ last;
3409+ }
3410+ last if !header_ok($h, $reply);
3411+
3412+ my @reply = split /\n/, $reply;
3413+ if (grep /<ErrCount>0/i, @reply) {
3414+ $config{$h}{'ip'} = $ip;
3415+ $config{$h}{'mtime'} = $now;
3416+ $config{$h}{'status'} = 'good';
3417+ success("updating %s: good: IP address set to %s", $h, $ip);
3418+ } else {
3419+ $config{$h}{'status'} = 'failed';
3420+ warning("SENT: %s", $url) unless opt('verbose');
3421+ warning("REPLIED: %s", $reply);
3422+ failed("updating %s: Invalid reply.", $h);
3423+ }
3424+ }
3425+}
3426+
3427+######################################################################
3428+
3429+
3430+######################################################################
3431+
3432+######################################################################
3433+## nic_sitelutions_examples
3434+######################################################################
3435+sub nic_sitelutions_examples {
3436+ return <<EoEXAMPLE;
3437+
3438+o 'sitelutions'
3439+
3440+The 'sitelutions' protocol is used by DNS services offered by www.sitelutions.com.
3441+
3442+Configuration variables applicable to the 'sitelutions' protocol are:
3443+ protocol=sitelutions ##
3444+ server=fqdn.of.service ## defaults to sitelutions.com
3445+ login=service-login ## login name and password registered with the service
3446+ password=service-password ##
3447+ A_record_id ## Id of the A record for the host registered with the service.
3448+
3449+Example ${program}.conf file entries:
3450+ ## single host update
3451+ protocol=sitelutions, \\
3452+ login=my-sitelutions.com-login, \\
3453+ password=my-sitelutions.com-password \\
3454+ my-sitelutions.com-id_of_A_record
3455+
3456+EoEXAMPLE
3457+}
3458+######################################################################
3459+## nic_sitelutions_update
3460+##
3461+## written by Mike W. Smith
3462+##
3463+## based on http://www.sitelutions.com/help/dynamic_dns_clients#updatespec
3464+## needs this url to update:
3465+## https://www.sitelutions.com/dnsup?id=990331&user=myemail@mydomain.com&pass=SecretPass&ip=192.168.10.4
3466+## domain=domain.com&password=domain_password&ip=your_ip
3467+##
3468+######################################################################
3469+sub nic_sitelutions_update {
3470+
3471+
3472+ debug("\nnic_sitelutions_update -------------------");
3473+
3474+ ## update each configured host
3475+ foreach my $h (@_) {
3476+ my $ip = delete $config{$h}{'wantip'};
3477+ info("setting IP address to %s for %s", $ip, $h);
3478+ verbose("UPDATE:","updating %s", $h);
3479+
3480+ my $url;
3481+ $url = "http://$config{$h}{'server'}/dnsup";
3482+ $url .= "?id=$h";
3483+ $url .= "&user=$config{$h}{'login'}";
3484+ $url .= "&pass=$config{$h}{'password'}";
3485+ $url .= "&ip=";
3486+ $url .= $ip if $ip;
3487+
3488+ my $reply = geturl(opt('proxy'), $url);
3489+ if (!defined($reply) || !$reply) {
3490+ failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
3491+ last;
3492+ }
3493+ last if !header_ok($h, $reply);
3494+
3495+ my @reply = split /\n/, $reply;
3496+ if (grep /success/i, @reply) {
3497+ $config{$h}{'ip'} = $ip;
3498+ $config{$h}{'mtime'} = $now;
3499+ $config{$h}{'status'} = 'good';
3500+ success("updating %s: good: IP address set to %s", $h, $ip);
3501+ } else {
3502+ $config{$h}{'status'} = 'failed';
3503+ warning("SENT: %s", $url) unless opt('verbose');
3504+ warning("REPLIED: %s", $reply);
3505+ failed("updating %s: Invalid reply.", $h);
3506+ }
3507+ }
3508+}
3509+
3510+######################################################################
3511+
3512+######################################################################
3513+## nic_freedns_examples
3514+######################################################################
3515+sub nic_freedns_examples {
3516+return <<EoEXAMPLE;
3517+
3518+o 'freedns'
3519+
3520+The 'freedns' protocol is used by DNS services offered by freedns.afraid.org.
3521+
3522+Configuration variables applicable to the 'freedns' protocol are:
3523+ protocol=freedns ##
3524+ server=fqdn.of.service ## defaults to freedns.afraid.org
3525+ login=service-login ## login name and password registered with the service
3526+ password=service-password ##
3527+ fully.qualified.host ## the host registered with the service.
3528+
3529+Example ${program}.conf file entries:
3530+ ## single host update
3531+ protocol=freedns, \\
3532+ login=my-freedns.afraid.org-login, \\
3533+ password=my-freedns.afraid.org-password \\
3534+ myhost.afraid.com
3535+
3536+EoEXAMPLE
3537+}
3538+######################################################################
3539+## nic_freedns_update
3540+##
3541+## written by John Haney
3542+##
3543+## based on http://freedns.afraid.org/api/
3544+## needs this url to update:
3545+## http://freedns.afraid.org/api/?action=getdyndns&sha=<sha1sum of login|password>
3546+## This returns a list of host|currentIP|updateURL lines.
3547+## Pick the line that matches myhost, and fetch the URL.
3548+## word 'Updated' for success, 'fail' for failure.
3549+##
3550+######################################################################
3551+sub nic_freedns_update {
3552+
3553+
3554+ debug("\nnic_freedns_update -------------------");
3555+
3556+ ## First get the list of updatable hosts
3557+ my $url;
3558+ $url = "http://$config{$_[0]}{'server'}/api/?action=getdyndns&sha=".&sha1_hex("$config{$_[0]}{'login'}|$config{$_[0]}{'password'}");
3559+ my $reply = geturl(opt('proxy'), $url);
3560+ if (!defined($reply) || !$reply || !header_ok($_[0], $reply)) {
3561+ failed("updating %s: Could not connect to %s for site list.", $_[0], $url);
3562+ return;
3563+ }
3564+ my @lines = split("\n", $reply);
3565+ my %freedns_hosts;
3566+ grep {
3567+ my @rec = split(/\|/, $_);
3568+ $freedns_hosts{$rec[0]} = \@rec if ($#rec > 0);
3569+ } @lines;
3570+ if (!keys %freedns_hosts) {
3571+ failed("Could not get freedns update URLs from %s", $config{$_[0]}{'server'});
3572+ return;
3573+ }
3574+ ## update each configured host
3575+ foreach my $h (@_) {
3576+ if(!$h){ next };
3577+ my $ip = delete $config{$h}{'wantip'};
3578+ info("setting IP address to %s for %s", $ip, $h);
3579+ verbose("UPDATE:","updating %s", $h);
3580+
3581+ if($ip eq $freedns_hosts{$h}->[1]) {
3582+ $config{$h}{'ip'} = $ip;
3583+ $config{$h}{'mtime'} = $now;
3584+ $config{$h}{'status'} = 'good';
3585+ success("update not necessary %s: good: IP address already set to %s", $h, $ip);
3586+ } else {
3587+ my $reply = geturl(opt('proxy'), $freedns_hosts{$h}->[2]);
3588+ if (!defined($reply) || !$reply) {
3589+ failed("updating %s: Could not connect to %s.", $h, $freedns_hosts{$h}->[2]);
3590+ last;
3591+ }
3592+ if(!header_ok($h, $reply)) {
3593+ $config{$h}{'status'} = 'failed';
3594+ last;
3595+ }
3596+
3597+ if($reply =~ /Updated.*$h.*to.*$ip/) {
3598+ $config{$h}{'ip'} = $ip;
3599+ $config{$h}{'mtime'} = $now;
3600+ $config{$h}{'status'} = 'good';
3601+ success("updating %s: good: IP address set to %s", $h, $ip);
3602+ } else {
3603+ $config{$h}{'status'} = 'failed';
3604+ warning("SENT: %s", $freedns_hosts{$h}->[2]) unless opt('verbose');
3605+ warning("REPLIED: %s", $reply);
3606+ failed("updating %s: Invalid reply.", $h);
3607+ }
3608+ }
3609+ }
3610+}
3611+
3612+######################################################################
3613+
3614+######################################################################
3615+## nic_dtdns_examples
3616+######################################################################
3617+sub nic_dtdns_examples {
3618+ return <<EoEXAMPLE;
3619+o 'dtdns'
3620+
3621+The 'dtdns' protocol is the protocol used by the dynamic hostname services
3622+of the 'DtDNS' dns services. This is currently used by the free
3623+dynamic DNS service offered by www.dtdns.com.
3624+
3625+Configuration variables applicable to the 'dtdns' protocol are:
3626+ protocol=dtdns ##
3627+ server=www.fqdn.of.service ## defaults to www.dtdns.com
3628+ password=service-password ## password registered with the service
3629+ client=name_of_updater ## defaults to $program (10 chars max, no spaces)
3630+ fully.qualified.host ## the host registered with the service.
3631+
3632+Example ${program}.conf file entries:
3633+ ## single host update
3634+ protocol=dtdns, \\
3635+ password=my-dydns.za.net-password, \\
3636+ client=ddclient \\
3637+ myhost.dtdns.net
3638+
3639+EoEXAMPLE
3640+}
3641+
3642+######################################################################
3643+## nic_dtdns_update
3644+## by Achim Franke
3645+######################################################################
3646+sub nic_dtdns_update {
3647+ debug("\nnic_dtdns_update -------------------");
3648+
3649+ ## update each configured host
3650+ foreach my $h (@_) {
3651+ my $ip = delete $config{$h}{'wantip'};
3652+ info("setting IP address to %s for %s", $ip, $h);
3653+ verbose("UPDATE:","updating %s", $h);
3654+
3655+ # Set the URL that we're going to to update
3656+ my $url;
3657+ $url = "http://$config{$h}{'server'}/api/autodns.cfm";
3658+ $url .= "?id=";
3659+ $url .= $h;
3660+ $url .= "&pw=";
3661+ $url .= $config{$h}{'password'};
3662+ $url .= "&ip=";
3663+ $url .= $ip;
3664+ $url .= "&client=";
3665+ $url .= $config{$h}{'client'};
3666+
3667+ # Try to get URL
3668+ my $reply = geturl(opt('proxy'), $url);
3669+
3670+ # No response, declare as failed
3671+ if (!defined($reply) || !$reply) {
3672+ failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
3673+ last;
3674+ }
3675+ last if !header_ok($h, $reply);
3676+
3677+ # Response found, just declare as success (this is ugly, we need more error checking)
3678+ if ($reply =~ /now\spoints\sto/)
3679+ {
3680+ $config{$h}{'ip'} = $ip;
3681+ $config{$h}{'mtime'} = $now;
3682+ $config{$h}{'status'} = 'good';
3683+ success("updating %s: good: IP address set to %s", $h, $ip);
3684+ }
3685+ else
3686+ {
3687+ my @reply = split /\n/, $reply;
3688+ my $returned = pop(@reply);
3689+ $config{$h}{'status'} = 'failed';
3690+ failed("updating %s: Server said: '$returned'", $h);
3691+ }
3692+ }
3693+}
3694+
3695+######################################################################
3696+# vim: ai ts=4 sw=4 tw=78 :
3697+
3698+
3699+__END__
3700
3701=== added file '.pc/maxinterval.diff/.timestamp'
3702=== added file '.pc/sample_ubuntu.diff/.timestamp'
3703=== added file '.pc/smc-barricade-fw-alt.diff/.timestamp'
3704=== modified file 'ddclient'
3705--- ddclient 2012-09-18 13:30:33 +0000
3706+++ ddclient 2014-12-10 04:48:25 +0000
3707@@ -1784,13 +1784,18 @@
3708 ######################################################################
3709 sub load_sha1_support {
3710 my $sha1_loaded = eval {require Digest::SHA1};
3711- unless ($sha1_loaded) {
3712+ my $sha_loaded = eval {require Digest::SHA};
3713+ unless ($sha1_loaded || $sha_loaded) {
3714 fatal(<<"EOM");
3715-Error loading the Perl module Digest::SHA1 needed for freedns update.
3716-On Debian, the package libdigest-sha1-perl must be installed.
3717+Error loading the Perl module Digest::SHA1 or Digest::SHA needed for freedns update.
3718+On Debian, the package libdigest-sha1-perl or libdigest-sha-perl must be installed.
3719 EOM
3720 }
3721- import Digest::SHA1 (qw/sha1_hex/);
3722+ if($sha1_loaded) {
3723+ import Digest::SHA1 (qw/sha1_hex/);
3724+ } elsif($sha_loaded) {
3725+ import Digest::SHA (qw/sha1_hex/);
3726+ }
3727 }
3728 ######################################################################
3729 ## geturl
3730
3731=== modified file 'debian/changelog'
3732--- debian/changelog 2013-07-29 14:49:34 +0000
3733+++ debian/changelog 2014-12-10 04:48:25 +0000
3734@@ -1,3 +1,10 @@
3735+ddclient (3.8.1-1ubuntu3) vivid; urgency=low
3736+
3737+ * Add patch from upstream which enables ddclient to find Digest::SHA to
3738+ fix FreeDNS support (LP: #1068884)
3739+
3740+ -- Scott Talbert <swt@techie.net> Sat, 06 Dec 2014 22:49:13 -0500
3741+
3742 ddclient (3.8.1-1ubuntu2) saucy; urgency=low
3743
3744 * debian/ddclient.NetworkManager
3745
3746=== modified file 'debian/control'
3747--- debian/control 2012-09-18 13:30:33 +0000
3748+++ debian/control 2014-12-10 04:48:25 +0000
3749@@ -1,7 +1,8 @@
3750 Source: ddclient
3751 Section: net
3752 Priority: extra
3753-Maintainer: Torsten Landschoff <torsten@debian.org>
3754+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
3755+XSBC-Original-Maintainer: Torsten Landschoff <torsten@debian.org>
3756 Uploaders: Marco Rodrigues <gothicx@sapo.pt>
3757 Build-Depends: debhelper (>= 5), po-debconf, xmlto, quilt
3758 Standards-Version: 3.8.4
3759
3760=== added file 'debian/patches/fix_digest_sha_freedns.diff'
3761--- debian/patches/fix_digest_sha_freedns.diff 1970-01-01 00:00:00 +0000
3762+++ debian/patches/fix_digest_sha_freedns.diff 2014-12-10 04:48:25 +0000
3763@@ -0,0 +1,27 @@
3764+Index: ddclient/ddclient
3765+===================================================================
3766+--- ddclient.orig/ddclient 2014-12-06 21:59:46.763693000 -0500
3767++++ ddclient/ddclient 2014-12-06 22:32:58.422405251 -0500
3768+@@ -1784,13 +1784,18 @@
3769+ ######################################################################
3770+ sub load_sha1_support {
3771+ my $sha1_loaded = eval {require Digest::SHA1};
3772+- unless ($sha1_loaded) {
3773++ my $sha_loaded = eval {require Digest::SHA};
3774++ unless ($sha1_loaded || $sha_loaded) {
3775+ fatal(<<"EOM");
3776+-Error loading the Perl module Digest::SHA1 needed for freedns update.
3777+-On Debian, the package libdigest-sha1-perl must be installed.
3778++Error loading the Perl module Digest::SHA1 or Digest::SHA needed for freedns update.
3779++On Debian, the package libdigest-sha1-perl or libdigest-sha-perl must be installed.
3780+ EOM
3781+ }
3782+- import Digest::SHA1 (qw/sha1_hex/);
3783++ if($sha1_loaded) {
3784++ import Digest::SHA1 (qw/sha1_hex/);
3785++ } elsif($sha_loaded) {
3786++ import Digest::SHA (qw/sha1_hex/);
3787++ }
3788+ }
3789+ ######################################################################
3790+ ## geturl
3791
3792=== modified file 'debian/patches/series'
3793--- debian/patches/series 2012-09-18 13:30:33 +0000
3794+++ debian/patches/series 2014-12-10 04:48:25 +0000
3795@@ -2,3 +2,4 @@
3796 config_path.diff
3797 maxinterval.diff
3798 sample_ubuntu.diff
3799+fix_digest_sha_freedns.diff

Subscribers

People subscribed via source and target branches

to all changes: