dmarcator 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog.md +8 -0
- data/README.md +5 -0
- data/lib/dmarcator/exceptions.rb +5 -0
- data/lib/dmarcator/parser.rb +42 -0
- data/lib/dmarcator/record.rb +6 -6
- data/lib/dmarcator/report/auth_result.rb +12 -1
- data/lib/dmarcator/report/metadata.rb +16 -1
- data/lib/dmarcator/report/policy.rb +16 -1
- data/lib/dmarcator/report/reason.rb +6 -1
- data/lib/dmarcator/report/record.rb +18 -1
- data/lib/dmarcator/version.rb +1 -1
- data/lib/dmarcator.rb +12 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab57f0c6599df86ed3331199b84f0abdd9aae8876405e4205f2070b9e457b5ca
|
|
4
|
+
data.tar.gz: 5466098000ff91848f6cb8208606ed8b3312eb9b476763074d917b3f7f377aca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71c83a39fc17926997e43465ef07210a7a062a67b922462623e4d6c0daa617acefc4ec2546194ffa2f6755bf62321504134140b5e35813ac9e34b8ed43abbf89
|
|
7
|
+
data.tar.gz: ebdc8623cdba9d7632188a52fbdc203d7c1f85efed580f72ce5ceebeafd8a7c5eae4f5f0797d8960b1a6fb5e1b2af9aeb9d5484ffc74a5f8adc4942af4de286a
|
data/ChangeLog.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
### 0.6.1 / 2026-10-29
|
|
2
|
+
|
|
3
|
+
* Remove a redundant `attr_reader :sp` that `Record#sp` already overrode, fixing a `method redefined; discarding old sp` warning emitted under `-w` (as CI enables).
|
|
4
|
+
* Add tests covering `Record#sp` falling back to `p` when `sp` is omitted.
|
|
5
|
+
* Document every report accessor, constant, and public class that YARD previously omitted; documentation coverage is now 100% with no warnings.
|
|
6
|
+
* Note the encrypted-transport guarantees in the README security section.
|
|
7
|
+
* No functional changes.
|
|
8
|
+
|
|
1
9
|
### 0.6.0 / 2026-10-28
|
|
2
10
|
|
|
3
11
|
* Add DNS over HTTPS (RFC 8484) and DNS over TLS (RFC 7858) transports, selected with `DMARCATOR_DNS` (`doh` or `dot`). No provider is bundled and plaintext remains the default.
|
data/README.md
CHANGED
|
@@ -177,6 +177,11 @@ DMARC records and XML reports are treated as untrusted input:
|
|
|
177
177
|
CVE-2026-80212/CVE-2026-80213.
|
|
178
178
|
* Domain names are validated before resolution (labels ≤ 63 octets, total ≤
|
|
179
179
|
248 octets, no leading/trailing hyphens).
|
|
180
|
+
* Optional [DNS over HTTPS and DNS over TLS](#dns-transport) transports verify
|
|
181
|
+
TLS certificates and fail closed: a transport failure raises
|
|
182
|
+
`DMARCator::ResolutionError` rather than being reported as a domain without a
|
|
183
|
+
DMARC record, so a blocked or downgraded lookup can never silently enforce no
|
|
184
|
+
policy.
|
|
180
185
|
|
|
181
186
|
## Requirements
|
|
182
187
|
|
data/lib/dmarcator/exceptions.rb
CHANGED
data/lib/dmarcator/parser.rb
CHANGED
|
@@ -50,6 +50,21 @@ module DMARCator
|
|
|
50
50
|
unknown_tag
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
#
|
|
54
|
+
# Defines a rule for a `tag=value` DMARC tag, capturing values that do not
|
|
55
|
+
# match the rule as `:invalid_tag` so strict mode can reject them.
|
|
56
|
+
#
|
|
57
|
+
# @param [Symbol] name
|
|
58
|
+
# The rule name, used as `dmarc_<name>`.
|
|
59
|
+
#
|
|
60
|
+
# @param [String] tag
|
|
61
|
+
# The DMARC tag name.
|
|
62
|
+
#
|
|
63
|
+
# @yieldreturn [Parslet::Atoms::Base]
|
|
64
|
+
# The parslet matching the tag's value.
|
|
65
|
+
#
|
|
66
|
+
# @api private
|
|
67
|
+
#
|
|
53
68
|
def self.tag_rule(name,tag,&block)
|
|
54
69
|
rule(:"dmarc_#{name}") do
|
|
55
70
|
(
|
|
@@ -123,6 +138,11 @@ module DMARCator
|
|
|
123
138
|
rule(:wsp) { str(' ') | str("\t") }
|
|
124
139
|
rule(:wsp?) { wsp.repeat }
|
|
125
140
|
|
|
141
|
+
#
|
|
142
|
+
# Converts the raw parse tree into tag attributes.
|
|
143
|
+
#
|
|
144
|
+
# @api private
|
|
145
|
+
#
|
|
126
146
|
class Transform < Parslet::Transform
|
|
127
147
|
|
|
128
148
|
rule(fo_opt: simple(:fo_opt)) { fo_opt }
|
|
@@ -155,8 +175,21 @@ module DMARCator
|
|
|
155
175
|
rule(rua: subtree(:uris)) { {rua: Array(uris)} }
|
|
156
176
|
rule(ruf: subtree(:uris)) { {ruf: Array(uris)} }
|
|
157
177
|
|
|
178
|
+
# Maximum length of error messages echoed from untrusted input.
|
|
158
179
|
MAX_ERROR_MESSAGE_LENGTH = 300
|
|
159
180
|
|
|
181
|
+
#
|
|
182
|
+
# Parses a `mailto:` URI from a parse tree slice.
|
|
183
|
+
#
|
|
184
|
+
# @param [Parslet::Slice] value
|
|
185
|
+
#
|
|
186
|
+
# @return [URI::MailTo]
|
|
187
|
+
#
|
|
188
|
+
# @raise [InvalidRecord]
|
|
189
|
+
# The URI is not a valid `mailto:` URI.
|
|
190
|
+
#
|
|
191
|
+
# @api private
|
|
192
|
+
#
|
|
160
193
|
def self.parse_uri(value)
|
|
161
194
|
uri = value.to_s
|
|
162
195
|
|
|
@@ -173,6 +206,15 @@ module DMARCator
|
|
|
173
206
|
parsed
|
|
174
207
|
end
|
|
175
208
|
|
|
209
|
+
#
|
|
210
|
+
# Truncates an error message to a bounded length.
|
|
211
|
+
#
|
|
212
|
+
# @param [String] message
|
|
213
|
+
#
|
|
214
|
+
# @return [String]
|
|
215
|
+
#
|
|
216
|
+
# @api private
|
|
217
|
+
#
|
|
176
218
|
def self.truncate(message)
|
|
177
219
|
message = message.to_s
|
|
178
220
|
|
data/lib/dmarcator/record.rb
CHANGED
|
@@ -7,10 +7,15 @@ require 'dmarcator/exceptions'
|
|
|
7
7
|
require 'resolv'
|
|
8
8
|
|
|
9
9
|
module DMARCator
|
|
10
|
+
#
|
|
11
|
+
# A parsed DMARC record, as published in a `_dmarc.<domain>` TXT record.
|
|
12
|
+
#
|
|
13
|
+
# @see https://www.rfc-editor.org/rfc/rfc7489
|
|
14
|
+
#
|
|
10
15
|
class Record
|
|
11
16
|
|
|
12
17
|
# `p` field.
|
|
13
|
-
#
|
|
18
|
+
#
|
|
14
19
|
# @return [:none, :quarantine, :reject]
|
|
15
20
|
attr_reader :p
|
|
16
21
|
|
|
@@ -24,11 +29,6 @@ module DMARCator
|
|
|
24
29
|
# @return [Array<Uri>]
|
|
25
30
|
attr_reader :ruf
|
|
26
31
|
|
|
27
|
-
# `sp` field.
|
|
28
|
-
#
|
|
29
|
-
# @return [:none, :quarantine, :reject]
|
|
30
|
-
attr_reader :sp
|
|
31
|
-
|
|
32
32
|
# `v` field.
|
|
33
33
|
#
|
|
34
34
|
# @return [:DMARC1]
|
|
@@ -10,9 +10,20 @@ module DMARCator
|
|
|
10
10
|
class AuthResult
|
|
11
11
|
include XmlHelper
|
|
12
12
|
|
|
13
|
+
# Attribute names exposed by this class.
|
|
13
14
|
ATTRIBUTES = [:type, :domain, :result, :selector, :scope]
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
# @!attribute [rw] type
|
|
17
|
+
# @return [String] the entry name (`dkim` or `spf`).
|
|
18
|
+
# @!attribute [rw] domain
|
|
19
|
+
# @return [String, nil] the authenticated domain.
|
|
20
|
+
# @!attribute [rw] result
|
|
21
|
+
# @return [String, nil] the authentication result.
|
|
22
|
+
# @!attribute [rw] selector
|
|
23
|
+
# @return [String, nil] the DKIM selector.
|
|
24
|
+
# @!attribute [rw] scope
|
|
25
|
+
# @return [String, nil] the SPF scope.
|
|
26
|
+
ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
|
|
16
27
|
|
|
17
28
|
def initialize(node)
|
|
18
29
|
@node = node
|
|
@@ -10,12 +10,27 @@ module DMARCator
|
|
|
10
10
|
class Metadata
|
|
11
11
|
include XmlHelper
|
|
12
12
|
|
|
13
|
+
# Attribute names exposed by this class.
|
|
13
14
|
ATTRIBUTES = [
|
|
14
15
|
:org_name, :email, :extra_contact_info, :report_id,
|
|
15
16
|
:begin_at, :end_at, :errors
|
|
16
17
|
]
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
# @!attribute [rw] org_name
|
|
20
|
+
# @return [String, nil] the reporting organization.
|
|
21
|
+
# @!attribute [rw] email
|
|
22
|
+
# @return [String, nil] the reporting organization's contact address.
|
|
23
|
+
# @!attribute [rw] extra_contact_info
|
|
24
|
+
# @return [String, nil] additional contact information.
|
|
25
|
+
# @!attribute [rw] report_id
|
|
26
|
+
# @return [String, nil] the unique report identifier.
|
|
27
|
+
# @!attribute [rw] begin_at
|
|
28
|
+
# @return [Time, nil] the start of the reporting interval.
|
|
29
|
+
# @!attribute [rw] end_at
|
|
30
|
+
# @return [Time, nil] the end of the reporting interval.
|
|
31
|
+
# @!attribute [rw] errors
|
|
32
|
+
# @return [Array<String>] errors reported by the reporting organization.
|
|
33
|
+
ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
|
|
19
34
|
|
|
20
35
|
def initialize(node)
|
|
21
36
|
@node = node
|
|
@@ -10,9 +10,24 @@ module DMARCator
|
|
|
10
10
|
class Policy
|
|
11
11
|
include XmlHelper
|
|
12
12
|
|
|
13
|
+
# Attribute names exposed by this class.
|
|
13
14
|
ATTRIBUTES = [:domain, :adkim, :aspf, :p, :sp, :pct, :fo]
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
# @!attribute [rw] domain
|
|
17
|
+
# @return [String, nil] the domain the policy applies to.
|
|
18
|
+
# @!attribute [rw] adkim
|
|
19
|
+
# @return [String, nil] the DKIM alignment mode (`r` or `s`).
|
|
20
|
+
# @!attribute [rw] aspf
|
|
21
|
+
# @return [String, nil] the SPF alignment mode (`r` or `s`).
|
|
22
|
+
# @!attribute [rw] p
|
|
23
|
+
# @return [String, nil] the requested policy.
|
|
24
|
+
# @!attribute [rw] sp
|
|
25
|
+
# @return [String, nil] the requested subdomain policy.
|
|
26
|
+
# @!attribute [rw] pct
|
|
27
|
+
# @return [Integer, nil] the percentage of messages the policy applied to.
|
|
28
|
+
# @!attribute [rw] fo
|
|
29
|
+
# @return [String, nil] the requested failure-reporting options.
|
|
30
|
+
ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
|
|
16
31
|
|
|
17
32
|
def initialize(node)
|
|
18
33
|
@node = node
|
|
@@ -10,9 +10,14 @@ module DMARCator
|
|
|
10
10
|
class Reason
|
|
11
11
|
include XmlHelper
|
|
12
12
|
|
|
13
|
+
# Attribute names exposed by this class.
|
|
13
14
|
ATTRIBUTES = [:type, :comment]
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
# @!attribute [rw] type
|
|
17
|
+
# @return [String, nil] the reason type.
|
|
18
|
+
# @!attribute [rw] comment
|
|
19
|
+
# @return [String, nil] an optional explanatory comment.
|
|
20
|
+
ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
|
|
16
21
|
|
|
17
22
|
def initialize(node)
|
|
18
23
|
@node = node
|
|
@@ -12,12 +12,29 @@ module DMARCator
|
|
|
12
12
|
class Record
|
|
13
13
|
include XmlHelper
|
|
14
14
|
|
|
15
|
+
# Attribute names exposed by this class.
|
|
15
16
|
ATTRIBUTES = [
|
|
16
17
|
:source_ip, :count, :disposition, :dkim, :spf,
|
|
17
18
|
:envelope_to, :envelope_from, :header_from
|
|
18
19
|
]
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
# @!attribute [rw] source_ip
|
|
22
|
+
# @return [String, nil] the sending IP address.
|
|
23
|
+
# @!attribute [rw] count
|
|
24
|
+
# @return [Integer, nil] the number of messages from this source.
|
|
25
|
+
# @!attribute [rw] disposition
|
|
26
|
+
# @return [String, nil] the action applied to the messages.
|
|
27
|
+
# @!attribute [rw] dkim
|
|
28
|
+
# @return [String, nil] the DKIM evaluation result.
|
|
29
|
+
# @!attribute [rw] spf
|
|
30
|
+
# @return [String, nil] the SPF evaluation result.
|
|
31
|
+
# @!attribute [rw] envelope_to
|
|
32
|
+
# @return [String, nil] the envelope recipient domain.
|
|
33
|
+
# @!attribute [rw] envelope_from
|
|
34
|
+
# @return [String, nil] the envelope sender domain.
|
|
35
|
+
# @!attribute [rw] header_from
|
|
36
|
+
# @return [String, nil] the `From` header domain.
|
|
37
|
+
ATTRIBUTES.each { |attribute| attr_accessor(attribute) }
|
|
21
38
|
attr_writer :auth_results, :reasons
|
|
22
39
|
|
|
23
40
|
def initialize(node)
|
data/lib/dmarcator/version.rb
CHANGED
data/lib/dmarcator.rb
CHANGED
|
@@ -6,3 +6,15 @@ require 'dmarcator/record'
|
|
|
6
6
|
require 'dmarcator/parser'
|
|
7
7
|
require 'dmarcator/report'
|
|
8
8
|
require 'dmarcator/version'
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Parses, queries, and validates DMARC records and aggregates.
|
|
12
|
+
#
|
|
13
|
+
# Use {DMARCator::Record} for `_dmarc.<domain>` TXT records, {DMARCator::Report}
|
|
14
|
+
# for aggregate/forensic XML reports, and {DMARCator::Resolver} to choose the
|
|
15
|
+
# DNS transport (plaintext, DNS over HTTPS, or DNS over TLS).
|
|
16
|
+
#
|
|
17
|
+
# @see https://www.rfc-editor.org/rfc/rfc7489
|
|
18
|
+
#
|
|
19
|
+
module DMARCator
|
|
20
|
+
end
|