From 7f5d162e91a1e6bf644831abb5389023ae3a693c Mon Sep 17 00:00:00 2001 From: Karchnu Date: Sat, 12 Dec 2020 05:38:16 +0100 Subject: [PATCH] Adding a client (draft) and domain test. --- src/client/lib/authd_api.cr | 13 +++ src/client/lib/dnsmanager-client.cr | 46 +++++++++ src/client/lib/yaml_uuid.cr | 15 +++ src/client/main.cr | 21 +++- src/lib.cr | 151 ++++++++++++++++++++++++++++ src/main.cr | 2 +- src/requests/zone.cr | 31 ++++++ src/responses/zone.cr | 11 ++ src/storage/zone.cr | 36 ++++++- 9 files changed, 320 insertions(+), 6 deletions(-) create mode 100644 src/client/lib/authd_api.cr create mode 100644 src/client/lib/dnsmanager-client.cr create mode 100644 src/client/lib/yaml_uuid.cr create mode 100644 src/lib.cr create mode 100644 src/requests/zone.cr create mode 100644 src/responses/zone.cr diff --git a/src/client/lib/authd_api.cr b/src/client/lib/authd_api.cr new file mode 100644 index 0000000..28e4dd7 --- /dev/null +++ b/src/client/lib/authd_api.cr @@ -0,0 +1,13 @@ + +def authd_get_token(key_file : String? = nil, login : String? = nil, pass : String? = nil) + authd = AuthD::Client.new + key_file.try do |file| # FIXME: fail if missing? + authd.key = File.read(file).chomp + end + + token = authd.get_token? login, pass + raise "cannot get a token" if token.nil? + authd.close + + token +end diff --git a/src/client/lib/dnsmanager-client.cr b/src/client/lib/dnsmanager-client.cr new file mode 100644 index 0000000..3384b29 --- /dev/null +++ b/src/client/lib/dnsmanager-client.cr @@ -0,0 +1,46 @@ +require "../../requests/*" + +class DNSManager::Client < IPC::Client + def initialize + initialize "dnsmanager" + end + + # TODO: parse_message should raise exception if response not anticipated + def parse_message(expected_messages, message) + em = Array(IPC::JSON.class).new + expected_messages.each do |e| + em << e + end + em << DNSManager::Response::Error + em.parse_ipc_json message + end +end + + +# Simple users. +class DNSManager::Client < IPC::Client + def login(token : String) + request = DNSManager::Request::Login.new token + send_now @server_fd.not_nil!, request + parse_message [ DNSManager::Response::Success ], read + end + + # Adding a full zone. + def user_zone_add(zone : DNSManager::Storage::Zone) + request = DNSManager::Request::AddZone.new zone + send_now @server_fd.not_nil!, request + parse_message [ DNSManager::Response::Success ], read + end +end + +# Admin stuff. +class DNSManager::Client < IPC::Client + def admin_maintainance(key : String, subject : DNSManager::Request::Maintainance::Subject, value : Int32? = nil) + request = DNSManager::Request::Maintainance.new(key,subject) + if value + request.value = value + end + send_now @server_fd.not_nil!, request + parse_message [ DNSManager::Response::Success ], read + end +end diff --git a/src/client/lib/yaml_uuid.cr b/src/client/lib/yaml_uuid.cr new file mode 100644 index 0000000..103a8b5 --- /dev/null +++ b/src/client/lib/yaml_uuid.cr @@ -0,0 +1,15 @@ + +# YAML UUID parser +def UUID.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + ctx.read_alias(node, UUID) do |obj| + return UUID.new obj + end + + if node.is_a?(YAML::Nodes::Scalar) + value = node.value + ctx.record_anchor(node, value) + UUID.new value + else + node.raise "Expected UUID, not #{node.class.name}" + end +end diff --git a/src/client/main.cr b/src/client/main.cr index be31721..fc5abe4 100644 --- a/src/client/main.cr +++ b/src/client/main.cr @@ -17,9 +17,9 @@ end require "./parser.cr" -#def read_searches -# Array(DNSManager::Request::BLAH).from_json_files Context.args.not_nil! -#end +def read_zones + Array(DNSManager::Storage::Zone).from_json_files Context.args.not_nil! +end class Actions property the_call = {} of String => Proc(Nil) @@ -34,7 +34,8 @@ class Actions # # Maintainance - @the_call["admin-maintainance"] = ->admin_maintainance + @the_call["admin-maintainance"] = ->admin_maintainance + @the_call["user-zone-add"] = ->user_zone_add end def admin_maintainance @@ -66,6 +67,18 @@ class Actions end end end + + def user_zone_add + zones = read_zones + zones.each do |zone| + begin + pp! zone + pp! @dnsmanagerd.user_zone_add zone + rescue e + puts "error for admin_maintainance #{subject}: #{e.message}" + end + end + end end def main diff --git a/src/lib.cr b/src/lib.cr new file mode 100644 index 0000000..5eb512c --- /dev/null +++ b/src/lib.cr @@ -0,0 +1,151 @@ +require "uuid" + +# YAML UUID parser +def UUID.new(ctx : YAML::ParseContext, node : YAML::Nodes::Node) + ctx.read_alias(node, UUID) do |obj| + return UUID.new obj + end + + if node.is_a?(YAML::Nodes::Scalar) + value = node.value + ctx.record_anchor(node, value) + UUID.new value + else + node.raise "Expected UUID, not #{node.class.name}" + end +end + +module YAML + # change dates in YAML formated content + def self.human_dates(content : String) + new_lines = Array(String).new + content.each_line do |line| + case line + when /(?.*date):[ \t]+NOW[ \t]*(?[-+])[ \t]*(?rand)?[ \t]*(?[0-9]+) *(?[a-z]+)?/ + date = $~["date"] + op = $~["op"] + delta = $~["delta"].to_i + rand = $~["rand"] rescue nil + scale = $~["scale"] rescue nil # days, hours + + unless rand.nil? + old = delta + delta = Random.rand(delta) + end + + vdelta = delta.days + case scale + when /day/ + # default one + when /hour/ + vdelta = delta.hours + else + # puts "scale infered: days" + end + + yaml_date = Time::Format::YAML_DATE.format(Time.local + vdelta) + case op + when /-/ + yaml_date = Time::Format::YAML_DATE.format(Time.local - vdelta) + # puts "-" + when /\+/ + # default one + # puts "+" + else + # puts "date operation not understood: #{op}, + infered" + end + + new_lines << "#{date}: #{yaml_date}" + next + when /(?.+date):[ \t]+NOW[ \t]*$/ + date = $~["date"] + yaml_date = Time::Format::YAML_DATE.format(Time.local) + + new_lines << "#{date}: #{yaml_date}" + next + when /(?[a-z]+_date):[ \t]+NOW[ \t]*$/ + date = $~["date"] + yaml_date = Time::Format::YAML_DATE.format(Time.local) + + new_lines << "#{date}: #{yaml_date}" + next + # when /(?[a-z]+_date):/ + # puts "date that does not compute: #{line}" + end + new_lines << line + end + + new_lines.join "\n" + end +end + +class Array(T) + def self.from_yaml_files(files) + values = Array(T).new + files.each do |file| + raise "File doesn't exist #{file}" unless File.exists? file + from_yaml_file(file).each do |v| + values << v + end + end + values + end + + def self.from_yaml_file(file) + from_yaml_content File.read file + end + + def self.from_yaml_content(input_content) + content = YAML.human_dates input_content + + values = Array(T).new + + begin + values << T.from_yaml content + rescue e + Baguette::Log.warning "reading the input #{e}" + begin + Array(T).from_yaml(content).each do |b| + values << b + end + rescue e + raise "wrong YAML content: #{e}" + end + end + + values + end + + def self.from_json_files(files) + values = Array(T).new + files.each do |file| + raise "File doesn't exist #{file}" unless File.exists? file + from_json_file(file).each do |v| + values << v + end + end + values + end + + def self.from_json_file(file) + from_json_content File.read file + end + + def self.from_json_content(content) + values = Array(T).new + + begin + values << T.from_json content + rescue e + begin + Array(T).from_json(content).each do |b| + values << b + end + rescue e + raise "wrong JSON content: #{e}" + end + end + + values + end +end diff --git a/src/main.cr b/src/main.cr index be446c8..39f3ae4 100644 --- a/src/main.cr +++ b/src/main.cr @@ -40,7 +40,7 @@ class DNSManager::Service < IPC::Server end def get_logged_user(event : IPC::Event::Events) - fd = event.connection.fd + fd = event.fd @logged_users[fd]? end diff --git a/src/requests/zone.cr b/src/requests/zone.cr new file mode 100644 index 0000000..2aaec9d --- /dev/null +++ b/src/requests/zone.cr @@ -0,0 +1,31 @@ +require "grok" + +class DNSManager::Request + + IPC::JSON.message AddOrUpdateZone, 10 do + property zone : DNSManager::Storage::Zone + + def initialize(@zone) + end + + def handle(dnsmanagerd : DNSManager::Service, event : IPC::Event::Events) + user = dnsmanagerd.get_logged_user event + raise NotLoggedException.new if user.nil? + + # TODO: test for zone validity. + if errors = zone.get_errors? + return DNSManager::Response::InvalidZone.new errors + end + + # In case there is no error, retrieve the zone in the DB. + #z = dnsmanagerd.storage.zones_by_domain.get? zone.domain + #if z + #else + # dnsmanagerd.storage.zones << @zone + #end + + Response::Success.new + end + end + DNSManager.requests << AddOrUpdateZone +end diff --git a/src/responses/zone.cr b/src/responses/zone.cr new file mode 100644 index 0000000..b589002 --- /dev/null +++ b/src/responses/zone.cr @@ -0,0 +1,11 @@ + +class DNSManager::Response + IPC::JSON.message InvalidZone, 10 do + # For now, Error is just an alias on String. + property errors : Array(DNSManager::Storage::Zone::Error) + def initialize(@errors) + end + end + DNSManager.responses << InvalidZone +end + diff --git a/src/storage/zone.cr b/src/storage/zone.cr index f348134..f685d43 100644 --- a/src/storage/zone.cr +++ b/src/storage/zone.cr @@ -9,6 +9,7 @@ class DNSManager::Storage::Zone def initialize(@domain) end + alias Error = String # Store a Resource Record: A, AAAA, TXT, PTR, CNAME… abstract class ResourceRecord @@ -27,7 +28,7 @@ class DNSManager::Storage::Zone } # Used to discriminate between classes. - property rrtype : String = "" + property rrtype : String = "" property name : String property ttl : UInt32 @@ -37,6 +38,10 @@ class DNSManager::Storage::Zone def initialize(@name, @ttl, @target) @rrtype = self.class.name.downcase.gsub /dnsmanager::storage::zone::/, "" end + + def get_error : Error? + nil + end end class SOA < ResourceRecord @@ -88,4 +93,33 @@ class DNSManager::Storage::Zone def to_s(io : IO) io << "TEST" end + + def get_errors? : Array(Error) + errors = [] of Error + unless Zone.is_domain_valid? @domain + errors << "invalid domain" + end + + @resources.each do |r| + if error = r.get_error + errors << error + end + end + + errors + end + + # This regex only is "good enough for now". + def self.is_domain_valid?(domain) : Bool + if domain =~ /^(((?!-))(xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*((xn--)?[a-z0-9][a-z0-9\-]{0,60}[a-z0-9]|(xn--)?[a-z0-9]{1,60})\.[a-z]{2,}$/ + true + else + false + end + rescue e + Baguette::Log.error "invalid zone domain #{domain}: #{e}" + false + end + + end