Building a Unix-Native DMARC Scanner with batch-dmarc
Hook
Most security tools try to be everything to everyone. batch-dmarc does exactly one thing: it reads domains from STDIN and spits out DMARC policies. In 2024, that kind of Unix philosophy feels almost radical.
Context
Email spoofing remains one of the most persistent attack vectors in cybersecurity. DMARC (Domain-based Message Authentication, Reporting, and Conformance) was created to combat this by allowing domain owners to publish policies that tell mail servers how to handle messages that fail authentication checks. The problem? Checking DMARC policies manually is tedious, and most enterprises manage hundreds or thousands of domains across subsidiaries, marketing campaigns, and legacy acquisitions.
Before tools like batch-dmarc, security teams faced an awkward choice: manually query DNS records one domain at a time using dig or nslookup, or invest in heavyweight enterprise platforms with complex UIs and SaaS pricing. What was missing was a Unix-native tool that treated DMARC checking as just another text processing task—something you could pipe, grep, and compose with other command-line utilities. batch-dmarc fills that gap by embracing the Unix philosophy: do one thing well, work with text streams, and stay out of the user's way.
Technical Insight
At its core, batch-dmarc is a Ruby script that performs DNS TXT record lookups on the _dmarc subdomain for each domain it receives. The architecture is deliberately minimal—there's no complex state management, no database, no web server. Just STDIN in, formatted text out.
The tool leverages Ruby's Resolv library for DNS queries. For each domain received, it constructs a query for _dmarc.example.com and extracts the TXT record containing the DMARC policy. Here's what that lookup pattern looks like in practice:
require 'resolv'
require 'colorize'
STDIN.each_line do |domain|
domain = domain.strip
begin
resolver = Resolv::DNS.new
dmarc_record = "_dmarc.#{domain}"
resolver.getresources(dmarc_record, Resolv::DNS::Resource::IN::TXT).each do |record|
policy = record.data
puts "#{domain}: #{policy}".colorize(:green) if policy.include?('v=DMARC1')
end
rescue => e
puts "#{domain}: No DMARC record found".colorize(:red)
end
end
This pattern demonstrates several architectural choices. First, it processes input line-by-line rather than loading everything into memory—critical when you're feeding it a list of 10,000 domains from your asset inventory. Second, it uses Ruby's exception handling to gracefully continue when domains lack DMARC records, rather than crashing the entire pipeline. Third, the colorize gem provides visual feedback that helps operators quickly scan results during interactive sessions, though it doesn't interfere with piping to other tools.
The tool's Unix integration is where it shines. You can compose it with standard utilities to build surprisingly powerful security workflows. For example, checking DMARC policies for all domains in your certificate transparency logs:
cat domains.txt | ruby batch-dmarc.rb | grep "p=none" > weak_policies.txt
Or combining it with parallel processing to speed up large batches:
cat domains.txt | parallel -j 20 "echo {} | ruby batch-dmarc.rb" > results.txt
The DMARC policy itself is a semicolon-delimited string containing directives like p=reject (reject unauthenticated mail), p=quarantine (send to spam), or p=none (monitor only). The subdomain policy sp= can differ from the main policy, and the pct= directive specifies what percentage of failing messages the policy applies to. batch-dmarc outputs the raw policy string, allowing downstream tools to parse these directives according to their specific needs.
One subtle but important design decision is the tool's error handling strategy. When a DNS lookup fails—whether due to network issues, nonexistent domains, or missing DMARC records—the tool outputs an error message but continues processing. This fail-open approach means a single misconfigured domain in your list doesn't halt the entire audit. For batch processing, this is exactly what you want: complete results with clear indications of what failed.
The reliance on Ruby's standard DNS resolution also means the tool respects your system's DNS configuration, including custom resolvers and timeout settings. This makes it easy to run in air-gapped environments or route queries through specific nameservers without modifying the tool itself.
Gotcha
The simplicity that makes batch-dmarc elegant also defines its boundaries. Most notably, it performs synchronous DNS lookups with no built-in concurrency. If you're checking thousands of domains and DNS queries average 200ms each, you're looking at significant runtime. While you can wrap it with GNU parallel or similar tools, the script itself won't optimize throughput.
The tool also provides no output format options. You get human-readable, colorized text—period. There's no JSON mode for programmatic parsing, no CSV export for spreadsheets, no structured data for ingestion into SIEM systems. If you need those formats, you'll be writing your own parser or reaching for jq and awk. Additionally, batch-dmarc only checks DMARC records. It won't verify SPF records, won't validate DKIM selectors, and won't assess whether your DMARC policy actually aligns with your SPF and DKIM configurations. For comprehensive email security auditing, you'll need a suite of tools rather than this single-purpose utility.
Finally, with only 3 GitHub stars and no apparent recent maintenance, you're adopting code that may not handle edge cases or newer DMARC extensions. There's no issue tracker history to learn from, no community of users sharing tips, and no guarantee that bugs will be fixed. You're essentially taking on maintenance yourself if problems arise.
Verdict
Use if: You need a lightweight, scriptable way to check DMARC policies across multiple domains, you're comfortable with Unix pipelines and composing simple tools together, you value simplicity and transparency over features, or you need something you can quickly audit and modify yourself. This is perfect for ad-hoc security assessments, compliance spot-checks, and situations where you want a tool that won't surprise you with complexity. Skip if: You need to process thousands of domains quickly and require built-in concurrency, you want JSON or structured output for integration with other systems, you need comprehensive email security checks beyond just DMARC, or you require actively maintained software with community support and ongoing updates. In those cases, look at checkdmarc, parsedmarc, or commercial email security platforms.