npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Domain-based Message Authentication, Reporting and Conformance (DMARC) is the cornerstone of email anti-spoofing protection. A DMARC rollout progresses through three phases: monitoring (p=none), quarantine (p=quarantine), and full enforcement (p=reject). When configured at p=reject, any email that fails both SPF and DKIM checks is outright rejected. Google and Yahoo now require DMARC for bulk senders (5,000+ emails), driving a 65% reduction in unauthenticated messages. The rollout typically takes 3-6 months for safe deployment.
When to Use
- When conducting security assessments that involve performing dmarc policy enforcement rollout
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- Administrative access to DNS management for the domain
- Understanding of SPF, DKIM, and DMARC protocols (RFC 7208, 6376, 7489)
- Complete inventory of all legitimate email sending sources
- DMARC reporting analysis tool (EasyDMARC, DMARCLY, Valimail, or dmarcian)
- Email gateway with DMARC enforcement capability
Key Concepts
DMARC Policy Levels
| Policy | Behavior | Use Case |
|---|---|---|
| p=none | Monitor only, no action on failures | Discovery phase |
| p=quarantine | Send failing messages to spam/junk | Transition phase |
| p=reject | Block failing messages entirely | Full enforcement |
DMARC Record Anatomy
v=DMARC1; p=quarantine; pct=25; rua=mailto:dmarc-agg@company.com; ruf=mailto:dmarc-forensic@company.com; adkim=r; aspf=r; fo=1- p: Policy for organizational domain
- sp: Policy for subdomains
- pct: Percentage of messages subject to policy (for gradual rollout)
- rua: Aggregate report destination (daily XML reports)
- ruf: Forensic report destination (per-failure reports)
- adkim: DKIM alignment mode (r=relaxed, s=strict)
- aspf: SPF alignment mode (r=relaxed, s=strict)
- fo: Failure reporting options (0=both fail, 1=either fails)
SPF and DKIM Alignment
- SPF Alignment: The domain in the Return-Path (envelope sender) must match the From header domain
- DKIM Alignment: The d= domain in the DKIM signature must match the From header domain
- Relaxed: Organizational domain match (sub.example.com matches example.com)
- Strict: Exact domain match required
Workflow
Step 1: Inventory All Sending Sources (Week 1-2)
- Audit all systems sending email as your domain (marketing, CRM, ticketing, transactional)
- Document third-party services: Salesforce, Mailchimp, SendGrid, Zendesk, etc.
- Identify internal mail servers, applications, and relay hosts
- Check for shadow IT email sending (departments using unauthorized services)
Step 2: Configure SPF and DKIM (Week 2-4)
- Consolidate SPF record with all legitimate sending IPs and includes
- Ensure SPF record stays under 10 DNS lookup limit
- Generate and publish DKIM keys for each sending source
- Verify DKIM signing works for all outbound mail paths
- Test with MX Toolbox or dmarcian SPF/DKIM validators
Step 3: Deploy DMARC in Monitoring Mode (Week 4-6)
- Publish initial DMARC record:
v=DMARC1; p=none; rua=mailto:dmarc@company.com; fo=1 - Wait 1-2 weeks to collect representative aggregate reports
- Analyze reports to identify unauthorized senders and alignment failures
- Fix SPF/DKIM for all legitimate sources showing failures
- Iterate until all legitimate mail passes DMARC
Step 4: Move to Quarantine with pct Tag (Week 6-12)
- Update to quarantine at 10%:
v=DMARC1; p=quarantine; pct=10; rua=... - Monitor for false positives (legitimate mail being quarantined)
- Increase pct gradually: 10% -> 25% -> 50% -> 75% -> 100%
- Each increase: wait 1-2 weeks and review reports before advancing
- Fix any remaining alignment issues discovered at each stage
Step 5: Advance to Reject Policy (Week 12-20)
- After stable quarantine at 100%, move to reject at 10%:
v=DMARC1; p=reject; pct=10; rua=... - Gradually increase pct: 10% -> 25% -> 50% -> 100%
- Monitor closely for legitimate mail being rejected
- Establish emergency rollback procedure (revert to quarantine)
- Apply subdomain policy:
sp=rejectfor subdomains
Step 6: Ongoing Monitoring and Maintenance
- Continuously monitor DMARC aggregate reports
- Add new sending sources before they start sending
- Review forensic reports for spoofing attempts
- Maintain SPF record as sending infrastructure changes
- Rotate DKIM keys annually
Tools & Resources
- EasyDMARC: DMARC monitoring dashboard with aggregate/forensic report analysis
- DMARCLY: SPF, DKIM, DMARC monitoring with auto-DNS updates
- dmarcian: DMARC deployment and management platform
- Valimail: Automated DMARC enforcement with hosted authentication
- MX Toolbox: DNS record lookup and DMARC validator
- Google Admin Toolbox: DMARC check and diagnostic tools
Validation
- DMARC record published and resolving correctly at _dmarc.domain.com
- All legitimate sending sources pass SPF and/or DKIM alignment
- Aggregate reports show >99% legitimate mail passing DMARC
- Spoofed messages from unauthorized senders are rejected
- No legitimate mail blocked after full p=reject enforcement
- Subdomain policy (sp=) also set to reject
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md0.9 KB
API Reference — Performing DMARC Policy Enforcement Rollout
Libraries Used
- dnspython (dns.resolver): DNS TXT record queries for DMARC, SPF, DKIM
CLI Interface
python agent.py check --domain example.com
python agent.py audit --domains example.com example.org [--selectors default google k1]Core Functions
check_dmarc(domain) — Query _dmarc.<domain> TXT
check_spf(domain) — Query domain TXT for v=spf1
check_dkim(domain, selector) — Query <selector>._domainkey.<domain>
audit_domains(domains, selectors) — Full DMARC/SPF/DKIM audit with scoring
DMARC Policy Levels
| Policy | Enforcement | Score |
|---|---|---|
none |
No enforcement (monitoring only) | 0 |
quarantine |
Suspicious mail sent to spam | +20 |
reject |
Unauthorized mail rejected | +40 |
Dependencies
pip install dnspython>=2.4standards.md1.7 KB
Standards & References: Performing DMARC Policy Enforcement Rollout
Core Protocol Standards
- RFC 7489: Domain-based Message Authentication, Reporting, and Conformance (DMARC)
- RFC 7208: Sender Policy Framework (SPF) for Authorizing Use of Domains in Email
- RFC 6376: DomainKeys Identified Mail (DKIM) Signatures
- RFC 8616: Email Authentication for Internationalized Mail
- RFC 8601: Message Header Field for Indicating Message Authentication Status
Regulatory Requirements (2025)
- Google Bulk Sender Requirements: DMARC required for senders of 5,000+ messages/day to Gmail
- Yahoo Sender Requirements: DMARC authentication required for bulk senders
- PCI DSS 4.0 Requirement 5.3: Anti-phishing mechanisms including email authentication
- NIST SP 800-177 Rev.1: Trustworthy Email guidance
DMARC Policy Progression
| Phase | Duration | Record | pct | Monitoring |
|---|---|---|---|---|
| Discovery | 2-4 weeks | p=none | N/A | Daily report review |
| Soft enforce | 4-6 weeks | p=quarantine | 10->100 | Twice-weekly review |
| Hard enforce | 4-6 weeks | p=reject | 10->100 | Weekly review |
| Maintenance | Ongoing | p=reject | 100 | Monthly review |
SPF Lookup Limit
- Maximum 10 DNS lookups per SPF evaluation (RFC 7208 Section 4.6.4)
- Each
include:,a:,mx:,redirect=, andexists:counts as one lookup - Exceeding 10 lookups causes SPF permerror (treated as fail by some receivers)
- Use SPF flattening tools to stay within limit
MITRE ATT&CK References
- T1566.001: Phishing: Spearphishing Attachment
- T1566.002: Phishing: Spearphishing Link
- T1586.002: Compromise Accounts: Email Accounts
- T1656: Impersonation
workflows.md2.7 KB
Workflows: Performing DMARC Policy Enforcement Rollout
Workflow 1: DMARC Phased Rollout
Week 1-2: Discovery
|
v
[Inventory all legitimate email sending sources]
+-- Internal mail servers
+-- Third-party SaaS (marketing, CRM, support)
+-- Transactional email services
+-- Application-generated email
|
v
Week 2-4: Foundation
|
v
[Configure SPF and DKIM for all sources]
+-- Publish SPF record with all includes
+-- Validate SPF under 10 lookup limit
+-- Generate DKIM keys per sending source
+-- Test outbound authentication
|
v
Week 4-6: Monitor
|
v
[Publish p=none DMARC record]
+-- Collect aggregate reports for 2 weeks
+-- Analyze: who is sending as your domain?
+-- Fix alignment failures for legitimate sources
+-- Identify unauthorized/spoofing sources
|
v
Week 6-12: Quarantine
|
v
[Move to p=quarantine with gradual pct increase]
+-- pct=10 (2 weeks) -> check false positives
+-- pct=25 (2 weeks) -> verify clean
+-- pct=50 (1 week) -> validate stability
+-- pct=100 (2 weeks) -> confirm all legitimate passes
|
v
Week 12-20: Reject
|
v
[Move to p=reject with gradual pct increase]
+-- pct=10 (2 weeks) -> monitor rejections
+-- pct=25 (2 weeks) -> verify no legitimate blocked
+-- pct=50 (1 week) -> near full enforcement
+-- pct=100 -> FULL ENFORCEMENT ACHIEVED
|
v
Ongoing: Maintenance
+-- Monitor aggregate reports monthly
+-- Update SPF/DKIM for new sending sources
+-- Rotate DKIM keys annuallyWorkflow 2: DMARC Report Analysis
Aggregate report received (daily XML)
|
v
[Parse report in DMARC analyzer]
|
v
[Categorize sending sources]
+-- PASS: Legitimate, properly authenticated
+-- FAIL (known): Legitimate source with auth issue -> FIX
+-- FAIL (unknown): Unauthorized sender -> INVESTIGATE
|
v
[For each FAIL (known)]
+-- Identify missing SPF include or DKIM config
+-- Update DNS records
+-- Wait for next report to confirm fix
|
v
[For each FAIL (unknown)]
+-- Is it spoofing? -> Document for enforcement case
+-- Is it shadow IT? -> Onboard or decommission
+-- Is it forwarding? -> ARC chain may be neededWorkflow 3: Emergency Rollback
Legitimate email being rejected (false positive detected)
|
v
[Immediate: Roll back pct or policy]
+-- Reduce pct to previous stable level
+-- OR roll back from reject to quarantine
+-- OR roll back from quarantine to none
|
v
[Investigate root cause]
+-- Check aggregate reports for failing source
+-- Verify SPF/DKIM configuration for source
+-- Check for forwarding or mailing list issues
|
v
[Fix and re-advance]
+-- Correct authentication issue
+-- Verify fix in next report cycle
+-- Resume gradual pct advancementScripts 2
agent.py5.4 KB
#!/usr/bin/env python3
"""Agent for performing DMARC policy enforcement rollout with DNS record analysis."""
import json
import argparse
from datetime import datetime
try:
import dns.resolver
except ImportError:
dns = None
def check_dmarc(domain):
"""Query and parse DMARC record for a domain."""
try:
answers = dns.resolver.resolve(f"_dmarc.{domain}", "TXT")
for rdata in answers:
txt = rdata.to_text().strip('"')
if txt.startswith("v=DMARC1"):
return parse_dmarc_tags(txt, domain)
return {"domain": domain, "dmarc_found": False}
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, Exception) as e:
return {"domain": domain, "dmarc_found": False, "error": str(e)}
def parse_dmarc_tags(record, domain):
tags = {}
for part in record.split(";"):
part = part.strip()
if "=" in part:
k, _, v = part.partition("=")
tags[k.strip()] = v.strip()
policy = tags.get("p", "none")
findings = []
if policy == "none":
findings.append({"severity": "HIGH", "finding": "DMARC policy is 'none' — no enforcement"})
elif policy == "quarantine":
findings.append({"severity": "MEDIUM", "finding": "DMARC policy is 'quarantine' — partial enforcement"})
if "rua" not in tags:
findings.append({"severity": "MEDIUM", "finding": "No aggregate report URI (rua) configured"})
pct = int(tags.get("pct", "100"))
if pct < 100:
findings.append({"severity": "INFO", "finding": f"Policy applied to {pct}% of messages"})
sp = tags.get("sp", policy)
if sp == "none" and policy != "none":
findings.append({"severity": "MEDIUM", "finding": "Subdomain policy (sp) is 'none'"})
return {
"domain": domain, "dmarc_found": True, "record": record,
"policy": policy, "subdomain_policy": sp, "percentage": pct,
"rua": tags.get("rua"), "ruf": tags.get("ruf"),
"adkim": tags.get("adkim", "r"), "aspf": tags.get("aspf", "r"),
"findings": findings,
"enforcement_level": "full" if policy == "reject" and pct == 100 else "partial" if policy != "none" else "none",
}
def check_spf(domain):
"""Query and analyze SPF record."""
try:
answers = dns.resolver.resolve(domain, "TXT")
for rdata in answers:
txt = rdata.to_text().strip('"')
if txt.startswith("v=spf1"):
mechanisms = txt.split()
qualifier = mechanisms[-1] if mechanisms else "~all"
return {"domain": domain, "spf_found": True, "record": txt,
"mechanisms": mechanisms, "qualifier": qualifier,
"strict": qualifier == "-all"}
return {"domain": domain, "spf_found": False}
except Exception as e:
return {"domain": domain, "spf_found": False, "error": str(e)}
def check_dkim(domain, selector="default"):
"""Query DKIM public key record."""
try:
fqdn = f"{selector}._domainkey.{domain}"
answers = dns.resolver.resolve(fqdn, "TXT")
for rdata in answers:
txt = rdata.to_text().strip('"')
if "p=" in txt:
return {"domain": domain, "selector": selector, "dkim_found": True, "record": txt[:200]}
return {"domain": domain, "selector": selector, "dkim_found": False}
except Exception as e:
return {"domain": domain, "selector": selector, "dkim_found": False, "error": str(e)}
def audit_domains(domains, selectors=None):
"""Audit multiple domains for DMARC/SPF/DKIM readiness."""
selectors = selectors or ["default", "google", "selector1", "selector2", "k1"]
results = []
for domain in domains:
domain = domain.strip()
if not domain:
continue
dmarc = check_dmarc(domain)
spf = check_spf(domain)
dkim_results = []
for sel in selectors:
dkim = check_dkim(domain, sel)
if dkim.get("dkim_found"):
dkim_results.append(dkim)
score = 0
if dmarc.get("policy") == "reject":
score += 40
elif dmarc.get("policy") == "quarantine":
score += 20
if spf.get("strict"):
score += 30
elif spf.get("spf_found"):
score += 15
if dkim_results:
score += 30
results.append({
"domain": domain, "dmarc": dmarc, "spf": spf,
"dkim": dkim_results, "security_score": score,
})
return {"timestamp": datetime.utcnow().isoformat(), "domains": results}
def main():
if not dns:
print(json.dumps({"error": "dnspython not installed"}))
return
parser = argparse.ArgumentParser(description="DMARC Policy Enforcement Rollout Agent")
sub = parser.add_subparsers(dest="command")
d = sub.add_parser("check", help="Check single domain")
d.add_argument("--domain", required=True)
a = sub.add_parser("audit", help="Audit multiple domains")
a.add_argument("--domains", nargs="+", required=True)
a.add_argument("--selectors", nargs="*", default=None)
args = parser.parse_args()
if args.command == "check":
result = {"dmarc": check_dmarc(args.domain), "spf": check_spf(args.domain)}
elif args.command == "audit":
result = audit_domains(args.domains, args.selectors)
else:
parser.print_help()
return
print(json.dumps(result, indent=2, default=str))
if __name__ == "__main__":
main()
process.py11.7 KB
#!/usr/bin/env python3
"""
DMARC Policy Enforcement Rollout Analyzer
Checks current DMARC, SPF, and DKIM status for a domain and provides
rollout recommendations. Parses DMARC aggregate reports to identify
sending sources and authentication failures.
Usage:
python process.py check --domain example.com
python process.py parse-report --report-file aggregate.xml
python process.py spf-audit --domain example.com
"""
import argparse
import json
import re
import sys
import xml.etree.ElementTree as ET
from dataclasses import dataclass, field, asdict
from collections import defaultdict
try:
import dns.resolver
HAS_DNS = True
except ImportError:
HAS_DNS = False
@dataclass
class DMARCStatus:
"""Current DMARC deployment status."""
domain: str = ""
dmarc_record: str = ""
dmarc_policy: str = ""
dmarc_pct: int = 100
subdomain_policy: str = ""
rua_configured: bool = False
ruf_configured: bool = False
spf_record: str = ""
spf_valid: bool = False
spf_lookup_count: int = 0
dkim_found: bool = False
current_phase: str = ""
next_action: str = ""
issues: list = field(default_factory=list)
@dataclass
class ReportSummary:
"""DMARC aggregate report summary."""
report_org: str = ""
report_domain: str = ""
date_range: str = ""
total_messages: int = 0
pass_count: int = 0
fail_count: int = 0
pass_rate: float = 0.0
sources: list = field(default_factory=list)
failing_sources: list = field(default_factory=list)
def count_spf_lookups(spf_record: str) -> int:
"""Count DNS lookups in SPF record (max 10 allowed)."""
lookup_count = 0
lookup_mechanisms = ['include:', 'a:', 'mx:', 'redirect=', 'exists:']
for mechanism in lookup_mechanisms:
lookup_count += spf_record.lower().count(mechanism)
# bare 'a' and 'mx' without ':' also count
parts = spf_record.split()
for part in parts:
p = part.lstrip('+').lstrip('-').lstrip('~').lstrip('?')
if p in ('a', 'mx'):
lookup_count += 1
return lookup_count
def check_dmarc_status(domain: str) -> DMARCStatus:
"""Check DMARC, SPF, and DKIM status for a domain."""
status = DMARCStatus(domain=domain)
if not HAS_DNS:
status.issues.append("dnspython not installed. Install with: pip install dnspython")
return status
# Check DMARC
try:
answers = dns.resolver.resolve(f"_dmarc.{domain}", 'TXT')
for rdata in answers:
txt = str(rdata).strip('"')
if txt.startswith('v=DMARC1'):
status.dmarc_record = txt
policy = re.search(r'p=(\w+)', txt)
if policy:
status.dmarc_policy = policy.group(1)
pct = re.search(r'pct=(\d+)', txt)
if pct:
status.dmarc_pct = int(pct.group(1))
sp = re.search(r'sp=(\w+)', txt)
if sp:
status.subdomain_policy = sp.group(1)
status.rua_configured = 'rua=' in txt
status.ruf_configured = 'ruf=' in txt
break
except Exception:
status.issues.append("No DMARC record found - start with p=none")
# Check SPF
try:
answers = dns.resolver.resolve(domain, 'TXT')
for rdata in answers:
txt = str(rdata).strip('"')
if txt.startswith('v=spf1'):
status.spf_record = txt
status.spf_lookup_count = count_spf_lookups(txt)
status.spf_valid = status.spf_lookup_count <= 10
if not status.spf_valid:
status.issues.append(
f"SPF record exceeds 10 lookup limit "
f"({status.spf_lookup_count} lookups). Use SPF flattening."
)
break
except Exception:
status.issues.append("No SPF record found")
# Check common DKIM selectors
selectors = ['default', 'google', 'selector1', 'selector2', 'proofpoint',
'mimecast', 's1', 's2', 'k1']
for selector in selectors:
try:
dns.resolver.resolve(f"{selector}._domainkey.{domain}", 'TXT')
status.dkim_found = True
break
except Exception:
continue
if not status.dkim_found:
status.issues.append("No DKIM record found for common selectors")
# Determine current phase and next action
if not status.dmarc_record:
status.current_phase = "Not started"
status.next_action = "Publish DMARC record with p=none and rua= for monitoring"
elif status.dmarc_policy == "none":
status.current_phase = "Monitoring (p=none)"
status.next_action = "Analyze reports, fix auth issues, then move to p=quarantine pct=10"
elif status.dmarc_policy == "quarantine":
if status.dmarc_pct < 100:
status.current_phase = f"Quarantine at {status.dmarc_pct}%"
next_pct = min(status.dmarc_pct * 2, 100)
status.next_action = f"Increase pct to {next_pct} after reviewing reports"
else:
status.current_phase = "Quarantine at 100%"
status.next_action = "Move to p=reject pct=10 after stable period"
elif status.dmarc_policy == "reject":
if status.dmarc_pct < 100:
status.current_phase = f"Reject at {status.dmarc_pct}%"
next_pct = min(status.dmarc_pct * 2, 100)
status.next_action = f"Increase pct to {next_pct} after reviewing reports"
else:
status.current_phase = "FULL ENFORCEMENT (p=reject 100%)"
status.next_action = "Maintain: monitor reports, update auth for new sources"
if not status.rua_configured and status.dmarc_record:
status.issues.append("No rua= tag - add aggregate report destination")
if status.dmarc_policy == "reject" and not status.subdomain_policy:
status.issues.append("No subdomain policy (sp=) - subdomains can still be spoofed")
return status
def parse_dmarc_report(xml_file: str) -> ReportSummary:
"""Parse DMARC aggregate report XML."""
summary = ReportSummary()
tree = ET.parse(xml_file)
root = tree.getroot()
# Report metadata
metadata = root.find('.//report_metadata')
if metadata is not None:
org = metadata.find('org_name')
if org is not None:
summary.report_org = org.text or ""
policy = root.find('.//policy_published')
if policy is not None:
domain = policy.find('domain')
if domain is not None:
summary.report_domain = domain.text or ""
date_range = root.find('.//date_range')
if date_range is not None:
begin = date_range.find('begin')
end = date_range.find('end')
if begin is not None and end is not None:
summary.date_range = f"{begin.text} - {end.text}"
# Process records
for record in root.findall('.//record'):
row = record.find('row')
if row is None:
continue
source_ip = ""
count = 0
spf_result = ""
dkim_result = ""
disposition = ""
ip_elem = row.find('source_ip')
if ip_elem is not None:
source_ip = ip_elem.text or ""
count_elem = row.find('count')
if count_elem is not None:
count = int(count_elem.text or 0)
policy_eval = row.find('policy_evaluated')
if policy_eval is not None:
dkim_elem = policy_eval.find('dkim')
spf_elem = policy_eval.find('spf')
disp_elem = policy_eval.find('disposition')
if dkim_elem is not None:
dkim_result = dkim_elem.text or ""
if spf_elem is not None:
spf_result = spf_elem.text or ""
if disp_elem is not None:
disposition = disp_elem.text or ""
summary.total_messages += count
passed = (spf_result == "pass" or dkim_result == "pass")
source_info = {
"source_ip": source_ip,
"count": count,
"spf": spf_result,
"dkim": dkim_result,
"disposition": disposition,
"passed": passed
}
summary.sources.append(source_info)
if passed:
summary.pass_count += count
else:
summary.fail_count += count
summary.failing_sources.append(source_info)
if summary.total_messages > 0:
summary.pass_rate = (summary.pass_count / summary.total_messages) * 100
return summary
def format_status_report(status: DMARCStatus) -> str:
"""Format DMARC status as readable report."""
lines = []
lines.append("=" * 60)
lines.append(" DMARC ENFORCEMENT ROLLOUT STATUS")
lines.append("=" * 60)
lines.append(f" Domain: {status.domain}")
lines.append(f" Current Phase: {status.current_phase}")
lines.append(f" Next Action: {status.next_action}")
lines.append("")
lines.append(f" DMARC Record: {status.dmarc_record or 'NOT FOUND'}")
lines.append(f" DMARC Policy: {status.dmarc_policy or 'N/A'}")
lines.append(f" DMARC pct: {status.dmarc_pct}%")
lines.append(f" Subdomain Policy: {status.subdomain_policy or 'Not set'}")
lines.append(f" RUA Reports: {'Yes' if status.rua_configured else 'No'}")
lines.append(f" RUF Reports: {'Yes' if status.ruf_configured else 'No'}")
lines.append("")
lines.append(f" SPF Record: {status.spf_record or 'NOT FOUND'}")
lines.append(f" SPF Lookups: {status.spf_lookup_count}/10")
lines.append(f" SPF Valid: {'Yes' if status.spf_valid else 'No'}")
lines.append(f" DKIM Found: {'Yes' if status.dkim_found else 'No'}")
if status.issues:
lines.append(f"\n [ISSUES] ({len(status.issues)})")
for i, issue in enumerate(status.issues, 1):
lines.append(f" {i}. {issue}")
lines.append("=" * 60)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="DMARC Policy Enforcement Rollout Analyzer")
subparsers = parser.add_subparsers(dest="command")
check_parser = subparsers.add_parser("check", help="Check DMARC deployment status")
check_parser.add_argument("--domain", required=True)
report_parser = subparsers.add_parser("parse-report", help="Parse DMARC aggregate report")
report_parser.add_argument("--report-file", required=True)
spf_parser = subparsers.add_parser("spf-audit", help="Audit SPF record")
spf_parser.add_argument("--domain", required=True)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
if args.command == "check":
result = check_dmarc_status(args.domain)
if args.json:
print(json.dumps(asdict(result), indent=2))
else:
print(format_status_report(result))
elif args.command == "parse-report":
result = parse_dmarc_report(args.report_file)
if args.json:
print(json.dumps(asdict(result), indent=2))
else:
print(f"Report from: {result.report_org}")
print(f"Domain: {result.report_domain}")
print(f"Total messages: {result.total_messages}")
print(f"Pass rate: {result.pass_rate:.1f}%")
if result.failing_sources:
print(f"\nFailing sources ({len(result.failing_sources)}):")
for src in result.failing_sources:
print(f" IP: {src['source_ip']} | Count: {src['count']} | "
f"SPF: {src['spf']} | DKIM: {src['dkim']}")
elif args.command == "spf-audit":
status = check_dmarc_status(args.domain)
print(f"SPF Record: {status.spf_record}")
print(f"DNS Lookups: {status.spf_lookup_count}/10")
print(f"Valid: {'Yes' if status.spf_valid else 'OVER LIMIT'}")
else:
parser.print_help()
if __name__ == "__main__":
main()