Changeset 548 for branches/stable/DNSDB.pm
- Timestamp:
- 12/11/13 16:01:18 (10 years ago)
- Location:
- branches/stable
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/stable
- Property svn:mergeinfo changed
/trunk merged: 493-534,536-543
- Property svn:mergeinfo changed
-
branches/stable/DNSDB.pm
r547 r548 3 3 ## 4 4 # $Id$ 5 # Copyright 2008-201 2Kris Deugau <kdeugau@deepnet.cx>5 # Copyright 2008-2013 Kris Deugau <kdeugau@deepnet.cx> 6 6 # 7 7 # This program is free software: you can redistribute it and/or modify … … 28 28 use Crypt::PasswdMD5; 29 29 use Net::SMTP; 30 use NetAddr::IP qw(:lower);30 use NetAddr::IP 4.027 qw(:lower); 31 31 use POSIX; 32 32 use Fcntl qw(:flock); 33 use Time::TAI64 qw(:tai64); 33 34 34 35 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); … … 40 41 &getPermissions &changePermissions &comparePermissions 41 42 &changeGroup 42 & loadConfig &connectDB &finish43 &connectDB &finish 43 44 &addDomain &delZone &domainName &revName &domainID &revID &addRDNS 44 45 &getZoneCount &getZoneList &getZoneLocation … … 49 50 &addLoc &updateLoc &delLoc &getLoc 50 51 &getLocCount &getLocList &getLocDropdown 51 &getSOA &updateSOA &getRecLine &get DomRecs&getRecCount52 &getSOA &updateSOA &getRecLine &getRecList &getRecCount 52 53 &addRec &updateRec &delRec 53 54 &getLogCount &getLogEntries … … 59 60 &export 60 61 &mailNotify 61 %typemap %reverse_typemap %config62 %typemap %reverse_typemap 62 63 @permtypes $permlist %permchains 63 64 ); … … 68 69 &getPermissions &changePermissions &comparePermissions 69 70 &changeGroup 70 & loadConfig &connectDB &finish71 &connectDB &finish 71 72 &addDomain &delZone &domainName &revName &domainID &revID &addRDNS 72 73 &getZoneCount &getZoneList &getZoneLocation … … 77 78 &addLoc &updateLoc &delLoc &getLoc 78 79 &getLocCount &getLocList &getLocDropdown 79 &getSOA &updateSOA &getRecLine &get DomRecs&getRecCount80 &getSOA &updateSOA &getRecLine &getRecList &getRecCount 80 81 &addRec &updateRec &delRec 81 82 &getLogCount &getLogEntries … … 87 88 &export 88 89 &mailNotify 89 %typemap %reverse_typemap %config90 %typemap %reverse_typemap 90 91 @permtypes $permlist %permchains 91 92 )] … … 94 95 our $errstr = ''; 95 96 our $resultstr = ''; 96 97 # Halfway sane defaults for SOA, TTL, etc.98 # serial defaults to 0 for convenience.99 # value will be either YYYYMMDDNN for BIND/etc, or auto-internal for tinydns100 our %def = qw (101 contact hostmaster.DOMAIN102 prins ns1.myserver.com103 serial 0104 soattl 86400105 refresh 10800106 retry 3600107 expire 604800108 minttl 10800109 ttl 10800110 );111 97 112 98 # Arguably defined wholly in the db, but little reason to change without supporting code changes … … 135 121 our %typemap; 136 122 our %reverse_typemap; 137 138 # Prepopulate a basic config. Note some of these *will* cause errors if left unset.139 # note: add appropriate stanzas in loadConfig to parse these140 our %config = (141 # Database connection info142 dbname => 'dnsdb',143 dbuser => 'dnsdb',144 dbpass => 'secret',145 dbhost => '',146 147 # Email notice settings148 mailhost => 'smtp.example.com',149 mailnotify => 'dnsdb@example.com', # to150 mailsender => 'dnsdb@example.com', # from151 mailname => 'DNS Administration',152 orgname => 'Example Corp',153 domain => 'example.com',154 155 # Template directory156 templatedir => 'templates/',157 # fmeh. this is a real web path, not a logical internal one. hm..158 # cssdir => 'templates/',159 sessiondir => 'session/',160 exportcache => 'cache/',161 162 # Session params163 timeout => '3600', # 1 hour default164 165 # Other miscellanea166 log_failures => 1, # log all evarthing by default167 perpage => 15,168 maxfcgi => 100, # reasonable default?169 );170 123 171 124 ## (Semi)private variables … … 221 174 my $class = ref($this) || $this; 222 175 my %args = @_; 223 ##fixme? to ponder: do we do some magic if the caller sets eg dbname to prevent parsing of the config file? 224 if (!loadConfig(basename => $args{configfile})) { 225 warn "Using default configuration; unable to load custom settings: $errstr\n"; 226 } 227 my $self = \%config; 228 $self->{configfile} = $args{configfile}; 176 177 # Prepopulate a basic config. Note some of these *will* cause errors if left unset. 178 # note: add appropriate stanzas in __cfgload() to parse these 179 my %defconfig = ( 180 # The only configuration options not loadable from a config file. 181 configfile => "/etc/dnsdb/dnsdb.conf", ##CFG_LEAF## 182 183 # Database connection info 184 dbname => 'dnsdb', 185 dbuser => 'dnsdb', 186 dbpass => 'secret', 187 dbhost => '', 188 189 # Email notice settings 190 mailhost => 'smtp.example.com', 191 mailnotify => 'dnsdb@example.com', # to 192 mailsender => 'dnsdb@example.com', # from 193 mailname => 'DNS Administration', 194 orgname => 'Example Corp', 195 domain => 'example.com', 196 197 # Template directory 198 templatedir => 'templates/', 199 # fmeh. this is a real web path, not a logical internal one. hm.. 200 # cssdir => 'templates/', 201 sessiondir => 'session/', 202 exportcache => 'cache/', 203 204 # Session params 205 timeout => '1h', # passed as-is to CGI::Session 206 207 # Other miscellanea 208 log_failures => 1, # log all evarthing by default 209 perpage => 15, 210 max_fcgi_requests => 100, # reasonable default? 211 force_refresh => 1, 212 lowercase => 0, # mangle as little as possible by default 213 ); 214 215 # Config file parse calls. 216 # If we are passed a blank argument for $args{configfile}, 217 # we should NOT parse the default config file - we will 218 # rely on hardcoded defaults OR caller-specified values. 219 # If we are passed a non-blank argument, parse that file. 220 # If no config file is specified, parse the default one. 221 my %siteconfig; 222 if (defined($args{configfile})) { 223 if ($args{configfile}) { 224 return if !__cfgload($args{configfile}, \%siteconfig); 225 } 226 } else { 227 return if !__cfgload($defconfig{configfile}, \%siteconfig); 228 } 229 230 # Assemble the object. Apply configuration hashes in order of precedence. 231 my $self = { 232 # Hardcoded defaults 233 %defconfig, 234 # Default config file OR caller-specified one, loaded above 235 %siteconfig, 236 # Caller-specified arguments 237 %args 238 }; 229 239 bless $self, $class; 240 241 # Several settings are booleans. Handle multiple possible ways of setting them. 242 for my $boolopt ('log_failures', 'force_refresh', 'lowercase') { 243 if ($self->{$boolopt} ne '1' && $self->{$boolopt} ne '0') { 244 # true/false, on/off, yes/no all valid. 245 if ($self->{$boolopt} =~ /^(?:true|false|t|f|on|off|yes|no)$/) { 246 if ($self->{$boolopt} =~ /(?:true|t|on|yes)/) { 247 $self->{$boolopt} = 1; 248 } else { 249 $self->{$boolopt} = 0; 250 } 251 } else { 252 warn "Bad $boolopt setting $self->{$boolopt}\n"; 253 $self->{$boolopt} = 1; 254 } 255 } 256 } 257 258 # Try to connect to the DB, and initialize a number of handy globals. 230 259 $self->{dbh} = connectDB($self->{dbname}, $self->{dbuser}, $self->{dbpass}, $self->{dbhost}) or return; 231 260 $self->initGlobals(); … … 236 265 sub DESTROY { 237 266 my $self = shift; 238 $self->{dbh}->disconnect ;267 $self->{dbh}->disconnect if $self->{dbh}; 239 268 } 269 270 sub errstr { $DNSDB::errstr; } 240 271 241 272 ## … … 370 401 371 402 ##fixme: farm out the actual logging to different subs for file, syslog, internal, etc based on config 372 # if ($ config{log_channel} eq 'sql') {403 # if ($self->{log_channel} eq 'sql') { 373 404 $dbh->do("INSERT INTO log (domain_id,rdns_id,group_id,entry,user_id,email,name) VALUES (?,?,?,?,?,?,?)", 374 405 undef, 375 406 ($args{domain_id}, $args{rdns_id}, $args{group_id}, $args{entry}, 376 407 $self->{loguserid}, $self->{logusername}, $self->{logfullname}) ); 377 # } elsif ($ config{log_channel} eq 'file') {378 # } elsif ($ config{log_channel} eq 'syslog') {408 # } elsif ($self->{log_channel} eq 'file') { 409 # } elsif ($self->{log_channel} eq 'syslog') { 379 410 # } 380 411 } # end _log … … 409 440 # or the intended parent domain for live records. 410 441 my $pname = ($args{defrec} eq 'y' ? 'DOMAIN' : $self->domainName($args{id})); 411 ${$args{host}} =~ s/\.*$/\.$pname/ if ${$args{host}} !~ /$pname$/;442 ${$args{host}} =~ s/\.*$/\.$pname/ if (${$args{host}} ne '@' && ${$args{host}} !~ /$pname$/); 412 443 413 444 # Check IP is well-formed, and that it's a v4 address … … 526 557 ${$args{val}} = "ZONE,${$args{val}}" unless ${$args{val}} =~ /^ZONE/; 527 558 } 528 ${$args{host}} =~ s/\.*$/\.$ config{domain}/ if ${$args{host}} !~ /(?:$config{domain}|ADMINDOMAIN)$/;559 ${$args{host}} =~ s/\.*$/\.$self->{domain}/ if ${$args{host}} !~ /(?:$self->{domain}|ADMINDOMAIN)$/; 529 560 } 530 561 … … 1202 1233 #major patterns: 1203 1234 #dashed IP, forward and reverse 1235 #underscoreed IP, forward and reverse 1204 1236 #dotted IP, forward and reverse (even if forward is... dumb) 1205 # -> %r for reverse, %i for forward, leading - or . to indicate separator, defaults to -1237 # -> %r for reverse, %i for forward, leading -, _, or . to indicate separator, defaults to - 1206 1238 # %r or %-r => %4d-%3d-%2d-%1d 1239 # %_r => %4d_%3d_%2d_%1d 1207 1240 # %.r => %4d.%3d.%2d.%1d 1208 1241 # %i or %-i => %1d-%2d-%3d-%4d 1242 # %_i => %1d_%2d_%3d_%4d 1209 1243 # %.i => %1d.%2d.%3d.%4d 1210 1244 $$tmpl =~ s/\%r/\%4d-\%3d-\%2d-\%1d/g; 1211 $$tmpl =~ s/\%([-. ])r/\%4d$1\%3d$1\%2d$1\%1d/g;1245 $$tmpl =~ s/\%([-._])r/\%4d$1\%3d$1\%2d$1\%1d/g; 1212 1246 $$tmpl =~ s/\%i/\%1d-\%2d-\%3d-\%4d/g; 1213 $$tmpl =~ s/\%([-. ])i/\%1d$1\%2d$1\%3d$1\%4d/g;1247 $$tmpl =~ s/\%([-._])i/\%1d$1\%2d$1\%3d$1\%4d/g; 1214 1248 1215 1249 #hex-coded IP … … 1232 1266 } # _template4_expand() 1233 1267 1268 # Broad syntactic check on the hostname. Checks for valid characters, correctly-expandable template patterns. 1269 # Takes the hostname, type, and live/default and forward/reverse flags 1270 # Returns true/false, sets errstr on failures 1271 sub _check_hostname_form { 1272 my ($hname,$rectype,$defrec,$revrec) = @_; 1273 1274 if ($hname =~ /\%/ && ($rectype == 65282 || $rectype == 65283) ) { 1275 my $tmphost = $hname; 1276 # we don't actually need to test with the real IP passed; that saves a bit of fiddling. 1277 _template4_expand(\$tmphost, '10.10.10.10'); 1278 if ($tmphost =~ /\%/) { 1279 $errstr = "Invalid template $hname"; 1280 return; 1281 } 1282 } elsif ($revrec eq 'y') { 1283 # Reverse zones don't support @ in hostnames 1284 # Also skip failure on revzone TXT records; the hostname contains the TXT content in that case. 1285 if ($rectype != $reverse_typemap{TXT} && lc($hname) !~ /^[0-9a-z_.-]+$/) { 1286 $errstr = "Hostnames may not contain anything other than (0-9 a-z . _)"; 1287 return; 1288 } 1289 } else { 1290 if (lc($hname) !~ /^(?:[0-9a-z_.-]+|@)$/) { 1291 # Don't mention @, because it would be far too wordy to explain the nuance of @ 1292 $errstr = "Hostnames may not contain anything other than (0-9 a-z . _)"; 1293 return; 1294 } 1295 } 1296 return 1; 1297 } # _check_hostname_form() 1298 1234 1299 1235 1300 ## … … 1237 1302 ## 1238 1303 1239 ## DNSDB::loadConfig()1240 # Load the minimum required initial state (DB connect info) from a config file1241 # Load misc other bits while we're at it.1242 # Takes an optional hash that may contain:1243 # - basename and config path to look for1244 # Populates the %config and %def hashes1245 sub loadConfig {1246 my %args = @_;1247 $args{configfile} = '' if !$args{configfile};1248 1249 ##fixme this is *intended* to load a system-default config template, and allow1250 # overriding on a per-tool or per-web-UI-instance basis with a secondary config1251 # file. The "default" config file can't be deleted in the current form.1252 1253 my $deferr = ''; # place to put error from default config file in case we can't find either one1254 1255 my $configroot = "/etc/dnsdb"; ##CFG_LEAF##1256 $configroot = '' if $args{configfile} =~ m|^/|; # allow passed siteconfig to specify an arbitrary absolute path1257 $args{configfile} .= ".conf" if $args{configfile} !~ /\.conf$/;1258 my $defconfig = "$configroot/dnsdb.conf";1259 my $siteconfig = "$configroot/$args{configfile}";1260 1261 # System defaults1262 __cfgload("$defconfig") or $deferr = $errstr;1263 1264 # Per-site-ish settings.1265 if ($args{configfile} ne '.conf') {1266 unless (__cfgload("$siteconfig")) {1267 $errstr = ($deferr ? "Error opening default config file $defconfig: $deferr\n" : '').1268 "Error opening site config file $siteconfig";1269 return;1270 }1271 }1272 1273 # Munge log_failures.1274 if ($config{log_failures} ne '1' && $config{log_failures} ne '0') {1275 # true/false, on/off, yes/no all valid.1276 if ($config{log_failures} =~ /^(?:true|false|on|off|yes|no)$/) {1277 if ($config{log_failures} =~ /(?:true|on|yes)/) {1278 $config{log_failures} = 1;1279 } else {1280 $config{log_failures} = 0;1281 }1282 } else {1283 $errstr = "Bad log_failures setting $config{log_failures}";1284 $config{log_failures} = 1;1285 # Bad setting shouldn't be fatal.1286 # return 2;1287 }1288 }1289 1290 # All good, clear the error and go home.1291 $errstr = '';1292 return 1;1293 } # end loadConfig()1294 1295 1296 1304 ## DNSDB::__cfgload() 1297 1305 # Private sub to parse a config file and load it into %config 1298 # Takes a filename 1306 # Takes a filename and a hashref to put the parsed entries in 1299 1307 sub __cfgload { 1300 1308 $errstr = ''; 1301 1309 my $cfgfile = shift; 1310 my $cfg = shift; 1302 1311 1303 1312 if (open CFG, "<$cfgfile") { … … 1310 1319 # $mode = $1 if /^\[(a-z)+]/; 1311 1320 # DB connect info 1312 $config{dbname} = $1 if /^dbname\s*=\s*([a-z0-9_.-]+)/i; 1313 $config{dbuser} = $1 if /^dbuser\s*=\s*([a-z0-9_.-]+)/i; 1314 $config{dbpass} = $1 if /^dbpass\s*=\s*([a-z0-9_.-]+)/i; 1315 $config{dbhost} = $1 if /^dbhost\s*=\s*([a-z0-9_.-]+)/i; 1316 # SOA defaults 1317 $def{contact} = $1 if /^contact\s*=\s*([a-z0-9_.-]+)/i; 1318 $def{prins} = $1 if /^prins\s*=\s*([a-z0-9_.-]+)/i; 1319 $def{soattl} = $1 if /^soattl\s*=\s*(\d+)/i; 1320 $def{refresh} = $1 if /^refresh\s*=\s*(\d+)/i; 1321 $def{retry} = $1 if /^retry\s*=\s*(\d+)/i; 1322 $def{expire} = $1 if /^expire\s*=\s*(\d+)/i; 1323 $def{minttl} = $1 if /^minttl\s*=\s*(\d+)/i; 1324 $def{ttl} = $1 if /^ttl\s*=\s*(\d+)/i; 1321 $cfg->{dbname} = $1 if /^dbname\s*=\s*([a-z0-9_.-]+)/i; 1322 $cfg->{dbuser} = $1 if /^dbuser\s*=\s*([a-z0-9_.-]+)/i; 1323 $cfg->{dbpass} = $1 if /^dbpass\s*=\s*([a-z0-9_.-]+)/i; 1324 $cfg->{dbhost} = $1 if /^dbhost\s*=\s*([a-z0-9_.-]+)/i; 1325 1325 # Mail settings 1326 $c onfig{mailhost} = $1 if /^mailhost\s*=\s*([a-z0-9_.-]+)/i;1327 $c onfig{mailnotify} = $1 if /^mailnotify\s*=\s*([a-z0-9_.\@-]+)/i;1328 $c onfig{mailsender} = $1 if /^mailsender\s*=\s*([a-z0-9_.\@-]+)/i;1329 $c onfig{mailname} = $1 if /^mailname\s*=\s*([a-z0-9\s_.-]+)/i;1330 $c onfig{orgname} = $1 if /^orgname\s*=\s*([a-z0-9\s_.,'-]+)/i;1331 $c onfig{domain} = $1 if /^domain\s*=\s*([a-z0-9_.-]+)/i;1326 $cfg->{mailhost} = $1 if /^mailhost\s*=\s*([a-z0-9_.-]+)/i; 1327 $cfg->{mailnotify} = $1 if /^mailnotify\s*=\s*([a-z0-9_.\@-]+)/i; 1328 $cfg->{mailsender} = $1 if /^mailsender\s*=\s*([a-z0-9_.\@-]+)/i; 1329 $cfg->{mailname} = $1 if /^mailname\s*=\s*([a-z0-9\s_.-]+)/i; 1330 $cfg->{orgname} = $1 if /^orgname\s*=\s*([a-z0-9\s_.,'-]+)/i; 1331 $cfg->{domain} = $1 if /^domain\s*=\s*([a-z0-9_.-]+)/i; 1332 1332 # session - note this is fed directly to CGI::Session 1333 $c onfig{timeout} = $1 if /^[tT][iI][mM][eE][oO][uU][tT]\s*=\s*(\d+[smhdwMy]?)/;1334 $c onfig{sessiondir} = $1 if m{^sessiondir\s*=\s*([a-z0-9/_.-]+)}i;1333 $cfg->{timeout} = $1 if /^[tT][iI][mM][eE][oO][uU][tT]\s*=\s*(\d+[smhdwMy]?)/; 1334 $cfg->{sessiondir} = $1 if m{^sessiondir\s*=\s*([a-z0-9/_.-]+)}i; 1335 1335 # misc 1336 $config{log_failures} = $1 if /^log_failures\s*=\s*([a-z01]+)/i; 1337 $config{perpage} = $1 if /^perpage\s*=\s*(\d+)/i; 1338 $config{exportcache} = $1 if m{^exportcache\s*=\s*([a-z0-9/_.-]+)}i; 1336 $cfg->{log_failures} = $1 if /^log_failures\s*=\s*([a-z01]+)/i; 1337 $cfg->{perpage} = $1 if /^perpage\s*=\s*(\d+)/i; 1338 $cfg->{exportcache} = $1 if m{^exportcache\s*=\s*([a-z0-9/_.-]+)}i; 1339 $cfg->{lowercase} = $1 if /^lowercase\s*=\s*([a-z01]+)/i; 1340 # not supported in dns.cgi yet 1341 # $cfg->{templatedir} = $1 if m{^templatedir\s*=\s*([a-z0-9/_.-]+)}i; 1342 # $cfg->{templateoverride} = $1 if m{^templateoverride\s*=\s*([a-z0-9/_.-]+)}i; 1339 1343 # RPC options 1340 $config{rpcmode} = $1 if /^rpc_mode\s*=\s*(socket|HTTP|XMLRPC)\s*$/i; 1341 $config{maxfcgi} = $1 if /^max_fcgi_requests\s*=\s*(\d+)\s*$/i; 1344 $cfg->{rpcmode} = $1 if /^rpc_mode\s*=\s*(socket|HTTP|XMLRPC)\s*$/i; 1345 $cfg->{maxfcgi} = $1 if /^max_fcgi_requests\s*=\s*(\d+)\s*$/i; 1346 $cfg->{force_refresh} = $1 if /^force_refresh\s*=\s*([a-z01]+)/i; 1342 1347 if (my ($tmp) = /^rpc_iplist\s*=\s*(.+)/i) { 1343 1348 my @ips = split /[,\s]+/, $tmp; 1344 1349 my $rpcsys = shift @ips; 1345 push @{$c onfig{rpcacl}{$rpcsys}}, @ips;1350 push @{$cfg->{rpcacl}{$rpcsys}}, @ips; 1346 1351 } 1347 1352 } 1348 1353 close CFG; 1349 1354 } else { 1350 $errstr = $!;1355 $errstr = "Couldn't load configuration file $cfgfile: $!"; 1351 1356 return; 1352 1357 } … … 1531 1536 # about having to open a file or a syslog channel 1532 1537 ##fixme Need to call _initActionLog_blah() for various logging channels, configured 1533 # via dnsdb.conf, in $ config{log_channel} or something1538 # via dnsdb.conf, in $self->{log_channel} or something 1534 1539 # See https://secure.deepnet.cx/trac/dnsadmin/ticket/21 1535 1540 sub initActionLog { … … 1552 1557 1553 1558 # convert to real check once we have other logging channels 1554 # if ($ config{log_channel} eq 'sql') {1559 # if ($self->{log_channel} eq 'sql') { 1555 1560 # Open Log, Sez Me! 1556 1561 # } … … 1594 1599 my $sth = $dbh->prepare($sql); 1595 1600 1596 $sth->execute($id) or die "argh: ".$sth->errstr; 1601 ##fixme? we don't trap other plain SELECT errors 1602 $sth->execute($id); 1597 1603 1598 1604 # my $permref = $sth->fetchrow_hashref; … … 1778 1784 my $msg = $@; 1779 1785 eval { $dbh->rollback; }; 1780 if ($ config{log_failures}) {1786 if ($self->{log_failures}) { 1781 1787 $self->_log(group_id => $oldgid, entry => "Error moving $type $entname to $newgname: $msg"); 1782 1788 $dbh->commit; # since we enabled transactions earlier … … 1802 1808 my $self = shift; 1803 1809 my $dbh = $self->{dbh}; 1804 return ('FAIL',"Need database handle") if !$dbh;1805 1810 my $domain = shift; 1806 return ('FAIL',"Domain must not be blank ") if !$domain;1811 return ('FAIL',"Domain must not be blank\n") if !$domain; 1807 1812 my $group = shift; 1808 return ('FAIL'," Need group") if !defined($group);1813 return ('FAIL',"Group must be specified\n") if !defined($group); 1809 1814 my $state = shift; 1810 return ('FAIL',"Need domain status") if !defined($state); 1815 return ('FAIL',"Domain status must be specified\n") if !defined($state); 1816 my $defloc = shift || ''; 1811 1817 1812 1818 $state = 1 if $state =~ /^active$/; … … 1817 1823 return ('FAIL',"Invalid domain status") if $state !~ /^\d+$/; 1818 1824 1825 $domain = lc($domain) if $self->{lowercase}; 1826 1819 1827 return ('FAIL', "Invalid characters in domain") if $domain !~ /^[a-zA-Z0-9_.-]+$/; 1820 1828 … … 1836 1844 eval { 1837 1845 # insert the domain... 1838 $dbh->do("INSERT INTO domains (domain,group_id,status) VALUES (?,?,?)", undef, ($domain, $group, $state)); 1846 $dbh->do("INSERT INTO domains (domain,group_id,status,default_location) VALUES (?,?,?,?)", undef, 1847 ($domain, $group, $state, $defloc)); 1839 1848 1840 1849 # get the ID... … … 1847 1856 # ... and now we construct the standard records from the default set. NB: group should be variable. 1848 1857 my $sth = $dbh->prepare("SELECT host,type,val,distance,weight,port,ttl FROM default_records WHERE group_id=?"); 1849 my $sth_in = $dbh->prepare("INSERT INTO records (domain_id,host,type,val,distance,weight,port,ttl )".1850 " VALUES ($dom_id,?,?,?,?,?,?,? )");1858 my $sth_in = $dbh->prepare("INSERT INTO records (domain_id,host,type,val,distance,weight,port,ttl,location)". 1859 " VALUES ($dom_id,?,?,?,?,?,?,?,?)"); 1851 1860 $sth->execute($group); 1852 while (my ($host, $type,$val,$dist,$weight,$port,$ttl) = $sth->fetchrow_array()) {1861 while (my ($host, $type, $val, $dist, $weight, $port, $ttl) = $sth->fetchrow_array()) { 1853 1862 $host =~ s/DOMAIN/$domain/g; 1854 1863 $val =~ s/DOMAIN/$domain/g; 1855 $sth_in->execute($host, $type,$val,$dist,$weight,$port,$ttl);1864 $sth_in->execute($host, $type, $val, $dist, $weight, $port, $ttl, $defloc); 1856 1865 if ($typemap{$type} eq 'SOA') { 1857 1866 my @tmp1 = split /:/, $host; … … 1877 1886 eval { $dbh->rollback; }; 1878 1887 $self->_log(group_id => $group, entry => "Failed adding domain $domain ($msg)") 1879 if $ config{log_failures};1888 if $self->{log_failures}; 1880 1889 $dbh->commit; # since we enabled transactions earlier 1881 1890 return ('FAIL',$msg); … … 1908 1917 return ('FAIL', ($revrec eq 'n' ? 'Domain' : 'Reverse zone')." ID $zoneid doesn't exist") if !$zone; 1909 1918 1910 # Set this up here since we may use if if $ config{log_failures} is enabled1919 # Set this up here since we may use if if $self->{log_failures} is enabled 1911 1920 my %loghash; 1912 1921 $loghash{domain_id} = $zoneid if $revrec eq 'n'; … … 1956 1965 eval { $dbh->rollback; }; 1957 1966 $loghash{entry} = "Error deleting $zone: $msg ($failmsg)"; 1958 if ($ config{log_failures}) {1967 if ($self->{log_failures}) { 1959 1968 $self->_log(%loghash); 1960 1969 $dbh->commit; # since we enabled transactions earlier … … 2008 2017 my ($domid) = $dbh->selectrow_array("SELECT domain_id FROM domains WHERE lower(domain) = lower(?)", 2009 2018 undef, ($domain) ); 2010 $errstr = $DBI::errstr if !$domid; 2019 if (!$domid) { 2020 if ($dbh->err) { 2021 $errstr = $DBI::errstr; 2022 } else { 2023 $errstr = "Domain $domain not present"; 2024 } 2025 } 2011 2026 return $domid if $domid; 2012 2027 } # end domainID() … … 2022 2037 my $revzone = shift; 2023 2038 my ($revid) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet=?", undef, ($revzone) ); 2024 $errstr = $DBI::errstr if !$revid; 2039 if (!$revid) { 2040 if ($dbh->err) { 2041 $errstr = $DBI::errstr; 2042 } else { 2043 $errstr = "Reverse zone $revzone not present"; 2044 } 2045 } 2025 2046 return $revid if $revid; 2026 2047 } # end revID() … … 2089 2110 } 2090 2111 2091 $host =~ s/ADMINDOMAIN/$ config{domain}/g;2112 $host =~ s/ADMINDOMAIN/$self->{domain}/g; 2092 2113 2093 2114 # Check to make sure the IP stubs will fit in the zone. Under most usage failures here should be rare. … … 2183 2204 eval { $dbh->rollback; }; 2184 2205 $self->_log(group_id => $group, entry => "Failed adding reverse zone $zone ($msg)") 2185 if $ config{log_failures};2206 if $self->{log_failures}; 2186 2207 $dbh->commit; # since we enabled transactions earlier 2187 2208 return ('FAIL',$msg); … … 2211 2232 2212 2233 my %args = @_; 2234 2235 # Fail on bad curgroup argument. There's no sane fallback on this one. 2236 if (!$args{curgroup} || $args{curgroup} !~ /^\d+$/) { 2237 $errstr = "Bad or missing curgroup argument"; 2238 return; 2239 } 2240 # Fail on bad childlist argument. This could be sanely ignored if bad, maybe. 2241 if ($args{childlist} && $args{childlist} !~ /^[\d,]+$/) { 2242 $errstr = "Bad childlist argument"; 2243 return; 2244 } 2213 2245 2214 2246 my @filterargs; … … 2250 2282 $args{sortorder} = 'ASC' if !$args{sortorder} || !grep /^$args{sortorder}$/, ('ASC','DESC'); 2251 2283 $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/; 2284 2285 # Fail on bad curgroup argument. There's no sane fallback on this one. 2286 if (!$args{curgroup} || $args{curgroup} !~ /^\d+$/) { 2287 $errstr = "Bad or missing curgroup argument"; 2288 return; 2289 } 2290 # Fail on bad childlist argument. This could be sanely ignored if bad, maybe. 2291 if ($args{childlist} && $args{childlist} !~ /^[\d,]+$/) { 2292 $errstr = "Bad childlist argument"; 2293 return; 2294 } 2252 2295 2253 2296 my @filterargs; … … 2277 2320 # A common tail. 2278 2321 $sql .= " ORDER BY ".($args{sortby} eq 'group' ? 'groups.group_name' : $args{sortby})." $args{sortorder} ". 2279 ($args{offset} eq 'all' ? '' : " LIMIT $ config{perpage}".2280 " OFFSET ".$args{offset}*$ config{perpage});2322 ($args{offset} eq 'all' ? '' : " LIMIT $self->{perpage}". 2323 " OFFSET ".$args{offset}*$self->{perpage}); 2281 2324 my $sth = $dbh->prepare($sql); 2282 2325 $sth->execute(@filterargs); … … 2404 2447 my $msg = $@; 2405 2448 eval { $dbh->rollback; }; 2406 if ($ config{log_failures}) {2449 if ($self->{log_failures}) { 2407 2450 $self->_log(group_id => $pargroup, entry => "Failed to add group $groupname: $msg"); 2408 2451 $dbh->commit; … … 2448 2491 my ($domcnt) = $dbh->selectrow_array("SELECT count(*) FROM domains WHERE group_id=?", undef, ($groupid)); 2449 2492 die "$domcnt domains still in group\n" if $domcnt; 2493 my ($revcnt) = $dbh->selectrow_array("SELECT count(*) FROM revzones WHERE group_id=?", undef, ($groupid)); 2494 die "$revcnt reverse zones still in group\n" if $revcnt; 2450 2495 my ($usercnt) = $dbh->selectrow_array("SELECT count(*) FROM users WHERE group_id=?", undef, ($groupid)); 2451 2496 die "$usercnt users still in group\n" if $usercnt; … … 2468 2513 my $msg = $@; 2469 2514 eval { $dbh->rollback; }; 2470 if ($ config{log_failures}) {2515 if ($self->{log_failures}) { 2471 2516 $self->_log(group_id => $parid, entry => "$failmsg: $msg"); 2472 2517 $dbh->commit; # since we enabled transactions earlier … … 2545 2590 my %args = @_; 2546 2591 2592 # Fail on bad curgroup argument. There's no sane fallback on this one. 2593 if (!$args{curgroup} || $args{curgroup} !~ /^\d+$/) { 2594 $errstr = "Bad or missing curgroup argument"; 2595 return; 2596 } 2597 # Fail on bad childlist argument. This could be sanely ignored if bad, maybe. 2598 if ($args{childlist} && $args{childlist} !~ /^[\d,]+$/) { 2599 $errstr = "Bad childlist argument"; 2600 return; 2601 } 2602 2547 2603 my @filterargs; 2548 2549 2604 $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/; 2550 2605 push @filterargs, "^$args{startwith}" if $args{startwith}; … … 2571 2626 my %args = @_; 2572 2627 2628 # Fail on bad curgroup argument. There's no sane fallback on this one. 2629 if (!$args{curgroup} || $args{curgroup} !~ /^\d+$/) { 2630 $errstr = "Bad or missing curgroup argument"; 2631 return; 2632 } 2633 # Fail on bad childlist argument. This could be sanely ignored if bad, maybe. 2634 if ($args{childlist} && $args{childlist} !~ /^[\d,]+$/) { 2635 $errstr = "Bad childlist argument"; 2636 return; 2637 } 2638 2573 2639 my @filterargs; 2574 2575 2640 $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/; 2576 2641 push @filterargs, "^$args{startwith}" if $args{startwith}; … … 2578 2643 2579 2644 # protection against bad or missing arguments 2580 $args{sortorder} = 'ASC' if !$args{sortorder}; 2645 $args{sortorder} = 'ASC' if !$args{sortorder} || !grep /^$args{sortorder}$/, ('ASC','DESC'); 2646 $args{sortby} = 'group' if !$args{sortby} || $args{sortby} !~ /^[\w_.]+$/; 2581 2647 $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/; 2582 2648 … … 2594 2660 " GROUP BY g.group_id, g.group_name, g2.group_name ". 2595 2661 " ORDER BY $args{sortby} $args{sortorder} ". 2596 ($args{offset} eq 'all' ? '' : " LIMIT $ config{perpage} OFFSET ".$args{offset}*$config{perpage});2662 ($args{offset} eq 'all' ? '' : " LIMIT $self->{perpage} OFFSET ".$args{offset}*$self->{perpage}); 2597 2663 my $glist = $dbh->selectall_arrayref($sql, { Slice => {} }, (@filterargs) ); 2598 2664 $errstr = $dbh->errstr if !$glist; … … 2749 2815 my $msg = $@; 2750 2816 eval { $dbh->rollback; }; 2751 if ($ config{log_failures}) {2817 if ($self->{log_failures}) { 2752 2818 $self->_log(group_id => $group, entry => "Error adding user $username: $msg"); 2753 2819 $dbh->commit; # since we enabled transactions earlier … … 2772 2838 my %args = @_; 2773 2839 2840 # Fail on bad curgroup argument. There's no sane fallback on this one. 2841 if (!$args{curgroup} || $args{curgroup} !~ /^\d+$/) { 2842 $errstr = "Bad or missing curgroup argument"; 2843 return; 2844 } 2845 # Fail on bad childlist argument. This could be sanely ignored if bad, maybe. 2846 if ($args{childlist} && $args{childlist} !~ /^[\d,]+$/) { 2847 $errstr = "Bad childlist argument"; 2848 return; 2849 } 2850 2774 2851 my @filterargs; 2775 2776 2852 $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/; 2777 2853 push @filterargs, "^$args{startwith}" if $args{startwith}; 2778 2854 push @filterargs, $args{filter} if $args{filter}; 2779 2780 2855 2781 2856 my $sql = "SELECT count(*) FROM users ". … … 2801 2876 my %args = @_; 2802 2877 2878 # Fail on bad curgroup argument. There's no sane fallback on this one. 2879 if (!$args{curgroup} || $args{curgroup} !~ /^\d+$/) { 2880 $errstr = "Bad or missing curgroup argument"; 2881 return; 2882 } 2883 # Fail on bad childlist argument. This could be sanely ignored if bad, maybe. 2884 if ($args{childlist} && $args{childlist} !~ /^[\d,]+$/) { 2885 $errstr = "Bad childlist argument"; 2886 return; 2887 } 2888 2803 2889 my @filterargs; 2804 2805 2890 $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/; 2806 2891 push @filterargs, "^$args{startwith}" if $args{startwith}; … … 2813 2898 2814 2899 # protection against bad or missing arguments 2815 $args{sortorder} = 'ASC' if !$args{sortorder} ;2816 $args{sortby} = 'u.username' if !$args{sortby} ;2900 $args{sortorder} = 'ASC' if !$args{sortorder} || !grep /^$args{sortorder}$/, ('ASC','DESC'); 2901 $args{sortby} = 'u.username' if !$args{sortby} || $args{sortby} !~ /^[\w_.]+$/; 2817 2902 $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/; 2818 2903 … … 2825 2910 " AND NOT u.type = 'R' ". 2826 2911 " ORDER BY $args{sortby} $args{sortorder} ". 2827 ($args{offset} eq 'all' ? '' : " LIMIT $ config{perpage} OFFSET ".$args{offset}*$config{perpage});2912 ($args{offset} eq 'all' ? '' : " LIMIT $self->{perpage} OFFSET ".$args{offset}*$self->{perpage}); 2828 2913 my $ulist = $dbh->selectall_arrayref($sql, { Slice => {} }, (@filterargs) ); 2829 2914 $errstr = $dbh->errstr if !$ulist; … … 2908 2993 my $msg = $@; 2909 2994 eval { $dbh->rollback; }; 2910 if ($ config{log_failures}) {2995 if ($self->{log_failures}) { 2911 2996 $self->_log(group_id => $group, entry => "Error updating user $username: $msg"); 2912 2997 $dbh->commit; # since we enabled transactions earlier … … 2947 3032 my $msg = $@; 2948 3033 eval { $dbh->rollback; }; 2949 if ($ config{log_failures}) {3034 if ($self->{log_failures}) { 2950 3035 $self->_log(group_id => $userdata->{group_id}, entry => "Error deleting user ID ". 2951 3036 "$userid/".$userdata->{username}.": $msg"); … … 3120 3205 my $msg = $@; 3121 3206 eval { $dbh->rollback; }; 3122 if ($ config{log_failures}) {3207 if ($self->{log_failures}) { 3123 3208 $shdesc = $loc if !$shdesc; 3124 3209 $self->_log(entry => "Failed adding location ($shdesc, '$iplist'): $msg"); … … 3167 3252 my $msg = $@; 3168 3253 eval { $dbh->rollback; }; 3169 if ($ config{log_failures}) {3254 if ($self->{log_failures}) { 3170 3255 $shdesc = $loc if !$shdesc; 3171 3256 $self->_log(entry => "Failed updating location ($shdesc, '$iplist'): $msg"); … … 3206 3291 my $msg = $@; 3207 3292 eval { $dbh->rollback; }; 3208 if ($ config{log_failures}) {3293 if ($self->{log_failures}) { 3209 3294 $self->_log(entry => "Failed to delete location ($olddesc, '$oldloc->{iplist}'): $msg"); 3210 3295 $dbh->commit; … … 3244 3329 my %args = @_; 3245 3330 3331 # Fail on bad curgroup argument. There's no sane fallback on this one. 3332 if (!$args{curgroup} || $args{curgroup} !~ /^\d+$/) { 3333 $errstr = "Bad or missing curgroup argument"; 3334 return; 3335 } 3336 # Fail on bad childlist argument. This could be sanely ignored if bad, maybe. 3337 if ($args{childlist} && $args{childlist} !~ /^[\d,]+$/) { 3338 $errstr = "Bad childlist argument"; 3339 return; 3340 } 3341 3246 3342 my @filterargs; 3247 3248 3343 $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/; 3249 3344 push @filterargs, "^$args{startwith}" if $args{startwith}; 3250 3345 push @filterargs, $args{filter} if $args{filter}; 3251 3252 3346 3253 3347 my $sql = "SELECT count(*) FROM locations ". … … 3268 3362 my %args = @_; 3269 3363 3364 # Fail on bad curgroup argument. There's no sane fallback on this one. 3365 if (!$args{curgroup} || $args{curgroup} !~ /^\d+$/) { 3366 $errstr = "Bad or missing curgroup argument"; 3367 return; 3368 } 3369 # Fail on bad childlist argument. This could be sanely ignored if bad, maybe. 3370 if ($args{childlist} && $args{childlist} !~ /^[\d,]+$/) { 3371 $errstr = "Bad childlist argument"; 3372 return; 3373 } 3374 3270 3375 my @filterargs; 3271 3272 3376 $args{startwith} = undef if $args{startwith} && $args{startwith} !~ /^(?:[a-z]|0-9)$/; 3273 3377 push @filterargs, "^$args{startwith}" if $args{startwith}; … … 3280 3384 3281 3385 # protection against bad or missing arguments 3282 $args{sortorder} = 'ASC' if !$args{sortorder} ;3283 $args{sortby} = 'l.description' if !$args{sortby} ;3386 $args{sortorder} = 'ASC' if !$args{sortorder} || !grep /^$args{sortorder}$/, ('ASC','DESC'); 3387 $args{sortby} = 'l.description' if !$args{sortby} || $args{sortby} !~ /^[\w_.]+$/; 3284 3388 $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/; 3285 3389 … … 3291 3395 ($args{filter} ? " AND l.description ~* ?" : ''). 3292 3396 " ORDER BY $args{sortby} $args{sortorder} ". 3293 ($args{offset} eq 'all' ? '' : " LIMIT $ config{perpage} OFFSET ".$args{offset}*$config{perpage});3397 ($args{offset} eq 'all' ? '' : " LIMIT $self->{perpage} OFFSET ".$args{offset}*$self->{perpage}); 3294 3398 my $ulist = $dbh->selectall_arrayref($sql, { Slice => {} }, (@filterargs) ); 3295 3399 $errstr = $dbh->errstr if !$ulist; … … 3411 3515 $logdata{entry} = "Error updating ".($defrec eq 'y' ? ($revrec eq 'y' ? 'default reverse zone ' : 'default ') : ''). 3412 3516 "SOA record for $parname: $msg"; 3413 if ($ config{log_failures}) {3517 if ($self->{log_failures}) { 3414 3518 $self->_log(%logdata); 3415 3519 $dbh->commit; … … 3433 3537 my $id = shift; 3434 3538 3539 ##fixme: do we need a knob to twist to switch between unix epoch and postgres time string? 3435 3540 my $sql = "SELECT record_id,host,type,val,ttl". 3436 3541 ($defrec eq 'n' ? ',location' : ''). 3437 3542 ($revrec eq 'n' ? ',distance,weight,port' : ''). 3438 (($defrec eq 'y') ? ',group_id FROM ' : ',domain_id,rdns_id FROM ').3543 (($defrec eq 'y') ? ',group_id FROM ' : ',domain_id,rdns_id,stamp,stamp < now() AS ispast,expires,stampactive FROM '). 3439 3544 _rectable($defrec,$revrec)." WHERE record_id=?"; 3440 3545 my $ret = $dbh->selectrow_hashref($sql, undef, ($id) ); … … 3465 3570 3466 3571 ##fixme: should use above (getRecLine()) to get lines for below? 3467 ## DNSDB::get DomRecs()3468 # Return records for a domain3469 # Takes a d atabase handle, default/live flag, group/domainID, start,3572 ## DNSDB::getRecList() 3573 # Return records for a group or zone 3574 # Takes a default/live flag, group or zone ID, start, 3470 3575 # number of records, sort field, and sort order 3471 3576 # Returns a reference to an array of hashes 3472 sub get DomRecs{3577 sub getRecList { 3473 3578 $errstr = ''; 3474 3579 my $self = shift; … … 3482 3587 3483 3588 # protection against bad or missing arguments 3484 $args{sortorder} = 'ASC' if !$args{sortorder}; 3485 $args{sortby} = 'host' if !$args{sortby} && $args{revrec} eq 'n'; # default sort by host on domain record list 3486 $args{sortby} = 'val' if !$args{sortby} && $args{revrec} eq 'y'; # default sort by IP on revzone record list 3589 $args{sortorder} = 'ASC' if !$args{sortorder} || !grep /^$args{sortorder}$/, ('ASC','DESC'); 3590 my $defsort; 3591 $defsort = 'host' if $args{revrec} eq 'n'; # default sort by host on domain record list 3592 $defsort = 'val' if $args{revrec} eq 'y'; # default sort by IP on revzone record list 3593 $args{sortby} = '' if !$args{sortby}; 3594 $args{sortby} = $defsort if !$args{revrec}; 3595 $args{sortby} = $defsort if $args{sortby} !~ /^[\w_,.]+$/; 3487 3596 $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/; 3597 my $perpage = ($args{nrecs} ? $args{nrecs} : $self->{perpage}); 3488 3598 3489 3599 # sort reverse zones on IP, correctly … … 3508 3618 $newsort =~ s/^,//; 3509 3619 3620 ##fixme: do we need a knob to twist to switch from unix epoch to postgres time string? 3621 my $sql = "SELECT r.record_id,r.host,r.type,r.val,r.ttl"; 3622 $sql .= ",l.description AS locname,stamp,r.stamp < now() AS ispast,r.expires,r.stampactive" 3623 if $args{defrec} eq 'n'; 3624 $sql .= ",r.distance,r.weight,r.port" if $args{revrec} eq 'n'; 3625 $sql .= " FROM "._rectable($args{defrec},$args{revrec})." r "; 3510 3626 $sql .= "INNER JOIN rectypes t ON r.type=t.val "; # for sorting by type alphabetically 3511 3627 $sql .= "LEFT JOIN locations l ON r.location=l.location " if $args{defrec} eq 'n'; … … 3516 3632 # ensure consistent ordering by sorting on record_id too 3517 3633 $sql .= ", record_id $args{sortorder}"; 3518 $sql .= ($args{offset} eq 'all' ? '' : " LIMIT $ config{perpage} OFFSET ".$args{offset}*$config{perpage});3634 $sql .= ($args{offset} eq 'all' ? '' : " LIMIT $perpage OFFSET ".$args{offset}*$perpage); 3519 3635 3520 3636 my @bindvars = ($args{id}); … … 3522 3638 3523 3639 my $ret = $dbh->selectall_arrayref($sql, { Slice => {} }, (@bindvars) ); 3640 $errstr = "Error retrieving records: ".$dbh->errstr if !$ret; 3641 3524 3642 return $ret; 3525 } # end get DomRecs()3643 } # end getRecList() 3526 3644 3527 3645 … … 3581 3699 $location = '' if !$location; 3582 3700 3701 my $expires = shift; 3702 $expires = 1 if $expires eq 'until'; # Turn some special values into the appropriate booleans. 3703 $expires = 0 if $expires eq 'after'; 3704 my $stamp = shift; 3705 $stamp = '' if !$stamp; # Timestamp should be a string at this point. 3706 3583 3707 # Spaces are evil. 3584 3708 $host =~ s/^\s+//; … … 3590 3714 } 3591 3715 3592 # Validation 3593 my $addr = NetAddr::IP->new($val); 3594 if ($rectype == $reverse_typemap{A}) { 3595 return ('FAIL',$typemap{$rectype}." record must be a valid IPv4 address") 3596 unless $addr && !$addr->{isv6}; 3597 } 3598 if ($rectype == $reverse_typemap{AAAA}) { 3599 return ('FAIL',$typemap{$rectype}." record must be a valid IPv6 address") 3600 unless $addr && $addr->{isv6}; 3601 } 3716 if ($self->{lowercase}) { 3717 if ($typemap{$$rectype} ne 'TXT') { 3718 $$host = lc($$host); 3719 $$val = lc($$val); 3720 } else { 3721 # TXT records should preserve user entry in the string. 3722 if ($revrec eq 'n') { 3723 $$host = lc($$host); 3724 } else { 3725 $$val = lc($$val); 3726 } 3727 } 3728 } 3729 3730 # prep for validation 3731 my $addr = NetAddr::IP->new($$val); 3732 $$host =~ s/\.+$//; # FQDNs ending in . are an internal detail, and really shouldn't be exposed in the UI. 3602 3733 3603 3734 my $domid = 0; … … 3610 3741 return ('FAIL', "TTL must be numeric") unless $ttl =~ /^\d+$/; 3611 3742 3612 # Quick check on hostname parts. Note the regex is more forgiving than the error message; 3613 # domain names technically are case-insensitive, and we use printf-like % codes for a couple 3614 # of types. Other things may also be added to validate default records of several flavours. 3615 return ('FAIL', "Hostnames may not contain anything other than (0-9 a-z . _)") 3616 if $defrec eq 'n' && ($revrec eq 'y' ? $$rectype != $reverse_typemap{TXT} : 1) && 3617 $$host !~ /^[0-9a-z_%.-]+$/i; 3743 # Quick check on hostname parts. There are enough variations to justify a sub now. 3744 return ('FAIL', $errstr) if ! _check_hostname_form($$host, $$rectype, $defrec, $revrec); 3618 3745 3619 3746 # Collect these even if we're only doing a simple A record so we can call *any* validation sub … … 3637 3764 push @vallist, ($$host,$$rectype,$$val,$ttl,$id); 3638 3765 3639 # locations are not for default records, silly coder!3640 3766 if ($defrec eq 'n') { 3767 # locations are not for default records, silly coder! 3641 3768 $fields .= ",location"; 3642 3769 push @vallist, $location; 3643 } 3770 # timestamps are rare. 3771 if ($stamp) { 3772 $fields .= ",stamp,expires,stampactive"; 3773 push @vallist, $stamp, $expires, 'y'; 3774 } else { 3775 $fields .= ",stampactive"; 3776 push @vallist, 'n'; 3777 } 3778 } 3779 3780 # a little magic to get the right number of ? placeholders based on how many values we're providing 3644 3781 my $vallen = '?'.(',?'x$#vallist); 3645 3782 … … 3669 3806 $logdata{entry} .= "', TTL $ttl"; 3670 3807 $logdata{entry} .= ", location ".$self->getLoc($location)->{description} if $location; 3808 $logdata{entry} .= ($expires eq 'after' ? ', valid after ' : ', expires at ').$stamp if $stamp; 3671 3809 3672 3810 # Allow transactions, and raise an exception on errors so we can catch it later. … … 3684 3822 my $msg = $@; 3685 3823 eval { $dbh->rollback; }; 3686 if ($ config{log_failures}) {3824 if ($self->{log_failures}) { 3687 3825 $logdata{entry} = "Failed adding ".($defrec eq 'y' ? 'default ' : ''). 3688 3826 "record '$$host $typemap{$$rectype} $$val', TTL $ttl ($msg)"; … … 3722 3860 $location = '' if !$location; 3723 3861 3724 # prep for validation 3725 my $addr = NetAddr::IP->new($$val); 3726 $$host =~ s/\.+$//; # FQDNs ending in . are an internal detail, and really shouldn't be exposed in the UI. 3862 my $expires = shift; 3863 $expires = 1 if $expires eq 'until'; # Turn some special values into the appropriate booleans. 3864 $expires = 0 if $expires eq 'after'; 3865 my $stamp = shift; 3866 $stamp = '' if !$stamp; # Timestamp should be a string at this point. 3867 3868 # just set it to an empty string; failures will be caught later. 3869 $$host = '' if !$$host; 3727 3870 3728 3871 # Spaces are evil. … … 3735 3878 } 3736 3879 3880 if ($self->{lowercase}) { 3881 if ($typemap{$$rectype} ne 'TXT') { 3882 $$host = lc($$host); 3883 $$val = lc($$val); 3884 } else { 3885 # TXT records should preserve user entry in the string. 3886 if ($revrec eq 'n') { 3887 $$host = lc($$host); 3888 } else { 3889 $$val = lc($$val); 3890 } 3891 } 3892 } 3893 3894 # prep for validation 3895 my $addr = NetAddr::IP->new($$val); 3896 $$host =~ s/\.+$//; # FQDNs ending in . are an internal detail, and really shouldn't be exposed in the UI. 3897 3737 3898 my $domid = 0; 3738 3899 my $revid = 0; … … 3744 3905 return ('FAIL', "TTL must be numeric") unless $ttl =~ /^\d+$/; 3745 3906 3746 # Quick check on hostname parts. Note the regex is more forgiving than the error message; 3747 # domain names technically are case-insensitive, and we use printf-like % codes for a couple 3748 # of types. Other things may also be added to validate default records of several flavours. 3749 return ('FAIL', "Hostnames may not contain anything other than (0-9 a-z - . _)") 3750 if $defrec eq 'n' && ($revrec eq 'y' ? $$rectype != $reverse_typemap{TXT} : 1) && 3751 $$host !~ /^[0-9a-z_%.-]+$/i; 3907 # Quick check on hostname parts. There are enough variations to justify a sub now. 3908 return ('FAIL', $errstr) if ! _check_hostname_form($$host, $$rectype, $defrec, $revrec); 3752 3909 3753 3910 # only MX and SRV will use these … … 3780 3937 ($defrec eq 'y' ? $oldrec->{group_id} : ($revrec eq 'n' ? $oldrec->{domain_id} : $oldrec->{rdns_id})) ); 3781 3938 3782 # locations are not for default records, silly coder!3783 3939 if ($defrec eq 'n') { 3940 # locations are not for default records, silly coder! 3784 3941 $fields .= ",location"; 3785 3942 push @vallist, $location; 3943 # timestamps are rare. 3944 if ($stamp) { 3945 $fields .= ",stamp,expires,stampactive"; 3946 push @vallist, $stamp, $expires, 'y'; 3947 } else { 3948 $fields .= ",stampactive"; 3949 push @vallist, 'n'; 3950 } 3786 3951 } 3787 3952 … … 3837 4002 $logdata{entry} .= "', TTL $oldrec->{ttl}"; 3838 4003 $logdata{entry} .= ", location ".$self->getLoc($oldrec->{location})->{description} if $oldrec->{location}; 4004 $logdata{entry} .= ($oldrec->{expires} ? ', expires at ' : ', valid after ').$oldrec->{stamp} 4005 if $oldrec->{stampactive}; 3839 4006 $logdata{entry} .= "\nto\n"; 3840 4007 # More NS special … … 3848 4015 $logdata{entry} .= "', TTL $ttl"; 3849 4016 $logdata{entry} .= ", location ".$self->getLoc($location)->{description} if $location; 4017 $logdata{entry} .= ($expires eq 'after' ? ', valid after ' : ', expires at ').$stamp if $stamp; 3850 4018 3851 4019 local $dbh->{AutoCommit} = 0; … … 3864 4032 my $msg = $@; 3865 4033 eval { $dbh->rollback; }; 3866 if ($ config{log_failures}) {4034 if ($self->{log_failures}) { 3867 4035 $logdata{entry} = "Failed updating ".($defrec eq 'y' ? 'default ' : ''). 3868 4036 "record '$oldrec->{host} $typemap{$oldrec->{type}} $oldrec->{val}', TTL $oldrec->{ttl} ($msg)"; … … 3975 4143 my $msg = $@; 3976 4144 eval { $dbh->rollback; }; 3977 if ($ config{log_failures}) {4145 if ($self->{log_failures}) { 3978 4146 $logdata{entry} = "Error deleting ".($defrec eq 'y' ? 'default record' : 'record'). 3979 4147 " '$oldrec->{host} $typemap{$oldrec->{type}} $oldrec->{val}', TTL $oldrec->{ttl} ($msg)"; … … 4011 4179 $args{logtype} = 'domain' if $args{logtype} eq 'dom'; # hack pthui 4012 4180 return if !grep /^$args{logtype}$/, ('group', 'domain', 'revzone', 'user'); 4013 4014 $args{logtype} = 'revzone' if $args{logtype} eq 'rdns'; # hack pthui4015 4181 4016 4182 my $sql = "SELECT count(*) FROM log ". … … 4047 4213 4048 4214 # Sorting defaults 4049 $args{sort by} = 'stamp' if !$args{sortby};4050 $args{sort order} = 'DESC' if !$args{sortorder};4215 $args{sortorder} = 'DESC' if !$args{sortorder} || !grep /^$args{sortorder}$/, ('ASC','DESC'); 4216 $args{sortby} = 'stamp' if !$args{sortby} || $args{sortby} !~ /^[\w_.]+$/; 4051 4217 $args{offset} = 0 if !$args{offset} || $args{offset} !~ /^(?:all|\d+)$/; 4052 4218 … … 4060 4226 ($args{filter} ? " AND entry ~* ?" : ''). 4061 4227 " ORDER BY $args{sortby} $args{sortorder}, log_id $args{sortorder}". 4062 ($args{offset} eq 'all' ? '' : " LIMIT $ config{perpage} OFFSET ".$args{offset}*$config{perpage});4228 ($args{offset} eq 'all' ? '' : " LIMIT $self->{perpage} OFFSET ".$args{offset}*$self->{perpage}); 4063 4229 my $loglist = $dbh->selectall_arrayref($sql, { Slice => {} }, ($args{id}, @filterargs) ); 4064 4230 $errstr = $dbh->errstr if !$loglist; … … 4111 4277 # default; forward zone types. technically $type eq 'f' but not worth the error message. 4112 4278 $sql .= "stdflag=1 OR stdflag=2"; 4279 $sql .= " AND val < 65280" if $recgroup eq 'fo'; # An extra flag to trim off the pseudotypes as well. 4113 4280 } 4114 4281 $sql .= " ORDER BY listorder"; … … 4243 4410 $limiter++; 4244 4411 ##fixme: how often will this happen on a live site? fail at max limiter <n>? 4412 # 2013/10/22 only seems to happen when you request an entity that doesn't exist. 4245 4413 warn "no results looking for $sql with id $id (depth $limiter)\n"; 4246 4414 last; … … 4841 5009 my %recflags; 4842 5010 4843 my $domsth = $dbh->prepare("SELECT domain_id,domain,status,changed FROM domains WHERE status=1");4844 my $recsth = $dbh->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location ".4845 "FROM records WHERE domain_id=? AND type < 65280"); # Just exclude all types relating to rDNS4846 my $zonesth = $dbh->prepare("UPDATE domains SET changed='n' WHERE domain_id=?");4847 $domsth->execute();4848 while (my ($domid,$dom,$domstat,$changed) = $domsth->fetchrow_array) {4849 ##fixme: need to find a way to block opening symlinked files without introducing a race.4850 # O_NOFOLLOW4851 # If pathname is a symbolic link, then the open fails. This is a FreeBSD extension, which was4852 # added to Linux in version 2.1.126. Symbolic links in earlier components of the pathname will4853 # still be followed.4854 # but that doesn't help other platforms. :/4855 sysopen(ZONECACHE, "$config{exportcache}/$dom", O_RDWR|O_CREAT);4856 flock(ZONECACHE, LOCK_EX);4857 if ($changed || -s "$config{exportcache}/$dom" == 0) {4858 $recsth->execute($domid);4859 while (my ($host,$type,$val,$dist,$weight,$port,$ttl,$recid,$loc) = $recsth->fetchrow_array) {4860 next if $recflags{$recid};4861 4862 $loc = '' if !$loc; # de-nullify - just in case4863 ##fixme: handle case of record-with-location-that-doesn't-exist better.4864 # note this currently fails safe (tested) - records with a location that4865 # doesn't exist will not be sent to any client4866 # $loc = '' if !$lochash->{$loc};4867 4868 ##fixme: record validity timestamp. tinydns supports fiddling with timestamps.4869 # note $ttl must be set to 0 if we want to use tinydns's auto-expiring timestamps.4870 # timestamps are TAI644871 # ~~ 2^62 + time()4872 my $stamp = '';4873 4874 # support tinydns' auto-TTL4875 $ttl = '' if $ttl == '0';4876 4877 # Spaces are evil.4878 $host =~ s/^\s+//;4879 $host =~ s/\s+$//;4880 if ($typemap{$type} ne 'TXT') {4881 # Leading or trailng spaces could be legit in TXT records.4882 $val =~ s/^\s+//;4883 $val =~ s/\s+$//;4884 }4885 4886 _printrec_tiny(*ZONECACHE, 'n', \%recflags,4887 $dom, $host, $type, $val, $dist, $weight, $port, $ttl, $loc, $stamp)4888 if *ZONECACHE;4889 4890 # in case the zone shrunk, get rid of garbage at the end of the file.4891 truncate(ZONECACHE, tell(ZONECACHE));4892 4893 $recflags{$recid} = 1;4894 } # while ($recsth)4895 }4896 # stream from cache, whether freshly created or existing4897 print $datafile $_ while <ZONECACHE>;4898 close ZONECACHE;4899 # mark domain as unmodified4900 $zonesth->execute($domid);4901 } # while ($domsth)4902 4903 my $revsth = $dbh->prepare("SELECT rdns_id,revnet,status,changed FROM revzones WHERE status=1 ".4904 "ORDER BY masklen(revnet) DESC");4905 4906 5011 # For reasons unknown, we can't sanely UNION these statements. Feh. 4907 5012 # Supposedly it should work though (note last 3 lines): … … 4918 5023 my $soasth = $dbh->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location ". 4919 5024 "FROM records WHERE rdns_id=? AND type=6"); 4920 $recsth = $dbh->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location".5025 my $recsth = $dbh->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location,extract(epoch from stamp),expires,stampactive ". 4921 5026 "FROM records WHERE rdns_id=? AND not type=6 ". 4922 5027 "ORDER BY masklen(CAST(val AS inet)) DESC, CAST(val AS inet)"); 4923 $zonesth = $dbh->prepare("UPDATE revzones SET changed='n' WHERE rdns_id=?"); 5028 my $revsth = $dbh->prepare("SELECT rdns_id,revnet,status,changed FROM revzones WHERE status=1 ". 5029 "ORDER BY masklen(revnet) DESC"); 5030 my $zonesth = $dbh->prepare("UPDATE revzones SET changed='n' WHERE rdns_id=?"); 4924 5031 $revsth->execute(); 4925 5032 while (my ($revid,$revzone,$revstat,$changed) = $revsth->fetchrow_array) { … … 4931 5038 # but that doesn't help other platforms. :/ 4932 5039 my $tmpzone = NetAddr::IP->new($revzone); 4933 sysopen(ZONECACHE, "$config{exportcache}/".$tmpzone->network->addr, O_RDWR|O_CREAT); 4934 flock(ZONECACHE, LOCK_EX); 4935 if ($changed || -s "$config{exportcache}/".$tmpzone->network->addr == 0) { 4936 # need to fetch this separately since the rest of the records all (should) have real IPs in val 4937 $soasth->execute($revid); 4938 my (@zsoa) = $soasth->fetchrow_array(); 4939 _printrec_tiny(*ZONECACHE,'y',\%recflags,$revzone, 4940 $zsoa[0],$zsoa[1],$zsoa[2],$zsoa[3],$zsoa[4],$zsoa[5],$zsoa[6],$zsoa[8],''); 4941 4942 $recsth->execute($revid); 4943 while (my ($host,$type,$val,$dist,$weight,$port,$ttl,$recid,$loc) = $recsth->fetchrow_array) { 4944 next if $recflags{$recid}; 4945 4946 $loc = '' if !$loc; # de-nullify - just in case 5040 ##fixme: locations/views? subnet mask? need to avoid possible collisions with zone/superzone 5041 ## (eg /20 vs /24, starting on .0.0) 5042 my $cz = $tmpzone->network->addr."-".$tmpzone->masklen; 5043 my $cachefile = "$self->{exportcache}/$cz"; 5044 my $tmpcache = "$self->{exportcache}/tmp.$cz.$$"; 5045 eval { 5046 5047 # only update the cache file if the zone has changed, or if the cache file has nothing in it. 5048 if ($self->{force_refresh} || $changed || !-e $cachefile || -z $cachefile) { 5049 open ZONECACHE, ">$tmpcache" or die "Error creating temporary file $tmpcache: $!\n"; 5050 5051 # need to fetch this separately since the rest of the records all (should) have real IPs in val 5052 $soasth->execute($revid); 5053 my (@zsoa) = $soasth->fetchrow_array(); 5054 _printrec_tiny(*ZONECACHE,'y',\%recflags,$revzone, 5055 $zsoa[0],$zsoa[1],$zsoa[2],$zsoa[3],$zsoa[4],$zsoa[5],$zsoa[6],$zsoa[8],''); 5056 5057 $recsth->execute($revid); 5058 while (my ($host,$type,$val,$dist,$weight,$port,$ttl,$recid,$loc,$stamp,$expires,$stampactive) = $recsth->fetchrow_array) { 5059 next if $recflags{$recid}; 5060 5061 # not sure this is necessary for revzones. 5062 # # Spaces are evil. 5063 # $val =~ s/^\s+//; 5064 # $val =~ s/\s+$//; 5065 # if ($typemap{$type} ne 'TXT') { 5066 # # Leading or trailng spaces could be legit in TXT records. 5067 # $host =~ s/^\s+//; 5068 # $host =~ s/\s+$//; 5069 # } 5070 5071 _printrec_tiny(*ZONECACHE, 'y', \%recflags, $revzone, 5072 $host, $type, $val, $dist, $weight, $port, $ttl, $loc, $stamp, $expires, $stampactive) 5073 if *ZONECACHE; 5074 5075 $recflags{$recid} = 1; 5076 5077 } # while ($recsth) 5078 5079 close ZONECACHE; # force the file to be written 5080 5081 # catch obvious write errors that leave an empty temp file 5082 if (-s $tmpcache) { 5083 rename $tmpcache, $cachefile 5084 or die "Error overwriting cache file $cachefile with temporary file: $!\n"; 5085 } 5086 5087 } # if $changed or cache filesize is 0 5088 5089 }; 5090 if ($@) { 5091 print "error writing new data for $revzone: $@\n"; 5092 # error! something borked, and we should be able to fall back on the old cache file 5093 # report the error, somehow. 5094 } else { 5095 # mark zone as unmodified. Only do this if no errors, that way 5096 # export failures should recover a little more automatically. 5097 $zonesth->execute($revid); 5098 } 5099 # Always stream the cache (even if stale or obsolete due to errors creating the new cache) 5100 open CACHE, "<$cachefile"; 5101 print $datafile $_ while <CACHE>; 5102 close CACHE; 5103 5104 } # while ($revsth) 5105 5106 my $domsth = $dbh->prepare("SELECT domain_id,domain,status,changed FROM domains WHERE status=1"); 5107 $recsth = $dbh->prepare("SELECT host,type,val,distance,weight,port,ttl,record_id,location,extract(epoch from stamp),expires,stampactive ". 5108 "FROM records WHERE domain_id=?"); # Just exclude all types relating to rDNS 5109 # "FROM records WHERE domain_id=? AND type < 65280"); # Just exclude all types relating to rDNS 5110 $zonesth = $dbh->prepare("UPDATE domains SET changed='n' WHERE domain_id=?"); 5111 $domsth->execute(); 5112 while (my ($domid,$dom,$domstat,$changed) = $domsth->fetchrow_array) { 5113 ##fixme: need to find a way to block opening symlinked files without introducing a race. 5114 # O_NOFOLLOW 5115 # If pathname is a symbolic link, then the open fails. This is a FreeBSD extension, which was 5116 # added to Linux in version 2.1.126. Symbolic links in earlier components of the pathname will 5117 # still be followed. 5118 # but that doesn't help other platforms. :/ 5119 my $cachefile = "$self->{exportcache}/$dom"; 5120 my $tmpcache = "$self->{exportcache}/tmp.$dom.$$"; 5121 eval { 5122 5123 # only update the cache file if the zone has changed, or if the cache file has nothing in it. 5124 if ($self->{force_refresh} || $changed || !-e $cachefile || -z $cachefile) { 5125 open ZONECACHE, ">$tmpcache" or die "Error creating temporary file $tmpcache: $!\n"; 5126 5127 $recsth->execute($domid); 5128 while (my ($host,$type,$val,$dist,$weight,$port,$ttl,$recid,$loc,$stamp,$expires,$stampactive) = $recsth->fetchrow_array) { 5129 next if $recflags{$recid}; 5130 5131 # Spaces are evil. 5132 $host =~ s/^\s+//; 5133 $host =~ s/\s+$//; 5134 if ($typemap{$type} ne 'TXT') { 5135 # Leading or trailng spaces could be legit in TXT records. 5136 $val =~ s/^\s+//; 5137 $val =~ s/\s+$//; 5138 } 5139 5140 _printrec_tiny(*ZONECACHE, 'n', \%recflags, 5141 $dom, $host, $type, $val, $dist, $weight, $port, $ttl, $loc, $stamp, $expires, $stampactive) 5142 if *ZONECACHE; 5143 5144 $recflags{$recid} = 1; 5145 5146 } # while ($recsth) 5147 5148 close ZONECACHE; # force the file to be written 5149 5150 # catch obvious write errors that leave an empty temp file 5151 if (-s $tmpcache) { 5152 rename $tmpcache, $cachefile 5153 or die "Error overwriting cache file $cachefile with temporary file: $!\n"; 5154 } 5155 5156 } # if $changed or cache filesize is 0 5157 5158 }; 5159 if ($@) { 5160 print "error writing new data for $dom: $@\n"; 5161 # error! something borked, and we should be able to fall back on the old cache file 5162 # report the error, somehow. 5163 } else { 5164 # mark domain as unmodified. Only do this if no errors, that way 5165 # export failures should recover a little more automatically. 5166 $zonesth->execute($domid); 5167 } 5168 # Always stream the cache (even if stale or obsolete due to errors creating the new cache) 5169 open CACHE, "<$cachefile"; 5170 print $datafile $_ while <CACHE>; 5171 close CACHE; 5172 5173 } # while ($domsth) 5174 5175 } # end __export_tiny() 5176 5177 5178 # Utility sub for __export_tiny above 5179 sub _printrec_tiny { 5180 my ($datafile,$revrec,$recflags,$zone,$host,$type,$val,$dist,$weight,$port,$ttl,$loc,$stamp,$expires,$stampactive) = @_; 5181 5182 $loc = '' if !$loc; # de-nullify - just in case 4947 5183 ##fixme: handle case of record-with-location-that-doesn't-exist better. 4948 5184 # note this currently fails safe (tested) - records with a location that … … 4950 5186 # $loc = '' if !$lochash->{$loc}; 4951 5187 4952 ##fixme: record validity timestamp. tinydns supports fiddling with timestamps. 4953 # note $ttl must be set to 0 if we want to use tinydns's auto-expiring timestamps. 4954 # timestamps are TAI64 4955 # ~~ 2^62 + time() 4956 my $stamp = ''; 4957 4958 # support tinydns' auto-TTL 4959 $ttl = '' if $ttl == '0'; 4960 4961 _printrec_tiny(*ZONECACHE, 'y', \%recflags, $revzone, 4962 $host, $type, $val, $dist, $weight, $port, $ttl, $loc, $stamp) 4963 if *ZONECACHE; 4964 4965 # in case the zone shrunk, get rid of garbage at the end of the file. 4966 truncate(ZONECACHE, tell(ZONECACHE)); 4967 4968 $recflags{$recid} = 1; 4969 } # while ($recsth) 4970 } 4971 # stream from cache, whether freshly created or existing 4972 print $datafile $_ while <ZONECACHE>; 4973 close ZONECACHE; 4974 # mark zone as unmodified 4975 $zonesth->execute($revid); 4976 } # while ($domsth) 4977 4978 } # end __export_tiny() 4979 4980 4981 # Utility sub for __export_tiny above 4982 sub _printrec_tiny { 4983 my ($datafile,$revrec,$recflags,$zone,$host,$type,$val,$dist,$weight,$port,$ttl,$loc,$stamp) = @_; 5188 5189 ## Records that are valid only before or after a set time 5190 5191 # record due to expire sometime is the complex case. we don't want to just 5192 # rely on tinydns' auto-adjusting TTLs, because the default TTL in that case 5193 # is one day instead of the SOA minttl as BIND might do. 5194 5195 # consider the case where a record is set to expire a week ahead, but the next 5196 # day later you want to change it NOW (or as NOWish as you get with your DNS 5197 # management practice). but now you're stuck, because someone, somewhere, 5198 # has just done a lookup before your latest change was published, and they'll 5199 # be caching that old, broken record for 1 day instead of your zone default 5200 # TTL. 5201 5202 # $stamp-$ttl is the *latest* we can publish the record with the defined TTL 5203 # to still have the expiry happen as scheduled, but we need to find some 5204 # *earlier* point. We can maybe guess, and 2x TTL is probably reasonable, 5205 # but we need info on the export frequency. 5206 5207 # export the normal, non-expiring record up until $stamp-<guesstimate>, then 5208 # switch to exporting a record with the TAI64 stamp and a 0 TTL so tinydns 5209 # takes over TTL management. 5210 5211 if ($stampactive) { 5212 if ($expires) { 5213 # record expires at $stamp; decide if we need to keep the TTL and ignore 5214 # the stamp for a time or if we need to change the TTL to 0 and convert 5215 # $stamp to TAI64 so tinydns can use $stamp to autoadjust the TTL on the fly. 5216 # extra hack, optimally needs more knowledge of data export frequency 5217 # smack the idiot customer who insists on 0 TTLs; they can suck up and 5218 # deal with a 10-minute TTL. especially on scheduled changes. note this 5219 # should be (export freq * 2), but we don't know the actual export frequency. 5220 $ttl = 300 if $ttl == 0; #hack phtui 5221 my $ahead = (86400 < $ttl*2 ? 86400 : $ttl*2); 5222 if ((time() + $ahead) < $stamp) { 5223 # more than 2x TTL OR more than one day (whichever is less) from expiry time; publish normal record 5224 $stamp = ''; 5225 } else { 5226 # less than 2x TTL from expiry time, let tinydns take over TTL management and publish the TAI64 stamp. 5227 $ttl = 0; 5228 $stamp = unixtai64($stamp); 5229 $stamp =~ s/\@//; 5230 } 5231 } else { 5232 # record is "active after"; convert epoch from database to TAI64, publish, and collect $200. 5233 $stamp = unixtai64($stamp); 5234 $stamp =~ s/\@//; 5235 } 5236 } else { 5237 # flag for active timestamp is false; don't actually put a timestamp in the output 5238 $stamp = ''; 5239 } 5240 5241 # support tinydns' auto-TTL 5242 $ttl = '' if $ttl == -1; 5243 # these are WAY FREAKING HIGH - higher even than most TLD registry TTLs! 5244 # NS 259200 => 3d 5245 # all others 86400 => 1d 5246 5247 if ($revrec eq 'y') { 5248 $val = $zone if $val eq '@'; 5249 } else { 5250 $host = $zone if $host eq '@'; 5251 } 4984 5252 4985 5253 ## Convert a bare number into an octal-coded pair of octets. … … 5202 5470 } elsif ($type == 65281) { # AAAA+PTR 5203 5471 5204 #$$recflags{$val}++;5472 $$recflags{$val}++; 5205 5473 # treat these as two separate records. since tinydns doesn't have 5206 5474 # a native combined type, we have to create them separately anyway. 5207 if ($revrec eq 'n') { 5208 $type = 28; 5209 } else { 5210 $type = 12; 5211 } 5212 _printrec_tiny($datafile,$revrec,$recflags,$zone,$host,$type,$val,$dist,$weight,$port,$ttl,$loc,$stamp); 5475 # print both; a dangling record is harmless, and impossible via web 5476 # UI anyway 5477 _printrec_tiny($datafile,$revrec,$recflags,$zone,$host,28,$val,$dist,$weight,$port,$ttl,$loc,$stamp); 5478 _printrec_tiny($datafile,$revrec,$recflags,$zone,$host,12,$val,$dist,$weight,$port,$ttl,$loc,$stamp); 5213 5479 ##fixme: add a config flag to indicate use of the patch from http://www.fefe.de/dns/ 5214 5480 # type 6 is for AAAA+PTR, type 3 is for AAAA … … 5309 5575 my ($subj,$message) = @_; 5310 5576 5311 return if $ config{mailhost} eq 'smtp.example.com'; # do nothing if still using default SMTP host.5312 5313 my $mailer = Net::SMTP->new($ config{mailhost}, Hello => "dnsadmin.$config{domain}");5314 5315 my $mailsender = ($ config{mailsender} ? $config{mailsender} : $config{mailnotify});5577 return if $self->{mailhost} eq 'smtp.example.com'; # do nothing if still using default SMTP host. 5578 5579 my $mailer = Net::SMTP->new($self->{mailhost}, Hello => "dnsadmin.$self->{domain}"); 5580 5581 my $mailsender = ($self->{mailsender} ? $self->{mailsender} : $self->{mailnotify}); 5316 5582 5317 5583 $mailer->mail($mailsender); 5318 $mailer->to($ config{mailnotify});5319 $mailer->data("From: \"$ config{mailname}\" <$mailsender>\n",5320 "To: <$ config{mailnotify}>\n",5584 $mailer->to($self->{mailnotify}); 5585 $mailer->data("From: \"$self->{mailname}\" <$mailsender>\n", 5586 "To: <$self->{mailnotify}>\n", 5321 5587 "Date: ".strftime("%a, %d %b %Y %H:%M:%S %z",localtime)."\n", 5322 5588 "Subject: $subj\n", 5323 "X-Mailer: DNSAdmin Notify v".sprintf("%.1d",$DNSDB::VERSION)."\n",5324 "Organization: $ config{orgname}\n",5589 "X-Mailer: DNSAdmin v".$DNSDB::VERSION." Notify\n", 5590 "Organization: $self->{orgname}\n", 5325 5591 "\n$message\n"); 5326 5592 $mailer->quit;
Note:
See TracChangeset
for help on using the changeset viewer.