mailshield 0.1.1 → 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 +4 -4
- data/lib/mailshield/docs/mailshield.png +0 -0
- data/lib/mailshield/secure_email_validator.rb +16 -0
- data/lib/mailshield/temp_domains.yml +3520 -0
- data/lib/mailshield/version.rb +1 -1
- data/lib/mailshield.rb +78 -114
- metadata +6 -3
data/lib/mailshield/version.rb
CHANGED
data/lib/mailshield.rb
CHANGED
@@ -1,133 +1,88 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative 'mailshield/version'
|
4
4
|
require 'resolv'
|
5
5
|
require 'csv'
|
6
|
+
require 'net/smtp'
|
6
7
|
|
7
8
|
module MailShield
|
9
|
+
EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/.freeze
|
8
10
|
|
9
|
-
class
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
def valid_emails
|
17
|
-
emails.select { |email| email[:valid] }.map { |email| email[:email] }
|
18
|
-
end
|
19
|
-
|
20
|
-
def invalid_emails
|
21
|
-
emails.reject { |email| email[:valid] }.map { |email| email[:email] }
|
22
|
-
end
|
23
|
-
|
24
|
-
def get_emails
|
25
|
-
emails.map { |email| { email: email[:email], valid: email[:valid] } }
|
26
|
-
end
|
27
|
-
end
|
11
|
+
class ValidationError < StandardError; end
|
12
|
+
class InvalidFormatError < ValidationError; end
|
13
|
+
class DomainNotFoundError < ValidationError; end
|
14
|
+
class SPFError < ValidationError; end
|
15
|
+
class DMARCError < ValidationError; end
|
16
|
+
class SMTPError < ValidationError; end
|
28
17
|
|
29
18
|
class << self
|
30
|
-
|
31
|
-
|
19
|
+
attr_accessor :dns_cache, :smtp_cache
|
32
20
|
|
33
|
-
|
34
|
-
|
35
|
-
result = {
|
36
|
-
email: email,
|
37
|
-
valid: true,
|
38
|
-
issues: []
|
39
|
-
}
|
40
|
-
|
41
|
-
unless valid_email_format?(email)
|
42
|
-
result[:valid] = false
|
43
|
-
result[:issues] << "The email address format is invalid."
|
44
|
-
return result
|
45
|
-
end
|
21
|
+
def validate_email(email, verify_by_send: false)
|
22
|
+
reset_caches
|
46
23
|
|
47
24
|
domain = extract_domain(email)
|
48
25
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
result[:valid] = false
|
58
|
-
result[:issues] << "The email domain does not have valid mail exchange records."
|
59
|
-
return result
|
60
|
-
elsif suspicious_mx_records?(mx_records)
|
61
|
-
result[:valid] = false
|
62
|
-
result[:issues] << "The email domain's mail exchange records suggest it might be used for temporary or disposable email services."
|
63
|
-
return result
|
26
|
+
begin
|
27
|
+
validate_format!(email)
|
28
|
+
validate_domain!(domain)
|
29
|
+
validate_spf!(domain)
|
30
|
+
validate_dmarc!(domain)
|
31
|
+
validate_smtp!(email) if verify_by_send
|
32
|
+
rescue ValidationError => e
|
33
|
+
return { valid: false, issues: [e.message] }
|
64
34
|
end
|
65
35
|
|
66
|
-
|
67
|
-
|
68
|
-
result[:issues] << "The email domain is missing important records that help confirm its authenticity."
|
69
|
-
return result
|
70
|
-
end
|
36
|
+
{ valid: true, issues: [] }
|
37
|
+
end
|
71
38
|
|
72
|
-
unless dmarc_record?(domain)
|
73
|
-
result[:valid] = false
|
74
|
-
result[:issues] << "The email domain is missing records that help protect against email fraud."
|
75
|
-
return result
|
76
|
-
end
|
77
39
|
|
78
|
-
|
40
|
+
def verify_address(email)
|
41
|
+
smtp_verify_email(email)
|
79
42
|
end
|
43
|
+
private
|
80
44
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
email = row['email']
|
86
|
-
validation_result = validate_email_for_csv(email)
|
87
|
-
email_results << { email: email, valid: validation_result }
|
88
|
-
end
|
45
|
+
def reset_caches
|
46
|
+
@dns_cache = {}
|
47
|
+
@smtp_cache = {}
|
48
|
+
end
|
89
49
|
|
90
|
-
|
50
|
+
def validate_format!(email)
|
51
|
+
raise InvalidFormatError, 'The email address format is invalid.' unless valid_email_format?(email)
|
91
52
|
end
|
92
53
|
|
93
|
-
def
|
94
|
-
|
95
|
-
|
54
|
+
def validate_domain!(domain)
|
55
|
+
raise DomainNotFoundError, 'Email Not Found.' if fetch_mx_records(domain).empty?
|
56
|
+
end
|
96
57
|
|
97
|
-
|
98
|
-
|
99
|
-
|
58
|
+
def validate_spf!(domain)
|
59
|
+
raise SPFError, 'Temporary / Disposable Email' unless spf_record?(domain)
|
60
|
+
end
|
100
61
|
|
101
|
-
|
62
|
+
def validate_dmarc!(domain)
|
63
|
+
raise DMARCError, 'Temporary / Disposable Email' unless dmarc_record?(domain)
|
102
64
|
end
|
103
65
|
|
104
|
-
|
66
|
+
def validate_smtp!(email)
|
67
|
+
raise SMTPError, 'Email Address Not Found' unless smtp_verify_email(email)
|
68
|
+
end
|
105
69
|
|
106
70
|
def extract_domain(email)
|
107
71
|
email.split('@').last.downcase
|
108
72
|
end
|
109
73
|
|
110
74
|
def valid_email_format?(email)
|
111
|
-
|
112
|
-
!!(email =~ email_regex)
|
113
|
-
end
|
114
|
-
|
115
|
-
def known_temp_domain?(domain)
|
116
|
-
TEMP_DOMAINS.include?(domain)
|
75
|
+
EMAIL_REGEX.match?(email)
|
117
76
|
end
|
118
77
|
|
119
78
|
def fetch_mx_records(domain)
|
120
|
-
|
121
|
-
|
122
|
-
|
79
|
+
dns_cache[domain] ||= begin
|
80
|
+
Resolv::DNS.open do |dns|
|
81
|
+
dns.getresources(domain, Resolv::DNS::Resource::IN::MX).map(&:exchange).map(&:to_s)
|
82
|
+
end
|
83
|
+
rescue Resolv::ResolvError
|
84
|
+
[]
|
123
85
|
end
|
124
|
-
rescue Resolv::ResolvError
|
125
|
-
[]
|
126
|
-
end
|
127
|
-
|
128
|
-
def suspicious_mx_records?(mx_records)
|
129
|
-
suspicious_patterns = [/mailinator/, /tempmail/, /guerrillamail/]
|
130
|
-
mx_records.any? { |mx| suspicious_patterns.any? { |pattern| mx.match?(pattern) } }
|
131
86
|
end
|
132
87
|
|
133
88
|
def spf_record?(domain)
|
@@ -141,27 +96,36 @@ module MailShield
|
|
141
96
|
end
|
142
97
|
|
143
98
|
def fetch_txt_records(domain)
|
144
|
-
|
145
|
-
|
146
|
-
|
99
|
+
dns_cache[domain] ||= begin
|
100
|
+
Resolv::DNS.open do |dns|
|
101
|
+
dns.getresources(domain, Resolv::DNS::Resource::IN::TXT).map(&:data)
|
102
|
+
end
|
103
|
+
rescue Resolv::ResolvError
|
104
|
+
[]
|
147
105
|
end
|
148
|
-
rescue Resolv::ResolvError
|
149
|
-
[]
|
150
106
|
end
|
151
|
-
end
|
152
|
-
end
|
153
107
|
|
108
|
+
def smtp_verify_email(email)
|
109
|
+
domain = extract_domain(email)
|
110
|
+
smtp_server = smtp_cache[domain] ||= get_smtp_server(domain)
|
111
|
+
|
112
|
+
return false unless smtp_server
|
113
|
+
|
114
|
+
begin
|
115
|
+
Net::SMTP.start(smtp_server, 25, 'localhost') do |smtp|
|
116
|
+
smtp.helo('localhost')
|
117
|
+
smtp.mailfrom('test@example.com')
|
118
|
+
response = smtp.rcptto(email)
|
119
|
+
response == '250 OK'
|
120
|
+
end
|
121
|
+
rescue Net::SMTPFatalError, Net::SMTPServerBusy, Net::SMTPSyntaxError, Errno::ECONNREFUSED => e
|
122
|
+
false
|
123
|
+
end
|
124
|
+
end
|
154
125
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
#
|
162
|
-
# DMARC (Domain-based Message Authentication, Reporting, and Conformance): A protocol that builds on SPF and DKIM (DomainKeys Identified Mail) to help email domain owners protect their domain from being used in email spoofing.
|
163
|
-
# It provides a way for domain owners to publish policies about their email authentication practices and how receiving mail servers should enforce them.
|
164
|
-
#
|
165
|
-
# References:
|
166
|
-
# https://www.dkim.org/
|
167
|
-
# https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-spf-record/
|
126
|
+
def get_smtp_server(domain)
|
127
|
+
mx_records = fetch_mx_records(domain)
|
128
|
+
mx_records.first
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mailshield
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: '1.1'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- jana
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: MailShield helps secure your application by identifying temporary or
|
14
14
|
disposable email domains.
|
@@ -19,6 +19,9 @@ extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
21
|
- lib/mailshield.rb
|
22
|
+
- lib/mailshield/docs/mailshield.png
|
23
|
+
- lib/mailshield/secure_email_validator.rb
|
24
|
+
- lib/mailshield/temp_domains.yml
|
22
25
|
- lib/mailshield/version.rb
|
23
26
|
homepage: https://github.com/janarthanan-shanmugam/mailshield
|
24
27
|
licenses:
|
@@ -43,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
46
|
- !ruby/object:Gem::Version
|
44
47
|
version: '0'
|
45
48
|
requirements: []
|
46
|
-
rubygems_version: 3.5.
|
49
|
+
rubygems_version: 3.5.18
|
47
50
|
signing_key:
|
48
51
|
specification_version: 4
|
49
52
|
summary: A gem to detect temporary or disposable email domains.
|