From 9b6457f57500df97d89d95e061ad68e2fc60f0db Mon Sep 17 00:00:00 2001 From: Philippe PITTOLI Date: Mon, 15 Apr 2024 23:13:57 +0200 Subject: [PATCH] DMARC. --- src/storage/zone.cr | 147 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 140 insertions(+), 7 deletions(-) diff --git a/src/storage/zone.cr b/src/storage/zone.cr index 6184ef8..4bec0c2 100644 --- a/src/storage/zone.cr +++ b/src/storage/zone.cr @@ -521,11 +521,11 @@ class DNSManager::Storage::Zone errors = [] of Error unless Zone.is_subdomain_valid? @name - errors << "CNAME invalid subdomain: #{@name}" + errors << "DKIM invalid subdomain: #{@name}" end if @ttl < Zone.ttl_limit_min - errors << "CNAME invalid ttl: #{@ttl}, shouldn't be less than #{Zone.ttl_limit_min}" + errors << "DKIM invalid ttl: #{@ttl}, shouldn't be less than #{Zone.ttl_limit_min}" end errors end @@ -542,22 +542,155 @@ class DNSManager::Storage::Zone # TODO class DMARC < ResourceRecord - def initialize(@name, @ttl, @target) + enum Version + DMARC1 + end + + class DMARCURI + include JSON::Serializable + property mail : String + property limit : UInt32? = nil + + def to_s(io : IO) + io << "#{mail}" + if l = @limit + io << "!#{l}k" + end + end + end + + enum ReportFormat + AFRF + end + + enum ConsistencyPolicy + Strict + Relaxed + end + + enum Policy + None + Quarantine + Reject + end + + enum ReportOccasion + BOTH + DKIMONLY + SPFONLY + ANY + end + + class DMARCProperties + include JSON::Serializable + + property p : Policy + property sp : Policy? = nil + property v : Version = Version::DMARC1 + property adkim : ConsistencyPolicy? = nil + property aspf : ConsistencyPolicy? = nil + property pct : UInt32? = nil + property fo : ReportOccasion? = nil + property rua : Array(DMARCURI)? = nil + property ruf : Array(DMARCURI)? = nil + property rf : Array(ReportFormat)? = nil + property ri : UInt32 + + def initialize(@p, @sp, @v, @adkim, @aspf, @pct, @fo, @rua, @ruf, @rf, @ri) + end + + def self.consistency_policy_to_bind9(p : ConsistencyPolicy) + p == ConsistencyPolicy::Strict ? "s" : "r" + end + + def self.report_occasion_to_bind9(fo : ReportOccasion) + case fo + when ReportOccasion::BOTH + "0" + when ReportOccasion::DKIMONLY + "d" + when ReportOccasion::SPFONLY + "s" + else + "1" + end + end + + def to_s(io : IO) + io << "v=#{v}" + io << ";p=#{@p.to_s.downcase}" + if val = @sp + io << ";sp=#{val.to_s.downcase}" + end + if val = adkim + io << ";adkim=#{DMARCProperties.consistency_policy_to_bind9(val)}" + end + if val = aspf + io << ";aspf=#{DMARCProperties.consistency_policy_to_bind9(val)}" + end + if val = pct + io << ";pct=#{val}" + end + if val = fo + io << ";fo=#{DMARCProperties.report_occasion_to_bind9(val)}" + end + if val = rua + io << ";rua=" + arr = val.map { |x| x.to_s } + while arr.size > 1 + io << arr.pop + io << "," + end + io << arr.pop + end + if val = ruf + io << ";ruf=" + arr = val.map { |x| x.to_s } + while arr.size > 1 + io << arr.pop + io << "," + end + io << arr.pop + end + if val = rf + io << ";rf=#{val}" + end + if val = ri + io << ";ri=#{val}" + end + end + end + + property dmarc : DMARCProperties + + def initialize(@name, @ttl, @target, + p, sp, v, adkim, aspf, pct, fo, rua, ruf, rf, ri) @rrtype = "DMARC" + @dmarc = DMARCProperties.new p, sp, v, adkim, aspf, pct, fo, rua, ruf, rf, ri end def get_errors : Array(Error) errors = [] of Error - unless Zone.is_subdomain_valid? @name - errors << "CNAME invalid subdomain: #{@name}" - end + # TODO: label can (and is expected to) start with an underscore. + #unless Zone.is_subdomain_valid? @name + # errors << "DMARC invalid subdomain: #{@name}" + #end if @ttl < Zone.ttl_limit_min - errors << "CNAME invalid ttl: #{@ttl}, shouldn't be less than #{Zone.ttl_limit_min}" + errors << "DMARC invalid ttl: #{@ttl}, shouldn't be less than #{Zone.ttl_limit_min}" end errors end + + def to_s(io : IO) + io << "(#{ "%4d" % @rrid }) " + io << "#{ "%30s" % @name} #{ "%6d" % @ttl} DMARC #{split_line dmarc.to_s}\n" + end + + def to_bind9(io : IO) + io << "#{@name} #{@ttl} IN TXT #{split_line dmarc.to_s}\n" + end end class MX < ResourceRecord