dmarcurator 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4db33b8efc1496dabb33d6612b082a56c47a1b28
4
+ data.tar.gz: 0a60be2fbc7fcc09ae27c6d4fcee25cbd1eb0fd9
5
+ SHA512:
6
+ metadata.gz: 25fd8f9c7898fc133c58b27645921e01ab085a8a86dc319618ddd110cee1f555ceee3067b01fd5c9436c4d53fae0d726a07acabd1e6d44c06519be99848cae2e
7
+ data.tar.gz: 908dc64c6d499f5e37e5068c1cc5270154ea7114e83b1ca2c10d226809232cfe3ca4cabfa21bce1590c56731af7411bf7548751b78489352d056643c9333f395
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ *.lock
3
+ .bundle
4
+ /tmp
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ayumi Yu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,21 @@
1
+ ## dmarcurator
2
+
3
+ Imports DMARC report XML into SQLite DB.
4
+
5
+ - [More info on email authentication](https://jl.ly/Email/authcheat.html)
6
+
7
+ ## install
8
+
9
+ Install sqlite3 bc it's cool and you need it
10
+ `brew install sqlite3`
11
+
12
+ `gem install dmarcurator`
13
+
14
+ ## usage
15
+
16
+ - Get some DMARC reports in XML and put them in a directory.
17
+ - `dmarcurator --db=./dmarc/reports.sqlite --reports-path=./dmarc/reports`
18
+
19
+ ## how to browse db
20
+
21
+ [SQLite-web](https://github.com/coleifer/sqlite-web) is a deec interface.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/dmarcurator ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "dmarcurator"
4
+ Dmarcurator::Cli::App.main
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ require File.expand_path("../lib/dmarcurator/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.add_dependency "ox", "~> 2.4"
6
+ s.add_dependency "sequel", "~> 4.38"
7
+ s.add_dependency "sqlite3", "~> 1.3"
8
+ s.add_development_dependency "bundler", "~> 1.13"
9
+ s.add_development_dependency "pry", "~> 0"
10
+ s.add_development_dependency "rake", "~> 0"
11
+ s.authors = ["Ayumi Yu"]
12
+ s.description = "Simple tool for viewing DMARC reports"
13
+ s.email = ["ayumi@ayumiyu.com"]
14
+ s.files = `git ls-files`.split("\n")
15
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ s.homepage = "https://github.com/ayumi/dmarcurator"
17
+ s.license = "MIT"
18
+ s.name = "dmarcurator"
19
+ s.summary = "Tool for viewing DMARC reports"
20
+ s.require_paths = ["lib"]
21
+ s.version = Dmarcurator::VERSION
22
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ module Dmarcurator
3
+ module Cli
4
+ class App
5
+ require "pry"
6
+
7
+ attr_reader :db_uri, :reports_path
8
+
9
+ def self.main
10
+ params = parse_options(ARGV)
11
+ new(params).run
12
+ end
13
+
14
+ def self.parse_options(options)
15
+ params = {}
16
+
17
+ opt_parser = OptionParser.new do |parser|
18
+ parser.banner = "dmarcurator parses DMARC reports and stores them into an SQLite3 DB.\nIt can also serve a basic web UI for viewing reports (-ui=true).\nUsage: dmarcurator [options]"
19
+
20
+ parser.on("-db", "--db-sqlite=SQLITE_DB", "[Required] Path to sqlite3 DB. (e.g. ./tmp/reports.sqlite)") do |value|
21
+ params[:db_path] = value
22
+ end
23
+
24
+ parser.on("-rp", "--reports-path=REPORTS_PATH", "Path to directory containing DMARC reports. (e.g. ./tmp/reports/)") do |value|
25
+ params[:reports_path] = value
26
+ end
27
+
28
+ parser.on("-h", "--help", "Halp pls") do
29
+ puts parser
30
+ exit 0
31
+ end
32
+
33
+ parser.on("-v", "--version", "Print version") do
34
+ puts ::Dmarcurator::VERSION
35
+ exit 0
36
+ end
37
+
38
+ if options.empty?
39
+ puts parser
40
+ exit 1
41
+ end
42
+ end
43
+
44
+ opt_parser.parse!(options)
45
+ if !params[:reports_path]
46
+ puts "Dmarcurator can import DMARC reports into an sqlite3 database -> Set --db and --reports-path)"
47
+ exit 0
48
+ end
49
+
50
+ params
51
+ end
52
+
53
+ def initialize(db_path:, reports_path: nil)
54
+ @db_uri = "sqlite://#{File.expand_path(db_path)}"
55
+ @reports_path = reports_path
56
+ end
57
+
58
+ def run
59
+ if reports_path
60
+ ::Dmarcurator::ImportReports.new(db_uri: db_uri, reports_path: reports_path).run
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ require "sequel"
3
+
4
+ module Dmarcurator
5
+ # Import DMARC XML reports into DB
6
+ class ImportReports
7
+ attr_reader :db, :reports_path
8
+
9
+ def initialize(db_uri:, reports_path:)
10
+ @db = Sequel.connect(db_uri)
11
+ @reports_path = reports_path
12
+ end
13
+
14
+ def run
15
+ puts "Importing #{reports_path}"
16
+ Dir.foreach(reports_path) do |path|
17
+ next if path == '.' || path == '..' || File.extname(path) != ".xml"
18
+ puts " #{path}"
19
+ parsed_report = ::Dmarcurator::Parser::Report.new(xml: "#{reports_path}/#{path}")
20
+ ::Dmarcurator::Store::Report.import_parsed(db: db, parsed: parsed_report)
21
+ end
22
+ puts "Done importing :)"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ require "open3"
3
+ require "ox"
4
+
5
+ module Dmarcurator
6
+ module Parser
7
+ # Base XML parser
8
+ class Base
9
+ attr_reader :doc
10
+
11
+ def initialize(xml: nil, parsed_xml: nil)
12
+ if xml
13
+ content = File.read(xml)
14
+ @doc = Ox.parse(content)
15
+ elsif parsed_xml
16
+ @doc = parsed_xml
17
+ else
18
+ raise "Either :xml or :parsed_xml are required"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ module Dmarcurator
3
+ module Parser
4
+ # Parsed XML of a record
5
+ class Record < Base
6
+ def source_ip
7
+ doc.locate("row/source_ip")[0].text
8
+ end
9
+
10
+ def count
11
+ doc.locate("row/count")[0].text.to_i
12
+ end
13
+
14
+ def disposition
15
+ doc.locate("row/policy_evaluated/disposition")[0].text
16
+ end
17
+
18
+ def policy_result_dkim
19
+ doc.locate("row/policy_evaluated/dkim")[0].text
20
+ end
21
+
22
+ def policy_result_spf
23
+ doc.locate("row/policy_evaluated/spf")[0].text
24
+ end
25
+
26
+ def envelope_to
27
+ doc.locate("identifiers/envelope_to")[0]&.text
28
+ end
29
+
30
+ def header_from
31
+ doc.locate("identifiers/header_from")[0].text
32
+ end
33
+
34
+ def auth_dkim_domain
35
+ doc.locate("auth_results/dkim/domain")[0]&.text
36
+ end
37
+
38
+ def auth_dkim_result
39
+ doc.locate("auth_results/dkim/result")[0]&.text
40
+ end
41
+
42
+ def auth_dkim_selector
43
+ doc.locate("auth_results/dkim/selector")[0]&.text
44
+ end
45
+
46
+ def auth_spf_domain
47
+ doc.locate("auth_results/spf/domain")[0].text
48
+ end
49
+
50
+ def auth_spf_result
51
+ doc.locate("auth_results/spf/result")[0].text
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ module Dmarcurator
3
+ module Parser
4
+ # Parsed XML of a report
5
+ class Report < Base
6
+ def org_name
7
+ doc.locate("feedback/report_metadata/org_name")[0].text
8
+ end
9
+
10
+ def email
11
+ doc.locate("feedback/report_metadata/email")[0].text
12
+ end
13
+
14
+ def extra_contact_info
15
+ doc.locate("feedback/report_metadata/extra_contact_info")[0]&.text
16
+ end
17
+
18
+ def dmarc_report_id
19
+ doc.locate("feedback/report_metadata/report_id")[0].text
20
+ end
21
+
22
+ def error
23
+ doc.locate("feedback/report_metadata/error")[0]&.text
24
+ end
25
+
26
+ def begin_at
27
+ Time.at(doc.locate("feedback/report_metadata/date_range/begin")[0].text.to_i)
28
+ end
29
+
30
+ def end_at
31
+ Time.at(doc.locate("feedback/report_metadata/date_range/end")[0].text.to_i)
32
+ end
33
+
34
+ def policy_domain
35
+ doc.locate("feedback/policy_published/domain")[0].text
36
+ end
37
+
38
+ def policy_adkim
39
+ doc.locate("feedback/policy_published/adkim")[0]&.text
40
+ end
41
+
42
+ def policy_aspf
43
+ doc.locate("feedback/policy_published/aspf")[0]&.text
44
+ end
45
+
46
+ def policy_p
47
+ doc.locate("feedback/policy_published/p")[0].text
48
+ end
49
+
50
+ def policy_sp
51
+ doc.locate("feedback/policy_published/sp")[0]&.text
52
+ end
53
+
54
+ def policy_pct
55
+ doc.locate("feedback/policy_published/pct")[0].text.to_i
56
+ end
57
+
58
+ def records
59
+ doc.locate("feedback/record").map do |record|
60
+ Record.new(parsed_xml: record)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # XML report parsers
4
+ module Dmarcurator
5
+ module Parser
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ require "sequel"
3
+
4
+ module Dmarcurator
5
+ class Store
6
+ # DMARC report. Contains many records.
7
+ class Record
8
+ attr_reader :db
9
+
10
+ def self.create_table(db:)
11
+ db.create_table :records do
12
+ primary_key :id
13
+ foreign_key(:report_id, :reports, key: :id)
14
+ String :source_ip
15
+ Integer :count
16
+ String :disposition
17
+ String :policy_result_dkim
18
+ String :policy_result_spf
19
+ String :envelope_to
20
+ String :header_from
21
+ String :auth_dkim_domain
22
+ String :auth_dkim_result
23
+ String :auth_dkim_selector
24
+ String :auth_spf_domain
25
+ String :auth_spf_result
26
+ end
27
+ end
28
+
29
+ def self.import_parsed(db:, parsed:, report_id:)
30
+ create_table(db: db) if !db.table_exists?(:records)
31
+
32
+ attributes = { report_id: report_id }
33
+ db[:records].columns.each do |attribute|
34
+ next if !parsed.respond_to?(attribute)
35
+ attributes[attribute] = parsed.public_send(attribute)
36
+ end
37
+ result = db[:records].insert(attributes)
38
+ STDOUT << "."
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+ # DMARC report. Contains many records.
3
+ module Dmarcurator
4
+ class Store
5
+ class Report
6
+ attr_reader :db
7
+
8
+ def self.create_table(db:)
9
+ puts "Creating table: reports"
10
+ db.create_table :reports do
11
+ primary_key :id
12
+ String :org_name
13
+ String :email
14
+ String :extra_contact_info
15
+ String :dmarc_report_id
16
+ String :error
17
+ DateTime :begin_at
18
+ DateTime :end_at
19
+ String :policy_domain
20
+ String :policy_adkim
21
+ String :policy_aspf
22
+ String :policy_p
23
+ String :policy_sp
24
+ Integer :policy_pct
25
+
26
+ index :dmarc_report_id
27
+ end
28
+ end
29
+
30
+ def self.import_parsed(db:, parsed:)
31
+ create_table(db: db) if !db.table_exists?(:reports)
32
+ if db[:reports].where(dmarc_report_id: parsed.dmarc_report_id).count > 0
33
+ puts "Report exists; skipping"
34
+ return
35
+ end
36
+
37
+ attributes = {}
38
+ db[:reports].columns.each do |attribute|
39
+ next if !parsed.respond_to?(attribute)
40
+ attributes[attribute] = parsed.public_send(attribute)
41
+ end
42
+ result = db[:reports].insert(attributes)
43
+ imported_report_id = db[:reports].select(:id).order('id ASC').limit(1).first[:id]
44
+
45
+ parsed.records.each do |parsed_record|
46
+ Record.import_parsed(db: db, parsed: parsed_record, report_id: imported_report_id)
47
+ end
48
+ puts "Report++"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SQL Store for reports
4
+ module Dmarcurator
5
+ class Store
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dmarcurator
4
+ VERSION = "0.1.1"
5
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require "dmarcurator/version"
3
+
4
+ module Dmarcurator
5
+ require "dmarcurator/cli/app"
6
+ require "dmarcurator/import_reports"
7
+ require "dmarcurator/parser"
8
+ require "dmarcurator/parser/base"
9
+ require "dmarcurator/parser/record"
10
+ require "dmarcurator/parser/report"
11
+ require "dmarcurator/store"
12
+ require "dmarcurator/store/record"
13
+ require "dmarcurator/store/report"
14
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dmarcurator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Ayumi Yu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ox
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sequel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.38'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.38'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.13'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.13'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Simple tool for viewing DMARC reports
98
+ email:
99
+ - ayumi@ayumiyu.com
100
+ executables:
101
+ - dmarcurator
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".ruby-version"
107
+ - Gemfile
108
+ - LICENSE
109
+ - README.md
110
+ - Rakefile
111
+ - bin/dmarcurator
112
+ - dmarcurator.gemspec
113
+ - lib/dmarcurator.rb
114
+ - lib/dmarcurator/cli/app.rb
115
+ - lib/dmarcurator/import_reports.rb
116
+ - lib/dmarcurator/parser.rb
117
+ - lib/dmarcurator/parser/base.rb
118
+ - lib/dmarcurator/parser/record.rb
119
+ - lib/dmarcurator/parser/report.rb
120
+ - lib/dmarcurator/store.rb
121
+ - lib/dmarcurator/store/record.rb
122
+ - lib/dmarcurator/store/report.rb
123
+ - lib/dmarcurator/version.rb
124
+ homepage: https://github.com/ayumi/dmarcurator
125
+ licenses:
126
+ - MIT
127
+ metadata: {}
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ required_rubygems_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ requirements: []
143
+ rubyforge_project:
144
+ rubygems_version: 2.5.1
145
+ signing_key:
146
+ specification_version: 4
147
+ summary: Tool for viewing DMARC reports
148
+ test_files: []