dmarc_report 1.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 +7 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +44 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +84 -0
- data/bin/dmarc_dns.rb +109 -0
- data/bin/dmarc_dump.rb +47 -0
- data/bin/dmarc_imap.rb +416 -0
- data/bin/dmarc_report.rb +161 -0
- data/bin/dmarc_ripmime.rb +144 -0
- data/bin/install-dmarc_report.rb +126 -0
- data/bin/rename_rfc.rb +44 -0
- data/examples/config/dmarc.yml +13 -0
- data/examples/dmarc-profile.sh +7 -0
- data/examples/rules/dmarc.yml +33 -0
- data/lib/xmltohash.rb +61 -0
- metadata +106 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: '008d36fdf239257774fba2efbb5a49503b7057bc728f247f16e3dbf0c4e3ce8e'
|
|
4
|
+
data.tar.gz: 8933c858c7fa067c9dfef91223f5bc6c01cb8bc59b7f30ea5f0d02ee78a4b09d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e82ae67b93a3e13e1e7d093f4ad3fdf5b9b3fc4cde861213aab513fae979246231799c1db7648d4d347cf8d83e8961db3928becba5c57b5eefe5b6ae3a05f59f
|
|
7
|
+
data.tar.gz: 9ab1f2409872e52297959960b0c48f429079a9a46729f9c30cfe893d83f0312e13ac96dafc3a50dc3e5ed71ec2ffe0bccd5b0df6fd081baf6010009cc22147f7
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
|
2
|
+
|
|
3
|
+
# rubocop configuration
|
|
4
|
+
|
|
5
|
+
AllCops:
|
|
6
|
+
NewCops: enable
|
|
7
|
+
|
|
8
|
+
Layout/SpaceInsideArrayLiteralBrackets:
|
|
9
|
+
EnforcedStyle: space
|
|
10
|
+
Exclude:
|
|
11
|
+
- 'blocklistshow.gemspec'
|
|
12
|
+
|
|
13
|
+
Layout/SpaceInsideParens:
|
|
14
|
+
EnforcedStyle: space
|
|
15
|
+
Exclude:
|
|
16
|
+
- 'blocklistshow.gemspec'
|
|
17
|
+
|
|
18
|
+
Layout/SpaceInsideRangeLiteral:
|
|
19
|
+
#EnforcedStyle: space
|
|
20
|
+
Enabled: false
|
|
21
|
+
|
|
22
|
+
Layout/SpaceInsideReferenceBrackets:
|
|
23
|
+
EnforcedStyle: space
|
|
24
|
+
Exclude:
|
|
25
|
+
- 'blocklistshow.gemspec'
|
|
26
|
+
|
|
27
|
+
Metrics/MethodLength:
|
|
28
|
+
Max: 20
|
|
29
|
+
|
|
30
|
+
Style/FrozenStringLiteralComment:
|
|
31
|
+
Enabled: false
|
|
32
|
+
|
|
33
|
+
Style/RegexpLiteral:
|
|
34
|
+
Enabled: false
|
|
35
|
+
|
|
36
|
+
Style/SpecialGlobalVars:
|
|
37
|
+
EnforcedStyle: use_perl_names
|
|
38
|
+
|
|
39
|
+
Style/SymbolArray:
|
|
40
|
+
EnforcedStyle: brackets
|
|
41
|
+
|
|
42
|
+
Style/WordArray:
|
|
43
|
+
EnforcedStyle: brackets
|
|
44
|
+
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2020-2023 Dirk Meyer
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# dmarc_report
|
|
2
|
+
This is the backend for parsing DMARC reports
|
|
3
|
+
|
|
4
|
+
Backend and frontend should run on diffrent machines.
|
|
5
|
+
This avoids to have you IMAP credentials on the public webserver.
|
|
6
|
+
The cron-job push the results via rsync to the webserver.
|
|
7
|
+
|
|
8
|
+
## Requirements:
|
|
9
|
+
* Install cron
|
|
10
|
+
* Install ruby
|
|
11
|
+
* Install ruby-rubygems
|
|
12
|
+
* Install ripmime
|
|
13
|
+
* Install gzip (gunzip)
|
|
14
|
+
* Install unzip
|
|
15
|
+
* Install rsync
|
|
16
|
+
* IMAP Account where DMARC reports are comming in
|
|
17
|
+
* Configuration in YAML
|
|
18
|
+
|
|
19
|
+
## Installation:
|
|
20
|
+
|
|
21
|
+
gem install --user-install dmarc_report
|
|
22
|
+
|
|
23
|
+
for Diagnostics, add the rubygem dir to your search path
|
|
24
|
+
|
|
25
|
+
FreeBSD:
|
|
26
|
+
|
|
27
|
+
vim .profile
|
|
28
|
+
PATH="$PATH:$HOME/.gem/ruby/3.1/bin"
|
|
29
|
+
|
|
30
|
+
Debian:
|
|
31
|
+
|
|
32
|
+
vim .profile
|
|
33
|
+
PATH="$PATH:$HOME/.local/share/gem/ruby/3.1.0/bin"
|
|
34
|
+
|
|
35
|
+
Create the working directory:
|
|
36
|
+
|
|
37
|
+
cd
|
|
38
|
+
mkdir -p dmarc
|
|
39
|
+
cd dmarc
|
|
40
|
+
install-dmarc_report.rb
|
|
41
|
+
|
|
42
|
+
Edit your IMAP account data:
|
|
43
|
+
|
|
44
|
+
vim config/dmarc.yml
|
|
45
|
+
|
|
46
|
+
Check your rulesets:
|
|
47
|
+
|
|
48
|
+
vim rules/dmarc.yml
|
|
49
|
+
|
|
50
|
+
Edit the target directory for your webserver:
|
|
51
|
+
|
|
52
|
+
vim dmarc_profile.sh
|
|
53
|
+
|
|
54
|
+
Check the script for your cron:
|
|
55
|
+
|
|
56
|
+
vim run-dmarc.sh
|
|
57
|
+
|
|
58
|
+
Check the function
|
|
59
|
+
|
|
60
|
+
./run-dmarc.sh
|
|
61
|
+
|
|
62
|
+
Edit your crontab:
|
|
63
|
+
|
|
64
|
+
crontab -e
|
|
65
|
+
# crontab for dmarc_report
|
|
66
|
+
#minute hour mday month wday command
|
|
67
|
+
13 8,12,18 * * * /home/user/dmarc/run-dmarc.sh
|
|
68
|
+
#
|
|
69
|
+
|
|
70
|
+
# Processing:
|
|
71
|
+
|
|
72
|
+
```mermaid
|
|
73
|
+
sequenceDiagram
|
|
74
|
+
IMAP ->> File: dmarc_imap.rb
|
|
75
|
+
File ->> Attachment: dmarc_ripmime.rb
|
|
76
|
+
Attachment ->> XML: dmarc_ripmime.rb
|
|
77
|
+
XML ->> CSV: dmarc_report.rbS
|
|
78
|
+
CSV ->> HTML: dmarc.rhtml
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
# Frontend:
|
|
82
|
+
|
|
83
|
+
See: https://github.com/dinoex/dmarc_view
|
|
84
|
+
|
data/bin/dmarc_dns.rb
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# = dmarc_dns.rb
|
|
4
|
+
#
|
|
5
|
+
# Author:: Dirk Meyer
|
|
6
|
+
# Copyright:: Copyright (c) 2020 - 2023 Dirk Meyer
|
|
7
|
+
# License:: Distributes under the same terms as Ruby
|
|
8
|
+
# SPDX-FileCopyrightText: 2020-2023 Dirk Meyer
|
|
9
|
+
# SPDX-License-Identifier: Ruby
|
|
10
|
+
#
|
|
11
|
+
# Maintain a DNS cache for IPs of Mail-servers
|
|
12
|
+
# Input: dmarc-report.csv
|
|
13
|
+
# Output: dmarc-dns.json
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
require 'resolv'
|
|
17
|
+
require 'ipaddr'
|
|
18
|
+
require 'json'
|
|
19
|
+
require 'csv'
|
|
20
|
+
|
|
21
|
+
# input file in CSV format
|
|
22
|
+
INPUT_FILE = 'dmarc-report.csv'.freeze
|
|
23
|
+
# output file in JSON format
|
|
24
|
+
DNS_CACHE_FILE = 'dmarc-dns.json'.freeze
|
|
25
|
+
|
|
26
|
+
# get ptr from host command (bind9)
|
|
27
|
+
def get_dns_host( ip )
|
|
28
|
+
result = nil
|
|
29
|
+
`host '#{ip}'`.split( "\n" ).each do |line|
|
|
30
|
+
return 'not found' if line =~ /not found/
|
|
31
|
+
return line.split( /\s/ ).last if line =~ /domain name pointer/
|
|
32
|
+
|
|
33
|
+
result = line
|
|
34
|
+
end
|
|
35
|
+
result
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# get ptr from DNS resolver
|
|
39
|
+
def getname_save( ip )
|
|
40
|
+
Resolv.getname( ip ).to_s
|
|
41
|
+
rescue Resolv::ResolvError
|
|
42
|
+
'not found'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# get ptr for IP
|
|
46
|
+
def get_dns( ip )
|
|
47
|
+
return 'not found' if ip == ''
|
|
48
|
+
return 'not found' if ip.nil?
|
|
49
|
+
|
|
50
|
+
# return get_dns_host( ip )
|
|
51
|
+
getname_save( ip )
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# get ptr from cache
|
|
55
|
+
def get_cached_dns( ip )
|
|
56
|
+
return @dns_cache[ ip ] if @dns_cache.key?( ip )
|
|
57
|
+
|
|
58
|
+
@dns_cache[ ip ] = get_dns( ip )
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# load json file
|
|
62
|
+
def load_json( filename )
|
|
63
|
+
return {} unless File.exist?( filename )
|
|
64
|
+
|
|
65
|
+
JSON.parse( File.read( filename ) )
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# load cache file
|
|
69
|
+
def load_cache
|
|
70
|
+
@dns_cache = load_json( DNS_CACHE_FILE )
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# save cache file
|
|
74
|
+
def save_cache
|
|
75
|
+
File.write( DNS_CACHE_FILE, "#{JSON.dump( @dns_cache )}\n" )
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# parse CSV for source ips
|
|
79
|
+
def run_csv
|
|
80
|
+
return unless File.exist?( INPUT_FILE )
|
|
81
|
+
|
|
82
|
+
CSV.foreach( INPUT_FILE, encoding: 'UTF-8', col_sep: ';' ) do |row|
|
|
83
|
+
ip = row[ 2 ]
|
|
84
|
+
next if ip == 'source_ip'
|
|
85
|
+
|
|
86
|
+
get_cached_dns( ip )
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# check arguments
|
|
91
|
+
ARGV.each do |option|
|
|
92
|
+
case option
|
|
93
|
+
when 'test' # diagnostics
|
|
94
|
+
ARGV.shift
|
|
95
|
+
ip = ARGV.shift
|
|
96
|
+
p get_dns( ip )
|
|
97
|
+
exit 0
|
|
98
|
+
else
|
|
99
|
+
warn "Fehler #{option}"
|
|
100
|
+
exit 65
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
load_cache
|
|
105
|
+
run_csv
|
|
106
|
+
save_cache
|
|
107
|
+
|
|
108
|
+
exit 0
|
|
109
|
+
# eof
|
data/bin/dmarc_dump.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# = dmarc_dump.rb
|
|
4
|
+
#
|
|
5
|
+
# Author:: Dirk Meyer
|
|
6
|
+
# Copyright:: Copyright (c) 2023 Dirk Meyer
|
|
7
|
+
# License:: Distributes under the same terms as Ruby
|
|
8
|
+
# SPDX-FileCopyrightText: 2023 Dirk Meyer
|
|
9
|
+
# SPDX-License-Identifier: Ruby
|
|
10
|
+
#
|
|
11
|
+
# Parse an DMARC report XML file and print it in JSON
|
|
12
|
+
# Input: xmlfile with DMARC report
|
|
13
|
+
# Output: formatted JSON text
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
require 'nokogiri'
|
|
17
|
+
require 'csv'
|
|
18
|
+
|
|
19
|
+
$: << 'lib'
|
|
20
|
+
|
|
21
|
+
require 'xmltohash'
|
|
22
|
+
|
|
23
|
+
# read and print xml file
|
|
24
|
+
def run_xml( fullname )
|
|
25
|
+
p fullname
|
|
26
|
+
raw = File.read( fullname )
|
|
27
|
+
return if raw.empty?
|
|
28
|
+
return if raw == 'unused'
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
h = Hash.from_xml( raw )
|
|
32
|
+
rescue NoMethodError
|
|
33
|
+
warn "Bad XML in #{fullname}"
|
|
34
|
+
pp 'raw'
|
|
35
|
+
return
|
|
36
|
+
end
|
|
37
|
+
pp h
|
|
38
|
+
# AOL can send empty XML files
|
|
39
|
+
nil if h.nil?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
ARGV.each do |arg|
|
|
43
|
+
run_xml( arg )
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
exit 0
|
|
47
|
+
# eof
|