mailshield 0.1.1 → 1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75a143d6ea43f54fb93d97111b110549b11af8e4095d8633a0983d29bc24d48a
4
- data.tar.gz: 7a7f03578febe47f990afc419a87af3b0c2eb3100adf9738c8f8188511bc779d
3
+ metadata.gz: 47694cab766631dd24097db7e130aeac98bbc12f2ad4aef7a5f065811796ea71
4
+ data.tar.gz: 52d6019eb905e12d7d4e5d6eee0cae2c36fba7e301a5e897c3350fa26aeb632f
5
5
  SHA512:
6
- metadata.gz: dcbab4e23187ea34d25a7f6a8a63083ed006d5456a40ac089b1e1dad4cab5c2dbc3d31833da47b03a6a49357d95cf0a7655805528ea1b9b14fa84748014f991b
7
- data.tar.gz: 27ce04f2c1422ee26667c891d037c55bffc55aa1865dd66d62449469dbcdd90577c60b8eb4aa8c48dfde299b1445fb3c1f86002de9c034a3074a721db196b853
6
+ metadata.gz: 41ec5aa1ae5db31d9e0e437eac3dc85da10561cecc9c1b57fe793fccda7990f7a707695c4f794c0518ed2fb23c99767daa18a7cca8488e7792b939e5a71d432e
7
+ data.tar.gz: 887fe66ea4b30711689cea1346c9466d326cb13c927b6e957c6d6d035ef908f0a5739751f27e6af17c4dbdb3d6df9d5bca4bf89087064fbc3d2427f8ce4a717b
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+ require 'set'
3
+
4
+ module MailShield
5
+ KNOWN_DISPOSABLE_DOMAINS = Set.new(
6
+ [
7
+ '10mail.org', '10minut.com.pl', '10minut.xyz', '10minutemail.nl', '11163.com',
8
+ '1secmail.com', '1secmail.net', '1secmail.org', '20minutemail.com', '2chmail.net',
9
+ '30wave.com', '4warding.net', '55hosting.net', '5july.org', '5ymail.com',
10
+ '672643.net', '6ip.us', '99.com', 'acentri.com', 'afrobacon.com',
11
+ 'ahk.jp', 'aiafhg.com', 'airsi.de', 'akgq701.com', 'aleeas.com',
12
+ 'alienware13.com', 'ama-trans.de', 'amiri.net', 'ano-mail.net', 'anonaddy.com',
13
+ 'anonmails.de', 'apkmd.com', 'armyspy.com', 'asorent.com', 'augmentationtechnology.com',
14
+ 'aver.com', 'averdov.com', 'axsup.net', 'b2cmail.de', 'banit.club',
15
+ 'bearsarefuzzy.com', 'belamail.org', 'bitwhites.top', 'bladesmail.net', 'bobmail.info',
16
+ 'boxformail.in', 'ccmail.uk', 'ceed.se', 'chammy.info', 'cheaphub.net',
17
+ 'choco.la', 'choicemail1.com', 'chong-mail.net', 'cocoro.uk', 'cocovpn.com',
18
+ 'crazymailing.com', 'cream.pink', 'cutradition.com', 'cuvox.de', 'd1yun.com',
19
+ 'd3p.dk', 'dab.ro', 'dandikmail.com', 'datum2.com', 'dayrep.com',
20
+ 'desoz.com', 'devnullmail.com', 'digdig.org', 'digitalsanctuary.com', 'dispo.in',
21
+ 'dispostable.com', 'dndent.com', 'domozmail.com', 'drdrb.com', 'drevo.si',
22
+ 'dropmail.me', 'dukedish.com', 'durandinterstellar.com', 'e4ward.com', 'eay.jp',
23
+ 'ee1.pl', 'efxs.ca', 'einmalmail.de', 'einrot.com', 'eintagsmail.de',
24
+ 'email.com', 'email.it', 'email60.com', 'emailinfive.com', 'emailresort.com',
25
+ 'emailxfer.com', 'emeraldwebmail.com', 'emlhub.com', 'emlpro.com', 'emltmp.com',
26
+ 'envy17.com', 'esemay.com', 'ether123.net', 'ethereum1.top', 'ethersports.org',
27
+ 'evyush.com', 'exdonuts.com', 'express.net.ua', 'eyepaste.com', 'fanclub.pm',
28
+ 'fangoh.com', 'fantasymail.de', 'fastmail.com', 'fatflap.com', 'fightallspam.com',
29
+ 'fiifke.de', 'findu.pl', 'fleckens.hu', 'foobarbot.net', 'fr33mail.info',
30
+ 'freeinbox.email', 'freeml.net', 'fukurou.ch', 'fuwamofu.com', 'fuwari.be',
31
+ 'gafy.net', 'gambling.com', 'gehensiemirnichtaufdensack.de', 'gelitik.in', 'geronra.com',
32
+ 'getmails.eu', 'getover.de', 'givememail.club', 'gmatch.org', 'go2vpn.net',
33
+ 'golemico.com', 'greenhousemail.com', 'guerillamail.biz', 'guerrillamail.com', 'gustr.com',
34
+ 'haltospam.com', 'hamham.uk', 'harakirimail.com', 'haydoo.com', 'headstrong.de',
35
+ 'heisei.be', 'herp.in', 'herpderp.nl', 'hidemyass.com', 'hidzz.com',
36
+ 'hola.org', 'honeys.be', 'honor-8.com', 'hornyalwary.top', 'hostcalls.com',
37
+ 'hotmai.com', 'hotsoup.be', 'ichigo.me', 'icx.ro', 'ieatspam.eu',
38
+ 'ige.es', 'imails.info', 'imstations.com', 'in-ulm.de', 'inboxalias.com',
39
+ 'inkomail.com', 'instaddr.ch', 'interstats.org', 'ip6.li', 'janproz.com',
40
+ 'jobposts.net', 'jourrapide.com', 'kagi.be', 'katztube.com', 'kbox.li',
41
+ 'kkmail.be', 'kksm.be', 'klipschx12.com', 'kmail.li', 'koszmail.pl',
42
+ 'kpay.be', 'kpost.be', 'kulturbetrieb.info', 'labworld.org', 'lee.mx',
43
+ 'lenovog4.com', 'lgxscreen.com', 'lifetotech.com', 'ligsb.com', 'lillemap.net',
44
+ 'locanto1.club', 'locantofuck.top', 'lukop.dk', 'macr2.com', 'macromaid.com',
45
+ 'magim.be', 'mail.darmajaya.ac.id', 'mail.ru', 'mail.tm', 'mail21.cc',
46
+ 'mailcker.com', 'maildrop.cc', 'maildx.com', 'mailfs.com', 'mailguard.me',
47
+ 'mailinater.com', 'mailinator.com', 'mailinator.net', 'mailinator.us', 'mailsac.com',
48
+ 'mailscrap.com', 'mailshell.com', 'mailtechx.com', 'mailtothis.com', 'mama3.org',
49
+ 'manybrain.com', 'mbox.re', 'meantinc.com', 'mepost.pw', 'merry.pink',
50
+ 'metalunits.com', 'midiharmonica.com', 'mirai.re', 'mobilevpn.top', 'mofu.be',
51
+ 'moimoi.re', 'monumentmail.com', 'morsin.com', 'mt2009.com', 'mt2014.com',
52
+ 'mt2015.com', 'mybitti.de', 'mymail-in.net', 'mymailoasis.com', 'mypacks.net',
53
+ 'myspamless.com', 'mystvpn.com', 'mytrashmail.com', 'na-cat.com', 'nada.email',
54
+ 'nagi.be', 'nanonym.ch', 'neko2.net', 'nekochan.fr', 'nervmich.net',
55
+ 'nextstopvalhalla.com', 'nezdiro.org', 'nezumi.be', 'niseko.be', 'nospamfor.us',
56
+ 'notmailinator.com', 'ntlhelp.net', 'nurfuerspam.de', 'nutpa.net', 'nypato.com',
57
+ 'okinawa.li', 'onemail.host', 'onetm.jp', 'online.ms', 'oopi.org',
58
+ 'ovomail.co', 'owleyes.ch', 'oxopoha.com', 'p33.org', 'pancakemail.com',
59
+ 'pavilionx2.com', 'payperex2.com', 'pecinan.net', 'pingir.com', 'placebomail10.com',
60
+ 'plw.me', 'porsh.net', 'pratikmail.com', 'pratikmail.org', 'prin.be',
61
+ 'privy-mail.de', 'pro-tag.org', 'projectcl.com', 'psh.me', 'put2.net',
62
+ 'putthisinyourspamdatabase.com', 'qtum-ico.com', 'quickemail.info', 'quicksend.ch', 'rabin.ca',
63
+ 'rapt.be', 'reallymymail.com', 'recode.me', 'redfeathercrow.com', 'reftoken.net',
64
+ 'rhyta.com', 'rootfest.net', 'royal.net', 'ruru.be', 'safetymail.info',
65
+ 'schrott-email.de', 'sendnow.win', 'sendspamhere.com', 'senseless-entertainment.com', 'sexyalwasmi.top',
66
+ 'shalar.net', 'sharklasers.com', 'shieldedmail.com', 'shieldemail.com', 'shipfromto.com',
67
+ 'shrib.com', 'sika3.com', 'simaenaga.com', 'simplelogin.co', 'sinaite.net',
68
+ 'slaskpost.se', 'sofia.re', 'sogetthis.com', 'spamdecoy.net', 'spamhereplease.com',
69
+ 'spamhole.com', 'stayhome.li', 'storiqax.top', 'storj99.com', 'storj99.top',
70
+ 'stromox.com', 'superrito.com', 'supersave.net', 'suremail.info', 'svk.jp',
71
+ 'tapi.re', 'teleworm.us', 'temp-mail.org', 'tempail.com', 'tempemail.net',
72
+ 'tempmail.dev', 'tempmail2.com', 'tempmailer.com', 'tempmailo.com', 'tensi.org',
73
+ 'testudine.com', 'thankyou2010.com', 'thisisnotmyrealemail.com', 'throwam.com', 'tilien.com',
74
+ 'tmail.ws', 'tokem.co', 'topless-new-world.com', 'topmail.com', 'torba.cc',
75
+ 'trickmail.net', 'trickmail.org', 'trixtrux1.com', 'truckeremail.net', 'tryalert.com',
76
+ 'turual.com', 'twddos.com', 'unmail.ru', 'uploadnolimit.com', 'uw5t6ds54.com',
77
+ 'ventolin.org', 'vintomaper.com', 'vmailcloud.com', 'vubby.com', 'vuiy.pw',
78
+ 'warpmail.net', 'watch-harry-potter.com', 'watchfulli.com', 'wep68.com', 'wh4f.org',
79
+ 'willhackforfood.biz', 'winbroadband.co.uk', 'workshop-liberal.com', 'wronghead.com', 'wwwmail.win',
80
+ 'x24.com', 'xn--9kq967o.com', 'xn--cg7bx4b4x1b.com', 'xsynergy.top', 'xxhamsterxx.com',
81
+ 'yepmail.net', 'yesspam.com', 'yo-site.com', 'yogotemail.com', 'yopmail.fr',
82
+ 'yopmail.gq', 'yopmail.net', 'yopmail.org', 'yourinbox.email', 'yuurok.com',
83
+ 'zainmax.net', 'ze.gy', 'zipmail.com', 'zoemail.net', 'zombie-hive.com',
84
+ 'zsero.com', 'zumrot.com'
85
+ ]
86
+ )
87
+
88
+
89
+ class << self
90
+ def disposable_domain?(domain)
91
+ KNOWN_DISPOSABLE_DOMAINS.include?(domain)
92
+ end
93
+ end
94
+ end
Binary file
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model'
4
+
5
+ module MailShield
6
+ class SecureEmailValidator < ActiveModel::Validator
7
+ def validate(record)
8
+ email = record.email
9
+ return if email.blank?
10
+
11
+ result = MailShield.validate_email(email)
12
+
13
+ return if result[:valid]
14
+
15
+ record.errors.add(:email, result[:issue])
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mailshield
4
- VERSION = "0.1.1"
4
+ VERSION = '1.2'
5
5
  end
