initial prototype
This commit is contained in:
commit
5858499113
5 changed files with 635 additions and 0 deletions
71
dns.d/mythicdns.template.erb
Normal file
71
dns.d/mythicdns.template.erb
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#
|
||||||
|
# Nameserver records.
|
||||||
|
#
|
||||||
|
@ <%= ttl %> NS ns1.mythic-beasts.com.
|
||||||
|
@ <%= ttl %> NS ns2.mythic-beasts.com.
|
||||||
|
|
||||||
|
% if ipv4?
|
||||||
|
#
|
||||||
|
# The domain name itself
|
||||||
|
#
|
||||||
|
@ <%= ttl %> A <%= ip %>
|
||||||
|
ftp <%= ttl %> A <%= ip %>
|
||||||
|
www <%= ttl %> A <%= ip %>
|
||||||
|
mail <%= ttl %> A <%= ip %>
|
||||||
|
mx <%= ttl %> A <%= ip %>
|
||||||
|
|
||||||
|
|
||||||
|
% end
|
||||||
|
% if ipv6?
|
||||||
|
|
||||||
|
@ <%= ttl %> AAAA <%= ipv6 %>
|
||||||
|
ftp <%= ttl %> AAAA <%= ipv6 %>
|
||||||
|
www <%= ttl %> AAAA <%= ipv6 %>
|
||||||
|
mail <%= ttl %> AAAA <%= ipv6 %>
|
||||||
|
mx <%= ttl %> AAAA <%= ipv6 %>
|
||||||
|
% end
|
||||||
|
#
|
||||||
|
# MX record -- no IP defined, as this is done separately above.
|
||||||
|
#
|
||||||
|
@ <%= ttl %> MX 15 mx
|
||||||
|
|
||||||
|
% if domain.respond_to?(:has_spf?) and domain.has_spf?
|
||||||
|
#
|
||||||
|
# SPF records
|
||||||
|
#
|
||||||
|
@ <%= ttl %> TXT <%= domain.spf_record %>
|
||||||
|
|
||||||
|
% end
|
||||||
|
% if domain.respond_to?(:has_dkim?) and domain.has_dkim?
|
||||||
|
#
|
||||||
|
# DKIM records
|
||||||
|
#
|
||||||
|
<%= domain.dkim_selector %>._domainkey <%= ttl %> TXT v=DKIM1; k=rsa; p=<%= domain.dkim_public_key_b64 %>
|
||||||
|
|
||||||
|
% end
|
||||||
|
% if domain.respond_to?(:has_dmarc?) and domain.has_dmarc?
|
||||||
|
#
|
||||||
|
# DMARC records
|
||||||
|
#
|
||||||
|
_dmarc <%= ttl %> TXT <%= domain.dmarc_record %>
|
||||||
|
|
||||||
|
% end
|
||||||
|
% if domain.respond_to?(:has_xmpp?) and domain.has_xmpp?
|
||||||
|
#
|
||||||
|
# SRV records for XMPP.
|
||||||
|
#
|
||||||
|
_xmpp-client._tcp <%= ttl %> SRV <%= domain.srv_record_for(0,5,5222, domain) %>
|
||||||
|
_xmpp-server._tcp <%= ttl %> SRV<%= domain.srv_record_for(0,5,5269, domain) %>
|
||||||
|
|
||||||
|
% end
|
||||||
|
% if domain.respond_to?(:mailboxes) and domain.mailboxes.length > 0
|
||||||
|
#
|
||||||
|
# SRV records for various mail services
|
||||||
|
#
|
||||||
|
_submission._tcp <%= ttl %> SRV <%= domain.srv_record_for(0,5,587, "mail."+domain) %>
|
||||||
|
_imap._tcp <%= ttl %> SRV <%= domain.srv_record_for(0,5,143, "mail."+domain) %>
|
||||||
|
_imaps._tcp <%= ttl %> SRV <%= domain.srv_record_for(0,5,993, "mail."+domain) %>
|
||||||
|
_pop3._tcp <%= ttl %> SRV <%= domain.srv_record_for(10,5,110, "mail."+domain) %>
|
||||||
|
_pop3s._tcp <%= ttl %> SRV <%= domain.srv_record_for(10,5,995, "mail."+domain) %>
|
||||||
|
|
||||||
|
% end
|
77
lib/symbiosis/config_files/mythicdns.rb
Normal file
77
lib/symbiosis/config_files/mythicdns.rb
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
require 'symbiosis/config_file'
|
||||||
|
require 'symbiosis/domain/dns'
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
module Symbiosis
|
||||||
|
module ConfigFiles
|
||||||
|
class Tinydns < Symbiosis::ConfigFile
|
||||||
|
|
||||||
|
def ok?
|
||||||
|
#
|
||||||
|
# TODO: parse the dns file and make sure it is sane.
|
||||||
|
#
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
###################################################
|
||||||
|
#
|
||||||
|
# The following methods are used in the template.
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return just the first IPv4.
|
||||||
|
#
|
||||||
|
def ip
|
||||||
|
ip = @domain.ipv4.first
|
||||||
|
warn "\tUsing one IP (#{ip}) where the domain has more than one configured!" if @domain.ipv4.length > 1 and $VERBOSE
|
||||||
|
raise ArgumentError, "No IPv4 addresses defined for this domain" if ip.nil?
|
||||||
|
|
||||||
|
ip.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns the domain's TTL
|
||||||
|
#
|
||||||
|
def ttl
|
||||||
|
@domain.ttl.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns true if the domain has an IPv4 address configured.
|
||||||
|
#
|
||||||
|
def ipv4?
|
||||||
|
!@domain.ipv4.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return just the first IPv6, in the tinydns format, i.e. in full with no colons.
|
||||||
|
#
|
||||||
|
def ipv6
|
||||||
|
ip = @domain.ipv6.first
|
||||||
|
warn "\tUsing one IP (#{ip}) where the domain has more than one configured!" if @domain.ipv6.length > 1 and $VERBOSE
|
||||||
|
raise ArgumentError, "No IPv6 addresses defined for this domain" if ip.nil?
|
||||||
|
ip.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns true if the domain has an IPv6 address configured.
|
||||||
|
#
|
||||||
|
def ipv6?
|
||||||
|
!@domain.ipv6.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
class Eruby < ::Erubis::Eruby
|
||||||
|
include Erubis::EscapeEnhancer
|
||||||
|
include Erubis::PercentLineEnhancer
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
self.erb = Eruby
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
119
lib/symbiosis/domain/dns.rb
Normal file
119
lib/symbiosis/domain/dns.rb
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
require 'symbiosis/domain/dkim'
|
||||||
|
|
||||||
|
module Symbiosis
|
||||||
|
|
||||||
|
class Domain
|
||||||
|
|
||||||
|
#
|
||||||
|
# This now returns false as the service has been withdrawn.
|
||||||
|
#
|
||||||
|
def uses_bytemark_antispam?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns true if a domain has SPF enabled.
|
||||||
|
#
|
||||||
|
def spf_enabled?
|
||||||
|
spf_record.is_a?(String)
|
||||||
|
end
|
||||||
|
|
||||||
|
alias has_spf? spf_enabled?
|
||||||
|
|
||||||
|
def spf_record
|
||||||
|
spf = get_param("spf", self.config_dir)
|
||||||
|
spf = "v=spf1 +a +mx ?all" if spf === true
|
||||||
|
|
||||||
|
if spf.is_a?(String)
|
||||||
|
# We encode just the first line, and remove any whitespace from the ends.
|
||||||
|
line = spf.split($/).first.strip
|
||||||
|
|
||||||
|
tinydns_encode(line)
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def srv_record_for(priority, weight, port, target)
|
||||||
|
data = ([priority, weight, port].pack("nnn").bytes.to_a +
|
||||||
|
target.split(".").collect{|x| [x.length, x]} +
|
||||||
|
[ 0 ]).flatten
|
||||||
|
data.collect{|x| tinydns_encode(x)}.join
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns the DNS TTL as defined in config/ttl, or 300 if no TTL has been set.
|
||||||
|
#
|
||||||
|
def ttl
|
||||||
|
ttl = get_param("ttl", self.config_dir)
|
||||||
|
if ttl.is_a?(String) and ttl =~ /([0-9]+)/
|
||||||
|
begin
|
||||||
|
ttl = Integer($1)
|
||||||
|
rescue ArgumentError
|
||||||
|
ttl = 300
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ttl = 300
|
||||||
|
end
|
||||||
|
|
||||||
|
if ttl < 60
|
||||||
|
ttl = 60
|
||||||
|
elsif ttl > 86400
|
||||||
|
ttl = 86400
|
||||||
|
end
|
||||||
|
|
||||||
|
ttl
|
||||||
|
end
|
||||||
|
|
||||||
|
def dmarc_enabled?
|
||||||
|
dmarc_record.is_a?(String)
|
||||||
|
end
|
||||||
|
|
||||||
|
alias has_dmarc? dmarc_enabled?
|
||||||
|
|
||||||
|
#
|
||||||
|
# Returns a DMARC record, based on various arguments in config/dmarc
|
||||||
|
#
|
||||||
|
def dmarc_record
|
||||||
|
raw_dmarc = get_param("dmarc", self.config_dir)
|
||||||
|
|
||||||
|
return nil unless raw_dmarc
|
||||||
|
|
||||||
|
return 'v=DMARC1; p=quarantine; sp=none' if true == raw_dmarc
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make sure we're not matching against things other than strings.
|
||||||
|
#
|
||||||
|
return nil unless raw_dmarc.is_a?(String)
|
||||||
|
|
||||||
|
if raw_dmarc =~ /^(v=DMARC\d(;\s+\S+=[^;]+)+)/
|
||||||
|
# Take this as a raw record
|
||||||
|
return tinydns_encode($1)
|
||||||
|
end
|
||||||
|
|
||||||
|
puts "\tThe DMARC record looks wrong: #{raw_dmarc.inspect}" if $VERBOSE
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
#
|
||||||
|
# Encodes a given string into a format suitable for consupmtion by TinyDNS
|
||||||
|
#
|
||||||
|
def tinydns_encode(s)
|
||||||
|
s = [s].pack("c") if (s.is_a?(Integer) and 255 > s)
|
||||||
|
|
||||||
|
s.chars.collect{|c| c =~ /[\w .=+;?-]/ ? c : c.bytes.collect{|b| "\\%03o" % b}.join}.join
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Decodes a given string from a format suitable for consupmtion by TinyDNS
|
||||||
|
#
|
||||||
|
def tinydns_decode(s)
|
||||||
|
s.gsub(/(?:\\([0-7]{3,3})|.)/){|r| $1 ? [$1.oct].pack("c*") : r}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
258
sbin/symbiosis-dns-generate
Executable file
258
sbin/symbiosis-dns-generate
Executable file
|
@ -0,0 +1,258 @@
|
||||||
|
#!/usr/bin/ruby
|
||||||
|
#
|
||||||
|
# NAME
|
||||||
|
#
|
||||||
|
# symbiosis-dns-generate - Generate DNS snippet files for Symbiosis domains.
|
||||||
|
#
|
||||||
|
# USAGE
|
||||||
|
#
|
||||||
|
# symbiosis-dns-generate [ --sleep SEC | -s SEC ] [ --template TEMPLATE | -t TEMPLATE ]
|
||||||
|
# [ --force | -f ] [ --verbose | -v ]
|
||||||
|
# [ --help | -h ] [ DOMAIN ]
|
||||||
|
#
|
||||||
|
# SYNOPSIS
|
||||||
|
#
|
||||||
|
# --sleep SEC sleep for a random amount of time before doing
|
||||||
|
# anything, up to a maximum of SEC seconds.
|
||||||
|
#
|
||||||
|
# --template TEMPLATE Specify an alternative template file to read.
|
||||||
|
#
|
||||||
|
# --force Force the re-creation of all DNS data.
|
||||||
|
# --upload Force the upload of all DNS data.
|
||||||
|
# --help Show the help information for this script.
|
||||||
|
# --verbose Show debugging information.
|
||||||
|
#
|
||||||
|
# DETAILS
|
||||||
|
#
|
||||||
|
# This script is designed to iterate over the domains hosted upon a Symbiosis
|
||||||
|
# system, and create TinyDNS snippets for each one. This can then be uploaded
|
||||||
|
# to the Bytemark content DNS service.
|
||||||
|
#
|
||||||
|
# Domains can also be specified manually on the command line, in which case
|
||||||
|
# only those domains will be processed.
|
||||||
|
#
|
||||||
|
# AUTHOR
|
||||||
|
#
|
||||||
|
# Steve Kemp <steve@bytemark.co.uk>
|
||||||
|
# Adapted for Mythic Beasts by Ben Charlton <ben@spod.cx>
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
require 'getoptlong'
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Entry point to the code
|
||||||
|
#
|
||||||
|
force = false
|
||||||
|
help = false
|
||||||
|
$VERBOSE = false
|
||||||
|
|
||||||
|
#
|
||||||
|
# Do we need to re-upload the data?
|
||||||
|
#
|
||||||
|
upload=true
|
||||||
|
|
||||||
|
#
|
||||||
|
# The root directory -- '/' by default.
|
||||||
|
#
|
||||||
|
root = "/"
|
||||||
|
dns_template = nil
|
||||||
|
sleep_for = nil
|
||||||
|
|
||||||
|
opts = GetoptLong.new(
|
||||||
|
[ '--force', '-f', GetoptLong::NO_ARGUMENT ],
|
||||||
|
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
||||||
|
[ '--sleep', '-s', GetoptLong::REQUIRED_ARGUMENT ],
|
||||||
|
[ '--template', '-t', GetoptLong::REQUIRED_ARGUMENT ],
|
||||||
|
[ '--upload', '-u', GetoptLong::NO_ARGUMENT ],
|
||||||
|
[ '--verbose', '-v', GetoptLong::NO_ARGUMENT ]
|
||||||
|
)
|
||||||
|
|
||||||
|
opts.each do |opt, arg|
|
||||||
|
case opt
|
||||||
|
when '--help'
|
||||||
|
help = true
|
||||||
|
when '--verbose'
|
||||||
|
$VERBOSE = true
|
||||||
|
when '--template'
|
||||||
|
dns_template = arg
|
||||||
|
when '--sleep'
|
||||||
|
sleep_for = arg
|
||||||
|
when '--root'
|
||||||
|
root = arg
|
||||||
|
when '--force'
|
||||||
|
force = true
|
||||||
|
when '--upload'
|
||||||
|
upload = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# CAUTION! Here be quality kode.
|
||||||
|
#
|
||||||
|
if help
|
||||||
|
# Open the file, stripping the shebang line
|
||||||
|
lines = File.open(__FILE__){|fh| fh.readlines}[2..-1]
|
||||||
|
|
||||||
|
lines.each do |line|
|
||||||
|
line.chomp!
|
||||||
|
break if line.empty?
|
||||||
|
puts line[2..-1].to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def verbose(s)
|
||||||
|
puts s if $VERBOSE
|
||||||
|
end
|
||||||
|
|
||||||
|
require 'symbiosis/domains'
|
||||||
|
require 'symbiosis/domain'
|
||||||
|
require 'symbiosis/config_files/mythicdns'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Set the default paths.
|
||||||
|
#
|
||||||
|
dns_template = File.join(root, "/etc/symbiosis/dns.d/mythicdns.template.erb") if dns_template.nil?
|
||||||
|
|
||||||
|
#
|
||||||
|
# Bail out if the template is missing
|
||||||
|
#
|
||||||
|
unless File.file?(dns_template)
|
||||||
|
verbose "Unable generate DNS data because the template #{dns_template.inspect} is missing."
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Work out if we need to sleep.
|
||||||
|
#
|
||||||
|
unless sleep_for.nil?
|
||||||
|
sleep_for = (sleep_for =~ /(\d+)/ ? rand($1.to_i) : 0)
|
||||||
|
verbose "Sleeping for #{sleep_for}s before starting work"
|
||||||
|
sleep sleep_for
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# Any arguments on the command line specify which domains to do.
|
||||||
|
#
|
||||||
|
domains_to_configure = ARGV
|
||||||
|
string_to_hash = []
|
||||||
|
|
||||||
|
#
|
||||||
|
# For each domain.
|
||||||
|
#
|
||||||
|
Symbiosis::Domains.each do |domain|
|
||||||
|
|
||||||
|
verbose "Domain: #{domain.name} "
|
||||||
|
|
||||||
|
next unless domains_to_configure.empty? or domains_to_configure.include?(domain.name)
|
||||||
|
|
||||||
|
begin
|
||||||
|
output = File.join(domain.config_dir, "dns", domain.name+".txt")
|
||||||
|
output_dir = File.dirname(output)
|
||||||
|
config = Symbiosis::ConfigFiles::Tinydns.new(output, "#")
|
||||||
|
config.domain = domain
|
||||||
|
config.template = dns_template
|
||||||
|
|
||||||
|
#
|
||||||
|
# Should the snippet be created?
|
||||||
|
#
|
||||||
|
do_create = false
|
||||||
|
|
||||||
|
if ( force )
|
||||||
|
verbose "\tForcing re-creation of snippet due to --force."
|
||||||
|
do_create = true
|
||||||
|
|
||||||
|
elsif config.exists?
|
||||||
|
|
||||||
|
if config.changed?
|
||||||
|
verbose "\tNot updating snippet, as it has been edited by hand."
|
||||||
|
|
||||||
|
elsif config.outdated?
|
||||||
|
verbose "\tRe-creating snippet as it is out of date."
|
||||||
|
do_create = true
|
||||||
|
|
||||||
|
else
|
||||||
|
verbose "\tDomain already present and up-to date."
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
verbose "\tConfiguring site for the first time"
|
||||||
|
do_create = true
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Check the TinyDNS syntax.. TODO!
|
||||||
|
#
|
||||||
|
if do_create
|
||||||
|
if config.ok?
|
||||||
|
|
||||||
|
verbose "\tWriting snippet to #{output}"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create directory with the same ownership as the parent
|
||||||
|
#
|
||||||
|
domain.create_dir(output_dir) unless File.exist?(output_dir)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Write the snippet
|
||||||
|
#
|
||||||
|
config.write
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make sure the ownership is correct.
|
||||||
|
#
|
||||||
|
File.chown(domain.uid, domain.gid, config.filename)
|
||||||
|
|
||||||
|
else
|
||||||
|
verbose "\tThe new DNS snippet is invalid -- no changes have been made."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rescue errors for this domain, but continue for others.
|
||||||
|
#
|
||||||
|
rescue StandardError => err
|
||||||
|
verbose "\tUnable to create DNS data for #{domain.name} because #{err.to_s}"
|
||||||
|
verbose "\t"+err.backtrace.join("\n\t")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
if upload
|
||||||
|
upload_script = "/usr/sbin/symbiosis-mythic-dns"
|
||||||
|
|
||||||
|
verbose "Uploading using #{upload_script}"
|
||||||
|
|
||||||
|
IO.popen("#{upload_script} 2>&1","r") do |io|
|
||||||
|
while !io.eof? do
|
||||||
|
verbose io.readline
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unless 0 == $?
|
||||||
|
raise StandardError, "#{upload_script.inspect} failed."
|
||||||
|
end
|
||||||
|
|
||||||
|
else
|
||||||
|
verbose "No need to upload as no changes in the data have been detected."
|
||||||
|
end
|
||||||
|
rescue StandardError => err
|
||||||
|
warn "Unable to upload DNS data because #{err.to_s}"
|
||||||
|
verbose "\t"+err.backtrace.join("\n\t")
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
#
|
||||||
|
# All done.
|
||||||
|
#
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
110
sbin/symbiosis-mythic-dns
Executable file
110
sbin/symbiosis-mythic-dns
Executable file
|
@ -0,0 +1,110 @@
|
||||||
|
#!/usr/bin/perl -w
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use WWW::Mechanize;
|
||||||
|
use Getopt::Std;
|
||||||
|
|
||||||
|
our ($opt_v, $opt_f);
|
||||||
|
getopts('vf');
|
||||||
|
|
||||||
|
my $domaindir = "/srv";
|
||||||
|
my $url = 'https://dnsapi.mythic-beasts.com/';
|
||||||
|
|
||||||
|
sub upload_dns($$$) {
|
||||||
|
my ($domain, $dnsfile, $password) = @_;
|
||||||
|
|
||||||
|
my $mech = WWW::Mechanize->new( autocheck => 0 );
|
||||||
|
|
||||||
|
my $response = $mech->post($url,
|
||||||
|
{ domain => $domain, password => $password, command => 'LIST' }
|
||||||
|
);
|
||||||
|
if (!$response->is_success()) {
|
||||||
|
warn $mech->content() ;
|
||||||
|
my $status = $response->status_line;
|
||||||
|
warn "status = $status\n";
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
my %existing;
|
||||||
|
foreach my $line (split /\n/, $mech->content()) {
|
||||||
|
$line =~ s/\s+$//;
|
||||||
|
$existing{$line} = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $update = 0;
|
||||||
|
open F, $dnsfile || die "Can't open $dnsfile";
|
||||||
|
my $commands = [ domain => $domain, password => $password ];
|
||||||
|
foreach my $record (<F>) {
|
||||||
|
chomp $record;
|
||||||
|
next if $record =~ m/^\s*\#/;
|
||||||
|
next if $record =~ m/^$/;
|
||||||
|
if (exists $existing{$record}) {
|
||||||
|
delete $existing{$record};
|
||||||
|
} else {
|
||||||
|
print "ADD $record\n" if ($opt_v);
|
||||||
|
push @$commands, ("command", "ADD $record");
|
||||||
|
$update++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach my $record (keys %existing) {
|
||||||
|
push @$commands, ("command", "DELETE $record");
|
||||||
|
print "DELETE $record\n" if ($opt_v);
|
||||||
|
$update++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($update) {
|
||||||
|
my $response = $mech->post($url,
|
||||||
|
$commands
|
||||||
|
);
|
||||||
|
return 1 if $response->is_success();
|
||||||
|
warn $mech->content() ;
|
||||||
|
my $status = $response->status_line;
|
||||||
|
warn "status = $status\n";
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
opendir(my $dh, $domaindir) || die "can't opendir $domaindir: $!";
|
||||||
|
while (my $d = readdir($dh)) {
|
||||||
|
|
||||||
|
my $target = "$domaindir/$d";
|
||||||
|
my $passwordfile = "$target/config/dns/mbpassword";
|
||||||
|
my $lastfile = "$target/config/dns/.lastuploaded";
|
||||||
|
my $dnsfile = "$domaindir/$d/config/dns/$d.txt";
|
||||||
|
|
||||||
|
# Does this look like a valid domain?
|
||||||
|
if (-d $target && -f $passwordfile) {
|
||||||
|
print "$d\n" if ($opt_v);
|
||||||
|
|
||||||
|
# ALWAYS restrict the password file.
|
||||||
|
chmod 0600, $passwordfile;
|
||||||
|
open F, $passwordfile;
|
||||||
|
my $password = <F>;
|
||||||
|
close F;
|
||||||
|
chomp($password);
|
||||||
|
|
||||||
|
# Check when the last successful upload was
|
||||||
|
my $laststamp = 0;
|
||||||
|
if (-e $lastfile) {
|
||||||
|
$laststamp = (stat($lastfile))[9];
|
||||||
|
}
|
||||||
|
my $tstamp = (stat($dnsfile))[9];
|
||||||
|
print "last uploaded $laststamp, last generated $tstamp\n" if ($opt_v);
|
||||||
|
# and upload if generated file is newer (or forced)
|
||||||
|
if ( ($opt_f) || ($tstamp > $laststamp)) {
|
||||||
|
print "Uploading...\n" if ($opt_v);
|
||||||
|
my $success = upload_dns($d, $dnsfile, $password);
|
||||||
|
if ($success) {
|
||||||
|
# only update lastfile on success
|
||||||
|
open F, ">", $lastfile;
|
||||||
|
close F;
|
||||||
|
utime(undef, undef, $lastfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir $dh;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue