This commit is contained in:
Sean Whalen
2018-07-18 10:37:14 -04:00
parent 4a0cf63f25
commit 3ad97903e9
11 changed files with 449 additions and 80 deletions
+3 -3
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Overview: module code &mdash; parsedmarc 3.6.0 documentation</title>
<title>Overview: module code &mdash; parsedmarc 3.7.0 documentation</title>
@@ -56,7 +56,7 @@
<div class="version">
3.6.0
3.7.0
</div>
@@ -177,7 +177,7 @@
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'../',
VERSION:'3.6.0',
VERSION:'3.7.0',
LANGUAGE:'None',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
+30 -21
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>parsedmarc &mdash; parsedmarc 3.6.0 documentation</title>
<title>parsedmarc &mdash; parsedmarc 3.7.0 documentation</title>
@@ -56,7 +56,7 @@
<div class="version">
3.6.0
3.7.0
</div>
@@ -188,7 +188,7 @@
<span class="kn">import</span> <span class="nn">dateparser</span>
<span class="kn">import</span> <span class="nn">mailparser</span>
<span class="n">__version__</span> <span class="o">=</span> <span class="s2">&quot;3.6.0&quot;</span>
<span class="n">__version__</span> <span class="o">=</span> <span class="s2">&quot;3.7.0&quot;</span>
<span class="n">logger</span> <span class="o">=</span> <span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
<span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
@@ -245,22 +245,23 @@
<span class="n">psl_path</span> <span class="o">=</span> <span class="s2">&quot;.public_suffix_list.dat&quot;</span>
<span class="k">def</span> <span class="nf">download_psl</span><span class="p">():</span>
<span class="n">fresh_psl</span> <span class="o">=</span> <span class="n">publicsuffix</span><span class="o">.</span><span class="n">fetch</span><span class="p">()</span>
<span class="n">fresh_psl</span> <span class="o">=</span> <span class="n">publicsuffix</span><span class="o">.</span><span class="n">fetch</span><span class="p">()</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">psl_path</span><span class="p">,</span> <span class="s2">&quot;w&quot;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">fresh_psl_file</span><span class="p">:</span>
<span class="n">fresh_psl_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">fresh_psl</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
<span class="k">return</span> <span class="n">publicsuffix</span><span class="o">.</span><span class="n">PublicSuffixList</span><span class="p">(</span><span class="n">fresh_psl</span><span class="p">)</span>
<span class="n">fresh_psl_file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">fresh_psl</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">psl_path</span><span class="p">):</span>
<span class="n">psl</span> <span class="o">=</span> <span class="n">download_psl</span><span class="p">()</span>
<span class="n">download_psl</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">psl_age</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span> <span class="o">-</span> <span class="n">datetime</span><span class="o">.</span><span class="n">fromtimestamp</span><span class="p">(</span>
<span class="n">os</span><span class="o">.</span><span class="n">stat</span><span class="p">(</span><span class="n">psl_path</span><span class="p">)</span><span class="o">.</span><span class="n">st_mtime</span><span class="p">)</span>
<span class="k">if</span> <span class="n">psl_age</span> <span class="o">&gt;</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">hours</span><span class="o">=</span><span class="mi">24</span><span class="p">):</span>
<span class="n">psl</span> <span class="o">=</span> <span class="n">download_psl</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">psl_path</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">psl_file</span><span class="p">:</span>
<span class="n">psl</span> <span class="o">=</span> <span class="n">publicsuffix</span><span class="o">.</span><span class="n">PublicSuffixList</span><span class="p">(</span><span class="n">psl_file</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">download_psl</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">error</span><span class="p">:</span>
<span class="n">logger</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s2">&quot;Failed to download an updated PSL - </span><span class="se">\</span>
<span class="s2"> </span><span class="si">{0}</span><span class="s2">&quot;</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">error</span><span class="p">))</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">psl_path</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span> <span class="k">as</span> <span class="n">psl_file</span><span class="p">:</span>
<span class="n">psl</span> <span class="o">=</span> <span class="n">publicsuffix</span><span class="o">.</span><span class="n">PublicSuffixList</span><span class="p">(</span><span class="n">psl_file</span><span class="p">)</span>
<span class="k">return</span> <span class="n">psl</span><span class="o">.</span><span class="n">get_public_suffix</span><span class="p">(</span><span class="n">domain</span><span class="p">)</span>
@@ -531,11 +532,15 @@
<span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;auth_results&quot;</span><span class="p">][</span><span class="s2">&quot;spf&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">new_result</span><span class="p">)</span>
<span class="k">if</span> <span class="s2">&quot;envelope_from&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;identifiers&quot;</span><span class="p">]:</span>
<span class="n">envelope_from</span> <span class="o">=</span> <span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;auth_results&quot;</span><span class="p">][</span><span class="s2">&quot;spf&quot;</span><span class="p">][</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="s2">&quot;domain&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="n">envelope_from</span> <span class="o">=</span> <span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;auth_results&quot;</span><span class="p">][</span><span class="s2">&quot;spf&quot;</span><span class="p">][</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="s2">&quot;domain&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="n">envelope_from</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">envelope_from</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">envelope_from</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;identifiers&quot;</span><span class="p">][</span><span class="s2">&quot;envelope_from&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">envelope_from</span>
<span class="k">elif</span> <span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;identifiers&quot;</span><span class="p">][</span><span class="s2">&quot;envelope_from&quot;</span><span class="p">]</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">envelope_from</span> <span class="o">=</span> <span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;auth_results&quot;</span><span class="p">][</span><span class="s2">&quot;spf&quot;</span><span class="p">][</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="s2">&quot;domain&quot;</span><span class="p">]</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="n">envelope_from</span> <span class="o">=</span> <span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;auth_results&quot;</span><span class="p">][</span><span class="s2">&quot;spf&quot;</span><span class="p">][</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="s2">&quot;domain&quot;</span><span class="p">]</span>
<span class="k">if</span> <span class="n">envelope_from</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">envelope_from</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">envelope_from</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="n">new_record</span><span class="p">[</span><span class="s2">&quot;identifiers&quot;</span><span class="p">][</span><span class="s2">&quot;envelope_from&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">envelope_from</span>
<span class="n">envelope_to</span> <span class="o">=</span> <span class="kc">None</span>
@@ -728,6 +733,10 @@
<span class="sd"> Returns:</span>
<span class="sd"> str: Parsed aggregate report data in flat CSV format, including headers</span>
<span class="sd"> &quot;&quot;&quot;</span>
<span class="k">def</span> <span class="nf">to_str</span><span class="p">(</span><span class="n">obj</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">obj</span><span class="p">)</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="n">fields</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;xml_schema&quot;</span><span class="p">,</span> <span class="s2">&quot;org_name&quot;</span><span class="p">,</span> <span class="s2">&quot;org_email&quot;</span><span class="p">,</span>
<span class="s2">&quot;org_extra_contact_info&quot;</span><span class="p">,</span> <span class="s2">&quot;report_id&quot;</span><span class="p">,</span> <span class="s2">&quot;begin_date&quot;</span><span class="p">,</span> <span class="s2">&quot;end_date&quot;</span><span class="p">,</span>
<span class="s2">&quot;errors&quot;</span><span class="p">,</span> <span class="s2">&quot;domain&quot;</span><span class="p">,</span> <span class="s2">&quot;adkim&quot;</span><span class="p">,</span> <span class="s2">&quot;aspf&quot;</span><span class="p">,</span> <span class="s2">&quot;p&quot;</span><span class="p">,</span> <span class="s2">&quot;sp&quot;</span><span class="p">,</span> <span class="s2">&quot;pct&quot;</span><span class="p">,</span> <span class="s2">&quot;fo&quot;</span><span class="p">,</span>
@@ -801,9 +810,9 @@
<span class="k">if</span> <span class="s2">&quot;selector&quot;</span> <span class="ow">in</span> <span class="n">dkim_result</span><span class="p">:</span>
<span class="n">dkim_selectors</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">dkim_result</span><span class="p">[</span><span class="s2">&quot;selector&quot;</span><span class="p">])</span>
<span class="n">dkim_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">dkim_result</span><span class="p">[</span><span class="s2">&quot;result&quot;</span><span class="p">])</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;dkim_domains&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dkim_domains</span><span class="p">)</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;dkim_selectors&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dkim_selectors</span><span class="p">)</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;dkim_results&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">dkim_results</span><span class="p">)</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;dkim_domains&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="n">to_str</span><span class="p">,</span> <span class="n">dkim_domains</span><span class="p">))</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;dkim_selectors&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="n">to_str</span><span class="p">,</span> <span class="n">dkim_selectors</span><span class="p">))</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;dkim_results&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="n">to_str</span><span class="p">,</span> <span class="n">dkim_results</span><span class="p">))</span>
<span class="n">spf_domains</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">spf_scopes</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">spf_results</span> <span class="o">=</span> <span class="p">[]</span>
@@ -811,9 +820,9 @@
<span class="n">spf_domains</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">spf_result</span><span class="p">[</span><span class="s2">&quot;domain&quot;</span><span class="p">])</span>
<span class="n">spf_scopes</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">spf_result</span><span class="p">[</span><span class="s2">&quot;scope&quot;</span><span class="p">])</span>
<span class="n">spf_results</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">spf_result</span><span class="p">[</span><span class="s2">&quot;result&quot;</span><span class="p">])</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;spf_domains&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">spf_domains</span><span class="p">)</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;spf_scopes&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">spf_scopes</span><span class="p">)</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;spf_results&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">spf_results</span><span class="p">)</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;spf_domains&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="n">to_str</span><span class="p">,</span> <span class="n">spf_domains</span><span class="p">))</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;spf_scopes&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="n">to_str</span><span class="p">,</span> <span class="n">spf_scopes</span><span class="p">))</span>
<span class="n">row</span><span class="p">[</span><span class="s2">&quot;spf_results&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;,&quot;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="n">to_str</span><span class="p">,</span> <span class="n">dkim_results</span><span class="p">))</span>
<span class="n">writer</span><span class="o">.</span><span class="n">writerow</span><span class="p">(</span><span class="n">row</span><span class="p">)</span>
<span class="n">csv_file_object</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
@@ -1618,7 +1627,7 @@
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'../',
VERSION:'3.6.0',
VERSION:'3.7.0',
LANGUAGE:'None',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
+3 -3
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>parsedmarc.elastic &mdash; parsedmarc 3.6.0 documentation</title>
<title>parsedmarc.elastic &mdash; parsedmarc 3.7.0 documentation</title>
@@ -56,7 +56,7 @@
<div class="version">
3.6.0
3.7.0
</div>
@@ -545,7 +545,7 @@
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'../../',
VERSION:'3.6.0',
VERSION:'3.7.0',
LANGUAGE:'None',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
+194 -19
View File
@@ -16,18 +16,29 @@ Welcome to parsedmarc's documentation!
:target: _static/screenshots/dmarc-summary-charts.png
``parsedmarc`` is a Python module and CLI utility for parsing DMARC reports.
When used with Elasticsearch and Kibana, it works as a self-hosted open source
alternative to commercial DMARC report processing services such as Agari,
Dmarcian, and OnDMARC.
Features
========
* Parses draft and 1.0 standard aggregate reports
* Parses forensic reports
* Parses draft and 1.0 standard aggregate/rua reports
* Parses forensic/failure/ruf reports
* Can parse reports from an inbox over IMAP
* Transparently handles gzip or zip compressed reports
* Consistent data structures
* Simple JSON and/or CSV output
* Optionally email the results
* Optionally send the results to Elasticsearch, for use with premade Kibana dashboards
* Optionally send the results to Elasticsearch, for use with premade Kibana
dashboards
Resources
=========
* `Demystifying DMARC`_
CLI help
========
@@ -102,7 +113,8 @@ SPF and DMARC record validation
===============================
If you are looking for SPF and DMARC record validation and parsing,
check out the sister project, `checkdmarc <https://domainaware.github.io/checkdmarc/>`_.
check out the sister project,
`checkdmarc <https://domainaware.github.io/checkdmarc/>`_.
Sample aggregate report output
==============================
@@ -254,6 +266,10 @@ Elasticsearch and Kibana
To set up visual dashboards of DMARC data, install Elasticsearch and Kibana.
.. note::
Elasticsearch/Kibana 6+ is required
.. code-block:: bash
sudo apt-get install -y openjdk-8-jre apt-transport-https
@@ -398,17 +414,17 @@ Om the same system as Elasticsearch, pass ``--save-aggregate`` and/or
.. warning::
``--save-aggregate`` and ``--save-forensic`` are separate options because
you may not want to save forensic reports to your Elasticsearch instance,
particularly if you are in a highly-regulated industry that handles
sensitive data, such as healthcare or finance. If your legitimate outgoing
email fails DMARC, it is possible that email may appear later in a
forensic report.
you may not want to save forensic reports (also known as failure reports)
to your Elasticsearch instance, particularly if you are in a
highly-regulated industry that handles sensitive data, such as healthcare
or finance. If your legitimate outgoing email fails DMARC, it is possible
that email may appear later in a forensic report.
Forensic reports contain the original headers of an email that failed a
DMARC check, and sometimes may also include the full message body,
depending on the policy of the reporting organisation.
depending on the policy of the reporting organization.
Most reporting organisations do not send forensic reports of any kind for
Most reporting organizations do not send forensic reports of any kind for
privacy reasons. While aggregate DMARC reports are sent at least daily,
it is normal to receive very few forensic reports.
@@ -483,14 +499,6 @@ Create the service configuration file
sudo nano /etc/systemd/system/parsedmarc.service
Edit the command line options of ``parsedmarc`` in the service's ``ExecStart``
setting to suit your needs.
.. note::
Always pass the ``--watch`` option to ``parsedmarc`` when running it as a
service. Use ``--silent`` to only log errors.
.. code-block:: ini
[Unit]
@@ -505,6 +513,23 @@ setting to suit your needs.
[Install]
WantedBy=multi-user.target
Edit the command line options of ``parsedmarc`` in the service's ``ExecStart``
setting to suit your needs.
.. note::
Always pass the ``--watch`` option to ``parsedmarc`` when running it as a
service. Use ``--silent`` to only log errors.
.. warning::
As mentioned earlier, forensic/failure reports contain copies of emails
that failed DMARC, including emails that may be legitimate and contain
sensitive customer or business information. For privacy and/or regulatory
reasons, you may not want to use the ``--save-forensic`` flag included in
the example service configuration ``ExecStart`` setting, which would save
these samples to Elasticsearch.
Then, enable the service
.. code-block:: bash
@@ -518,6 +543,154 @@ Then, enable the service
You must also run the above commands whenever you edit
``parsedmarc.service``.
Use this command to check the status of the service:
.. code-block:: bash
sudo service parsedmarc status
Using the Kibana dashboards
===========================
The Kibana DMARC dashboards are a human-friendly way to understand the results
from incoming DMARC reports.
.. note::
The default dashboard is DMARC Summary. To switch between dashboards,
click on the Dashboard link in the left side menu of Kibana.
DMARC Summary
-------------
As the name suggests, this dashboard is the best place to start reviewing your
aggregate DMARC data.
Across the top of the dashboard, three pie charts display the percentage of
alignment pass/fail for SPF, DKIM, and DMARC. Clicking on any chart segment
will filter for that value.
.. note::
Messages should not be considered malicious just because they failed to pass
DMARC; especially if you have just started collecting data. It may be a
legitimate service that needs SPF and DKIM configured correctly.
Start by filtering the results to only show failed DKIM alignment. While DMARC
passes if a message passes SPF or DKIM alignment, only DKIM alignment remains
valid when a message is forwarded without changing the from address, which is
often caused by a mailbox forwarding rule. This is because DKIM signatures are
part of the message headers, whereas SPF relies on SMTP session headers.
Underneath the pie charts. you can see graphs of DMARC passage and message
disposition over time.
Under the graphs you will find the most useful data tables on the dashboard. On
the left, there is a list of organizations that are sending you DMARC reports.
In the center, there is a list of sending servers grouped by the base domain
in their reverse DNS. On the right, there is a list of email from domains,
sorted by message volume.
By hovering your mouse over a data table value and using the magnifying glass
icons, you can filter on our filter out different values. Start by looking at
the Message Sources by Reverse DNS table. Find a sender that you recognize,
such as an email marketing service, hover over it, and click on the plus (+)
magnifying glass icon, to add a filter that only shows results for that sender.
Now, look at the Message From Header table to the right. That shows you the
domains that a sender is sending as, which might tell you which brand/business
is using a particular service. With that information, you can contact them and
have them set up DKIM.
.. note::
If you have a lot of B2C customers, you may see a high volume of emails as
your domains coming from consumer email services, such as Google/Gmail and
Yahoo! This occurs when customers have mailbox rules in place that forward
emails from an old account to a new account, which is why DKIM
authentication is so important, as mentioned earlier. Similar patterns may
be observed with businesses who send from reverse DNS addressees of
parent, subsidiary, and outdated brands.
Further down the dashboard, you can filter by source country or source IP
address.
Tables showing SPF and DKIM alignment details are located under the IP address
table.
.. note::
Previously, the alignment tables were included in a separate dashboard
called DMARC Alignment Failures. That dashboard has been consolidated into
the DMARC Summary dashboard. To view failures only, use the pie chart.
Any other filters work the same way. You can also add your own custom temporary
filters by clicking on Add Filter at the upper right of the page.
DMARC Forensic Samples
----------------------
The DMARC Forensic Samples dashboard contains information on DMARC forensic
reports (also known as failure reports or ruf reports). These reports contain
samples of emails that have failed to pass DMARC.
.. note::
Most recipients do not send forensic/failure/ruf reports at all to avoid
privacy leaks. Some recipients (notably Chinese webmail services) will only
supply the headers of sample emails. Very few provide the entire email.
DMARC Alignment Guide
=====================
DMARC ensures that SPF and DKM authentication mechanisms actually authenticate
against the same domain that the end user sees.
A message passes a DMARC check by passing DKIM or SPF, **as long as the related
indicators are also in alignment**.
+-----------------------+-----------------------+-----------------------+
| | **DKIM** | **SPF** |
+-----------------------+-----------------------+-----------------------+
| **Passing** | The signature in the | The mail servers IP |
| | DKIM header is | address is listed in |
| | validated using a | the SPF record of the |
| | public key that is | domain in the SMTP |
| | published as a DNS | envelopes mail from |
| | record of the domain | header |
| | name specified in the | |
| | signature | |
+-----------------------+-----------------------+-----------------------+
| **Alignment** | The signing domain | The domain in the |
| | aligns with the | SMTP envelopes mail |
| | domain in the | from header aligns |
| | messages from header | with the domain in |
| | | the messages from |
| | | header |
+-----------------------+-----------------------+-----------------------+
What if a sender won't support DKIM/DMARC?
==========================================
#. Some vendors dont know about DMARC yet; ask about SPF and DKIM/email
authentication.
#. Check if they can send through your email relays instead of theirs.
#. Do they really need to spoof your domain? Why not use the display
name instead?
#. Worst case, have that vendor send email as a specific subdomain of
your domain (e.g. ``noreply@news.example.com``), and then create
separate SPF and DMARC records on ``news.example.com``, and set
``p=none`` in that DMARC record.
.. warning ::
Do not alter the ``p`` or ``sp`` values of the DMARC record on the
Top-Level Domain (TLD) that would leave you vulnerable to spoofing of
your TLD and/or any subdomain.
API
===
@@ -545,6 +718,8 @@ Indices and tables
.. |Build Status| image:: https://travis-ci.org/domainaware/parsedmarc.svg?branch=master
:target: https://travis-ci.org/domainaware/parsedmarc
.. _Demystifying DMARC: https://seanthegeek.net/459/demystifying-dmarc/
.. _X-Pack: https://www.elastic.co/products/x-pack
.. _kibana_saved_objects.json: https://raw.githubusercontent.com/domainaware/parsedmarc/master/kibana/kibana_saved_objects.json
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '3.6.0',
VERSION: '3.7.0',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
+3 -3
View File
@@ -9,7 +9,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index &mdash; parsedmarc 3.6.0 documentation</title>
<title>Index &mdash; parsedmarc 3.7.0 documentation</title>
@@ -57,7 +57,7 @@
<div class="version">
3.6.0
3.7.0
</div>
@@ -310,7 +310,7 @@
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'./',
VERSION:'3.6.0',
VERSION:'3.7.0',
LANGUAGE:'None',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
+207 -22
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome to parsedmarcs documentation! &mdash; parsedmarc 3.6.0 documentation</title>
<title>Welcome to parsedmarcs documentation! &mdash; parsedmarc 3.7.0 documentation</title>
@@ -56,7 +56,7 @@
<div class="version">
3.6.0
3.7.0
</div>
@@ -84,6 +84,7 @@
<div class="local-toc"><ul>
<li><a class="reference internal" href="#">Welcome to parsedmarcs documentation!</a><ul>
<li><a class="reference internal" href="#features">Features</a></li>
<li><a class="reference internal" href="#resources">Resources</a></li>
<li><a class="reference internal" href="#cli-help">CLI help</a></li>
<li><a class="reference internal" href="#spf-and-dmarc-record-validation">SPF and DMARC record validation</a></li>
<li><a class="reference internal" href="#sample-aggregate-report-output">Sample aggregate report output</a><ul>
@@ -99,6 +100,13 @@
<li><a class="reference internal" href="#running-parsedmarc-as-a-systemd-service">Running parsedmarc as a systemd service</a></li>
</ul>
</li>
<li><a class="reference internal" href="#using-the-kibana-dashboards">Using the Kibana dashboards</a><ul>
<li><a class="reference internal" href="#dmarc-summary">DMARC Summary</a></li>
<li><a class="reference internal" href="#dmarc-forensic-samples">DMARC Forensic Samples</a></li>
</ul>
</li>
<li><a class="reference internal" href="#dmarc-alignment-guide">DMARC Alignment Guide</a></li>
<li><a class="reference internal" href="#what-if-a-sender-won-t-support-dkim-dmarc">What if a sender wont support DKIM/DMARC?</a></li>
<li><a class="reference internal" href="#module-parsedmarc">API</a><ul>
<li><a class="reference internal" href="#module-parsedmarc.elastic">parsedmarc.elastic</a></li>
</ul>
@@ -174,18 +182,28 @@
<h1>Welcome to parsedmarcs documentation!<a class="headerlink" href="#welcome-to-parsedmarc-s-documentation" title="Permalink to this headline"></a></h1>
<p><a class="reference external" href="https://travis-ci.org/domainaware/parsedmarc"><img alt="Build Status" src="https://travis-ci.org/domainaware/parsedmarc.svg?branch=master" /></a></p>
<a class="reference external image-reference" href="_static/screenshots/dmarc-summary-charts.png"><img alt="A screenshot of DMARC summary charts in Kibana" class="align-center" src="_images/dmarc-summary-charts.png" style="width: 597.0px; height: 381.0px;" /></a>
<p><code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> is a Python module and CLI utility for parsing DMARC reports.</p>
<p><code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> is a Python module and CLI utility for parsing DMARC reports.
When used with Elasticsearch and Kibana, it works as a self-hosted open source
alternative to commercial DMARC report processing services such as Agari,
Dmarcian, and OnDMARC.</p>
<div class="section" id="features">
<h2>Features<a class="headerlink" href="#features" title="Permalink to this headline"></a></h2>
<ul class="simple">
<li>Parses draft and 1.0 standard aggregate reports</li>
<li>Parses forensic reports</li>
<li>Parses draft and 1.0 standard aggregate/rua reports</li>
<li>Parses forensic/failure/ruf reports</li>
<li>Can parse reports from an inbox over IMAP</li>
<li>Transparently handles gzip or zip compressed reports</li>
<li>Consistent data structures</li>
<li>Simple JSON and/or CSV output</li>
<li>Optionally email the results</li>
<li>Optionally send the results to Elasticsearch, for use with premade Kibana dashboards</li>
<li>Optionally send the results to Elasticsearch, for use with premade Kibana
dashboards</li>
</ul>
</div>
<div class="section" id="resources">
<h2>Resources<a class="headerlink" href="#resources" title="Permalink to this headline"></a></h2>
<ul class="simple">
<li><a class="reference external" href="https://seanthegeek.net/459/demystifying-dmarc/">Demystifying DMARC</a></li>
</ul>
</div>
<div class="section" id="cli-help">
@@ -259,7 +277,8 @@
<div class="section" id="spf-and-dmarc-record-validation">
<h2>SPF and DMARC record validation<a class="headerlink" href="#spf-and-dmarc-record-validation" title="Permalink to this headline"></a></h2>
<p>If you are looking for SPF and DMARC record validation and parsing,
check out the sister project, <a class="reference external" href="https://domainaware.github.io/checkdmarc/">checkdmarc</a>.</p>
check out the sister project,
<a class="reference external" href="https://domainaware.github.io/checkdmarc/">checkdmarc</a>.</p>
</div>
<div class="section" id="sample-aggregate-report-output">
<h2>Sample aggregate report output<a class="headerlink" href="#sample-aggregate-report-output" title="Permalink to this headline"></a></h2>
@@ -388,6 +407,10 @@ above commands.</p>
<div class="section" id="elasticsearch-and-kibana">
<h3>Elasticsearch and Kibana<a class="headerlink" href="#elasticsearch-and-kibana" title="Permalink to this headline"></a></h3>
<p>To set up visual dashboards of DMARC data, install Elasticsearch and Kibana.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Elasticsearch/Kibana 6+ is required</p>
</div>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo apt-get install -y openjdk-8-jre apt-transport-https
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch <span class="p">|</span> sudo apt-key add -
<span class="nb">echo</span> <span class="s2">&quot;deb https://artifacts.elastic.co/packages/6.x/apt stable main&quot;</span> <span class="p">|</span> sudo tee -a /etc/apt/sources.list.d/elastic-6.x.list
@@ -499,15 +522,15 @@ it.</p>
<div class="admonition warning">
<p class="first admonition-title">Warning</p>
<p><code class="docutils literal notranslate"><span class="pre">--save-aggregate</span></code> and <code class="docutils literal notranslate"><span class="pre">--save-forensic</span></code> are separate options because
you may not want to save forensic reports to your Elasticsearch instance,
particularly if you are in a highly-regulated industry that handles
sensitive data, such as healthcare or finance. If your legitimate outgoing
email fails DMARC, it is possible that email may appear later in a
forensic report.</p>
you may not want to save forensic reports (also known as failure reports)
to your Elasticsearch instance, particularly if you are in a
highly-regulated industry that handles sensitive data, such as healthcare
or finance. If your legitimate outgoing email fails DMARC, it is possible
that email may appear later in a forensic report.</p>
<p>Forensic reports contain the original headers of an email that failed a
DMARC check, and sometimes may also include the full message body,
depending on the policy of the reporting organisation.</p>
<p class="last">Most reporting organisations do not send forensic reports of any kind for
depending on the policy of the reporting organization.</p>
<p class="last">Most reporting organizations do not send forensic reports of any kind for
privacy reasons. While aggregate DMARC reports are sent at least daily,
it is normal to receive very few forensic reports.</p>
</div>
@@ -543,13 +566,6 @@ arrive.</p>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo nano /etc/systemd/system/parsedmarc.service
</pre></div>
</div>
<p>Edit the command line options of <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> in the services <code class="docutils literal notranslate"><span class="pre">ExecStart</span></code>
setting to suit your needs.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Always pass the <code class="docutils literal notranslate"><span class="pre">--watch</span></code> option to <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> when running it as a
service. Use <code class="docutils literal notranslate"><span class="pre">--silent</span></code> to only log errors.</p>
</div>
<div class="highlight-ini notranslate"><div class="highlight"><pre><span></span><span class="k">[Unit]</span>
<span class="na">Description</span><span class="o">=</span><span class="s">parsedmarc mailbox watcher</span>
<span class="na">Documentation</span><span class="o">=</span><span class="s">https://domainaware.github.io/parsedmarc/</span>
@@ -563,6 +579,22 @@ service. Use <code class="docutils literal notranslate"><span class="pre">--sile
<span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</pre></div>
</div>
<p>Edit the command line options of <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> in the services <code class="docutils literal notranslate"><span class="pre">ExecStart</span></code>
setting to suit your needs.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Always pass the <code class="docutils literal notranslate"><span class="pre">--watch</span></code> option to <code class="docutils literal notranslate"><span class="pre">parsedmarc</span></code> when running it as a
service. Use <code class="docutils literal notranslate"><span class="pre">--silent</span></code> to only log errors.</p>
</div>
<div class="admonition warning">
<p class="first admonition-title">Warning</p>
<p class="last">As mentioned earlier, forensic/failure reports contain copies of emails
that failed DMARC, including emails that may be legitimate and contain
sensitive customer or business information. For privacy and/or regulatory
reasons, you may not want to use the <code class="docutils literal notranslate"><span class="pre">--save-forensic</span></code> flag included in
the example service configuration <code class="docutils literal notranslate"><span class="pre">ExecStart</span></code> setting, which would save
these samples to Elasticsearch.</p>
</div>
<p>Then, enable the service</p>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo systemctl daemon-reload
sudo systemctl <span class="nb">enable</span> parsedmarc.service
@@ -574,8 +606,161 @@ sudo service parsedmarc restart
<p class="last">You must also run the above commands whenever you edit
<code class="docutils literal notranslate"><span class="pre">parsedmarc.service</span></code>.</p>
</div>
<p>Use this command to check the status of the service:</p>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>sudo service parsedmarc status
</pre></div>
</div>
</div>
</div>
<div class="section" id="using-the-kibana-dashboards">
<h2>Using the Kibana dashboards<a class="headerlink" href="#using-the-kibana-dashboards" title="Permalink to this headline"></a></h2>
<p>The Kibana DMARC dashboards are a human-friendly way to understand the results
from incoming DMARC reports.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">The default dashboard is DMARC Summary. To switch between dashboards,
click on the Dashboard link in the left side menu of Kibana.</p>
</div>
<div class="section" id="dmarc-summary">
<h3>DMARC Summary<a class="headerlink" href="#dmarc-summary" title="Permalink to this headline"></a></h3>
<p>As the name suggests, this dashboard is the best place to start reviewing your
aggregate DMARC data.</p>
<p>Across the top of the dashboard, three pie charts display the percentage of
alignment pass/fail for SPF, DKIM, and DMARC. Clicking on any chart segment
will filter for that value.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Messages should not be considered malicious just because they failed to pass
DMARC; especially if you have just started collecting data. It may be a
legitimate service that needs SPF and DKIM configured correctly.</p>
</div>
<p>Start by filtering the results to only show failed DKIM alignment. While DMARC
passes if a message passes SPF or DKIM alignment, only DKIM alignment remains
valid when a message is forwarded without changing the from address, which is
often caused by a mailbox forwarding rule. This is because DKIM signatures are
part of the message headers, whereas SPF relies on SMTP session headers.</p>
<p>Underneath the pie charts. you can see graphs of DMARC passage and message
disposition over time.</p>
<p>Under the graphs you will find the most useful data tables on the dashboard. On
the left, there is a list of organizations that are sending you DMARC reports.
In the center, there is a list of sending servers grouped by the base domain
in their reverse DNS. On the right, there is a list of email from domains,
sorted by message volume.</p>
<p>By hovering your mouse over a data table value and using the magnifying glass
icons, you can filter on our filter out different values. Start by looking at
the Message Sources by Reverse DNS table. Find a sender that you recognize,
such as an email marketing service, hover over it, and click on the plus (+)
magnifying glass icon, to add a filter that only shows results for that sender.
Now, look at the Message From Header table to the right. That shows you the
domains that a sender is sending as, which might tell you which brand/business
is using a particular service. With that information, you can contact them and
have them set up DKIM.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">If you have a lot of B2C customers, you may see a high volume of emails as
your domains coming from consumer email services, such as Google/Gmail and
Yahoo! This occurs when customers have mailbox rules in place that forward
emails from an old account to a new account, which is why DKIM
authentication is so important, as mentioned earlier. Similar patterns may
be observed with businesses who send from reverse DNS addressees of
parent, subsidiary, and outdated brands.</p>
</div>
<p>Further down the dashboard, you can filter by source country or source IP
address.</p>
<p>Tables showing SPF and DKIM alignment details are located under the IP address
table.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Previously, the alignment tables were included in a separate dashboard
called DMARC Alignment Failures. That dashboard has been consolidated into
the DMARC Summary dashboard. To view failures only, use the pie chart.</p>
</div>
<p>Any other filters work the same way. You can also add your own custom temporary
filters by clicking on Add Filter at the upper right of the page.</p>
</div>
<div class="section" id="dmarc-forensic-samples">
<h3>DMARC Forensic Samples<a class="headerlink" href="#dmarc-forensic-samples" title="Permalink to this headline"></a></h3>
<p>The DMARC Forensic Samples dashboard contains information on DMARC forensic
reports (also known as failure reports or ruf reports). These reports contain
samples of emails that have failed to pass DMARC.</p>
<div class="admonition note">
<p class="first admonition-title">Note</p>
<p class="last">Most recipients do not send forensic/failure/ruf reports at all to avoid
privacy leaks. Some recipients (notably Chinese webmail services) will only
supply the headers of sample emails. Very few provide the entire email.</p>
</div>
</div>
</div>
<div class="section" id="dmarc-alignment-guide">
<h2>DMARC Alignment Guide<a class="headerlink" href="#dmarc-alignment-guide" title="Permalink to this headline"></a></h2>
<p>DMARC ensures that SPF and DKM authentication mechanisms actually authenticate
against the same domain that the end user sees.</p>
<p>A message passes a DMARC check by passing DKIM or SPF, <strong>as long as the related
indicators are also in alignment</strong>.</p>
<table border="1" class="docutils">
<colgroup>
<col width="33%" />
<col width="33%" />
<col width="33%" />
</colgroup>
<tbody valign="top">
<tr class="row-odd"><td>&#160;</td>
<td><strong>DKIM</strong></td>
<td><strong>SPF</strong></td>
</tr>
<tr class="row-even"><td><strong>Passing</strong></td>
<td>The signature in the
DKIM header is
validated using a
public key that is
published as a DNS
record of the domain
name specified in the
signature</td>
<td>The mail servers IP
address is listed in
the SPF record of the
domain in the SMTP
envelopes mail from
header</td>
</tr>
<tr class="row-odd"><td><strong>Alignment</strong></td>
<td>The signing domain
aligns with the
domain in the
messages from header</td>
<td>The domain in the
SMTP envelopes mail
from header aligns
with the domain in
the messages from
header</td>
</tr>
</tbody>
</table>
</div>
<div class="section" id="what-if-a-sender-won-t-support-dkim-dmarc">
<h2>What if a sender wont support DKIM/DMARC?<a class="headerlink" href="#what-if-a-sender-won-t-support-dkim-dmarc" title="Permalink to this headline"></a></h2>
<ol class="arabic simple">
<li>Some vendors dont know about DMARC yet; ask about SPF and DKIM/email
authentication.</li>
<li>Check if they can send through your email relays instead of theirs.</li>
<li>Do they really need to spoof your domain? Why not use the display
name instead?</li>
<li>Worst case, have that vendor send email as a specific subdomain of
your domain (e.g. <code class="docutils literal notranslate"><span class="pre">noreply&#64;news.example.com</span></code>), and then create
separate SPF and DMARC records on <code class="docutils literal notranslate"><span class="pre">news.example.com</span></code>, and set
<code class="docutils literal notranslate"><span class="pre">p=none</span></code> in that DMARC record.</li>
</ol>
<blockquote>
<div><div class="admonition warning">
<p class="first admonition-title">Warning</p>
<p class="last">Do not alter the <code class="docutils literal notranslate"><span class="pre">p</span></code> or <code class="docutils literal notranslate"><span class="pre">sp</span></code> values of the DMARC record on the
Top-Level Domain (TLD) that would leave you vulnerable to spoofing of
your TLD and/or any subdomain.</p>
</div>
</div></blockquote>
</div>
<div class="section" id="module-parsedmarc">
<span id="api"></span><h2>API<a class="headerlink" href="#module-parsedmarc" title="Permalink to this headline"></a></h2>
<p>A Python package for parsing DMARC reports</p>
@@ -1059,7 +1244,7 @@ to a callback function</p>
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'./',
VERSION:'3.6.0',
VERSION:'3.7.0',
LANGUAGE:'None',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
+1 -1
View File
@@ -1,5 +1,5 @@
# Sphinx inventory version 2
# Project: parsedmarc
# Version: 3.6.0
# Version: 3.7.0
# The remainder of this file is compressed using zlib.
xÚ­”KNÃ0†÷=…lSÑmw©‹HU‹ÄÒríibÉŽ-{RRV\ƒëqì$¥¥°ˆ ;kòÿx±e΃ÐÌqb÷smD£€ÜY juÝDz’Mì—xº(òåƒsÆE Z¥©Éì”<GêSRäeé d+°Æa2_ä«» ÙGã ö’§âËxt‰õ®‹§Ôbeâ(hš+Lì×l"9å”vBð‘ß65û•²Ãh©ëLÑÐAà!¨«Ël·ÃÔþÏVÆcB™šIòûF¥P-:Æ‘¶ZfÊp·î4TëéÖæ²1m’ÉЬWiGcU£YMQjðÈ´aš10Ú£;þÜ„­TöHicoq¶9‰ô¶›ýehzÕâGÙ>Žû]ªÏYíÉ6ݯg´Íø¾½0äÕ˜]-¡î"Ä£˜+¶9D;YÄãä(†Gщâ7a7Ñã%>ÞÞ= ÚFC,¦¿š„‡ó—tvŸ>ô~Eÿæö‰‡·åÑÇzñºÿ¾d%L>%;¤ë
+3 -3
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Python Module Index &mdash; parsedmarc 3.6.0 documentation</title>
<title>Python Module Index &mdash; parsedmarc 3.7.0 documentation</title>
@@ -59,7 +59,7 @@
<div class="version">
3.6.0
3.7.0
</div>
@@ -200,7 +200,7 @@
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'./',
VERSION:'3.6.0',
VERSION:'3.7.0',
LANGUAGE:'None',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
+3 -3
View File
@@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Search &mdash; parsedmarc 3.6.0 documentation</title>
<title>Search &mdash; parsedmarc 3.7.0 documentation</title>
@@ -56,7 +56,7 @@
<div class="version">
3.6.0
3.7.0
</div>
@@ -188,7 +188,7 @@
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT:'./',
VERSION:'3.6.0',
VERSION:'3.7.0',
LANGUAGE:'None',
COLLAPSE_INDEX:false,
FILE_SUFFIX:'.html',
+1 -1
View File
File diff suppressed because one or more lines are too long