data/lib/mailshield.rb CHANGED
@@ -1,133 +1,89 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "mailshield/version"
3
+ require_relative 'mailshield/version'
4
+ require_relative 'mailshield/disposable_domains'
4
5
  require 'resolv'
5
6
  require 'csv'
7
+ require 'net/smtp'
6
8
 
7
9
  module MailShield
10
+ EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/.freeze
8
11
 
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
12
+ class ValidationError < StandardError; end
13
+ class InvalidFormatError < ValidationError; end
14
+ class DomainNotFoundError < ValidationError; end
15
+ class SPFError < ValidationError; end
16
+ class DMARCError < ValidationError; end
17
+ class SMTPError < ValidationError; end
28
18
 
29
19
  class << self
30
- TEMP_DOMAINS = %w[mailinator.com tempmail.com guerrillamail.com].freeze # Known temporary domains
31
-
20
+ attr_accessor :dns_cache, :smtp_cache
32
21
 
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
22
+ def validate_email(email, verify_by_send: false)
23
+ reset_caches
46
24
 
47
25
  domain = extract_domain(email)
48
26
 
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
64
- end
65
-
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
27
+ begin
28
+ validate_format!(email)
29
+ validate_domain!(domain)
30
+ validate_spf!(domain)
31
+ validate_dmarc!(domain)
32
+ validate_smtp!(email) if verify_by_send
33
+ rescue ValidationError => e
34
+ return { valid: false, issues: [e.message] }
70
35
  end
