dmarcurator 0.1.1

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 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: []