From bd0c3d5fddcd25d7ffa4b154bc2fc9e950a7cb3f Mon Sep 17 00:00:00 2001 From: Philippe Pittoli Date: Sun, 25 Feb 2024 04:13:18 +0100 Subject: [PATCH] GenerateAllZoneFiles + GenerateZoneFile: working as expected. --- src/client/lib/dnsmanager-client.cr | 6 ++++++ src/client/main.cr | 11 ++++++++-- src/client/parser.cr | 8 ++++++++ src/requests/admin.cr | 31 +++++++++++++++++++++-------- src/responses/user.cr | 6 ++++++ src/storage.cr | 30 ++++++++++++++++++++++++++++ src/storage/zone.cr | 17 ++++++++-------- 7 files changed, 91 insertions(+), 18 deletions(-) diff --git a/src/client/lib/dnsmanager-client.cr b/src/client/lib/dnsmanager-client.cr index 1b121ff..7f5dd91 100644 --- a/src/client/lib/dnsmanager-client.cr +++ b/src/client/lib/dnsmanager-client.cr @@ -110,6 +110,12 @@ class DNSManager::Client < IPC parse_message [ Response::Success ], read end + def generate_all_zonefiles + request = Request::GenerateAllZoneFiles.new + send_now request + parse_message [ Response::Success ], read + end + # # Utils # diff --git a/src/client/main.cr b/src/client/main.cr index fe0fe1a..72dee6a 100644 --- a/src/client/main.cr +++ b/src/client/main.cr @@ -35,6 +35,7 @@ class Actions # Maintenance. @the_call["admin-maintenance"] = ->admin_maintenance @the_call["admin-generate-zonefile"] = ->admin_generate_zonefile + @the_call["admin-generate-all-zonefiles"] = ->admin_generate_all_zonefiles # Domain operations. @the_call["user-domain-add"] = ->user_domain_add @@ -85,11 +86,17 @@ class Actions pp! domain pp! @dnsmanagerd.generate_zonefile domain rescue e - puts "error for user_domain_add: #{e.message}" + puts "error for generate_zonefile: #{e.message}" end end end + def admin_generate_all_zonefiles + pp! @dnsmanagerd.generate_all_zonefiles + rescue e + puts "error for generate_all_zonefiles: #{e.message}" + end + def user_domain_add domains = Context.args.not_nil! domains.each do |domain| @@ -236,7 +243,7 @@ def main logged_message = dnsmanagerd.login token case logged_message when DNSManager::Response::Logged - Baguette::Log.info "logged to dnsmanagerd" + Baguette::Log.info "logged to dnsmanagerd as '#{login}'" Baguette::Log.debug "from logging message, accepted domains:" logged_message.accepted_domains.each do |d| Baguette::Log.debug "- #{d}" diff --git a/src/client/parser.cr b/src/client/parser.cr index 3f46245..197bc1e 100644 --- a/src/client/parser.cr +++ b/src/client/parser.cr @@ -106,6 +106,14 @@ def parsing_cli(authd_config : Baguette::Configuration::Auth) unrecognized_args_to_context_args.call parser, nil, 1 end + # Generate all zone files. + parser.on("genall", "Generate all zone files.") do + Baguette::Log.info "generate all zone files on the server." + Context.command = "admin-generate-all-zonefiles" + parser.banner = "COMMAND: admin genall" + unrecognized_args_to_context_args.call parser, 0, nil + end + end # User section. diff --git a/src/requests/admin.cr b/src/requests/admin.cr index c5f4744..81ac5d8 100644 --- a/src/requests/admin.cr +++ b/src/requests/admin.cr @@ -20,8 +20,7 @@ class DNSManager::Request user = dnsmanagerd.get_logged_user event return Response::ErrorUserNotLogged.new unless user - # This request means serious business. - # TODO: check for admin. + return Response::InsufficientRights.new unless user.admin case @subject when Subject::Verbosity @@ -39,7 +38,25 @@ class DNSManager::Request end DNSManager.requests << Maintenance - IPC::JSON.message GenerateZoneFile, 100 do + # Generate all zone files. + # TODO: should conveniently skip already generated zones. + IPC::JSON.message GenerateAllZoneFiles, 100 do + def initialize() + end + + def handle(dnsmanagerd : DNSManager::Service, event : IPC::Event) : IPC::JSON + user = dnsmanagerd.get_logged_user event + return Response::ErrorUserNotLogged.new unless user + + udata = dnsmanagerd.storage.get_user_data user + return Response::InsufficientRights.new unless udata.admin + dnsmanagerd.storage.generate_all_zonefiles + end + end + DNSManager.requests << GenerateAllZoneFiles + + # Force the generation of a zone file. + IPC::JSON.message GenerateZoneFile, 101 do property domain : String def initialize(@domain) @@ -49,11 +66,9 @@ class DNSManager::Request user = dnsmanagerd.get_logged_user event return Response::ErrorUserNotLogged.new unless user - return Response::Error.new "unauthorized" unless user.admin - # This request means serious business. - # TODO: check for admin. - - Response::Error.new "not implemented" + udata = dnsmanagerd.storage.get_user_data user + return Response::InsufficientRights.new unless udata.admin + dnsmanagerd.storage.generate_zonefile @domain end end DNSManager.requests << GenerateZoneFile diff --git a/src/responses/user.cr b/src/responses/user.cr index c8ffab0..39658f5 100644 --- a/src/responses/user.cr +++ b/src/responses/user.cr @@ -10,4 +10,10 @@ class DNSManager::Response end end DNSManager.responses << NoOwnership + + IPC::JSON.message InsufficientRights, 52 do + def initialize + end + end + DNSManager.responses << InsufficientRights end diff --git a/src/storage.cr b/src/storage.cr index eb58847..05108b8 100644 --- a/src/storage.cr +++ b/src/storage.cr @@ -12,12 +12,19 @@ class DNSManager::Storage getter zones : DODB::CachedDataBase(Zone) getter zones_by_domain : DODB::Index(Zone) + getter root : String + getter zonefiledir : String + def initialize(@root : String, reindex : Bool = false) @user_data = DODB::CachedDataBase(UserData).new "#{@root}/user-data" @user_data_by_uid = @user_data.new_index "uid", &.uid.to_s @zones = DODB::CachedDataBase(Zone).new "#{@root}/zones" @zones_by_domain = @zones.new_index "domain", &.domain + @zonefiledir = "#{@root}/bind9-zones" + # TODO: create the directory + Dir.mkdir_p @zonefiledir + Baguette::Log.info "storage initialized" if reindex @@ -55,6 +62,29 @@ class DNSManager::Storage user_data end + # Only an admin can access this function. + def generate_all_zonefiles() : IPC::JSON + Baguette::Log.info "writing all zone files in #{@zonefiledir}/" + zones.each do |zone| + File.open("#{@zonefiledir}/#{zone.domain}", "w") do |file| + zone.to_bind9 file + end + end + Response::Success.new + end + + # Only an admin can access this function. + def generate_zonefile(domain : String) : IPC::JSON + zone = zones_by_domain.get? domain + return Response::DomainNotFound.new unless zone + + Baguette::Log.info "writing zone file #{@zonefiledir}/#{zone.domain}" + File.open("#{@zonefiledir}/#{zone.domain}", "w") do |file| + zone.to_bind9 file + end + Response::Success.new + end + def new_domain(accepted_domains : Array(String), template_directory : String, user_id : Int32, diff --git a/src/storage/zone.cr b/src/storage/zone.cr index 2b5cbb8..0898624 100644 --- a/src/storage/zone.cr +++ b/src/storage/zone.cr @@ -97,12 +97,13 @@ class DNSManager::Storage::Zone end def to_bind9(io : IO) - io << "#{name} #{ttl} IN SOA (#{mname} #{rname}\n" - io << "\t\t#{serial } ; serial\n" - io << "\t\t#{refresh} ; refresh\n" - io << "\t\t#{retry } ; retry\n" - io << "\t\t#{expire } ; expire\n" - io << "\t\t#{minttl } ; minttl\n" + # No "name" because the name of the SOA RR is the origin (FQDN). + io << "@ #{ttl} IN SOA (#{mname} #{rname}\n" + io << "\t\t#{ "%10d" % serial } ; serial\n" + io << "\t\t#{ "%10d" % refresh} ; refresh\n" + io << "\t\t#{ "%10d" % retry } ; retry\n" + io << "\t\t#{ "%10d" % expire } ; expire\n" + io << "\t\t#{ "%10d" % minttl } ; minttl\n" io << "\t)\n" end @@ -331,14 +332,14 @@ class DNSManager::Storage::Zone end def to_s(io : IO) - io << "domain: #{@domain}\n" + io << "DOMAIN #{@domain}.\n" @resources.each do |rr| io << rr end end def to_bind9(io : IO) - io << "DOMAIN: #{@domain}\n" + io << "$ORIGIN #{@domain}.\n" @resources.each do |rr| rr.to_bind9 io end