71
36
 
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
37
+ { valid: true }
38
+ end
77
39
 
78
- result
40
+ def verify_address(email)
41
+ smtp_verify_email(email)
79
42
  end
80
43
 
81
- def validate_emails_from_csv(input_file_path)
82
- email_results = []
44
+ private
83
45
 
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
46
+ def reset_caches
47
+ @dns_cache = {}
48
+ @smtp_cache = {}
49
+ end
89
50
 
90
- ValidationResult.new(email_results)
51
+ def validate_format!(email)
52
+ raise InvalidFormatError, 'The email address format is invalid.' unless valid_email_format?(email)
91
53
  end
92
54
 
93
- def validate_email_for_csv(email)
94
- return false unless valid_email_format?(email)
95
- domain = extract_domain(email)
55
+ def validate_domain!(domain)
56
+ raise DomainNotFoundError, 'Email Not Found.' if fetch_mx_records(domain).empty?
57
+ end
96
58
 
97
- return false if known_temp_domain?(domain)
98
- return false unless spf_record?(domain)
99
- return false unless dmarc_record?(domain)
59
+ def validate_spf!(domain)
60
+ raise SPFError, 'Temporary / Disposable Email' unless spf_record?(domain)
61
+ end
100
62
 
101
- true
63
+ def validate_dmarc!(domain)
64
+ raise DMARCError, 'Temporary / Disposable Email' unless dmarc_record?(domain)
102
65
  end
