Delegation: new Edit and Reset mechanisms.
This commit is contained in:
parent
7750052aed
commit
99f94a30e7
3 changed files with 150 additions and 31 deletions
|
|
@ -18,4 +18,44 @@ class DNSManager::Request
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
DNSManager.requests << DelegateDomain
|
DNSManager.requests << DelegateDomain
|
||||||
|
|
||||||
|
IPC::JSON.message EditDelegation, 26 do
|
||||||
|
property domain : String
|
||||||
|
property nameserver1 : String
|
||||||
|
property nameserver2 : String
|
||||||
|
def initialize(@domain, @nameserver1, @nameserver2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io : IO)
|
||||||
|
super io
|
||||||
|
io << " (domain: #{@domain}, ns1: #{@nameserver1}, ns2: #{@nameserver2}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(dnsmanagerd : DNSManager::Service, event : IPC::Event) : IPC::JSON
|
||||||
|
user = dnsmanagerd.get_logged_user event
|
||||||
|
return Response::ErrorUserNotLogged.new unless user
|
||||||
|
dnsmanagerd.storage.edit_delegation user.uid, @domain, @nameserver1, @nameserver2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
DNSManager.requests << EditDelegation
|
||||||
|
|
||||||
|
IPC::JSON.message ResetDelegation, 27 do
|
||||||
|
property domain : String
|
||||||
|
property nameserver1 : String
|
||||||
|
property nameserver2 : String
|
||||||
|
def initialize(@domain, @nameserver1, @nameserver2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s(io : IO)
|
||||||
|
super io
|
||||||
|
io << " (domain: #{@domain}, ns1: #{@nameserver1}, ns2: #{@nameserver2}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle(dnsmanagerd : DNSManager::Service, event : IPC::Event) : IPC::JSON
|
||||||
|
user = dnsmanagerd.get_logged_user event
|
||||||
|
return Response::ErrorUserNotLogged.new unless user
|
||||||
|
dnsmanagerd.storage.reset_delegation user.uid, @domain
|
||||||
|
end
|
||||||
|
end
|
||||||
|
DNSManager.requests << ResetDelegation
|
||||||
end
|
end
|
||||||
|
|
|
||||||
140
src/storage.cr
140
src/storage.cr
|
|
@ -24,7 +24,7 @@ class DNSManager::Storage
|
||||||
|
|
||||||
getter root : String
|
getter root : String
|
||||||
getter zonefiledir : String
|
getter zonefiledir : String
|
||||||
getter delegationdir : String
|
getter delegationdir : String
|
||||||
|
|
||||||
property dnsmanagerd : DNSManager::Service? = nil
|
property dnsmanagerd : DNSManager::Service? = nil
|
||||||
|
|
||||||
|
|
@ -165,34 +165,24 @@ class DNSManager::Storage
|
||||||
Response::GeneratedZone.new domain, (String.new io.buffer, io.pos)
|
Response::GeneratedZone.new domain, (String.new io.buffer, io.pos)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a new domain.
|
def remove_first_label (domain : String) : String
|
||||||
def new_domain(user_id : UserDataID, domain : String) : IPC::JSON
|
domain.gsub /^[^.]+./, ""
|
||||||
|
end
|
||||||
|
|
||||||
|
# `has_valid_tld?` takes a domain and returns its TLD if it matches one of the available TLD.
|
||||||
|
def has_valid_tld? (domain : String) : String?
|
||||||
accepted_domains = dnsmanagerd.configuration.accepted_domains.not_nil!
|
accepted_domains = dnsmanagerd.configuration.accepted_domains.not_nil!
|
||||||
|
|
||||||
|
new_domain_tld = remove_first_label domain
|
||||||
|
|
||||||
|
tld = accepted_domains & [ new_domain_tld ]
|
||||||
|
|
||||||
|
return tld.pop unless tld.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Provides a zone based on a template (selected based on the tld).
|
||||||
|
def get_new_zone(tld : String, domain : String) : Zone
|
||||||
template_directory = dnsmanagerd.configuration.template_directory
|
template_directory = dnsmanagerd.configuration.template_directory
|
||||||
|
|
||||||
# Prevent future very confusing errors.
|
|
||||||
domain = domain.downcase
|
|
||||||
|
|
||||||
return Response::DomainAlreadyExists.new if zones_by_domain.get? domain
|
|
||||||
|
|
||||||
# Verify if the domain is acceptable.
|
|
||||||
matching_domains = accepted_domains.select { |adomain| domain.ends_with? adomain }
|
|
||||||
unless matching_domains.size > 0
|
|
||||||
Baguette::Log.warning "trying to add an unacceptable domain: '#{domain}'"
|
|
||||||
return Response::UnacceptableDomain.new
|
|
||||||
end
|
|
||||||
|
|
||||||
matching_domains.each do |md|
|
|
||||||
# Prevent empty domains (from crafted requests) to be accepted.
|
|
||||||
return Response::InvalidDomainName.new unless (domain.chomp md).size > 1
|
|
||||||
#Baguette::Log.info "Add new domain #{domain} (matching domain #{md})"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Verify the domain name validity.
|
|
||||||
return Response::InvalidDomainName.new unless Zone.is_domain_valid? domain
|
|
||||||
|
|
||||||
# Fill a template zone.
|
|
||||||
tld = matching_domains.pop
|
|
||||||
default_zone = Zone.from_json File.read "#{template_directory}/#{tld}.json"
|
default_zone = Zone.from_json File.read "#{template_directory}/#{tld}.json"
|
||||||
|
|
||||||
# Replace domain by the real domain.
|
# Replace domain by the real domain.
|
||||||
|
|
@ -204,17 +194,43 @@ class DNSManager::Storage
|
||||||
# configuration.
|
# configuration.
|
||||||
default_zone.reset_serial
|
default_zone.reset_serial
|
||||||
|
|
||||||
|
default_zone
|
||||||
|
end
|
||||||
|
|
||||||
|
# Adds a new domain.
|
||||||
|
def new_domain(user_id : UserDataID, domain : String) : IPC::JSON
|
||||||
|
|
||||||
|
# Prevent future very confusing errors.
|
||||||
|
domain = domain.downcase
|
||||||
|
|
||||||
|
# First test: verify the domain name validity.
|
||||||
|
return Response::InvalidDomainName.new unless Zone.is_domain_valid? domain
|
||||||
|
|
||||||
|
# Second test: check on the TLD.
|
||||||
|
# This also rejects empty domains and a few other invalid names as a side effect.
|
||||||
|
new_domain_tld = has_valid_tld? domain
|
||||||
|
if new_domain_tld.nil?
|
||||||
|
Baguette::Log.warning "trying to add an unacceptable domain: '#{domain}'"
|
||||||
|
return Response::UnacceptableDomain.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# Last check: verify the availability of the domain name.
|
||||||
|
return Response::DomainAlreadyExists.new if zones_by_domain.get? domain
|
||||||
|
|
||||||
#
|
#
|
||||||
# Actually write data on-disk.
|
# Actually write data on-disk.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
Baguette::Log.debug "Add new domain #{domain} (matching domain #{new_domain_tld})"
|
||||||
|
|
||||||
# Add the new domain.
|
# Add the new domain.
|
||||||
the_new_domain = Domain.new domain
|
the_new_domain = Domain.new domain
|
||||||
the_new_domain.owners = [user_id]
|
the_new_domain.owners = [user_id]
|
||||||
@domains << the_new_domain
|
@domains << the_new_domain
|
||||||
|
|
||||||
# Add the new zone in the database.
|
# Fill a template zone then add it to the database.
|
||||||
update_zone default_zone
|
the_new_zone = get_new_zone new_domain_tld, domain
|
||||||
|
update_zone the_new_zone
|
||||||
|
|
||||||
Response::DomainAdded.new domain
|
Response::DomainAdded.new domain
|
||||||
end
|
end
|
||||||
|
|
@ -340,12 +356,13 @@ class DNSManager::Storage
|
||||||
|
|
||||||
# Any modification of the zone must be performed here.
|
# Any modification of the zone must be performed here.
|
||||||
# This function updates the SOA serial before storing the modified zone.
|
# This function updates the SOA serial before storing the modified zone.
|
||||||
# Also, this function generate the Bind9 file.
|
# Also, this function generates the Bind9 file unless the zone is delegated.
|
||||||
def update_zone(zone : Zone) : Nil
|
def update_zone(zone : Zone) : Nil
|
||||||
zone.update_serial
|
zone.update_serial
|
||||||
zones_by_domain.update_or_create zone.domain, zone
|
zones_by_domain.update_or_create zone.domain, zone
|
||||||
|
|
||||||
generate_bind9_zonefile zone.domain
|
# Do not generate a bind9 file if the zone is delegated.
|
||||||
|
generate_bind9_zonefile zone.domain unless zone.delegation
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_rr(user_id : UserDataID, domain : String, rr : Zone::ResourceRecord) : IPC::JSON
|
def update_rr(user_id : UserDataID, domain : String, rr : Zone::ResourceRecord) : IPC::JSON
|
||||||
|
|
@ -449,6 +466,67 @@ class DNSManager::Storage
|
||||||
Response::Zone.new zone
|
Response::Zone.new zone
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# `edit_delegation` enables users to update the delegation name servers.
|
||||||
|
def edit_delegation(user_id : UserDataID, domain_name : String,
|
||||||
|
nameserver1 : String, nameserver2 : String) : IPC::JSON
|
||||||
|
|
||||||
|
# Verifies the provided name server domains.
|
||||||
|
return Response::InvalidDomainName.new unless Zone.is_domain_valid? nameserver1
|
||||||
|
return Response::InvalidDomainName.new unless Zone.is_domain_valid? nameserver2
|
||||||
|
|
||||||
|
zone = zone_must_exist! domain_name
|
||||||
|
user_should_own! user_id, domain_name
|
||||||
|
|
||||||
|
# Make sure both name servers are "absolute" domain names.
|
||||||
|
ns1 = if nameserver1.ends_with? '.'
|
||||||
|
nameserver1
|
||||||
|
else
|
||||||
|
"#{nameserver1}."
|
||||||
|
end
|
||||||
|
ns2 = if nameserver2.ends_with? '.'
|
||||||
|
nameserver2
|
||||||
|
else
|
||||||
|
"#{nameserver2}."
|
||||||
|
end
|
||||||
|
|
||||||
|
# Clone the zone because the update mechanism needs the old values in order to remove their files.
|
||||||
|
zone_clone = zone.clone
|
||||||
|
|
||||||
|
# Edit the name servers used for delegation.
|
||||||
|
zone_clone.delegation = Zone::Delegation.new ns1, ns2
|
||||||
|
|
||||||
|
# On-disk update of the name servers used for delegation.
|
||||||
|
zone_clone.update_delegation @delegationdir
|
||||||
|
|
||||||
|
# Once the new delegation file has been written, the script generating the (root) zone file must
|
||||||
|
# be informed by touching a file (named "delegation token file" in the source code).
|
||||||
|
update_delegation_token_file
|
||||||
|
|
||||||
|
update_zone zone_clone
|
||||||
|
|
||||||
|
Response::Success.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# `reset_delegation` removes the delegation of a domain, auto-fill the zone with a template.
|
||||||
|
def reset_delegation(user_id : UserDataID, domain_name : String) : IPC::JSON
|
||||||
|
user_should_own! user_id, domain_name
|
||||||
|
|
||||||
|
# Removes all traces of the zone (including the delegation file).
|
||||||
|
wipe_zone user_id, domain_name
|
||||||
|
|
||||||
|
domain_tld = has_valid_tld? domain_name
|
||||||
|
if domain_tld.nil?
|
||||||
|
Baguette::Log.warning "trying to add an unacceptable domain: '#{domain_name}'"
|
||||||
|
return Response::UnacceptableDomain.new
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates and auto-fill a new zone from a template then add it to the database.
|
||||||
|
the_new_zone = get_new_zone domain_tld, domain_name
|
||||||
|
update_zone the_new_zone
|
||||||
|
|
||||||
|
Response::Success.new
|
||||||
|
end
|
||||||
|
|
||||||
# Deletes the zone and its tokens to wipe the zone data then delegates the domain.
|
# Deletes the zone and its tokens to wipe the zone data then delegates the domain.
|
||||||
def delegate_domain(user_id : UserDataID, domain_name : String,
|
def delegate_domain(user_id : UserDataID, domain_name : String,
|
||||||
nameserver1 : String, nameserver2 : String) : IPC::JSON
|
nameserver1 : String, nameserver2 : String) : IPC::JSON
|
||||||
|
|
|
||||||
|
|
@ -1029,6 +1029,7 @@ class DNSManager::Storage::Zone
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: this verification is superficial. To be replaced.
|
||||||
# Valid names: valid subdomains, wildcards alone or followed by a subdomain.
|
# Valid names: valid subdomains, wildcards alone or followed by a subdomain.
|
||||||
def self.is_valid_name?(subdomain) : Bool
|
def self.is_valid_name?(subdomain) : Bool
|
||||||
if subdomain =~ subdomain_pattern
|
if subdomain =~ subdomain_pattern
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue