mailshield 0.1.0 → 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.0"
4
+ VERSION = "1.1"
5
5
  end
data/lib/mailshield.rb CHANGED
@@ -1,64 +1,131 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "mailshield/version"
3
+ require_relative 'mailshield/version'
4
4
  require 'resolv'
5
+ require 'csv'
6
+ require 'net/smtp'
5
7
 
6
8
  module MailShield
7
- class DomainChecker
8
- TEMP_DOMAINS = %w[mailinator.com tempmail.com guerrillamail.com].freeze # If any one want to contribute, please add the known more domains here.
9
+ EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/.freeze
10
+
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
17
+
18
+ class << self
19
+ attr_accessor :dns_cache, :smtp_cache
20
+
21
+ def validate_email(email, verify_by_send: false)
22
+ reset_caches
9
23
 
10
- def self.temporary_email?(email)
11
24
  domain = extract_domain(email)
12
- return true if known_temp_domain?(domain)
13
25
 
14
- mx_records = fetch_mx_records(domain)
15
- return true if suspicious_mx_records?(mx_records)
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] }
34
+ end
35
+
36
+ { valid: true, issues: [] }
37
+ end
16
38
 
17
- return true unless spf_record?(domain)
18
- return true unless dmarc_record?(domain)
19
39
 
20
- false
40
+ def verify_address(email)
41
+ smtp_verify_email(email)
21
42
  end
43
+ private
22
44
 
23
- def self.extract_domain(email)
24
- email.split('@').last.downcase
45
+ def reset_caches
46
+ @dns_cache = {}
47
+ @smtp_cache = {}
25
48
  end
26
49
 
27
- def self.known_temp_domain?(domain)
28
- TEMP_DOMAINS.include?(domain)
50
+ def validate_format!(email)
51
+ raise InvalidFormatError, 'The email address format is invalid.' unless valid_email_format?(email)
29
52
  end
30
53
 
31
- def self.fetch_mx_records(domain)
32
- Resolv::DNS.open do |dns|
33
- mx_records = dns.getresources(domain, Resolv::DNS::Resource::IN::MX)
34
- mx_records.map(&:exchange).map(&:to_s)
35
- end
36
- rescue Resolv::ResolvError
37
- []
54
+ def validate_domain!(domain)
55
+ raise DomainNotFoundError, 'Email Not Found.' if fetch_mx_records(domain).empty?
56
+ end
57
+
58
+ def validate_spf!(domain)
59
+ raise SPFError, 'Temporary / Disposable Email' unless spf_record?(domain)
60
+ end
61
+
62
+ def validate_dmarc!(domain)
63
+ raise DMARCError, 'Temporary / Disposable Email' unless dmarc_record?(domain)
64
+ end
65
+
66
+ def validate_smtp!(email)
67
+ raise SMTPError, 'Email Address Not Found' unless smtp_verify_email(email)
38
68
  end
39
69
 
40
- def self.suspicious_mx_records?(mx_records)
41
- suspicious_patterns = [/mailinator/, /tempmail/, /guerrillamail/]
42
- mx_records.any? { |mx| suspicious_patterns.any? { |pattern| mx.match?(pattern) } }
70
+ def extract_domain(email)
71
+ email.split('@').last.downcase
72
+ end
73
+
74
+ def valid_email_format?(email)
75
+ EMAIL_REGEX.match?(email)
76
+ end
77
+
78
+ def fetch_mx_records(domain)
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
+ []
85
+ end
43
86
  end
44
87
 
45
- def self.spf_record?(domain)
88
+ def spf_record?(domain)
46
89
  spf_records = fetch_txt_records(domain)
47
90
  spf_records.any? { |record| record.include?('v=spf1') }
48
91
  end
49
92
 
50
- def self.dmarc_record?(domain)
93
+ def dmarc_record?(domain)
51
94
  dmarc_records = fetch_txt_records("_dmarc.#{domain}")
52
95
  dmarc_records.any? { |record| record.include?('v=DMARC1') }
53
96
  end
54
97
 
55
- def self.fetch_txt_records(domain)
56
- Resolv::DNS.open do |dns|
57
- records = dns.getresources(domain, Resolv::DNS::Resource::IN::TXT)
58
- records.map(&:data)
98
+ def fetch_txt_records(domain)
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
+ []
59
105
  end
60
- rescue Resolv::ResolvError
61
- []
106
+ end
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
125
+
126
+ def get_smtp_server(domain)
127
+ mx_records = fetch_mx_records(domain)
128
+ mx_records.first
62
129
  end
63
130
  end
64
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.0
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.