2
#!/usr/local/bin/perl -w
3
######################################################################
4
# $Id: ddclient 106 2008-12-04 18:05:23Z wimpunk $
6
# DDCLIENT - a Perl client for updating DynDNS information
8
# Author: Paul Burry (paul+ddclient@burry.ca)
9
# ddclient-developers: see https://sourceforge.net/project/memberlist.php?group_id=116817
11
# website: http://ddclient.sf.net
13
# Support for multiple IP numbers added by
14
# Astaro AG, Ingo Schwarze <ischwarze-OOs/4mkCeqbQT0dZR+AlfA@public.gmane.org> September 16, 2008
16
######################################################################
23
my $version = "3.8.0";
25
$programd =~ s%^.*/%%;
26
my $program = $programd;
29
my $hostname = hostname();
30
my $etc = ($program =~ /test/i) ? './' : '/etc/';
31
my $cachedir = ($program =~ /test/i) ? './' : '/var/cache/ddclient/';
32
my $savedir = ($program =~ /test/i) ? 'URL/' : '/tmp/';
36
use vars qw($file $lineno);
40
$ENV{'PATH'} = (exists($ENV{PATH}) ? "$ENV{PATH}:" : "") . "/sbin:/usr/sbin:/bin:/usr/bin:/etc:/usr/lib:";
43
sub T_STRING {'string'};
44
sub T_EMAIL {'e-mail address'};
45
sub T_NUMBER {'number'};
46
sub T_DELAY {'time delay (ie. 1d, 1hour, 1m)'};
47
sub T_LOGIN {'login'};
48
sub T_PASSWD {'password'};
49
sub T_BOOL {'boolean value'};
50
sub T_FQDN {'fully qualified host name'};
51
sub T_OFQDN {'optional fully qualified host name'};
52
sub T_FILE {'file name'};
53
sub T_FQDNP {'fully qualified host name and optional port number'};
54
sub T_PROTO {'protocol'}
55
sub T_USE {'ip strategy'}
56
sub T_IF {'interface'}
57
sub T_PROG {'program name'}
59
sub T_POSTS {'postscript'};
61
## strategies for obtaining an ip address.
63
'dyndns' => { 'url' => 'http://checkip.dyndns.org/', 'skip' =>
64
'Current IP Address:', },
65
'dnspark' => { 'url' => 'http://ipdetect.dnspark.com/', 'skip' => 'Current Address:', },
68
'watchguard-soho' => {
69
'name' => 'Watchguard SOHO FW',
70
'url' => '/pubnet.htm',
71
'skip' => 'NAME=IPAddress VALUE=',
74
'name' => 'Netopia R910 FW',
75
'url' => '/WanEvtLog',
79
'name' => 'SMC Barricade FW',
80
'url' => '/status.htm',
81
'skip' => 'IP Address',
83
'smc-barricade-alt' => {
84
'name' => 'SMC Barricade FW (alternate config)',
85
'url' => '/status.HTM',
88
'smc-barricade-7401bra' => {
89
'name' => 'SMC Barricade 7401BRA FW',
90
'url' => '/admin/wan1.htm',
91
'skip' => 'IP Address',
94
'name' => 'Netgear FW',
95
'url' => '/mtenSysStatus.html',
96
'skip' => 'IP Address',
98
'elsa-lancom-dsl10' => {
99
'name' => 'ELSA LanCom DSL/10 DSL FW',
100
'url' => '/config/1/6/8/3/',
101
'skip' => 'IP.Address',
103
'elsa-lancom-dsl10-ch01' => {
104
'name' => 'ELSA LanCom DSL/10 DSL FW (isdn ch01)',
105
'url' => '/config/1/6/8/3/',
106
'skip' => 'IP.Address.*?CH01',
108
'elsa-lancom-dsl10-ch02' => {
109
'name' => 'ELSA LanCom DSL/10 DSL FW (isdn ch01)',
110
'url' => '/config/1/6/8/3/',
111
'skip' => 'IP.Address.*?CH02',
114
'name' => 'Linksys FW',
115
'url' => '/Status.htm',
116
'skip' => 'WAN.*?Address',
119
'name' => 'Linksys FW version 2',
120
'url' => '/RouterStatus.htm',
121
'skip' => 'WAN.*?Address',
124
'name' => 'Linksys FW version 3',
125
'url' => '/Status_Router.htm',
126
'skip' => 'WAN.*?Address',
128
'linksys-wrt854g' => {
129
'name' => 'Linksys WRT854G FW',
130
'url' => '/Status_Router.asp',
131
'skip' => 'IP Address:',
133
'maxgate-ugate3x00' => {
134
'name' => 'MaxGate UGATE-3x00 FW',
135
'url' => '/Status.htm',
136
'skip' => 'WAN.*?IP Address',
139
'name' => 'NetComm NB3',
140
'url' => '/MainPage?id=6',
144
'name' => '3com 3c886a 56k Lan Modem',
145
'url' => '/stat3.htm',
146
'skip' => 'IP address in use',
148
'sohoware-nbg800' => {
149
'name' => 'SOHOWare BroadGuard NBG800',
150
'url' => '/status.htm',
151
'skip' => 'Internet IP',
154
'name' => 'Xsense Aero',
155
'url' => '/A_SysInfo.htm',
156
'skip' => 'WAN.*?IP Address',
159
'name' => 'Alcatel Speed Touch Pro',
160
'url' => '/cgi/router/',
164
'name' => 'Alcatel Speed Touch 510',
169
'name' => 'Allnet 1298',
170
'url' => '/cgi/router/',
173
'3com-oc-remote812' => {
174
'name' => '3com OfficeConnect Remote 812',
175
'url' => '/callEvent',
179
'name' => 'E-tech Router',
180
'url' => '/Status.htm',
181
'skip' => 'Public IP Address',
184
'name' => 'Cayman 3220-H DSL',
185
'url' => '/shell/show+ip+interfaces',
189
'name' => 'Vigor 2200 USB',
190
'url' => '/doc/online.sht',
194
'name' => 'D-Link DI-614+',
195
'url' => '/st_devic.html',
199
'name' => 'D-Link DI-604',
200
'url' => '/st_devic.html',
201
'skip' => 'WAN.*?IP.*Address',
204
'name' => 'olitec-SX200',
205
'url' => '/doc/wan.htm',
206
'skip' => 'st_wan_ip[0] = "',
209
'name' => 'Westell C90-610015-06 DSL Router',
210
'url' => '/advstat.htm',
211
'skip' => 'IP.+?Address',
214
'name' => '2Wire 1701HG Gateway',
215
'url' => '/xslt?PAGE=B01',
216
'skip' => 'Internet Address:',
218
'linksys-rv042-wan1' => {
219
'name' => 'Linksys RV042 Dual Homed Router WAN Port 2',
220
'url' => '/home.htm',
223
'linksys-rv042-wan2' => {
224
'name' => 'Linksys RV042 Dual Homed Router WAN Port 2',
225
'url' => '/home.htm',
229
'name' => 'Netgear RP614 FW',
230
'url' => '/sysstatus.html',
231
'skip' => 'IP Address',
233
'watchguard-edge-x' => {
234
'name' => 'Watchguard Edge X FW',
235
'url' => '/netstat.htm',
236
'skip' => 'inet addr:',
239
'name' => 'D-Link DI-524',
240
'url' => '/st_device.html',
241
'skip' => 'WAN.*?Addres',
244
'name' => 'Linksys RTP300',
245
'url' => '/cgi-bin/webcm?getpage=%2Fusr%2Fwww_safe%2Fhtml%2Fstatus%2FRouter.html',
246
'skip' => 'Internet.*?IP Address',
248
'netgear-wpn824' => {
249
'name' => 'Netgear WPN824 FW',
250
'url' => '/RST_status.htm',
251
'skip' => 'IP Address',
253
'linksys-wcg200' => {
254
'name' => 'Linksys WCG200 FW',
255
'url' => '/RgStatus.asp',
256
'skip' => 'WAN.IP.*?Address',
258
'netgear-dg834g' => {
259
'name' => 'netgear-dg834g',
260
'url' => '/setup.cgi?next_file=s_status.htm&todo=cfg_init',
263
'netgear-wgt624' => {
264
'name' => 'Netgear WGT624',
265
'url' => '/RST_st_dhcp.htm',
266
'skip' => 'IP Address</B></td><TD NOWRAP width="50%">',
269
'name' => 'Sveasoft WRT54G/WRT54GS',
270
'url' => '/Status_Router.asp',
271
'skip' => 'var wan_ip',
273
'smc-barricade-7004vbr' => {
274
'name' => 'SMC Barricade FW (7004VBR model config)',
275
'url' => '/status_main.stm',
276
'skip' => 'var wan_ip=',
279
'name' => 'Sitecom DC-202 FW',
280
'url' => '/status.htm',
281
'skip' => 'Internet IP Address',
284
my %ip_strategies = (
285
'ip' => ": obtain IP from -ip {address}",
286
'web' => ": obtain IP from an IP discovery page on the web",
287
'fw' => ": obtain IP from the firewall specified by -fw {type|address}",
288
'if' => ": obtain IP from the -if {interface}",
289
'cmd' => ": obtain IP from the -cmd {external-command}",
290
'cisco' => ": obtain IP from Cisco FW at the -fw {address}",
291
map { $_ => sprintf ": obtain IP from %s at the -fw {address}", $builtinfw{$_}->{'name'} } keys %builtinfw,
293
sub ip_strategies_usage {
294
return map { sprintf(" -use=%-22s %s.", $_, $ip_strategies{$_}) } sort keys %ip_strategies;
297
my %web_strategies = (
313
'global-defaults' => {
314
'daemon' => setv(T_DELAY, 0, 0, 1, 0, interval('60s')),
315
'file' => setv(T_FILE, 0, 0, 1, "$etc$program.conf", undef),
316
'cache' => setv(T_FILE, 0, 0, 1, "$cachedir$program.cache", undef),
317
'pid' => setv(T_FILE, 0, 0, 1, "", undef),
318
'proxy' => setv(T_FQDNP, 0, 0, 1, '', undef),
319
'protocol' => setv(T_PROTO, 0, 0, 1, 'dyndns2', undef),
321
'use' => setv(T_USE, 0, 0, 1, 'ip', undef),
322
'ip' => setv(T_IP, 0, 0, 1, undef, undef),
323
'if' => setv(T_IF, 0, 0, 1, 'ppp0', undef),
324
'if-skip' => setv(T_STRING,1, 0, 1, '', undef),
325
'web' => setv(T_STRING,0, 0, 1, 'dyndns', undef),
326
'web-skip' => setv(T_STRING,1, 0, 1, '', undef),
327
'fw' => setv(T_ANY, 0, 0, 1, '', undef),
328
'fw-skip' => setv(T_STRING,1, 0, 1, '', undef),
329
'fw-login' => setv(T_LOGIN, 1, 0, 1, '', undef),
330
'fw-password' => setv(T_PASSWD,1, 0, 1, '', undef),
331
'cmd' => setv(T_PROG, 0, 0, 1, '', undef),
332
'cmd-skip' => setv(T_STRING,1, 0, 1, '', undef),
334
'timeout' => setv(T_DELAY, 0, 0, 1, interval('120s'), interval('120s')),
335
'retry' => setv(T_BOOL, 0, 0, 0, 0, undef),
336
'force' => setv(T_BOOL, 0, 0, 0, 0, undef),
337
'ssl' => setv(T_BOOL, 0, 0, 0, 0, undef),
339
'syslog' => setv(T_BOOL, 0, 0, 1, 0, undef),
340
'facility' => setv(T_STRING,0, 0, 1, 'daemon', undef),
341
'priority' => setv(T_STRING,0, 0, 1, 'notice', undef),
342
'mail' => setv(T_EMAIL, 0, 0, 1, '', undef),
343
'mail-failure' => setv(T_EMAIL, 0, 0, 1, '', undef),
345
'exec' => setv(T_BOOL, 0, 0, 1, 1, undef),
346
'debug' => setv(T_BOOL, 0, 0, 1, 0, undef),
347
'verbose' => setv(T_BOOL, 0, 0, 1, 0, undef),
348
'quiet' => setv(T_BOOL, 0, 0, 1, 0, undef),
349
'help' => setv(T_BOOL, 0, 0, 1, 0, undef),
350
'test' => setv(T_BOOL, 0, 0, 1, 0, undef),
351
'geturl' => setv(T_STRING,0, 0, 0, '', undef),
353
'postscript' => setv(T_POSTS, 0, 0, 1, '', undef),
355
'service-common-defaults' => {
356
'server' => setv(T_FQDNP, 1, 0, 1, 'members.dyndns.org', undef),
357
'login' => setv(T_LOGIN, 1, 0, 1, '', undef),
358
'password' => setv(T_PASSWD, 1, 0, 1, '', undef),
359
'host' => setv(T_STRING, 1, 1, 1, '', undef),
361
'use' => setv(T_USE, 0, 0, 1, 'ip', undef),
362
'if' => setv(T_IF, 0, 0, 1, 'ppp0', undef),
363
'if-skip' => setv(T_STRING,0, 0, 1, '', undef),
364
'web' => setv(T_STRING,0, 0, 1, 'dyndns', undef),
365
'web-skip' => setv(T_STRING,0, 0, 1, '', undef),
366
'fw' => setv(T_ANY, 0, 0, 1, '', undef),
367
'fw-skip' => setv(T_STRING,0, 0, 1, '', undef),
368
'fw-login' => setv(T_LOGIN, 0, 0, 1, '', undef),
369
'fw-password' => setv(T_PASSWD,0, 0, 1, '', undef),
370
'cmd' => setv(T_PROG, 0, 0, 1, '', undef),
371
'cmd-skip' => setv(T_STRING,0, 0, 1, '', undef),
373
'ip' => setv(T_IP, 0, 1, 0, undef, undef),
374
'wtime' => setv(T_DELAY, 0, 1, 1, 0, interval('30s')),
375
'mtime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
376
'atime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
377
'status' => setv(T_ANY, 0, 1, 0, '', undef),
378
'min-interval' => setv(T_DELAY, 0, 0, 1, interval('30s'), 0),
379
'max-interval' => setv(T_DELAY, 0, 0, 1, interval('30d'), 0),
380
'min-error-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),
382
'warned-min-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
383
'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
385
'dyndns-common-defaults' => {
386
'static' => setv(T_BOOL, 0, 1, 1, 0, undef),
387
'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef),
388
'mx' => setv(T_OFQDN, 0, 1, 1, '', undef),
389
'backupmx' => setv(T_BOOL, 0, 1, 1, 0, undef),
391
'easydns-common-defaults' => {
392
'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef),
393
'mx' => setv(T_OFQDN, 0, 1, 1, '', undef),
394
'backupmx' => setv(T_BOOL, 0, 1, 1, 0, undef),
396
'dnspark-common-defaults' => {
397
'mx' => setv(T_OFQDN, 0, 1, 1, '', undef),
398
'mxpri' => setv(T_NUMBER, 0, 0, 1, 5, undef),
400
'noip-common-defaults' => {
401
'static' => setv(T_BOOL, 0, 1, 1, 0, undef),
403
'noip-service-common-defaults' => {
404
'server' => setv(T_FQDNP, 1, 0, 1, 'dynupdate.no-ip.com', undef),
405
'login' => setv(T_LOGIN, 1, 0, 1, '', undef),
406
'password' => setv(T_PASSWD, 1, 0, 1, '', undef),
407
'host' => setv(T_STRING, 1, 1, 1, '', undef),
408
'ip' => setv(T_IP, 0, 1, 0, undef, undef),
409
'wtime' => setv(T_DELAY, 0, 1, 1, 0, interval('30s')),
410
'mtime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
411
'atime' => setv(T_NUMBER, 0, 1, 0, 0, undef),
412
'status' => setv(T_ANY, 0, 1, 0, '', undef),
413
'min-interval' => setv(T_DELAY, 0, 0, 1, interval('30s'), 0),
414
'max-interval' => setv(T_DELAY, 0, 0, 1, interval('25d'), 0),
415
'min-error-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),
416
'warned-min-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
417
'warned-min-error-interval' => setv(T_ANY, 0, 1, 0, 0, undef),
422
'updateable' => \&nic_dyndns2_updateable,
423
'update' => \&nic_dyndns1_update,
424
'examples' => \&nic_dyndns1_examples,
425
'variables' => merge(
426
$variables{'dyndns-common-defaults'},
427
$variables{'service-common-defaults'},
431
'updateable' => \&nic_dyndns2_updateable,
432
'update' => \&nic_dyndns2_update,
433
'examples' => \&nic_dyndns2_examples,
434
'variables' => merge(
435
{ 'custom' => setv(T_BOOL, 0, 1, 1, 0, undef), },
436
# { 'offline' => setv(T_BOOL, 0, 1, 1, 0, undef), },
437
$variables{'dyndns-common-defaults'},
438
$variables{'service-common-defaults'},
442
'updateable' => undef,
443
'update' => \&nic_noip_update,
444
'examples' => \&nic_noip_examples,
445
'variables' => merge(
446
{ 'custom' => setv(T_BOOL, 0, 1, 1, 0, undef), },
447
$variables{'noip-common-defaults'},
448
$variables{'noip-service-common-defaults'},
452
'updateable' => undef,
453
'update' => \&nic_concont_update,
454
'examples' => \&nic_concont_examples,
455
'variables' => merge(
456
$variables{'service-common-defaults'},
457
{ 'mx' => setv(T_OFQDN, 0, 1, 1, '', undef), },
458
{ 'wildcard' => setv(T_BOOL, 0, 1, 1, 0, undef), },
462
'updateable' => undef,
463
'update' => \&nic_dslreports1_update,
464
'examples' => \&nic_dslreports1_examples,
465
'variables' => merge(
466
{ 'host' => setv(T_NUMBER, 1, 1, 1, 0, undef) },
467
$variables{'service-common-defaults'},
471
'updateable' => undef,
472
'update' => \&nic_hammernode1_update,
473
'examples' => \&nic_hammernode1_examples,
474
'variables' => merge(
475
{ 'server' => setv(T_FQDNP, 1, 0, 1, 'dup.hn.org', undef) },
476
{ 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
477
$variables{'service-common-defaults'},
481
'updateable' => undef,
482
'update' => \&nic_zoneedit1_update,
483
'examples' => \&nic_zoneedit1_examples,
484
'variables' => merge(
485
{ 'server' => setv(T_FQDNP, 1, 0, 1, 'dynamic.zoneedit.com', undef) },
486
{ 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
487
$variables{'service-common-defaults'},
491
'updateable' => undef,
492
'update' => \&nic_easydns_update,
493
'examples' => \&nic_easydns_examples,
494
'variables' => merge(
495
{ 'server' => setv(T_FQDNP, 1, 0, 1, 'members.easydns.com', undef) },
496
{ 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
497
$variables{'easydns-common-defaults'},
498
$variables{'service-common-defaults'},
502
'updateable' => undef,
503
'update' => \&nic_dnspark_update,
504
'examples' => \&nic_dnspark_examples,
505
'variables' => merge(
506
{ 'server' => setv(T_FQDNP, 1, 0, 1, 'www.dnspark.com', undef) },
507
{ 'min-interval' => setv(T_DELAY, 0, 0, 1, interval('5m'), 0),},
508
$variables{'dnspark-common-defaults'},
509
$variables{'service-common-defaults'},
513
'updateable' => undef,
514
'update' => \&nic_namecheap_update,
515
'examples' => \&nic_namecheap_examples,
516
'variables' => merge(
517
{ 'server' => setv(T_FQDNP, 1, 0, 1, 'dynamicdns.park-your-domain.com', undef) },
518
{ 'min-interval' => setv(T_DELAY, 0, 0, 1, 0, interval('5m')),},
519
$variables{'service-common-defaults'},
523
'updateable' => undef,
524
'update' => \&nic_sitelutions_update,
525
'examples' => \&nic_sitelutions_examples,
526
'variables' => merge(
527
{ 'server' => setv(T_FQDNP, 1, 0, 1, 'www.sitelutions.com', undef) },
528
{ 'min-interval' => setv(T_DELAY, 0, 0, 1, 0, interval('5m')),},
529
$variables{'service-common-defaults'},
533
$variables{'merged'} = merge($variables{'global-defaults'},
534
$variables{'service-common-defaults'},
535
$variables{'dyndns-common-defaults'},
536
map { $services{$_}{'variables'} } keys %services,
540
"usage: ${program} [options]",
542
[ "daemon", "=s", "-daemon delay : run as a daemon, specify delay as an interval." ],
543
[ "proxy", "=s", "-proxy host : use 'host' as the HTTP proxy" ],
544
[ "server", "=s", "-server host : update DNS information on 'host'" ],
545
[ "protocol", "=s", "-protocol type : update protocol used" ],
546
[ "file", "=s", "-file path : load configuration information from 'path'" ],
547
[ "cache", "=s", "-cache path : record address used in 'path'" ],
548
[ "pid", "=s", "-pid path : record process id in 'path'" ],
550
[ "use", "=s", "-use which : how the should IP address be obtained." ],
551
&ip_strategies_usage(),
553
[ "ip", "=s", "-ip address : set the IP address to 'address'" ],
555
[ "if", "=s", "-if interface : obtain IP address from 'interface'" ],
556
[ "if-skip", "=s", "-if-skip pattern : skip any IP addresses before 'pattern' in the output of ifconfig {if}" ],
558
[ "web", "=s", "-web provider|url : obtain IP address from provider's IP checking page" ],
559
[ "web-skip", "=s", "-web-skip pattern : skip any IP addresses before 'pattern' on the web provider|url" ],
561
[ "fw", "=s", "-fw address|url : obtain IP address from firewall at 'address'" ],
562
[ "fw-skip", "=s", "-fw-skip pattern : skip any IP addresses before 'pattern' on the firewall address|url" ],
563
[ "fw-login", "=s", "-fw-login login : use 'login' when getting IP from fw" ],
564
[ "fw-password", "=s", "-fw-password secret : use password 'secret' when getting IP from fw" ],
566
[ "cmd", "=s", "-cmd program : obtain IP address from by calling {program}" ],
567
[ "cmd-skip", "=s", "-cmd-skip pattern : skip any IP addresses before 'pattern' in the output of {cmd}" ],
569
[ "login", "=s", "-login user : login as 'user'" ],
570
[ "password", "=s", "-password secret : use password 'secret'" ],
571
[ "host", "=s", "-host host : update DNS information for 'host'" ],
573
[ "options", "=s", "-options opt,opt : optional per-service arguments (see below)" ],
575
[ "ssl", "!", "-{no}ssl : do updates over encrypted SSL connection" ],
576
[ "retry", "!", "-{no}retry : retry failed updates." ],
577
[ "force", "!", "-{no}force : force an update even if the update may be unnecessary" ],
578
[ "timeout", "=i", "-timeout max : wait at most 'max' seconds for the host to respond" ],
580
[ "syslog", "!", "-{no}syslog : log messages to syslog" ],
581
[ "facility", "=s", "-facility {type} : log messages to syslog to facility {type}" ],
582
[ "priority", "=s", "-priority {pri} : log messages to syslog with priority {pri}" ],
583
[ "mail", "=s", "-mail address : e-mail messages to {address}" ],
584
[ "mail-failure","=s", "-mail-failure address : e-mail messages for failed updates to {address}" ],
585
[ "exec", "!", "-{no}exec : do {not} execute; just show what would be done" ],
586
[ "debug", "!", "-{no}debug : print {no} debugging information" ],
587
[ "verbose", "!", "-{no}verbose : print {no} verbose information" ],
588
[ "quiet", "!", "-{no}quiet : print {no} messages for unnecessary updates" ],
589
[ "help", "", "-help : this message" ],
590
[ "postscript", "", "-postscript : script to run after updating ddclient, has new IP as param" ],
592
[ "query", "!", "-{no}query : print {no} ip addresses and exit" ],
593
[ "test", "!", "" ], ## hidden
594
[ "geturl", "=s", "" ], ## hidden
597
"$program version $version, ",
598
" originally written by Paul Burry, paul+ddclient\@burry.ca",
599
" project now maintained on http://ddclient.sourceforge.net"
603
my ($opt_usage, %opt) = process_args(@opt);
604
my ($result, %config, %globals, %cache);
605
my $saved_cache = '';
606
my %saved_opt = %opt;
609
test_geturl(opt('geturl')) if opt('geturl');
611
## process help option
617
## read config file because 'daemon' mode may be defined there.
618
read_config(define($opt{'file'}, default('file')), \%config, \%globals);
620
test_possible_ip() if opt('query');
622
if (!opt('daemon') && $programd =~ /d$/) {
623
$opt{'daemon'} = minimum('daemon');
628
$SIG{'HUP'} = sub { $caught_hup = 1; };
629
$SIG{'TERM'} = sub { $caught_term = 1; };
630
$SIG{'KILL'} = sub { $caught_kill = 1; };
631
if (opt('daemon') && !opt('force')) {
632
$SIG{'CHLD'} = 'IGNORE';
635
print STDERR "${program}: can not fork ($!)\n";
640
$SIG{'CHLD'} = 'DEFAULT';
642
open(STDOUT, ">/dev/null");
643
open(STDERR, ">/dev/null");
644
open(STDIN, "</dev/null");
657
printf("Help found");
661
read_config(define($opt{'file'}, default('file')), \%config, \%globals);
663
read_cache(opt('cache'), \%cache);
664
print_info() if opt('debug') && opt('verbose');
666
# usage("invalid argument '-use %s'; possible values are:\n\t%s", $opt{'use'}, join("\n\t,",sort keys %ip_strategies))
667
usage("invalid argument '-use %s'; possible values are:\n%s", $opt{'use'}, join("\n",ip_strategies_usage()))
668
unless exists $ip_strategies{lc opt('use')};
670
$daemon = $opt{'daemon'};
671
$daemon = 0 if opt('force');
676
debug("sleep %s", $daemon);
680
while (($left > 0) && !$caught_hup && !$caught_term && !$caught_kill) {
681
my $delay = $left > 10 ? 10 : $left;
683
$0 = sprintf("%s - sleeping for %s seconds", $program, $left);
684
$left -= sleep $delay;
689
} elsif (! scalar(%config)) {
690
warning("no hosts to update.") unless !opt('quiet') || opt('verbose') || !$daemon;
694
$result = $result eq 'OK' ? 0 : 1;
696
} while ($daemon && !$result && !$caught_term && !$caught_kill);
698
warning("caught SIGKILL; exiting") if $caught_kill;
704
######################################################################
706
######################################################################
711
if ( defined $globals{postscript} ) {
712
if ( -x $globals{postscript}) {
713
system ("$globals{postscript} $ip &");
715
warning ("Can not execute post script: %s", $globals{postscript});
720
######################################################################
722
######################################################################
726
foreach my $s (sort keys %services) {
727
my (@hosts, %ips) = ();
728
my $updateable = $services{$s}{'updateable'};
729
my $update = $services{$s}{'update'};
731
foreach my $h (sort keys %config) {
732
next if $config{$h}{'protocol'} ne lc($s);
734
my $use = $config{$h}{'use'} || opt('use');
735
local $opt{$use} = $config{$h}{$use} if $config{$h}{$use};
736
my $ip = get_ip($use);
737
if (!defined $ip || !$ip) {
738
warning("unable to determine IP address")
739
if !$daemon || opt('verbose');
742
if ($ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
743
warning("malformed IP address (%s)", $ip);
746
$config{$h}{'wantip'} = $ip;
747
next if !nic_updateable($h, $updateable);
752
$0 = sprintf("%s - updating %s", $program, join(',', @hosts));
754
runpostscript(join ' ', keys %ips);
757
foreach my $h (sort keys %config) {
758
if (!exists $examined{$h}) {
759
failed("%s was not updated because protocol %s is not supported.",
760
$h, define($config{$h}{'protocol'}, '<undefined>')
764
write_cache(opt('cache'));
766
######################################################################
768
######################################################################
770
if (opt('pid') && opt('daemon')) {
775
######################################################################
777
######################################################################
779
my $file = opt('pid');
781
if ($file && opt('daemon')) {
783
if (! open(FD, "> $file")) {
784
warning("Cannot create file '%s'. ($!)", $file);
793
######################################################################
794
## write_cache($file)
795
######################################################################
799
## merge the updated host entries into the cache.
800
foreach my $h (keys %config) {
801
if (! exists $cache{$h} || $config{$h}{'update'}) {
802
map {$cache{$h}{$_} = $config{$h}{$_} } @{$config{$h}{'cacheable'}};
805
map {$cache{$h}{$_} = $config{$h}{$_} } qw(atime wtime status);
809
## construct the cache file.
811
foreach my $h (sort keys %cache) {
812
my $opt = join(',', map { "$_=".define($cache{$h}{$_},'') } sort keys %{$cache{$h}});
814
$cache .= sprintf "%s%s%s\n", $opt, ($opt ? ' ' : ''), $h;
816
$file = '' if defined($saved_cache) && $cache eq $saved_cache;
818
## write the updates and other entries to the cache file.
820
$saved_cache = undef;
822
if (! open(FD, "> $file")) {
823
fatal("Cannot create file '%s'. ($!)", $file);
825
printf FD "## $program-$version\n";
826
printf FD "## last updated at %s (%d)\n", prettytime($now), $now;
832
######################################################################
833
## read_cache($file) - called before reading the .conf
834
######################################################################
841
## read the cache file ignoring anything on the command-line.
845
$saved_cache = _read_config($config, $globals, "##\\s*$program-$version\\s*", $file);
848
foreach my $h (keys %cache) {
849
if (exists $config->{$h}) {
850
foreach (qw(atime mtime wtime ip status)) {
851
$config->{$h}{$_} = $cache{$h}{$_} if exists $cache{$h}{$_};
857
######################################################################
858
## parse_assignments(string) return (rest, %variables)
859
## parse_assignment(string) return (name, value, rest)
860
######################################################################
861
sub parse_assignments {
869
($name, $value, $rest) = parse_assignment($rest, @args);
871
$variables{$name} = $value;
876
return ($rest, %variables);
878
sub parse_assignment {
880
my $stop = @_ ? shift : '[\n\s,]';
881
my ($c, $name, $value);
882
my ($escape, $quote) = (0, '');
884
if ($rest =~ /^\s*([a-z][a-z_-]*)=(.*)/i) {
885
($name, $rest, $value) = ($1, $2, '');
887
while (length($c = substr($rest,0,1))) {
888
$rest = substr($rest,1);
892
} elsif ($c eq "\\") {
894
} elsif ($quote && $c eq $quote) {
896
} elsif (!$quote && $c =~ /[\'\"]/) {
898
} elsif (!$quote && $c =~ /^${stop}/) {
905
warning("assignment ended with an open quote") if $quote;
906
return ($name, $value, $rest);
908
######################################################################
910
######################################################################
917
_read_config($config, $globals, '', $file, %globals);
929
if (! open(FD, "< $file")) {
930
# fatal("Cannot open file '%s'. ($!)", $file);
931
warning("Cannot open file '%s'. ($!)", $file);
933
# Check for only owner has any access to config file
934
my ($dev, $ino, $mode, @statrest) = stat(FD);
936
if (-f FD && (chmod 0600, $file)) {
937
warning("file $file must be accessible only by its owner (fixed).");
939
# fatal("file $file must be accessible only by its owner.");
940
warning("file $file must be accessible only by its owner.");
945
my $continuation = '';
952
## check for the program version stamp
953
if (($. == 1) && $stamp && ($_ !~ /^$stamp$/i)) {
954
warning("program version mismatch; ignoring %s", $file);
958
warning("whitespace follows the \\ at the end-of-line.\nIf you meant to have a line continuation, remove the trailing whitespace.");
961
$content .= "$_\n" unless /^#/;
963
## parsing passwords is special
964
if (/^([^#]*\s)?([^#]*?password\S*?)\s*=\s*('.*'|[^']\S*)(.*)/) {
965
my ($head, $key, $value, $tail) = ($1 || '', $2, $3, $4);
966
$value = $1 if $value =~ /^'(.*)'$/;
967
$passwords{$key} = $value;
968
$_ = "${head}${key}=dummy${tail}";
974
## handle continuation lines
975
$_ = "$continuation$_";
983
s/^\s+//; # remove leading white space
984
s/\s+$//; # remove trailing white space
985
s/\s+/ /g; # canonify
988
## expected configuration line is:
989
## [opt=value,opt=..] [host [login [password]]]
991
($_, %locals) = parse_assignments($_);
995
## verify that keywords are valid...and check the value
996
foreach my $k (keys %locals) {
997
$locals{$k} = $passwords{$k} if defined $passwords{$k};
998
if (!exists $variables{'merged'}{$k}) {
999
warning("unrecognized keyword '%s' (ignored)", $k);
1002
my $def = $variables{'merged'}{$k};
1003
my $value = check_value($locals{$k}, $def);
1004
if (!defined($value)) {
1005
warning("Invalid Value for keyword '%s' = '%s'", $k, $locals{$k});
1007
} else { $locals{$k} = $value; }
1010
if (exists($locals{'host'})) {
1011
$args[0] = @args ? "$args[0],$locals{host}" : "$locals{host}";
1013
## accumulate globals
1015
map { $globals{$_} = $locals{$_} } keys %locals;
1018
## process this host definition
1020
my ($host, $login, $password) = @args;
1022
## add in any globals..
1023
%locals = %{ merge(\%locals, \%globals) };
1025
## override login and password if specified the old way.
1026
$locals{'login'} = $login if defined $login;
1027
$locals{'password'} = $password if defined $password;
1029
## allow {host} to be a comma separated list of hosts
1030
foreach my $h (split_by_comma($host)) {
1031
## save a copy of the current globals
1032
$config{$h} = { %locals };
1033
$config{$h}{'host'} = $h;
1040
warning("file ends while expecting a continuation line.")
1043
%$globals = %globals;
1048
######################################################################
1050
######################################################################
1055
$opt{'quiet'} = 0 if opt('verbose');
1057
## infer the IP strategy if possible
1058
$opt{'use'} = 'ip' if !define($opt{'use'}) && defined($opt{'ip'});
1059
$opt{'use'} = 'if' if !define($opt{'use'}) && defined($opt{'if'});
1060
$opt{'use'} = 'web' if !define($opt{'use'}) && defined($opt{'web'});
1063
$opt{'max-interval'} = min(interval(opt('max-interval')), interval(default('max-interval')));
1064
$opt{'min-interval'} = max(interval(opt('min-interval')), interval(default('min-interval')));
1065
$opt{'min-error-interval'} = max(interval(opt('min-error-interval')), interval(default('min-error-interval')));
1067
$opt{'timeout'} = 0 if opt('timeout') < 0;
1069
## only set $opt{'daemon'} if it has been explicitly passed in
1070
if (define($opt{'daemon'},$globals{'daemon'},0)) {
1071
$opt{'daemon'} = interval(opt('daemon'));
1072
$opt{'daemon'} = minimum('daemon')
1073
if ($opt{'daemon'} < minimum('daemon'));
1076
## define or modify host options specified on the command-line
1077
if (exists $opt{'options'} && defined $opt{'options'}) {
1078
## collect cmdline configuration options.
1080
foreach my $opt (split_by_comma($opt{'options'})) {
1081
my ($name,$var) = split /\s*=\s*/, $opt;
1082
$options{$name} = $var;
1084
## determine hosts specified with -host
1086
if (exists $opt{'host'}) {
1087
foreach my $h (split_by_comma($opt{'host'})) {
1091
## and those in -options=...
1092
if (exists $options{'host'}) {
1093
foreach my $h (split_by_comma($options{'host'})) {
1096
delete $options{'host'};
1098
## merge options into host definitions or globals
1100
foreach my $h (@hosts) {
1101
$config{$h} = merge(\%options, $config{$h});
1103
$opt{'host'} = join(',', @hosts);
1105
%globals = %{ merge(\%options, \%globals) };
1109
## override global options with those on the command-line.
1110
foreach my $o (keys %opt) {
1111
if (defined $opt{$o} && exists $variables{'global-defaults'}{$o}) {
1112
$globals{$o} = $opt{$o};
1117
if (defined $opt{'host'} && defined $opt{'retry'}) {
1118
usage("options -retry and -host (or -option host=..) are mutually exclusive");
1121
## determine hosts to update (those on the cmd-line, config-file, or failed cached)
1122
my @hosts = keys %config;
1124
@hosts = split_by_comma($opt{'host'});
1127
@hosts = map { $_ if $cache{$_}{'status'} ne 'good' } keys %cache;
1130
## remove any other hosts
1132
map { $hosts{$_} = undef } @hosts;
1133
map { delete $config{$_} unless exists $hosts{$_} } keys %config;
1135
## collect the cacheable variables.
1136
foreach my $proto (keys %services) {
1138
foreach my $k (keys %{$services{$proto}{'variables'}}) {
1139
push @cacheable, $k if $services{$proto}{'variables'}{$k}{'cache'};
1141
$services{$proto}{'cacheable'} = [ @cacheable ];
1145
## make sure config entries have all defaults and they meet minimums
1146
## first the globals...
1147
foreach my $k (keys %globals) {
1148
my $def = $variables{'merged'}{$k};
1149
my $ovalue = define($globals{$k}, $def->{'default'});
1150
my $value = check_value($ovalue, $def);
1151
if ($def->{'required'} && !defined $value) {
1152
$value = default($k);
1153
warning("'%s=%s' is an invalid %s. (using default of %s)", $k, $ovalue, $def->{'type'}, $value);
1155
$globals{$k} = $value;
1158
## now the host definitions...
1160
foreach my $h (keys %config) {
1162
$proto = $config{$h}{'protocol'};
1163
$proto = opt('protocol') if !defined($proto);
1165
if (!exists($services{$proto})) {
1166
warning("skipping host: %s: unrecognized protocol '%s'", $h, $proto);
1170
my $svars = $services{$proto}{'variables'};
1171
my $conf = { 'protocol' => $proto };
1173
foreach my $k (keys %$svars) {
1174
my $def = $svars->{$k};
1175
my $ovalue = define($config{$h}{$k}, $def->{'default'});
1176
my $value = check_value($ovalue, $def);
1177
if ($def->{'required'} && !defined $value) {
1178
warning("skipping host: %s: '%s=%s' is an invalid %s.", $h, $k, $ovalue, $def->{'type'});
1182
$conf->{$k} = $value;
1185
$config{$h} = $conf;
1186
$config{$h}{'cacheable'} = [ @{$services{$proto}{'cacheable'}} ];
1191
######################################################################
1193
######################################################################
1196
$exitcode = shift if @_ != 0; # use first arg if given
1200
$msg .= sprintf $format, @_;
1201
1 while chomp($msg);
1204
printf STDERR "%s%s\n", $msg, $opt_usage;
1209
######################################################################
1211
######################################################################
1219
my ($key, $specifier, $arg_usage) = @$_;
1220
my $value = default($key);
1222
## add a option specifier
1223
push @spec, $key . $specifier;
1225
## define the default value which can be overwritten later
1228
next unless $arg_usage;
1230
## add a line to the usage;
1231
$usage .= " $arg_usage";
1232
if (defined($value) && $value ne '') {
1233
$usage .= " (default: ";
1234
if ($specifier eq '!') {
1235
$usage .= "no" if ($specifier eq '!') && !$value;
1248
## process the arguments
1249
if (! GetOptions(\%opt, @spec)) {
1252
return ($usage, %opt);
1254
######################################################################
1255
## test_possible_ip - print possible IPs
1256
######################################################################
1257
sub test_possible_ip {
1258
local $opt{'debug'} = 0;
1260
printf "use=ip, ip=%s address is %s\n", opt('ip'), define(get_ip('ip'), 'NOT FOUND')
1261
if defined opt('ip');
1264
local $opt{'use'} = 'if';
1265
foreach my $if (grep {/^[a-zA-Z]/} `ifconfig -a`) {
1266
$if =~ s/:?\s.*//is;
1267
local $opt{'if'} = $if;
1268
printf "use=if, if=%s address is %s\n", opt('if'), define(get_ip('if'), 'NOT FOUND');
1272
if (opt('fw') !~ m%/%) {
1273
foreach my $fw (sort keys %builtinfw) {
1274
local $opt{'use'} = $fw;
1275
printf "use=$fw address is %s\n", define(get_ip($fw), 'NOT FOUND');
1278
local $opt{'use'} = 'fw';
1279
printf "use=fw, fw=%s address is %s\n", opt('fw'), define(get_ip(opt('fw')), 'NOT FOUND')
1280
if ! exists $builtinfw{opt('fw')};
1284
local $opt{'use'} = 'web';
1285
foreach my $web (sort keys %builtinweb) {
1286
local $opt{'web'} = $web;
1287
printf "use=web, web=$web address is %s\n", define(get_ip('web'), 'NOT FOUND');
1289
printf "use=web, web=%s address is %s\n", opt('web'), define(get_ip('web'), 'NOT FOUND')
1290
if ! exists $builtinweb{opt('web')};
1293
local $opt{'use'} = 'cmd';
1294
printf "use=cmd, cmd=%s address is %s\n", opt('cmd'), define(get_ip('cmd'), 'NOT FOUND');
1296
exit 0 unless opt('debug');
1298
######################################################################
1299
## test_geturl - print (and save if -test) result of fetching a URL
1300
######################################################################
1304
my $reply = geturl(opt('proxy'), $url, opt('login'), opt('password'));
1305
print "URL $url\n";;
1306
print defined($reply) ? $reply : "<undefined>\n";
1309
######################################################################
1311
######################################################################
1316
if (exists($ENV{'TEST_CASE'})) {
1317
my $try = "$file-$ENV{'TEST_CASE'}";
1318
$file = $try if -f $try;
1322
if (open(FD, "< $file")) {
1323
read(FD, $buffer, -s FD);
1325
debug("Loaded %d bytes from %s", length($buffer), $file);
1327
debug("Load failed from %s ($!)", $file);
1331
######################################################################
1333
######################################################################
1335
my ($file, $buffer, $opt) = @_;
1337
$file .= "-$ENV{'TEST_CASE'}" if exists $ENV{'TEST_CASE'};
1340
while (-f "$file-$i") {
1341
if ('unique' =~ /^$opt/i) {
1342
my $a = join('\n', grep {!/^Date:/} split /\n/, $buffer);
1343
my $b = join('\n', grep {!/^Date:/} split /\n/, load_file("$file-$i"));
1350
debug("Saving to %s", $file);
1352
open(FD, "> $file") or return;
1357
######################################################################
1363
######################################################################
1365
my ($string, $ptr) = @_;
1368
if (! defined($ptr)) {
1369
$value = "<undefined>";
1370
} elsif (ref $ptr eq 'HASH') {
1371
foreach my $key (sort keys %$ptr) {
1372
_print_hash("${string}\{$key\}", $ptr->{$key});
1376
printf "%-36s : %s\n", $string, $value;
1379
my ($string, $hash) = @_;
1380
printf "=== %s ====\n", $string;
1381
_print_hash($string, $hash);
1383
sub print_opt { print_hash("opt", \%opt); }
1384
sub print_globals { print_hash("globals", \%globals); }
1385
sub print_config { print_hash("config", \%config); }
1386
sub print_cache { print_hash("cache", \%cache); }
1393
######################################################################
1394
## pipecmd - run an external command
1397
######################################################################
1400
my $stdin = join("\n", @_);
1403
## remove trailing newlines
1404
1 while chomp($stdin);
1406
## override when debugging.
1407
$cmd = opt('exec') ? "| $cmd" : "> /dev/null";
1409
## execute the command.
1411
if (! open(FD, $cmd)) {
1412
printf STDERR "$program: cannot execute command %s.\n", $cmd;
1414
} elsif ($stdin && (! print FD "$stdin\n")) {
1415
printf STDERR "$program: failed writting to %s.\n", $cmd;
1418
} elsif (! close(FD)) {
1419
printf STDERR "$program: failed closing %s.($@)\n", $cmd;
1421
} elsif (opt('exec') && $?) {
1422
printf STDERR "$program: failed %s. ($@)\n", $cmd;
1430
if (opt('syslog') && opt('facility') && opt('priority')) {
1431
my $facility = opt('facility');
1432
my $priority = opt('priority');
1433
return pipecmd("logger -p$facility.$priority -t${program}\[$$\]", @_);
1438
my $recipients = opt('mail');
1440
if (opt('mail-failure') && ($result ne 'OK' && $result ne '0')) {
1441
$recipients = opt('mail-failure');
1443
if ($msgs && $recipients && $msgs ne $last_msgs) {
1444
pipecmd("sendmail -oi $recipients",
1446
"Subject: status report from $program\@$hostname",
1451
" $program\@$hostname (version $version)"
1457
######################################################################
1463
######################################################################
1464
sub split_by_comma {
1467
return split /\s*[, ]\s*/, $string if defined $string;
1472
foreach my $h (@_) {
1473
foreach my $k (keys %$h) {
1474
$merged{$k} = $h->{$k} unless exists $merged{$k};
1481
return $variables{'merged'}{$v}{'default'};
1485
return $variables{'merged'}{$v}{'minimum'};
1489
return $opt{$v} if defined $opt{$v};
1490
return $globals{$v} if defined $globals{$v};
1491
return default($v) if defined default($v);
1496
foreach my $arg (@_) {
1497
$min = $arg if $arg < $min;
1503
foreach my $arg (@_) {
1504
$max = $arg if $arg > $max;
1508
######################################################################
1510
######################################################################
1513
return $_ if defined $_;
1517
######################################################################
1519
######################################################################
1521
my ($value, $yes, $no, $undef) = @_;
1523
return $no if !defined($value) || !$value;
1524
return $yes if $value eq '1';
1525
foreach (qw(yes true)) {
1526
return $yes if $_ =~ /^$value/i;
1528
foreach (qw(no false)) {
1529
return $no if $_ =~ /^$value/i;
1533
######################################################################
1538
######################################################################
1543
my $buffer = sprintf $format, @_;
1546
$prefix = sprintf "%-9s ", $prefix if $prefix;
1548
$prefix .= "file $file";
1549
$prefix .= ", line $lineno" if $lineno;
1553
$buffer = "$prefix$buffer";
1554
$buffer =~ s/\n/\n$prefix /g;
1559
$msgs .= $buffer if $log;
1560
logger($buffer) if $log;
1563
sub msg { _msg(0, '', @_); }
1564
sub verbose { _msg(1, @_) if opt('verbose'); }
1565
sub info { _msg(1, 'INFO:', @_) if opt('verbose'); }
1566
sub debug { _msg(0, 'DEBUG:', @_) if opt('debug'); }
1567
sub debug2 { _msg(0, 'DEBUG:', @_) if opt('debug') && opt('verbose');}
1568
sub warning { _msg(1, 'WARNING:', @_); }
1569
sub fatal { _msg(1, 'FATAL:', @_); sendmail(); exit(1); }
1570
sub success { _msg(1, 'SUCCESS:', @_); }
1571
sub failed { _msg(1, 'FAILED:', @_); $result = 'FAILED'; }
1572
sub prettytime { return scalar(localtime(shift)); }
1574
sub prettyinterval {
1575
my $interval = shift;
1577
my $s = $interval % 60; $interval /= 60;
1578
my $m = $interval % 60; $interval /= 60;
1579
my $h = $interval % 24; $interval /= 24;
1583
$string .= "$d day" if $d;
1584
$string .= "s" if $d > 1;
1585
$string .= ", " if $string && $h;
1586
$string .= "$h hour" if $h;
1587
$string .= "s" if $h > 1;
1588
$string .= ", " if $string && $m;
1589
$string .= "$m minute" if $m;
1590
$string .= "s" if $m > 1;
1591
$string .= ", " if $string && $s;
1592
$string .= "$s second" if $s;
1593
$string .= "s" if $s > 1;
1598
if ($value =~ /^(\d+)(seconds|s)/i) {
1600
} elsif ($value =~ /^(\d+)(minutes|m)/i) {
1602
} elsif ($value =~ /^(\d+)(hours|h)/i) {
1603
$value = $1 * 60*60;
1604
} elsif ($value =~ /^(\d+)(days|d)/i) {
1605
$value = $1 * 60*60*24;
1606
} elsif ($value !~ /^\d+$/) {
1611
sub interval_expired {
1612
my ($host, $time, $interval) = @_;
1614
return 1 if !exists $cache{$host};
1615
return 1 if !exists $cache{$host}{$time} || !$cache{$host}{$time};
1616
return 1 if !exists $config{$host}{$interval} || !$config{$host}{$interval};
1618
return $now > ($cache{$host}{$time} + $config{$host}{$interval});
1623
######################################################################
1625
######################################################################
1627
my ($value, $def) = @_;
1628
my $type = $def->{'type'};
1629
my $min = $def->{'minimum'};
1630
my $required = $def->{'required'};
1632
if (!defined $value && !$required) {
1635
} elsif ($type eq T_DELAY) {
1636
$value = interval($value);
1637
$value = $min if defined($value) && defined($min) && $value < $min;
1639
} elsif ($type eq T_NUMBER) {
1640
return undef if $value !~ /^\d+$/;
1641
$value = $min if defined($min) && $value < $min;
1643
} elsif ($type eq T_BOOL) {
1644
if ($value =~ /^y(es)?$|^t(true)?$|^1$/i) {
1646
} elsif ($value =~ /^n(o)?$|^f(alse)?$|^0$/i) {
1651
} elsif ($type eq T_FQDN || $type eq T_OFQDN && $value ne '') {
1653
return undef if $value !~ /[^.]\.[^.]/;
1655
} elsif ($type eq T_FQDNP) {
1657
return undef if $value !~ /[^.]\.[^.].*(:\d+)?$/;
1659
} elsif ($type eq T_PROTO) {
1661
return undef if ! exists $services{$value};
1663
} elsif ($type eq T_USE) {
1665
return undef if ! exists $ip_strategies{$value};
1667
} elsif ($type eq T_FILE) {
1668
return undef if $value eq "";
1670
} elsif ($type eq T_IF) {
1671
return undef if $value !~ /^[a-z0-9:._-]+$/;
1673
} elsif ($type eq T_PROG) {
1674
return undef if $value eq "";
1676
} elsif ($type eq T_LOGIN) {
1677
return undef if $value eq "";
1679
# } elsif ($type eq T_PASSWD) {
1680
# return undef if $value =~ /:/;
1682
} elsif ($type eq T_IP) {
1683
return undef if $value !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
1687
######################################################################
1688
## encode_base64 - from MIME::Base64
1689
######################################################################
1690
sub encode_base64 ($;$) {
1693
$eol = "\n" unless defined $eol;
1694
pos($_[0]) = 0; # ensure start at the beginning
1695
while ($_[0] =~ /(.{1,45})/gs) {
1696
$res .= substr(pack('u', $1), 1);
1699
$res =~ tr|` -_|AA-Za-z0-9+/|; # `# help emacs
1701
# fix padding at the end
1702
my $padding = (3 - length($_[0]) % 3) % 3;
1703
$res =~ s/.{$padding}$/'=' x $padding/e if $padding;
1705
# break encoded string into lines of no more than 76 characters each
1707
$res =~ s/(.{1,76})/$1$eol/g;
1711
######################################################################
1713
######################################################################
1714
sub load_ssl_support {
1715
my $ssl_loaded = eval {require IO::Socket::SSL};
1716
unless ($ssl_loaded) {
1718
Error loading the Perl module IO::Socket::SSL needed for SSL connect.
1719
On Debian, the package libio-socket-ssl-perl must be installed.
1722
import IO::Socket::SSL;
1723
{ no warnings; $IO::Socket::SSL::DEBUG = 0; }
1725
######################################################################
1727
######################################################################
1729
my $proxy = shift || '';
1730
my $url = shift || '';
1731
my $login = shift || '';
1732
my $password = shift || '';
1733
my ($peer, $server, $port, $default_port, $use_ssl);
1734
my ($sd, $rq, $request, $reply);
1736
debug("proxy = $proxy");
1737
debug("url = $url");
1738
## canonify proxy and url
1739
$proxy =~ s%^https?://%%i;
1740
$url =~ s%^https?://%%i;
1743
$url = "/" unless $url =~ m%/%;
1744
$url =~ s%^[^/]*/%%;
1746
debug("server = $server");
1747
opt('fw') && debug("opt(fw = ",opt('fw'),")");
1748
$globals{'fw'} && debug("glo fw = $globals{'fw'}");
1749
#if ( $globals{'ssl'} and $server ne $globals{'fw'} ) {
1750
## always omit SSL for connections to local router
1751
if ( $globals{'ssl'} and (caller(1))[3] ne 'main::get_ip' ) {
1753
$default_port = 443;
1760
## determine peer and port to use.
1761
$peer = $proxy || $server;
1765
$port = $default_port unless $port =~ /^\d+$/;
1768
my $to = sprintf "%s%s", $server, $proxy ? " via proxy $peer:$port" : "";
1769
verbose("CONNECT:", "%s", $to);
1772
$request .= "http://$server" if $proxy;
1773
$request .= "/$url HTTP/1.0\n";
1774
$request .= "Host: $server\n";
1776
my $auth = encode_base64("${login}:${password}");
1777
$request .= "Authorization: Basic $auth" if $login || $password;
1778
$request .= "User-Agent: ${program}/${version}\n";
1779
$request .= "Connection: close\n";
1782
## make sure newlines are <cr><lf> for some pedantic proxy servers
1783
($rq = $request) =~ s/\n/\r\n/g;
1786
$0 = sprintf("%s - connecting to %s port %s", $program, $peer, $port);
1787
if (! opt('exec')) {
1788
debug("skipped network connection");
1789
verbose("SENDING:", $request);
1790
} elsif ($use_ssl) {
1791
$sd = IO::Socket::SSL->new(
1796
Timeout => opt('timeout'),
1798
defined $sd or warning("cannot connect to $peer:$port socket: $@ " . IO::Socket::SSL::errstr());
1800
$sd = IO::Socket::INET->new(
1805
Timeout => opt('timeout'),
1807
defined $sd or warning("cannot connect to $peer:$port socket: $@");
1811
## send the request to the http server
1812
verbose("CONNECTED: ", $use_ssl ? 'using SSL' : 'using HTTP');
1813
verbose("SENDING:", $request);
1815
$0 = sprintf("%s - sending to %s port %s", $program, $peer, $port);
1816
my $result = syswrite $sd, $rq;
1817
if ($result != length($rq)) {
1818
warning("cannot send to $peer:$port ($!).");
1822
local $SIG{'ALRM'} = sub { $timeout = 1; };
1824
$0 = sprintf("%s - reading from %s port %s", $program, $peer, $port);
1825
alarm(opt('timeout')) if opt('timeout') > 0;
1826
while (!$timeout && ($_ = <$sd>)) {
1827
$0 = sprintf("%s - read from %s port %s", $program, $peer, $port);
1828
verbose("RECEIVE:", "%s", define($_, "<undefined>"));
1829
$reply .= $_ if defined $_;
1831
if (opt('timeout') > 0) {
1836
warning("TIMEOUT: %s after %s seconds", $to, opt('timeout'));
1839
$reply = '' if !defined $reply;
1842
$0 = sprintf("%s - closed %s port %s", $program, $peer, $port);
1844
## during testing simulate reading the URL
1846
my $filename = "$server/$url";
1847
$filename =~ s|/|%2F|g;
1849
$reply = save_file("${savedir}$filename", $reply, 'unique');
1851
$reply = load_file("${savedir}$filename");
1855
$reply =~ s/\r//g if defined $reply;
1858
######################################################################
1860
######################################################################
1863
my ($ip, $arg, $reply, $url, $skip) = (undef, opt($use), '');
1864
$arg = '' unless $arg;
1870
} elsif ($use eq 'if') {
1871
$skip = opt('if-skip') || '';
1872
$reply = `ifconfig $arg 2> /dev/null`;
1875
} elsif ($use eq 'cmd') {
1877
$skip = opt('cmd-skip') || '';
1882
} elsif ($use eq 'web') {
1883
$url = opt('web') || '';
1884
$skip = opt('web-skip') || '';
1886
if (exists $builtinweb{$url}) {
1887
$skip = $builtinweb{$url}->{'skip'} unless $skip;
1888
$url = $builtinweb{$url}->{'url'};
1893
$reply = geturl(opt('proxy'), $url) || '';
1896
} elsif (($use eq 'cisco')) {
1897
# Stuff added to support Cisco router ip http daemon
1898
# User fw-login should only have level 1 access to prevent
1899
# password theft. This is pretty harmless.
1900
my $queryif = opt('if');
1901
$skip = opt('fw-skip') || '';
1903
# Convert slashes to protected value "\/"
1904
$queryif =~ s%\/%\\\/%g;
1906
# Protect special HTML characters (like '?')
1907
$queryif =~ s/([\?&= ])/sprintf("%%%02x",ord($1))/ge;
1909
$url = "http://".opt('fw')."/level/1/exec/show/ip/interface/brief/${queryif}/CR";
1910
$reply = geturl('', $url, opt('fw-login'), opt('fw-password')) || '';
1914
$url = opt('fw') || '';
1915
$skip = opt('fw-skip') || '';
1917
if (exists $builtinfw{$use}) {
1918
$skip = $builtinfw{$use}->{'skip'} unless $skip;
1919
$url = "http://${url}" . $builtinfw{$use}->{'url'} unless $url =~ /\//;
1924
$reply = geturl('', $url, opt('fw-login'), opt('fw-password')) || '';
1927
if (!defined $reply) {
1931
$skip =~ s/ /\\s/is;
1932
$reply =~ s/^.*?${skip}//is;
1934
if ($reply =~ /^.*?\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b.*/is) {
1937
if (($use ne 'ip') && (define($ip,'') eq '0.0.0.0')) {
1941
debug("get_ip: using %s, %s reports %s", $use, $arg, define($ip, "<undefined>"));
1945
######################################################################
1947
######################################################################
1948
sub group_hosts_by {
1949
my ($hosts, $attributes) = @_;
1952
foreach my $h (@$hosts) {
1953
my @keys = (@$attributes, 'wantip');
1954
map { $config{$h}{$_} = '' unless exists $config{$h}{$_} } @keys;
1955
my $sig = join(',', map { "$_=$config{$h}{$_}" } @keys);
1957
push @{$groups{$sig}}, $h;
1961
######################################################################
1963
######################################################################
1967
foreach my $s (sort keys %services) {
1968
my $subr = $services{$s}{'examples'};
1971
if (defined($subr) && ($example = &$subr())) {
1973
$examples .= $example;
1974
$examples .= "\n\n$separator";
1978
my $intro = <<EoEXAMPLE;
1979
== CONFIGURING ${program}
1981
The configuration file, ${program}.conf, can be used to define the
1982
default behaviour and operation of ${program}. The file consists of
1983
sequences of global variable definitions and host definitions.
1985
Global definitions look like:
1986
name=value [,name=value]*
1991
proxy=proxy.myisp.com
1994
specifies that ${program} should operate as a daemon, checking the
1995
eth0 interface for an IP address change every 5 minutes and use the
1996
'dyndns2' protocol by default. The daemon interval can be specified
1997
as seconds (600s), minutes (5m), hours (1h) or days (1d).
1999
Host definitions look like:
2000
[name=value [,name=value]*]* a.host.domain [,b.host.domain] [login] [password]
2003
protocol=hammernode1, \\
2004
login=my-hn-login, password=my-hn-password myhost.hn.org
2005
login=my-login, password=my-password myhost.dyndns.org,my2nd.dyndns.org
2007
specifies two host definitions.
2009
The first definition will use the hammernode1 protocol,
2010
my-hn-login and my-hn-password to update the ip-address of
2011
myhost.hn.org and my2ndhost.hn.org.
2013
The second host definition will use the current default protocol
2014
('dyndns2'), my-login and my-password to update the ip-address of
2015
myhost.dyndns.org and my2ndhost.dyndns.org.
2017
The order of this sequence is significant because the values of any
2018
global variable definitions are bound to a host definition when the
2019
host definition is encountered.
2021
See the sample-${program}.conf file for further examples.
2023
$intro .= "\n== NIC specific variables and examples:\n$examples" if $examples;
2026
######################################################################
2028
######################################################################
2029
sub nic_updateable {
2033
my $ip = $config{$host}{'wantip'};
2035
if ($config{$host}{'login'} eq '') {
2036
warning("null login name specified for host %s.", $host);
2038
} elsif ($config{$host}{'password'} eq '') {
2039
warning("null password specified for host %s.", $host);
2041
} elsif ($opt{'force'}) {
2042
info("forcing update of %s.", $host);
2045
} elsif (!exists($cache{$host})) {
2046
info("forcing updating %s because no cached entry exists.", $host);
2049
} elsif ($cache{$host}{'wtime'} && $cache{$host}{'wtime'} > $now) {
2050
warning("cannot update %s from %s to %s until after %s.",
2052
($cache{$host}{'ip'} ? $cache{$host}{'ip'} : '<nothing>'), $ip,
2053
prettytime($cache{$host}{'wtime'})
2056
} elsif ($cache{$host}{'mtime'} && interval_expired($host, 'mtime', 'max-interval')) {
2057
warning("forcing update of %s from %s to %s; %s since last update on %s.",
2059
($cache{$host}{'ip'} ? $cache{$host}{'ip'} : '<nothing>'), $ip,
2060
prettyinterval($config{$host}{'max-interval'}),
2061
prettytime($cache{$host}{'mtime'})
2065
} elsif ((!exists($cache{$host}{'ip'})) ||
2066
("$cache{$host}{'ip'}" ne "$ip")) {
2067
if (($cache{$host}{'status'} eq 'good') &&
2068
!interval_expired($host, 'mtime', 'min-interval')) {
2070
warning("skipping update of %s from %s to %s.\nlast updated %s.\nWait at least %s between update attempts.",
2072
($cache{$host}{'ip'} ? $cache{$host}{'ip'} : '<nothing>'),
2074
($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : '<never>'),
2075
prettyinterval($config{$host}{'min-interval'})
2077
if opt('verbose') || !define($cache{$host}{'warned-min-interval'}, 0);
2079
$cache{$host}{'warned-min-interval'} = $now;
2081
} elsif (($cache{$host}{'status'} ne 'good') && !interval_expired($host, 'atime', 'min-error-interval')) {
2083
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.",
2085
($cache{$host}{'ip'} ? $cache{$host}{'ip'} : '<nothing>'),
2087
($cache{$host}{'mtime'} ? prettytime($cache{$host}{'mtime'}) : '<never>'),
2088
($cache{$host}{'atime'} ? prettytime($cache{$host}{'atime'}) : '<never>'),
2089
prettyinterval($config{$host}{'min-error-interval'})
2091
if opt('verbose') || !define($cache{$host}{'warned-min-error-interval'}, 0);
2093
$cache{$host}{'warned-min-error-interval'} = $now;
2099
} elsif (defined($sub) && &$sub($host)) {
2102
} elsif (($cache{$host}{'static'} ne $config{$host}{'static'}) ||
2103
($cache{$host}{'wildcard'} ne $config{$host}{'wildcard'}) ||
2104
($cache{$host}{'mx'} ne $config{$host}{'mx'}) ||
2105
($cache{$host}{'backupmx'} ne $config{$host}{'backupmx'})) {
2106
info("updating %s because host settings have been changed.", $host);
2110
success("%s: skipped: IP address was already set to %s.", $host, $ip)
2113
$config{$host}{'status'} = define($cache{$host}{'status'},'');
2114
$config{$host}{'update'} = $update;
2116
$config{$host}{'status'} = 'noconnect';
2117
$config{$host}{'atime'} = $now;
2118
$config{$host}{'wtime'} = 0;
2119
$config{$host}{'warned-min-interval'} = 0;
2120
$config{$host}{'warned-min-error-interval'} = 0;
2122
delete $cache{$host}{'warned-min-interval'};
2123
delete $cache{$host}{'warned-min-error-interval'};
2128
######################################################################
2130
######################################################################
2132
my ($host, $line) = @_;
2135
if ($line =~ m%^s*HTTP/1.*\s+(\d+)%i) {
2138
if ($result eq '200') {
2141
} elsif ($result eq '401') {
2142
failed("updating %s: authorization failed (%s)", $host, $line);
2146
failed("updating %s: unexpected line (%s)", $host, $line);
2150
######################################################################
2151
## nic_dyndns1_examples
2152
######################################################################
2153
sub nic_dyndns1_examples {
2157
The 'dyndns1' protocol is a deprecated protocol used by the free dynamic
2158
DNS service offered by www.dyndns.org. The 'dyndns2' should be used to
2159
update the www.dyndns.org service. However, other services are also
2160
using this protocol so support is still provided by ${program}.
2162
Configuration variables applicable to the 'dyndns1' protocol are:
2164
server=fqdn.of.service ## defaults to members.dyndns.org
2165
backupmx=no|yes ## indicates that this host is the primary MX for the domain.
2166
mx=any.host.domain ## a host MX'ing for this host definition.
2167
wildcard=no|yes ## add a DNS wildcard CNAME record that points to {host}
2168
login=service-login ## login name and password registered with the service
2169
password=service-password ##
2170
fully.qualified.host ## the host registered with the service.
2172
Example ${program}.conf file entries:
2173
## single host update
2174
protocol=dyndns1, \\
2175
login=my-dyndns.org-login, \\
2176
password=my-dyndns.org-password \\
2179
## multiple host update with wildcard'ing mx, and backupmx
2180
protocol=dyndns1, \\
2181
login=my-dyndns.org-login, \\
2182
password=my-dyndns.org-password, \\
2183
mx=a.host.willing.to.mx.for.me,backupmx=yes,wildcard=yes \\
2184
myhost.dyndns.org,my2ndhost.dyndns.org
2187
######################################################################
2188
## nic_dyndns1_update
2189
######################################################################
2190
sub nic_dyndns1_update {
2191
debug("\nnic_dyndns1_update -------------------");
2192
## update each configured host
2193
foreach my $h (@_) {
2194
my $ip = delete $config{$h}{'wantip'};
2195
info("setting IP address to %s for %s", $ip, $h);
2196
verbose("UPDATE:","updating %s", $h);
2199
$url = "http://$config{$h}{'server'}/nic/";
2200
$url .= ynu($config{$h}{'static'}, 'statdns', 'dyndns', 'dyndns');
2201
$url .= "?action=edit&started=1&hostname=YES&host_id=$h";
2204
$url .= "&wildcard=ON" if ynu($config{$h}{'wildcard'}, 1, 0, 0);
2205
if ($config{$h}{'mx'}) {
2206
$url .= "&mx=$config{$h}{'mx'}";
2207
$url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
2210
my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2211
if (!defined($reply) || !$reply) {
2212
failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
2215
last if !header_ok($h, $reply);
2217
my @reply = split /\n/, $reply;
2218
my ($title, $return_code, $error_code) = ('','','');
2219
foreach my $line (@reply) {
2220
$title = $1 if $line =~ m%<TITLE>\s*(.*)\s*</TITLE>%i;
2221
$return_code = $1 if $line =~ m%^return\s+code\s*:\s*(.*)\s*$%i;
2222
$error_code = $1 if $line =~ m%^error\s+code\s*:\s*(.*)\s*$%i;
2225
if ($return_code ne 'NOERROR' || $error_code ne 'NOERROR' || !$title) {
2226
$config{$h}{'status'} = 'failed';
2227
$title = "incomplete response from $config{$h}{server}" unless $title;
2228
warning("SENT: %s", $url) unless opt('verbose');
2229
warning("REPLIED: %s", $reply);
2230
failed("updating %s: %s", $h, $title);
2233
$config{$h}{'ip'} = $ip;
2234
$config{$h}{'mtime'} = $now;
2235
$config{$h}{'status'} = 'good';
2236
success("updating %s: %s: IP address set to %s (%s)", $h, $return_code, $ip, $title);
2240
######################################################################
2241
## nic_dyndns2_updateable
2242
######################################################################
2243
sub nic_dyndns2_updateable {
2247
if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
2248
info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
2251
} elsif ($config{$host}{'mx'} && (ynu($config{$host}{'backupmx'},1,2,3) ne ynu($config{$host}{'backupmx'},1,2,3))) {
2252
info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'},"YES","NO","NO"));
2255
} elsif ($config{$host}{'static'} ne $cache{$host}{'static'}) {
2257
info("forcing updating %s because 'static' has changed to %s.", $host, ynu($config{$host}{'static'},"YES","NO","NO"));
2263
######################################################################
2264
## nic_dyndns2_examples
2265
######################################################################
2266
sub nic_dyndns2_examples {
2270
The 'dyndns2' protocol is a newer low-bandwidth protocol used by a
2271
free dynamic DNS service offered by www.dyndns.org. It supports
2272
features of the older 'dyndns1' in addition to others. [These will be
2273
supported in a future version of ${program}.]
2275
Configuration variables applicable to the 'dyndns2' protocol are:
2277
server=fqdn.of.service ## defaults to members.dyndns.org
2278
backupmx=no|yes ## indicates that this host is the primary MX for the domain.
2279
static=no|yes ## indicates that this host has a static IP address.
2280
custom=no|yes ## indicates that this host is a 'custom' top-level domain name.
2281
mx=any.host.domain ## a host MX'ing for this host definition.
2282
wildcard=no|yes ## add a DNS wildcard CNAME record that points to {host}
2283
login=service-login ## login name and password registered with the service
2284
password=service-password ##
2285
fully.qualified.host ## the host registered with the service.
2287
Example ${program}.conf file entries:
2288
## single host update
2289
protocol=dyndns2, \\
2290
login=my-dyndns.org-login, \\
2291
password=my-dyndns.org-password \\
2294
## multiple host update with wildcard'ing mx, and backupmx
2295
protocol=dyndns2, \\
2296
login=my-dyndns.org-login, \\
2297
password=my-dyndns.org-password, \\
2298
mx=a.host.willing.to.mx.for.me,backupmx=yes,wildcard=yes \\
2299
myhost.dyndns.org,my2ndhost.dyndns.org
2301
## multiple host update to the custom DNS service
2302
protocol=dyndns2, \\
2303
login=my-dyndns.org-login, \\
2304
password=my-dyndns.org-password \\
2305
my-toplevel-domain.com,my-other-domain.com
2308
######################################################################
2309
## nic_dyndns2_update
2310
######################################################################
2311
sub nic_dyndns2_update {
2312
debug("\nnic_dyndns2_update -------------------");
2314
## group hosts with identical attributes together
2315
my %groups = group_hosts_by([ @_ ], [ qw(login password server static custom wildcard mx backupmx) ]);
2318
'badauth' => 'Bad authorization (username or password)',
2319
'badsys' => 'The system parameter given was not valid',
2321
'notfqdn' => 'A Fully-Qualified Domain Name was not provided',
2322
'nohost' => 'The hostname specified does not exist in the database',
2323
'!yours' => 'The hostname specified exists, but not under the username currently being used',
2324
'!donator' => 'The offline setting was set, when the user is not a donator',
2325
'!active' => 'The hostname specified is in a Custom DNS domain which has not yet been activated.',
2326
'abuse', => 'The hostname specified is blocked for abuse; you should receive an email notification ' .
2327
'which provides an unblock request link. More info can be found on ' .
2328
'https://www.dyndns.com/support/abuse.html',
2330
'numhost' => 'System error: Too many or too few hosts found. Contact support@dyndns.org',
2331
'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org',
2333
'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive',
2336
## update each set of hosts that had similar configurations
2337
foreach my $sig (keys %groups) {
2338
my @hosts = @{$groups{$sig}};
2339
my $hosts = join(',', @hosts);
2341
my $ip = $config{$h}{'wantip'};
2342
delete $config{$_}{'wantip'} foreach @hosts;
2344
info("setting IP address to %s for %s", $ip, $hosts);
2345
verbose("UPDATE:","updating %s", $hosts);
2347
## Select the DynDNS system to update
2348
my $url = "http://$config{$h}{'server'}/nic/update?system=";
2349
if ($config{$h}{'custom'}) {
2350
warning("updating %s: 'custom' and 'static' may not be used together. ('static' ignored)", $hosts)
2351
if $config{$h}{'static'};
2352
# warning("updating %s: 'custom' and 'offline' may not be used together. ('offline' ignored)", $hosts)
2353
# if $config{$h}{'offline'};
2356
} elsif ($config{$h}{'static'}) {
2357
# warning("updating %s: 'static' and 'offline' may not be used together. ('offline' ignored)", $hosts)
2358
# if $config{$h}{'offline'};
2365
$url .= "&hostname=$hosts";
2369
## some args are not valid for a custom domain.
2370
$url .= "&wildcard=ON" if ynu($config{$h}{'wildcard'}, 1, 0, 0);
2371
if ($config{$h}{'mx'}) {
2372
$url .= "&mx=$config{$h}{'mx'}";
2373
$url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
2376
my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2377
if (!defined($reply) || !$reply) {
2378
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
2381
last if !header_ok($hosts, $reply);
2383
my @reply = split /\n/, $reply;
2384
my $state = 'header';
2385
foreach my $line (@reply) {
2386
if ($state eq 'header') {
2389
} elsif ($state eq 'body') {
2390
$state = 'results' if $line eq '';
2392
} elsif ($state =~ /^results/) {
2393
$state = 'results2';
2395
my ($status, $ip) = split / /, lc $line;
2396
my $h = shift @hosts;
2398
$config{$h}{'status'} = $status;
2399
if ($status eq 'good') {
2400
$config{$h}{'ip'} = $ip;
2401
$config{$h}{'mtime'} = $now;
2402
success("updating %s: %s: IP address set to %s", $h, $status, $ip);
2404
} elsif (exists $errors{$status}) {
2405
if ($status eq 'nochg') {
2406
warning("updating %s: %s: %s", $h, $status, $errors{$status});
2407
$config{$h}{'ip'} = $ip;
2408
$config{$h}{'mtime'} = $now;
2409
$config{$h}{'status'} = 'good';
2412
failed("updating %s: %s: %s", $h, $status, $errors{$status});
2415
} elsif ($status =~ /w(\d+)(.)/) {
2416
my ($wait, $units) = ($1, lc $2);
2417
my ($sec, $scale) = ($wait, 1);
2419
($scale, $units) = (1, 'seconds') if $units eq 's';
2420
($scale, $units) = (60, 'minutes') if $units eq 'm';
2421
($scale, $units) = (60*60, 'hours') if $units eq 'h';
2423
$sec = $wait * $scale;
2424
$config{$h}{'wtime'} = $now + $sec;
2425
warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
2428
failed("updating %s: %s: unexpected status (%s)", $h, $line);
2432
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
2433
if $state ne 'results2';
2438
######################################################################
2440
## Note: uses same features as nic_dyndns2_update, less return codes
2441
######################################################################
2442
sub nic_noip_update {
2443
debug("\nnic_noip_update -------------------");
2445
## group hosts with identical attributes together
2446
my %groups = group_hosts_by([ @_ ], [ qw(login password server static custom wildcard mx backupmx) ]);
2449
'badauth' => 'Invalid username or password',
2450
'badagent' => 'Invalid user agent',
2451
'nohost' => 'The hostname specified does not exist in the database',
2452
'!donator' => 'The offline setting was set, when the user is not a donator',
2453
'abuse', => 'The hostname specified is blocked for abuse; open a trouble ticket at http://www.no-ip.com',
2454
'numhost' => 'System error: Too many or too few hosts found. open a trouble ticket at http://www.no-ip.com',
2455
'dnserr' => 'System error: DNS error encountered. Contact support@dyndns.org',
2456
'nochg' => 'No update required; unnecessary attempts to change to the current address are considered abusive',
2459
## update each set of hosts that had similar configurations
2460
foreach my $sig (keys %groups) {
2461
my @hosts = @{$groups{$sig}};
2462
my $hosts = join(',', @hosts);
2464
my $ip = $config{$h}{'wantip'};
2465
delete $config{$_}{'wantip'} foreach @hosts;
2467
info("setting IP address to %s for %s", $ip, $hosts);
2468
verbose("UPDATE:","updating %s", $hosts);
2470
my $url = "http://$config{$h}{'server'}/nic/update?system=";
2472
$url .= "&hostname=$hosts";
2477
print "here..." . $config{$h}{'login'} . " --> " . $config{$h}{'password'} . "\n";
2480
my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2481
if (!defined($reply) || !$reply) {
2482
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
2485
last if !header_ok($hosts, $reply);
2487
my @reply = split /\n/, $reply;
2488
my $state = 'header';
2489
foreach my $line (@reply) {
2490
if ($state eq 'header') {
2493
} elsif ($state eq 'body') {
2494
$state = 'results' if $line eq '';
2496
} elsif ($state =~ /^results/) {
2497
$state = 'results2';
2499
my ($status, $ip) = split / /, lc $line;
2500
my $h = shift @hosts;
2502
$config{$h}{'status'} = $status;
2503
if ($status eq 'good') {
2504
$config{$h}{'ip'} = $ip;
2505
$config{$h}{'mtime'} = $now;
2506
success("updating %s: %s: IP address set to %s", $h, $status, $ip);
2508
} elsif (exists $errors{$status}) {
2509
if ($status eq 'nochg') {
2510
warning("updating %s: %s: %s", $h, $status, $errors{$status});
2511
$config{$h}{'ip'} = $ip;
2512
$config{$h}{'mtime'} = $now;
2513
$config{$h}{'status'} = 'good';
2516
failed("updating %s: %s: %s", $h, $status, $errors{$status});
2519
} elsif ($status =~ /w(\d+)(.)/) {
2520
my ($wait, $units) = ($1, lc $2);
2521
my ($sec, $scale) = ($wait, 1);
2523
($scale, $units) = (1, 'seconds') if $units eq 's';
2524
($scale, $units) = (60, 'minutes') if $units eq 'm';
2525
($scale, $units) = (60*60, 'hours') if $units eq 'h';
2527
$sec = $wait * $scale;
2528
$config{$h}{'wtime'} = $now + $sec;
2529
warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
2532
failed("updating %s: %s: unexpected status (%s)", $h, $line);
2536
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
2537
if $state ne 'results2';
2540
######################################################################
2541
## nic_noip_examples
2542
######################################################################
2543
sub nic_noip_examples {
2547
The 'No-IP Compatible' protocol is used to make dynamic dns updates
2548
over an http request. Details of the protocol are outlined at:
2549
http://www.no-ip.com/integrate/
2551
Configuration variables applicable to the 'noip' protocol are:
2553
server=fqdn.of.service ## defaults to dynupdate.no-ip.com
2554
login=service-login ## login name and password registered with the service
2555
password=service-password ##
2556
fully.qualified.host ## the host registered with the service.
2558
Example ${program}.conf file entries:
2559
## single host update
2561
login=userlogin\@domain.com, \\
2562
password=noip-password \\
2569
######################################################################
2570
## nic_concont_examples
2571
######################################################################
2572
sub nic_concont_examples {
2576
The 'concont' protocol is the protocol used by the content management
2577
system ConCont's dydns module. This is currently used by the free
2578
dynamic DNS service offered by Tyrmida at www.dydns.za.net
2580
Configuration variables applicable to the 'concont' protocol are:
2582
server=www.fqdn.of.service ## for example www.dydns.za.net (for most add a www)
2583
login=service-login ## login registered with the service
2584
password=service-password ## password registered with the service
2585
mx=mail.server.fqdn ## fqdn of the server handling domain\'s mail (leave out for none)
2586
wildcard=yes|no ## set yes for wild (*.host.domain) support
2587
fully.qualified.host ## the host registered with the service.
2589
Example ${program}.conf file entries:
2590
## single host update
2591
protocol=concont, \\
2592
login=dydns.za.net, \\
2593
password=my-dydns.za.net-password, \\
2594
mx=mailserver.fqdn, \\
2600
######################################################################
2601
## nic_concont_update
2602
######################################################################
2603
sub nic_concont_update {
2604
debug("\nnic_concont_update -------------------");
2606
## update each configured host
2607
foreach my $h (@_) {
2608
my $ip = delete $config{$h}{'wantip'};
2609
info("setting IP address to %s for %s", $ip, $h);
2610
verbose("UPDATE:","updating %s", $h);
2612
# Set the URL that we're going to to update
2614
$url = "http://$config{$h}{'server'}/modules/dydns/update.php";
2615
$url .= "?username=";
2616
$url .= $config{$h}{'login'};
2617
$url .= "&password=";
2618
$url .= $config{$h}{'password'};
2619
$url .= "&wildcard=";
2620
$url .= $config{$h}{'wildcard'};
2622
$url .= $config{$h}{'mx'};
2629
my $reply = geturl(opt('proxy'), $url);
2631
# No response, declare as failed
2632
if (!defined($reply) || !$reply) {
2633
failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
2636
last if !header_ok($h, $reply);
2638
# Response found, just declare as success (this is ugly, we need more error checking)
2639
if ($reply =~ /SUCCESS/)
2641
$config{$h}{'ip'} = $ip;
2642
$config{$h}{'mtime'} = $now;
2643
$config{$h}{'status'} = 'good';
2644
success("updating %s: good: IP address set to %s", $h, $ip);
2648
my @reply = split /\n/, $reply;
2649
my $returned = pop(@reply);
2650
$config{$h}{'status'} = 'failed';
2651
failed("updating %s: Server said: '$returned'", $h);
2655
######################################################################
2656
## nic_dslreports1_examples
2657
######################################################################
2658
sub nic_dslreports1_examples {
2662
The 'dslreports1' protocol is used by a free DSL monitoring service
2663
offered by www.dslreports.com.
2665
Configuration variables applicable to the 'dslreports1' protocol are:
2666
protocol=dslreports1 ##
2667
server=fqdn.of.service ## defaults to www.dslreports.com
2668
login=service-login ## login name and password registered with the service
2669
password=service-password ##
2670
unique-number ## the host registered with the service.
2672
Example ${program}.conf file entries:
2673
## single host update
2674
protocol=dslreports1, \\
2675
server=www.dslreports.com, \\
2676
login=my-dslreports-login, \\
2677
password=my-dslreports-password \\
2680
Note: DSL Reports uses a unique number as the host name. This number
2681
can be found on the Monitor Control web page.
2684
######################################################################
2685
## nic_dslreports1_update
2686
######################################################################
2687
sub nic_dslreports1_update {
2688
debug("\nnic_dslreports1_update -------------------");
2689
## update each configured host
2690
foreach my $h (@_) {
2691
my $ip = delete $config{$h}{'wantip'};
2692
info("setting IP address to %s for %s", $ip, $h);
2693
verbose("UPDATE:","updating %s", $h);
2696
$url = "http://$config{$h}{'server'}/nic/";
2697
$url .= ynu($config{$h}{'static'}, 'statdns', 'dyndns', 'dyndns');
2698
$url .= "?action=edit&started=1&hostname=YES&host_id=$h";
2702
my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2703
if (!defined($reply) || !$reply) {
2704
failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
2708
my @reply = split /\n/, $reply;
2709
my $return_code = '';
2710
foreach my $line (@reply) {
2711
$return_code = $1 if $line =~ m%^return\s+code\s*:\s*(.*)\s*$%i;
2714
if ($return_code !~ /NOERROR/) {
2715
$config{$h}{'status'} = 'failed';
2716
warning("SENT: %s", $url) unless opt('verbose');
2717
warning("REPLIED: %s", $reply);
2718
failed("updating %s", $h);
2721
$config{$h}{'ip'} = $ip;
2722
$config{$h}{'mtime'} = $now;
2723
$config{$h}{'status'} = 'good';
2724
success("updating %s: %s: IP address set to %s", $h, $return_code, $ip);
2728
######################################################################
2729
## nic_hammernode1_examples
2730
######################################################################
2731
sub nic_hammernode1_examples {
2735
The 'hammernode1' protocol is the protocol used by the free dynamic
2736
DNS service offered by Hammernode at www.hn.org
2738
Configuration variables applicable to the 'hammernode1' protocol are:
2739
protocol=hammernode1 ##
2740
server=fqdn.of.service ## defaults to members.dyndns.org
2741
login=service-login ## login name and password registered with the service
2742
password=service-password ##
2743
fully.qualified.host ## the host registered with the service.
2745
Example ${program}.conf file entries:
2746
## single host update
2747
protocol=hammernode1, \\
2748
login=my-hn.org-login, \\
2749
password=my-hn.org-password \\
2752
## multiple host update
2753
protocol=hammernode1, \\
2754
login=my-hn.org-login, \\
2755
password=my-hn.org-password, \\
2756
myhost.hn.org,my2ndhost.hn.org
2759
######################################################################
2760
## nic_hammernode1_update
2761
######################################################################
2762
sub nic_hammernode1_update {
2763
debug("\nnic_hammernode1_update -------------------");
2765
## update each configured host
2766
foreach my $h (@_) {
2767
my $ip = delete $config{$h}{'wantip'};
2768
info("setting IP address to %s for %s", $ip, $h);
2769
verbose("UPDATE:","updating %s", $h);
2772
$url = "http://$config{$h}{'server'}/vanity/update";
2777
my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2778
if (!defined($reply) || !$reply) {
2779
failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
2782
last if !header_ok($h, $reply);
2784
my @reply = split /\n/, $reply;
2785
if (grep /<!--\s+DDNS_Response_Code=101\s+-->/i, @reply) {
2786
$config{$h}{'ip'} = $ip;
2787
$config{$h}{'mtime'} = $now;
2788
$config{$h}{'status'} = 'good';
2789
success("updating %s: good: IP address set to %s", $h, $ip);
2791
$config{$h}{'status'} = 'failed';
2792
warning("SENT: %s", $url) unless opt('verbose');
2793
warning("REPLIED: %s", $reply);
2794
failed("updating %s: Invalid reply.", $h);
2798
######################################################################
2799
## nic_zoneedit1_examples
2800
######################################################################
2801
sub nic_zoneedit1_examples {
2805
The 'zoneedit1' protocol is used by a DNS service offered by
2808
Configuration variables applicable to the 'zoneedit1' protocol are:
2809
protocol=zoneedit1 ##
2810
server=fqdn.of.service ## defaults to www.zoneedit.com
2811
login=service-login ## login name and password registered with the service
2812
password=service-password ##
2813
your.domain.name ## the host registered with the service.
2815
Example ${program}.conf file entries:
2816
## single host update
2817
protocol=zoneedit1, \\
2818
server=dynamic.zoneedit.com, \\
2819
login=my-zoneedit-login, \\
2820
password=my-zoneedit-password \\
2825
######################################################################
2826
## nic_zoneedit1_updateable
2827
######################################################################
2828
sub nic_zoneedit1_updateable {
2832
######################################################################
2833
## nic_zoneedit1_update
2834
# <SUCCESS CODE="200" TEXT="Update succeeded." ZONE="trialdomain.com" IP="127.0.0.12">
2835
# <SUCCESS CODE="201" TEXT="No records need updating." ZONE="bannedware.com">
2836
# <ERROR CODE="701" TEXT="Zone is not set up in this account." ZONE="bad.com">
2837
######################################################################
2838
sub nic_zoneedit1_update {
2839
debug("\nnic_zoneedit1_update -------------------");
2841
## group hosts with identical attributes together
2842
my %groups = group_hosts_by([ @_ ], [ qw(login password server) ]);
2844
## update each set of hosts that had similar configurations
2845
foreach my $sig (keys %groups) {
2846
my @hosts = @{$groups{$sig}};
2847
my $hosts = join(',', @hosts);
2849
my $ip = $config{$h}{'wantip'};
2850
delete $config{$_}{'wantip'} foreach @hosts;
2852
info("setting IP address to %s for %s", $ip, $hosts);
2853
verbose("UPDATE:","updating %s", $hosts);
2856
$url .= "http://$config{$h}{'server'}/auth/dynamic.html";
2857
$url .= "?host=$hosts";
2858
$url .= "&dnsto=$ip" if $ip;
2860
my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
2861
if (!defined($reply) || !$reply) {
2862
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
2865
last if !header_ok($hosts, $reply);
2867
my @reply = split /\n/, $reply;
2868
foreach my $line (@reply) {
2869
if ($line =~ /^[^<]*<(SUCCESS|ERROR)\s+([^>]+)>(.*)/) {
2870
my ($status, $assignments, $rest) = ($1, $2, $3);
2871
my ($left, %var) = parse_assignments($assignments);
2874
my ($status_code, $status_text, $status_ip) = ('999', '', $ip);
2875
$status_code = $var{'CODE'} if exists $var{'CODE'};
2876
$status_text = $var{'TEXT'} if exists $var{'TEXT'};
2877
$status_ip = $var{'IP'} if exists $var{'IP'};
2879
if ($status eq 'SUCCESS' || ($status eq 'ERROR' && $var{'CODE'} eq '707')) {
2880
$config{$h}{'ip'} = $status_ip;
2881
$config{$h}{'mtime'} = $now;
2882
$config{$h}{'status'} = 'good';
2884
success("updating %s: IP address set to %s (%s: %s)", $h, $ip, $status_code, $status_text);
2887
$config{$h}{'status'} = 'failed';
2888
failed("updating %s: %s: %s", $h, $status_code, $status_text);
2892
$hosts = join(',', @hosts);
2898
failed("updating %s: no response from %s", $hosts, $config{$h}{'server'})
2902
######################################################################
2903
## nic_easydns_updateable
2904
######################################################################
2905
sub nic_easydns_updateable {
2909
if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
2910
info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
2913
} elsif ($config{$host}{'mx'} && (ynu($config{$host}{'backupmx'},1,2,3) ne ynu($config{$host}{'backupmx'},1,2,3))) {
2914
info("forcing updating %s because 'backupmx' has changed to %s.", $host, ynu($config{$host}{'backupmx'},"YES","NO","NO"));
2917
} elsif ($config{$host}{'static'} ne $cache{$host}{'static'}) {
2919
info("forcing updating %s because 'static' has changed to %s.", $host, ynu($config{$host}{'static'},"YES","NO","NO"));
2925
######################################################################
2926
## nic_easydns_examples
2927
######################################################################
2928
sub nic_easydns_examples {
2932
The 'easydns' protocol is used by the for fee DNS service offered
2935
Configuration variables applicable to the 'easydns' protocol are:
2937
server=fqdn.of.service ## defaults to members.easydns.com
2938
backupmx=no|yes ## indicates that EasyDNS should be the secondary MX
2939
## for this domain or host.
2940
mx=any.host.domain ## a host MX'ing for this host or domain.
2941
wildcard=no|yes ## add a DNS wildcard CNAME record that points to {host}
2942
login=service-login ## login name and password registered with the service
2943
password=service-password ##
2944
fully.qualified.host ## the host registered with the service.
2946
Example ${program}.conf file entries:
2947
## single host update
2948
protocol=easydns, \\
2949
login=my-easydns.com-login, \\
2950
password=my-easydns.com-password \\
2953
## multiple host update with wildcard'ing mx, and backupmx
2954
protocol=easydns, \\
2955
login=my-easydns.com-login, \\
2956
password=my-easydns.com-password, \\
2957
mx=a.host.willing.to.mx.for.me, \\
2960
my-toplevel-domain.com,my-other-domain.com
2962
## multiple host update to the custom DNS service
2963
protocol=easydns, \\
2964
login=my-easydns.com-login, \\
2965
password=my-easydns.com-password \\
2966
my-toplevel-domain.com,my-other-domain.com
2969
######################################################################
2970
## nic_easydns_update
2971
######################################################################
2972
sub nic_easydns_update {
2973
debug("\nnic_easydns_update -------------------");
2975
## group hosts with identical attributes together
2976
## my %groups = group_hosts_by([ @_ ], [ qw(login password server wildcard mx backupmx) ]);
2978
## each host is in a group by itself
2979
my %groups = map { $_ => [ $_ ] } @_;
2982
'NOACCESS' => 'Authentication failed. This happens if the username/password OR host or domain are wrong.',
2983
'NOSERVICE'=> 'Dynamic DNS is not turned on for this domain.',
2984
'ILLEGAL' => 'Client sent data that is not allowed in a dynamic DNS update.',
2985
'TOOSOON' => 'Update frequency is too short.',
2988
## update each set of hosts that had similar configurations
2989
foreach my $sig (keys %groups) {
2990
my @hosts = @{$groups{$sig}};
2991
my $hosts = join(',', @hosts);
2993
my $ip = $config{$h}{'wantip'};
2994
delete $config{$_}{'wantip'} foreach @hosts;
2996
info("setting IP address to %s for %s", $ip, $hosts);
2997
verbose("UPDATE:","updating %s", $hosts);
2999
#'http://members.easydns.com/dyn/dyndns.php?hostname=test.burry.ca&myip=10.20.30.40&wildcard=ON'
3002
$url = "http://$config{$h}{'server'}/dyn/dyndns.php?";
3003
$url .= "hostname=$hosts";
3006
$url .= "&wildcard=" . ynu($config{$h}{'wildcard'}, 'ON', 'OFF', 'OFF') if defined $config{$h}{'wildcard'};
3008
if ($config{$h}{'mx'}) {
3009
$url .= "&mx=$config{$h}{'mx'}";
3010
$url .= "&backmx=" . ynu($config{$h}{'backupmx'}, 'YES', 'NO');
3013
my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
3014
if (!defined($reply) || !$reply) {
3015
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
3018
last if !header_ok($hosts, $reply);
3020
my @reply = split /\n/, $reply;
3021
my $state = 'header';
3022
foreach my $line (@reply) {
3023
if ($state eq 'header') {
3026
} elsif ($state eq 'body') {
3027
$state = 'results' if $line eq '';
3029
} elsif ($state =~ /^results/) {
3030
$state = 'results2';
3032
my ($status) = $line =~ /^(\S*)\b.*/;
3033
my $h = shift @hosts;
3035
$config{$h}{'status'} = $status;
3036
if ($status eq 'NOERROR') {
3037
$config{$h}{'ip'} = $ip;
3038
$config{$h}{'mtime'} = $now;
3039
success("updating %s: %s: IP address set to %s", $h, $status, $ip);
3041
} elsif ($status =~ /TOOSOON/) {
3042
## make sure we wait at least a little
3043
my ($wait, $units) = (5, 'm');
3044
my ($sec, $scale) = ($wait, 1);
3046
($scale, $units) = (1, 'seconds') if $units eq 's';
3047
($scale, $units) = (60, 'minutes') if $units eq 'm';
3048
($scale, $units) = (60*60, 'hours') if $units eq 'h';
3049
$config{$h}{'wtime'} = $now + $sec;
3050
warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
3052
} elsif (exists $errors{$status}) {
3053
failed("updating %s: %s: %s", $h, $line, $errors{$status});
3056
failed("updating %s: %s: unexpected status (%s)", $h, $line);
3061
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
3062
if $state ne 'results2';
3065
######################################################################
3067
######################################################################
3068
## nic_dnspark_updateable
3069
######################################################################
3070
sub nic_dnspark_updateable {
3074
if ($config{$host}{'mx'} ne $cache{$host}{'mx'}) {
3075
info("forcing updating %s because 'mx' has changed to %s.", $host, $config{$host}{'mx'});
3078
} elsif ($config{$host}{'mx'} && ($config{$host}{'mxpri'} ne $cache{$host}{'mxpri'})) {
3079
info("forcing updating %s because 'mxpri' has changed to %s.", $host, $config{$host}{'mxpri'});
3084
######################################################################
3085
## nic_dnspark_examples
3086
######################################################################
3087
sub nic_dnspark_examples {
3091
The 'dnspark' protocol is used by DNS service offered by www.dnspark.com.
3093
Configuration variables applicable to the 'dnspark' protocol are:
3095
server=fqdn.of.service ## defaults to www.dnspark.com
3096
backupmx=no|yes ## indicates that DNSPark should be the secondary MX
3097
## for this domain or host.
3098
mx=any.host.domain ## a host MX'ing for this host or domain.
3099
mxpri=priority ## MX priority.
3100
login=service-login ## login name and password registered with the service
3101
password=service-password ##
3102
fully.qualified.host ## the host registered with the service.
3104
Example ${program}.conf file entries:
3105
## single host update
3106
protocol=dnspark, \\
3107
login=my-dnspark.com-login, \\
3108
password=my-dnspark.com-password \\
3111
## multiple host update with wildcard'ing mx, and backupmx
3112
protocol=dnspark, \\
3113
login=my-dnspark.com-login, \\
3114
password=my-dnspark.com-password, \\
3115
mx=a.host.willing.to.mx.for.me, \\
3117
my-toplevel-domain.com,my-other-domain.com
3119
## multiple host update to the custom DNS service
3120
protocol=dnspark, \\
3121
login=my-dnspark.com-login, \\
3122
password=my-dnspark.com-password \\
3123
my-toplevel-domain.com,my-other-domain.com
3126
######################################################################
3127
## nic_dnspark_update
3128
######################################################################
3129
sub nic_dnspark_update {
3130
debug("\nnic_dnspark_update -------------------");
3132
## group hosts with identical attributes together
3133
## my %groups = group_hosts_by([ @_ ], [ qw(login password server wildcard mx backupmx) ]);
3135
## each host is in a group by itself
3136
my %groups = map { $_ => [ $_ ] } @_;
3139
'nochange' => 'No changes made to the hostname(s). Continual updates with no changes lead to blocked clients.',
3140
'nofqdn' => 'No valid FQDN (fully qualified domain name) was specified',
3141
'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.',
3142
'abuse' => 'The hostname specified has been blocked for abuse.',
3143
'unauth' => 'The username specified is not authorized to update this hostname and domain.',
3144
'blocked' => 'The dynamic update client (specified by the user-agent) has been blocked from the system.',
3145
'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.',
3148
## update each set of hosts that had similar configurations
3149
foreach my $sig (keys %groups) {
3150
my @hosts = @{$groups{$sig}};
3151
my $hosts = join(',', @hosts);
3153
my $ip = $config{$h}{'wantip'};
3154
delete $config{$_}{'wantip'} foreach @hosts;
3156
info("setting IP address to %s for %s", $ip, $hosts);
3157
verbose("UPDATE:","updating %s", $hosts);
3159
#'http://www.dnspark.com:80/visitors/update.html?myip=10.20.30.40&hostname=test.burry.ca'
3162
$url = "http://$config{$h}{'server'}/visitors/update.html";
3163
$url .= "?hostname=$hosts";
3167
if ($config{$h}{'mx'}) {
3168
$url .= "&mx=$config{$h}{'mx'}";
3169
$url .= "&mxpri=" . $config{$h}{'mxpri'};
3172
my $reply = geturl(opt('proxy'), $url, $config{$h}{'login'}, $config{$h}{'password'});
3173
if (!defined($reply) || !$reply) {
3174
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'});
3177
last if !header_ok($hosts, $reply);
3179
my @reply = split /\n/, $reply;
3180
my $state = 'header';
3181
foreach my $line (@reply) {
3182
if ($state eq 'header') {
3185
} elsif ($state eq 'body') {
3186
$state = 'results' if $line eq '';
3188
} elsif ($state =~ /^results/) {
3189
$state = 'results2';
3191
my ($status) = $line =~ /^(\S*)\b.*/;
3194
$config{$h}{'status'} = $status;
3195
if ($status eq 'ok') {
3196
$config{$h}{'ip'} = $ip;
3197
$config{$h}{'mtime'} = $now;
3198
success("updating %s: %s: IP address set to %s", $h, $status, $ip);
3200
} elsif ($status =~ /TOOSOON/) {
3201
## make sure we wait at least a little
3202
my ($wait, $units) = (5, 'm');
3203
my ($sec, $scale) = ($wait, 1);
3205
($scale, $units) = (1, 'seconds') if $units eq 's';
3206
($scale, $units) = (60, 'minutes') if $units eq 'm';
3207
($scale, $units) = (60*60, 'hours') if $units eq 'h';
3208
$config{$h}{'wtime'} = $now + $sec;
3209
warning("updating %s: %s: wait $wait $units before further updates", $h, $status, $ip);
3211
} elsif (exists $errors{$status}) {
3212
failed("updating %s: %s: %s", $h, $line, $errors{$status});
3215
failed("updating %s: %s: unexpected status (%s)", $h, $line);
3220
failed("updating %s: Could not connect to %s.", $hosts, $config{$h}{'server'})
3221
if $state ne 'results2';
3225
######################################################################
3227
######################################################################
3228
## nic_namecheap_examples
3229
######################################################################
3230
sub nic_namecheap_examples {
3235
The 'namecheap' protocol is used by DNS service offered by www.namecheap.com.
3237
Configuration variables applicable to the 'namecheap' protocol are:
3238
protocol=namecheap ##
3239
server=fqdn.of.service ## defaults to dynamicdns.park-your-domain.com
3240
login=service-login ## login name and password registered with the service
3241
password=service-password ##
3242
fully.qualified.host ## the host registered with the service.
3244
Example ${program}.conf file entries:
3245
## single host update
3246
protocol=namecheap, \\
3247
login=my-namecheap.com-login, \\
3248
password=my-namecheap.com-password \\
3249
myhost.namecheap.com
3253
######################################################################
3254
## nic_namecheap_update
3256
## written by Dan Boardman
3258
## based on http://www.namecheap.com/resources/help/index.asp?t=dynamicdns
3259
## needs this url to update:
3260
## http://dynamicdns.park-your-domain.com/update?host=host_name&
3261
## domain=domain.com&password=domain_password[&ip=your_ip]
3263
######################################################################
3264
sub nic_namecheap_update {
3267
debug("\nnic_namecheap1_update -------------------");
3269
## update each configured host
3270
foreach my $h (@_) {
3271
my $ip = delete $config{$h}{'wantip'};
3272
info("setting IP address to %s for %s", $ip, $h);
3273
verbose("UPDATE:","updating %s", $h);
3276
$url = "http://$config{$h}{'server'}/update";
3278
$url .= "&domain=$config{$h}{'login'}";
3279
$url .= "&password=$config{$h}{'password'}";
3283
my $reply = geturl(opt('proxy'), $url);
3284
if (!defined($reply) || !$reply) {
3285
failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
3288
last if !header_ok($h, $reply);
3290
my @reply = split /\n/, $reply;
3291
if (grep /<ErrCount>0/i, @reply) {
3292
$config{$h}{'ip'} = $ip;
3293
$config{$h}{'mtime'} = $now;
3294
$config{$h}{'status'} = 'good';
3295
success("updating %s: good: IP address set to %s", $h, $ip);
3297
$config{$h}{'status'} = 'failed';
3298
warning("SENT: %s", $url) unless opt('verbose');
3299
warning("REPLIED: %s", $reply);
3300
failed("updating %s: Invalid reply.", $h);
3305
######################################################################
3308
######################################################################
3310
######################################################################
3311
## nic_sitelutions_examples
3312
######################################################################
3313
sub nic_sitelutions_examples {
3318
The 'sitelutions' protocol is used by DNS services offered by www.sitelutions.com.
3320
Configuration variables applicable to the 'sitelutions' protocol are:
3321
protocol=sitelutions ##
3322
server=fqdn.of.service ## defaults to sitelutions.com
3323
login=service-login ## login name and password registered with the service
3324
password=service-password ##
3325
A_record_id ## Id of the A record for the host registered with the service.
3327
Example ${program}.conf file entries:
3328
## single host update
3329
protocol=sitelutions, \\
3330
login=my-sitelutions.com-login, \\
3331
password=my-sitelutions.com-password \\
3332
my-sitelutions.com-id_of_A_record
3336
######################################################################
3337
## nic_sitelutions_update
3339
## written by Mike W. Smith
3341
## based on http://www.sitelutions.com/help/dynamic_dns_clients#updatespec
3342
## needs this url to update:
3343
## https://www.sitelutions.com/dnsup?id=990331&user=myemail@mydomain.com&pass=SecretPass&ip=192.168.10.4
3344
## domain=domain.com&password=domain_password&ip=your_ip
3346
######################################################################
3347
sub nic_sitelutions_update {
3350
debug("\nnic_sitelutions_update -------------------");
3352
## update each configured host
3353
foreach my $h (@_) {
3354
my $ip = delete $config{$h}{'wantip'};
3355
info("setting IP address to %s for %s", $ip, $h);
3356
verbose("UPDATE:","updating %s", $h);
3359
$url = "http://$config{$h}{'server'}/dnsup";
3361
$url .= "&user=$config{$h}{'login'}";
3362
$url .= "&pass=$config{$h}{'password'}";
3366
my $reply = geturl(opt('proxy'), $url);
3367
if (!defined($reply) || !$reply) {
3368
failed("updating %s: Could not connect to %s.", $h, $config{$h}{'server'});
3371
last if !header_ok($h, $reply);
3373
my @reply = split /\n/, $reply;
3374
if (grep /success/i, @reply) {
3375
$config{$h}{'ip'} = $ip;
3376
$config{$h}{'mtime'} = $now;
3377
$config{$h}{'status'} = 'good';
3378
success("updating %s: good: IP address set to %s", $h, $ip);
3380
$config{$h}{'status'} = 'failed';
3381
warning("SENT: %s", $url) unless opt('verbose');
3382
warning("REPLIED: %s", $reply);
3383
failed("updating %s: Invalid reply.", $h);
3388
######################################################################
3389
# vim: ai ts=4 sw=4 tw=78 :