mailshield 0.1.1 → 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mailshield
4
- VERSION = "0.1.1"
4
+ VERSION = "1.1"
5
5
  end
data/lib/mailshield.rb CHANGED
@@ -1,133 +1,88 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "mailshield/version"
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 ValidationResult
10
- attr_reader :emails
11
-
12
- def initialize(emails)
13
- @emails = emails
14
- end
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
- TEMP_DOMAINS = %w[mailinator.com tempmail.com guerrillamail.com].freeze # Known temporary domains
31
-
19
+ attr_accessor :dns_cache, :smtp_cache
32
20
 
33
- # Main method to validate an email and return results in a hash
34
- def validate_email(email)
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
- if known_temp_domain?(domain)
50
- result[:valid] = false
51
- result[:issues] << "The email domain is known to be temporary or disposable."
52
- return result
53
- end
54
-
55
- mx_records = fetch_mx_records(domain)
56
- if mx_records.empty?
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
- unless spf_record?(domain)
67
- result[:valid] = false
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
- result
40
+ def verify_address(email)
41
+ smtp_verify_email(email)
79
42
  end
43
+ private
80
44
 
81
- def validate_emails_from_csv(input_file_path)
82
- email_results = []
83
-
84
- CSV.foreach(input_file_path, headers: true) do |row|
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
- ValidationResult.new(email_results)
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 validate_email_for_csv(email)
94
- return false unless valid_email_format?(email)
95
- domain = extract_domain(email)
54
+ def validate_domain!(domain)
55
+ raise DomainNotFoundError, 'Email Not Found.' if fetch_mx_records(domain).empty?
56
+ end
96
57
 
97
- return false if known_temp_domain?(domain)
98
- return false unless spf_record?(domain)
99
- return false unless dmarc_record?(domain)
58
+ def validate_spf!(domain)
59
+ raise SPFError, 'Temporary / Disposable Email' unless spf_record?(domain)
60
+ end
100
61
 
101
- true
62
+ def validate_dmarc!(domain)
63
+ raise DMARCError, 'Temporary / Disposable Email' unless dmarc_record?(domain)
102
64
  end
103
65
 
104
- private
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
- email_regex = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/
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
- Resolv::DNS.open do |dns|
121
- mx_records = dns.getresources(domain, Resolv::DNS::Resource::IN::MX)
122
- mx_records.map(&:exchange).map(&:to_s)
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
- Resolv::DNS.open do |dns|
145
- records = dns.getresources(domain, Resolv::DNS::Resource::IN::TXT)
146
- records.map(&:data)
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
- # SPF Record Explanation:
156
- #
157
- # SPF (Sender Policy Framework): A mechanism that helps email servers verify that an email claiming to come from a specific domain is actually sent by an authorized mail server.
158
- # This prevents spammers from sending messages with forged "From" addresses.
159
- #
160
- # DMARC Record Explanation:
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: 0.1.1
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-25 00:00:00.000000000 Z
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.11
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.