dmarcator 0.6.1 → 0.7.0
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 +10 -0
- data/README.md +12 -4
- data/lib/dmarcator/query.rb +9 -6
- data/lib/dmarcator/record.rb +0 -7
- data/lib/dmarcator/report.rb +3 -1
- data/lib/dmarcator/resolver/doh.rb +22 -3
- data/lib/dmarcator/uri.rb +9 -4
- data/lib/dmarcator/version.rb +1 -1
- data/lib/dmarcator/xml_helper.rb +0 -24
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dfd0c6f8b9f84d311a49c5ad8a6b0e43618bd151ebd9cb7532a76486f4ea600a
|
|
4
|
+
data.tar.gz: 1b0eb0947c60844e369594d4c28e68510a91246faceb4a099f2c08dd7391a3fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a151f9aabc45f12dc8107a0d4fb8c61bcb4f66880698045ba6df8dc97664f4840db0dc98af62fce3f2c77243f9250ee0df6d886908816a71b853d55f94d4ca8a
|
|
7
|
+
data.tar.gz: 7d7d50f10d95e4d762e8a8f3f1deb9fab301f690b942e65f77129f0ee633afa108281a86947ac6fe81a59e8062fe62f51e9b492ab0f2e08940b6483ad27f8248
|
data/ChangeLog.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
### 0.7.0 / 2026-10-29
|
|
2
|
+
|
|
3
|
+
* Plaintext DNS lookups now fail closed: a lookup failure raises `DMARCator::ResolutionError` instead of being reported as a domain without a DMARC record. Previously a timeout or resolver failure looked like no policy, which failed open. This is a behavior change and applies to every transport.
|
|
4
|
+
* DoH responses must use the `application/dns-message` content type (RFC 8484) and are capped at 65,535 bytes.
|
|
5
|
+
* `DMARCator::Uri` no longer forwards calls to private methods of the wrapped `URI::MailTo`.
|
|
6
|
+
* Remove the unused `XmlHelper#get_int`/`#get_timestamp` helpers and the deprecated `Record.from_txt`.
|
|
7
|
+
* Memoize a missing report version instead of recomputing it, and scope spec constants under `DMARCator`.
|
|
8
|
+
* Add `changelog_uri` and `documentation_uri` gem metadata, and document that domains must be passed as A-labels.
|
|
9
|
+
* Add a CI workflow that runs the specs on the minimum supported Ruby and the current release, loads the gem with warnings enabled, fails on YARD warnings, and builds the gem.
|
|
10
|
+
|
|
1
11
|
### 0.6.1 / 2026-10-29
|
|
2
12
|
|
|
3
13
|
* 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).
|
data/README.md
CHANGED
|
@@ -93,6 +93,11 @@ record = DMARCator::Record.query("twitter.com")
|
|
|
93
93
|
# => #<DMARCator::Record ... @p=:reject, @rua=[...], ...>
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
Domains must be ASCII (A-labels): pass punycode such as
|
|
97
|
+
`xn--mnchen-3ya.de` rather than `münchen.de`. A lookup failure raises
|
|
98
|
+
`DMARCator::ResolutionError`; `nil` only means the domain genuinely has no
|
|
99
|
+
DMARC record.
|
|
100
|
+
|
|
96
101
|
## Strict parsing
|
|
97
102
|
|
|
98
103
|
By default, unknown tags and tags with invalid values are ignored. Pass
|
|
@@ -149,9 +154,11 @@ hostname.
|
|
|
149
154
|
|
|
150
155
|
Encrypted transports fail closed: a transport failure raises
|
|
151
156
|
`DMARCator::ResolutionError` instead of being reported as a domain without a
|
|
152
|
-
DMARC record.
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
DMARC record. This applies to every transport, including plaintext —
|
|
158
|
+
`Resolv` returns an empty result for NXDOMAIN and raises for timeouts and
|
|
159
|
+
other failures, so a failed lookup is never mistaken for an absent record.
|
|
160
|
+
Plaintext remains the default because forcing a public resolver breaks private
|
|
161
|
+
and split-horizon zones, and some networks block port 853 or public resolvers.
|
|
155
162
|
|
|
156
163
|
A resolver can also be built explicitly:
|
|
157
164
|
|
|
@@ -181,7 +188,8 @@ DMARC records and XML reports are treated as untrusted input:
|
|
|
181
188
|
TLS certificates and fail closed: a transport failure raises
|
|
182
189
|
`DMARCator::ResolutionError` rather than being reported as a domain without a
|
|
183
190
|
DMARC record, so a blocked or downgraded lookup can never silently enforce no
|
|
184
|
-
policy.
|
|
191
|
+
policy. Plaintext lookups fail closed the same way. DoH responses must be
|
|
192
|
+
`application/dns-message` and are capped at 65,535 bytes.
|
|
185
193
|
|
|
186
194
|
## Requirements
|
|
187
195
|
|
data/lib/dmarcator/query.rb
CHANGED
|
@@ -104,11 +104,11 @@ module DMARCator
|
|
|
104
104
|
# plaintext resolvers receive {DNS_TIMEOUTS}.
|
|
105
105
|
#
|
|
106
106
|
# @return [String, nil]
|
|
107
|
-
# The domain's DMARC record or `nil` if none
|
|
107
|
+
# The domain's DMARC record, or `nil` if the domain simply has none.
|
|
108
108
|
#
|
|
109
|
-
# @raise [
|
|
110
|
-
#
|
|
111
|
-
#
|
|
109
|
+
# @raise [ResolutionError]
|
|
110
|
+
# The lookup failed. A failure is never reported as a missing record, so a
|
|
111
|
+
# blocked or degraded resolver cannot silently enforce no policy.
|
|
112
112
|
#
|
|
113
113
|
# @api semipublic
|
|
114
114
|
#
|
|
@@ -121,10 +121,13 @@ module DMARCator
|
|
|
121
121
|
|
|
122
122
|
host = "_dmarc.#{domain}"
|
|
123
123
|
|
|
124
|
+
# Resolv returns an empty result for NXDOMAIN and raises for timeouts and
|
|
125
|
+
# other failures, so any exception here is a transport failure, not an
|
|
126
|
+
# absent record.
|
|
124
127
|
begin
|
|
125
128
|
records = resolver.getresources(host, Resolv::DNS::Resource::IN::TXT)
|
|
126
|
-
rescue Resolv::ResolvError
|
|
127
|
-
|
|
129
|
+
rescue Resolv::ResolvError => error
|
|
130
|
+
raise ResolutionError, "DNS lookup failed for #{host}: #{error.message}"
|
|
128
131
|
end
|
|
129
132
|
|
|
130
133
|
# Per RFC 7489 §6.6.3, use the TXT record beginning with "v=DMARC1".
|
data/lib/dmarcator/record.rb
CHANGED
|
@@ -288,13 +288,6 @@ module DMARCator
|
|
|
288
288
|
raise InvalidRecord.new(truncate_error_message(error.message), error.cause)
|
|
289
289
|
end
|
|
290
290
|
|
|
291
|
-
#
|
|
292
|
-
# @deprecated use {parse} instead.
|
|
293
|
-
#
|
|
294
|
-
def self.from_txt(rec)
|
|
295
|
-
parse(rec)
|
|
296
|
-
end
|
|
297
|
-
|
|
298
291
|
# Maximum length of error messages echoed from parsing untrusted input.
|
|
299
292
|
MAX_ERROR_MESSAGE_LENGTH = 500
|
|
300
293
|
|
data/lib/dmarcator/report.rb
CHANGED
|
@@ -25,6 +25,12 @@ module DMARCator
|
|
|
25
25
|
# Default connect/read timeout in seconds.
|
|
26
26
|
DEFAULT_TIMEOUT = 5
|
|
27
27
|
|
|
28
|
+
# Maximum accepted response body in bytes (the DNS message limit).
|
|
29
|
+
MAX_RESPONSE_BYTES = 65_535
|
|
30
|
+
|
|
31
|
+
# Media type RFC 8484 requires for DNS messages.
|
|
32
|
+
CONTENT_TYPE = 'application/dns-message'
|
|
33
|
+
|
|
28
34
|
# @return [URI::HTTPS] the DoH endpoint.
|
|
29
35
|
attr_reader :url
|
|
30
36
|
|
|
@@ -92,8 +98,8 @@ module DMARCator
|
|
|
92
98
|
http.ipaddr = @ipaddr if @ipaddr
|
|
93
99
|
|
|
94
100
|
request = Net::HTTP::Post.new(@url.request_uri)
|
|
95
|
-
request['content-type'] =
|
|
96
|
-
request['accept'] =
|
|
101
|
+
request['content-type'] = CONTENT_TYPE
|
|
102
|
+
request['accept'] = CONTENT_TYPE
|
|
97
103
|
request.body = query.encode
|
|
98
104
|
|
|
99
105
|
response = http.start { |connection| connection.request(request) }
|
|
@@ -104,7 +110,20 @@ module DMARCator
|
|
|
104
110
|
raise ResolutionError, "DoH endpoint returned HTTP #{status}"
|
|
105
111
|
end
|
|
106
112
|
|
|
107
|
-
response.
|
|
113
|
+
content_type = response['content-type'].to_s
|
|
114
|
+
|
|
115
|
+
unless content_type.start_with?(CONTENT_TYPE)
|
|
116
|
+
raise ResolutionError, "DoH endpoint returned unexpected content-type: #{content_type.inspect}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
body = response.body.to_s.b
|
|
120
|
+
|
|
121
|
+
if body.bytesize > MAX_RESPONSE_BYTES
|
|
122
|
+
raise ResolutionError,
|
|
123
|
+
"DoH response exceeds maximum size of #{MAX_RESPONSE_BYTES} bytes"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
body
|
|
108
127
|
rescue ResolutionError
|
|
109
128
|
raise
|
|
110
129
|
rescue StandardError => error
|
data/lib/dmarcator/uri.rb
CHANGED
|
@@ -118,19 +118,24 @@ module DMARCator
|
|
|
118
118
|
private
|
|
119
119
|
|
|
120
120
|
#
|
|
121
|
-
# Pass
|
|
121
|
+
# Pass public methods through to {#uri}.
|
|
122
|
+
#
|
|
123
|
+
# Private methods are not forwarded: delegation is limited to the wrapped
|
|
124
|
+
# URI's public interface.
|
|
122
125
|
#
|
|
123
126
|
def method_missing(name,*arguments,&block)
|
|
124
|
-
@uri.
|
|
127
|
+
return super unless @uri.respond_to?(name)
|
|
128
|
+
|
|
129
|
+
@uri.public_send(name,*arguments,&block)
|
|
125
130
|
end
|
|
126
131
|
|
|
127
132
|
#
|
|
128
|
-
# Indicates whether a missing method can be handled by {#uri}.
|
|
133
|
+
# Indicates whether a missing public method can be handled by {#uri}.
|
|
129
134
|
#
|
|
130
135
|
# @return [Boolean]
|
|
131
136
|
#
|
|
132
137
|
def respond_to_missing?(name, include_private = false)
|
|
133
|
-
@uri.respond_to?(name
|
|
138
|
+
@uri.respond_to?(name) || super
|
|
134
139
|
end
|
|
135
140
|
|
|
136
141
|
end
|
data/lib/dmarcator/version.rb
CHANGED
data/lib/dmarcator/xml_helper.rb
CHANGED
|
@@ -20,30 +20,6 @@ module DMARCator
|
|
|
20
20
|
@node.at_xpath(xpath)&.text
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
# Returns a Unix timestamp as a `Time`.
|
|
25
|
-
#
|
|
26
|
-
# @param [String] xpath
|
|
27
|
-
# The XPath expression to match.
|
|
28
|
-
#
|
|
29
|
-
# @return [Time, nil]
|
|
30
|
-
#
|
|
31
|
-
def get_timestamp(xpath)
|
|
32
|
-
parse_timestamp(get_text(xpath))
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
# Returns an integer value.
|
|
37
|
-
#
|
|
38
|
-
# @param [String] xpath
|
|
39
|
-
# The XPath expression to match.
|
|
40
|
-
#
|
|
41
|
-
# @return [Integer, nil]
|
|
42
|
-
#
|
|
43
|
-
def get_int(xpath)
|
|
44
|
-
parse_integer(get_text(xpath))
|
|
45
|
-
end
|
|
46
|
-
|
|
47
23
|
#
|
|
48
24
|
# Parses a Unix timestamp from raw element text.
|
|
49
25
|
#
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dmarcator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Noah Berman
|
|
@@ -92,6 +92,8 @@ licenses:
|
|
|
92
92
|
metadata:
|
|
93
93
|
source_code_uri: https://github.com/bermannoah/dmarcator
|
|
94
94
|
bug_tracker_uri: https://github.com/bermannoah/dmarcator/issues
|
|
95
|
+
changelog_uri: https://github.com/bermannoah/dmarcator/blob/main/ChangeLog.md
|
|
96
|
+
documentation_uri: https://rubydoc.info/gems/dmarcator
|
|
95
97
|
rubygems_mfa_required: 'true'
|
|
96
98
|
rdoc_options: []
|
|
97
99
|
require_paths:
|