dmarcator 0.9.0 → 0.11.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 +24 -0
- data/README.md +41 -0
- data/lib/dmarcator/config.rb +66 -0
- data/lib/dmarcator/exceptions.rb +17 -6
- data/lib/dmarcator/query.rb +11 -12
- data/lib/dmarcator/record.rb +2 -11
- data/lib/dmarcator/report/{record.rb → row.rb} +4 -1
- data/lib/dmarcator/report.rb +3 -3
- data/lib/dmarcator/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 689cd037f395f7803f4157fd148305f1d9d6c3dbb19fd42b61311b8e37d57124
|
|
4
|
+
data.tar.gz: 80518a23a140cb7102cb4d20a5fa896852ca8b104a3047939bcf4ba018e89699
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3b5f59702e05d0ffeeec17c9c3e7f8c1bd078448ba029fd4cf6bc8e6cbd8107dbda2e3633c228d1706f09832023a8a64b9806d6ee843b1fe13d449b19379e0c
|
|
7
|
+
data.tar.gz: de98c5111a0eb8023fa10807c7227d18bead12e17f2f43d084d8c5e3d27a2b1a4e86212c0c075fa2e75a97e5710e738de2baad45b833c31249d54a1abec80bb2
|
data/ChangeLog.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
### 0.11.0 / 2026-10-29
|
|
2
|
+
|
|
3
|
+
* Add `DMARCator.configure` for process-wide defaults, so a resolver can be set from code instead of only through the `DMARCATOR_DNS` environment variables. A configured resolver takes precedence over the environment, and `DMARCator.reset_configuration!` discards it.
|
|
4
|
+
* A configured resolver may be a callable, in which case it is invoked for each lookup. This lets threaded applications use one resolver per lookup instead of sharing a `Resolv::DNS`, which does not serialise concurrent lookups.
|
|
5
|
+
* Run CI on macOS 15 as well as Ubuntu 24.04, covering the Socket/OpenSSL and Net::HTTP paths used by the DoT and DoH transports.
|
|
6
|
+
|
|
7
|
+
### 0.10.0 / 2026-10-29
|
|
8
|
+
|
|
9
|
+
Breaking changes, taken before 1.0 so the release does not carry them:
|
|
10
|
+
|
|
11
|
+
* Add `DMARCator::Error` as the base class for every error the library raises. `InvalidRecord`, `InvalidReport` and `ResolutionError` inherit from it, so one `rescue DMARCator::Error` catches all three.
|
|
12
|
+
* `InvalidRecord` no longer inherits from `Parslet::ParseFailed`, removing the parser implementation from the public exception contract. Parse failures still expose the underlying parser error as their `cause`.
|
|
13
|
+
* Remove the deprecated `DMARCator.query` and `DMARCator::Record.query` aliases. Use `DMARCator.dmarc` and `DMARCator::Record.lookup`.
|
|
14
|
+
* Rename `DMARCator::Report::Record` to `DMARCator::Report::Row`, so aggregate rows are not confused with `DMARCator::Record`, the parsed DMARC TXT record.
|
|
15
|
+
|
|
16
|
+
Migration:
|
|
17
|
+
|
|
18
|
+
| Before | After |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `DMARCator.query(domain)` | `DMARCator.dmarc(domain)` |
|
|
21
|
+
| `DMARCator::Record.query(domain)` | `DMARCator::Record.lookup(domain)` |
|
|
22
|
+
| `DMARCator::Report::Record` | `DMARCator::Report::Row` |
|
|
23
|
+
| `rescue Parslet::ParseFailed` | `rescue DMARCator::Error` |
|
|
24
|
+
|
|
1
25
|
### 0.9.0 / 2026-10-29
|
|
2
26
|
|
|
3
27
|
* Add `DMARCator.txt_records`, which returns the TXT resource records instead of strings, so callers that cache lookups can read each record's `ttl`. `DMARCator.txt` is now defined in terms of it and behaves the same.
|
data/README.md
CHANGED
|
@@ -177,6 +177,47 @@ more than the lookup on a fast resolver); `Resolv::DNS` does not lock per query,
|
|
|
177
177
|
so use one resolver per thread when parallelising; and DoT/DoH open a new TLS
|
|
178
178
|
connection per lookup, so plaintext is the better choice for bulk work.
|
|
179
179
|
|
|
180
|
+
### SPF and DKIM
|
|
181
|
+
|
|
182
|
+
The TXT primitives also cover the other two records a checker needs. There is
|
|
183
|
+
no record-type-specific API, because the semantics belong to the caller:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
DMARCator.txt("example.com") # SPF (RFC 7208)
|
|
187
|
+
DMARCator.txt("s1._domainkey.example.com") # DKIM (RFC 6376)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Both need the raw list rather than a single value: more than one SPF record is
|
|
191
|
+
a `permerror`, and DKIM keys can be duplicated, rotated or revoked (`p=`). A
|
|
192
|
+
helper returning "the" record would hide exactly those conditions. DKIM is also
|
|
193
|
+
per-selector and selectors cannot be enumerated, so any convenience method would
|
|
194
|
+
have to take a selector and would only build the name above.
|
|
195
|
+
|
|
196
|
+
## Configuration
|
|
197
|
+
|
|
198
|
+
Process-wide defaults can be set from code, and take precedence over the
|
|
199
|
+
`DMARCATOR_DNS` environment variables:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
DMARCator.configure do |config|
|
|
203
|
+
config.resolver = DMARCator::Resolver::DoH.new(url: "https://dns.example/dns-query")
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
A resolver object is shared by every thread, and `Resolv::DNS` does not
|
|
208
|
+
serialise concurrent lookups, so threaded applications should configure a
|
|
209
|
+
factory instead:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
DMARCator.configure do |config|
|
|
213
|
+
config.resolver = -> { Resolv::DNS.new }
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`DMARCator.reset_configuration!` discards configured values, which is useful in
|
|
218
|
+
tests. Passing `resolver:` to a lookup still overrides both the configured
|
|
219
|
+
default and the environment.
|
|
220
|
+
|
|
180
221
|
## Strict parsing
|
|
181
222
|
|
|
182
223
|
By default, unknown tags and tags with invalid values are ignored. Pass
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DMARCator
|
|
4
|
+
#
|
|
5
|
+
# Process-wide defaults.
|
|
6
|
+
#
|
|
7
|
+
# @since 0.11.0
|
|
8
|
+
#
|
|
9
|
+
class Config
|
|
10
|
+
#
|
|
11
|
+
# The default resolver used when a lookup is not given one.
|
|
12
|
+
#
|
|
13
|
+
# Either a resolver ({Resolv::DNS}, {Resolver::DoH}, {Resolver::DoT}) or a
|
|
14
|
+
# callable returning one. Prefer a callable in threaded applications: a
|
|
15
|
+
# resolver object is shared by every thread, and `Resolv::DNS` does not
|
|
16
|
+
# serialise concurrent lookups.
|
|
17
|
+
#
|
|
18
|
+
# @return [Object, #call, nil]
|
|
19
|
+
#
|
|
20
|
+
attr_accessor :resolver
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#
|
|
24
|
+
# The process-wide configuration.
|
|
25
|
+
#
|
|
26
|
+
# @return [Config]
|
|
27
|
+
#
|
|
28
|
+
# @since 0.11.0
|
|
29
|
+
#
|
|
30
|
+
def self.config
|
|
31
|
+
@config ||= Config.new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#
|
|
35
|
+
# Configures process-wide defaults.
|
|
36
|
+
#
|
|
37
|
+
# DMARCator.configure do |config|
|
|
38
|
+
# config.resolver = -> { Resolv::DNS.new }
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# When set, a configured resolver takes precedence over `DMARCATOR_DNS` and
|
|
42
|
+
# is used by {txt}, {txt_records}, {dmarc} and {Record.lookup} whenever no
|
|
43
|
+
# resolver is passed explicitly.
|
|
44
|
+
#
|
|
45
|
+
# @yieldparam [Config] config
|
|
46
|
+
#
|
|
47
|
+
# @return [Config]
|
|
48
|
+
#
|
|
49
|
+
# @since 0.11.0
|
|
50
|
+
#
|
|
51
|
+
def self.configure
|
|
52
|
+
yield config if block_given?
|
|
53
|
+
config
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# Discards any configured defaults.
|
|
58
|
+
#
|
|
59
|
+
# @return [Config]
|
|
60
|
+
#
|
|
61
|
+
# @since 0.11.0
|
|
62
|
+
#
|
|
63
|
+
def self.reset_configuration!
|
|
64
|
+
@config = Config.new
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/dmarcator/exceptions.rb
CHANGED
|
@@ -1,30 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'parslet'
|
|
4
|
-
|
|
5
3
|
module DMARCator
|
|
4
|
+
#
|
|
5
|
+
# Base class for every error raised by this library.
|
|
6
|
+
#
|
|
7
|
+
# Rescuing this catches parse failures, report failures and DNS lookup
|
|
8
|
+
# failures alike.
|
|
9
|
+
#
|
|
10
|
+
# @since 0.10.0
|
|
11
|
+
#
|
|
12
|
+
class Error < StandardError
|
|
13
|
+
end
|
|
14
|
+
|
|
6
15
|
#
|
|
7
16
|
# Raised when a DMARC record cannot be parsed.
|
|
8
17
|
#
|
|
9
18
|
# @since 0.2.0
|
|
10
19
|
#
|
|
11
|
-
class InvalidRecord <
|
|
20
|
+
class InvalidRecord < Error
|
|
12
21
|
end
|
|
13
22
|
|
|
14
23
|
#
|
|
15
24
|
# Raised when a DMARC XML report cannot be parsed.
|
|
16
25
|
#
|
|
17
|
-
|
|
26
|
+
# @since 0.3.0
|
|
27
|
+
#
|
|
28
|
+
class InvalidReport < Error
|
|
18
29
|
end
|
|
19
30
|
|
|
20
31
|
#
|
|
21
|
-
# Raised when
|
|
32
|
+
# Raised when a DNS transport fails to return a usable answer.
|
|
22
33
|
#
|
|
23
34
|
# This is deliberately distinct from a missing record: a failed lookup must
|
|
24
35
|
# not be mistaken for a domain without a DMARC policy.
|
|
25
36
|
#
|
|
26
37
|
# @since 0.6.0
|
|
27
38
|
#
|
|
28
|
-
class ResolutionError <
|
|
39
|
+
class ResolutionError < Error
|
|
29
40
|
end
|
|
30
41
|
end
|
data/lib/dmarcator/query.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'resolv'
|
|
4
4
|
require 'rubygems'
|
|
5
5
|
|
|
6
|
+
require_relative 'config'
|
|
6
7
|
require_relative 'resolver'
|
|
7
8
|
|
|
8
9
|
module DMARCator
|
|
@@ -125,7 +126,11 @@ module DMARCator
|
|
|
125
126
|
end
|
|
126
127
|
|
|
127
128
|
#
|
|
128
|
-
#
|
|
129
|
+
# The default resolver: a {configure}d one, or one built from the
|
|
130
|
+
# environment.
|
|
131
|
+
#
|
|
132
|
+
# A configured resolver may be an object or a callable; a callable is called
|
|
133
|
+
# for each lookup, which is what threaded applications should use.
|
|
129
134
|
#
|
|
130
135
|
# @return [Resolv::DNS, Resolver::DoH, Resolver::DoT]
|
|
131
136
|
#
|
|
@@ -135,7 +140,11 @@ module DMARCator
|
|
|
135
140
|
# @since 0.6.0
|
|
136
141
|
#
|
|
137
142
|
def self.default_resolver
|
|
138
|
-
|
|
143
|
+
configured = config.resolver
|
|
144
|
+
|
|
145
|
+
return configured.call if configured.respond_to?(:call)
|
|
146
|
+
|
|
147
|
+
configured || Resolver.build
|
|
139
148
|
end
|
|
140
149
|
|
|
141
150
|
#
|
|
@@ -246,16 +255,6 @@ module DMARCator
|
|
|
246
255
|
dmarc_records.first if dmarc_records.one?
|
|
247
256
|
end
|
|
248
257
|
|
|
249
|
-
#
|
|
250
|
-
# @deprecated Use {dmarc}, which names what it returns. Kept for
|
|
251
|
-
# compatibility; it will be removed in a future release.
|
|
252
|
-
#
|
|
253
|
-
# @see dmarc
|
|
254
|
-
#
|
|
255
|
-
def self.query(domain,resolver=default_resolver)
|
|
256
|
-
dmarc(domain, resolver: resolver)
|
|
257
|
-
end
|
|
258
|
-
|
|
259
258
|
#
|
|
260
259
|
# Looks up the TXT records at an already-validated name.
|
|
261
260
|
#
|
data/lib/dmarcator/record.rb
CHANGED
|
@@ -285,7 +285,8 @@ module DMARCator
|
|
|
285
285
|
|
|
286
286
|
new(attributes)
|
|
287
287
|
rescue Parslet::ParseFailed => error
|
|
288
|
-
|
|
288
|
+
# Raising inside the rescue sets this error as the cause automatically.
|
|
289
|
+
raise InvalidRecord, truncate_error_message(error.message)
|
|
289
290
|
end
|
|
290
291
|
|
|
291
292
|
# Maximum length of error messages echoed from parsing untrusted input.
|
|
@@ -342,16 +343,6 @@ module DMARCator
|
|
|
342
343
|
end
|
|
343
344
|
end
|
|
344
345
|
|
|
345
|
-
#
|
|
346
|
-
# @deprecated Use {lookup}, which names what it returns. Kept for
|
|
347
|
-
# compatibility; it will be removed in a future release.
|
|
348
|
-
#
|
|
349
|
-
# @see lookup
|
|
350
|
-
#
|
|
351
|
-
def self.query(domain,resolver=DMARCator.default_resolver)
|
|
352
|
-
lookup(domain, resolver: resolver)
|
|
353
|
-
end
|
|
354
|
-
|
|
355
346
|
#
|
|
356
347
|
# Converts the record to a Hash.
|
|
357
348
|
#
|
|
@@ -9,7 +9,10 @@ module DMARCator
|
|
|
9
9
|
#
|
|
10
10
|
# Represents a single `record` element of a DMARC XML report.
|
|
11
11
|
#
|
|
12
|
-
|
|
12
|
+
# Named `Row` so it is not confused with {DMARCator::Record}, the parsed
|
|
13
|
+
# DMARC TXT record.
|
|
14
|
+
#
|
|
15
|
+
class Row
|
|
13
16
|
include XmlHelper
|
|
14
17
|
|
|
15
18
|
# Attribute names exposed by this class.
|
data/lib/dmarcator/report.rb
CHANGED
|
@@ -8,7 +8,7 @@ require_relative 'xml_helper'
|
|
|
8
8
|
require_relative 'exceptions'
|
|
9
9
|
require_relative 'report/metadata'
|
|
10
10
|
require_relative 'report/policy'
|
|
11
|
-
require_relative 'report/
|
|
11
|
+
require_relative 'report/row'
|
|
12
12
|
|
|
13
13
|
module DMARCator
|
|
14
14
|
#
|
|
@@ -114,11 +114,11 @@ module DMARCator
|
|
|
114
114
|
#
|
|
115
115
|
# The report records.
|
|
116
116
|
#
|
|
117
|
-
# @return [Array<Report::
|
|
117
|
+
# @return [Array<Report::Row>]
|
|
118
118
|
#
|
|
119
119
|
def records
|
|
120
120
|
@records ||= @node.xpath('feedback/record').map do |node|
|
|
121
|
-
|
|
121
|
+
Row.new(node)
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
end
|
data/lib/dmarcator/version.rb
CHANGED
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.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Noah Berman
|
|
@@ -69,6 +69,7 @@ files:
|
|
|
69
69
|
- LICENSE.txt
|
|
70
70
|
- README.md
|
|
71
71
|
- lib/dmarcator.rb
|
|
72
|
+
- lib/dmarcator/config.rb
|
|
72
73
|
- lib/dmarcator/exceptions.rb
|
|
73
74
|
- lib/dmarcator/parser.rb
|
|
74
75
|
- lib/dmarcator/query.rb
|
|
@@ -78,7 +79,7 @@ files:
|
|
|
78
79
|
- lib/dmarcator/report/metadata.rb
|
|
79
80
|
- lib/dmarcator/report/policy.rb
|
|
80
81
|
- lib/dmarcator/report/reason.rb
|
|
81
|
-
- lib/dmarcator/report/
|
|
82
|
+
- lib/dmarcator/report/row.rb
|
|
82
83
|
- lib/dmarcator/resolver.rb
|
|
83
84
|
- lib/dmarcator/resolver/doh.rb
|
|
84
85
|
- lib/dmarcator/resolver/dot.rb
|