103
66
 
104
- private
67
+ def validate_smtp!(email)
68
+ raise SMTPError, 'Email Address Not Found' unless smtp_verify_email(email)
69
+ end
105
70
 
106
71
  def extract_domain(email)
107
72
  email.split('@').last.downcase
108
73
  end
109
74
 
110
75
  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)
76
+ EMAIL_REGEX.match?(email)
117
77
  end
118
78
 
119
79
  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)
80
+ dns_cache[domain] ||= begin
81
+ Resolv::DNS.open do |dns|
82
+ dns.getresources(domain, Resolv::DNS::Resource::IN::MX).map(&:exchange).map(&:to_s)
83
+ end
84
+ rescue Resolv::ResolvError
85
+ []
123
86
  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
87
  end
132
88
 
133
89
  def spf_record?(domain)
@@ -141,27 +97,36 @@ module MailShield
141
97
  end
142
98
 
143
99
  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)
100
+ dns_cache[domain] ||= begin
101
+ Resolv::DNS.open do |dns|
102
+ dns.getresources(domain, Resolv::DNS::Resource::IN::TXT).map(&:data)
103
+ end
104
+ rescue Resolv::ResolvError
105
+ []
147
106
  end
148
- rescue Resolv::ResolvError
149
- []
150
107
  end
151
- end
152
- end
153
108
 
109
+ def smtp_verify_email(email)
110
+ domain = extract_domain(email)
111
+ smtp_server = smtp_cache[domain] ||= get_smtp_server(domain)
112
+
113
+ return false unless smtp_server
114
+
115
+ begin
116
+ Net::SMTP.start(smtp_server, 25, 'localhost') do |smtp|
117
+ smtp.helo('localhost')
118
+ smtp.mailfrom('test@example.com')
119
+ response = smtp.rcptto(email)
120
+ response == '250 OK'
121
+ end
122
+ rescue Net::SMTPFatalError, Net::SMTPServerBusy, Net::SMTPSyntaxError, Errno::ECONNREFUSED
123
+ false
124
+ end
125
+ end
154
126
 
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/
127
+ def get_smtp_server(domain)
128
+ mx_records = fetch_mx_records(domain)
129
+ mx_records.first
130
+ end
131
+ end
132
+ 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.2'
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-09-03 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/disposable_domains.rb
23
+ - lib/mailshield/docs/mailshield.png
24
+ - lib/mailshield/secure_email_validator.rb
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.