Delegation: new Edit and Reset mechanisms.

This commit is contained in:
Philippe Pittoli 2025-10-28 17:50:48 +01:00
parent 7750052aed
commit 99f94a30e7
3 changed files with 150 additions and 31 deletions

View file

@ -18,4 +18,44 @@ class DNSManager::Request
end
end
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

View file

@ -24,7 +24,7 @@ class DNSManager::Storage
getter root : String
getter zonefiledir : String
getter delegationdir : String
getter delegationdir : String
property dnsmanagerd : DNSManager::Service? = nil
@ -165,34 +165,24 @@ class DNSManager::Storage
Response::GeneratedZone.new domain, (String.new io.buffer, io.pos)
end
# Adds a new domain.
def new_domain(user_id : UserDataID, domain : String) : IPC::JSON
def remove_first_label (domain : String) : String
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!
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
# 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"
# Replace domain by the real domain.
@ -204,17 +194,43 @@ class DNSManager::Storage
# configuration.
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.
#
Baguette::Log.debug "Add new domain #{domain} (matching domain #{new_domain_tld})"
# Add the new domain.
the_new_domain = Domain.new domain
the_new_domain.owners = [user_id]
@domains << the_new_domain
# Add the new zone in the database.
update_zone default_zone
# Fill a template zone then add it to the database.
the_new_zone = get_new_zone new_domain_tld, domain
update_zone the_new_zone
Response::DomainAdded.new domain
end
@ -340,12 +356,13 @@ class DNSManager::Storage
# Any modification of the zone must be performed here.
# 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
zone.update_serial
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
def update_rr(user_id : UserDataID, domain : String, rr : Zone::ResourceRecord) : IPC::JSON
@ -449,6 +466,67 @@ class DNSManager::Storage
Response::Zone.new zone
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.
def delegate_domain(user_id : UserDataID, domain_name : String,
nameserver1 : String, nameserver2 : String) : IPC::JSON

View file

@ -1029,6 +1029,7 @@ class DNSManager::Storage::Zone
false
end
# TODO: this verification is superficial. To be replaced.
# Valid names: valid subdomains, wildcards alone or followed by a subdomain.
def self.is_valid_name?(subdomain) : Bool
if subdomain =~ subdomain_pattern