truemail 2.2.1 → 2.3.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 +4 -4
- data/.codeclimate.yml +1 -1
- data/.reek.yml +8 -0
- data/.rubocop.yml +48 -0
- data/CHANGELOG.md +67 -0
- data/Gemfile.lock +56 -43
- data/LICENSE.txt +1 -1
- data/README.md +49 -20
- data/bin/console +3 -10
- data/lib/truemail.rb +1 -1
- data/lib/truemail/audit/base.rb +0 -1
- data/lib/truemail/audit/dns.rb +1 -1
- data/lib/truemail/audit/ip.rb +1 -1
- data/lib/truemail/audit/ptr.rb +4 -3
- data/lib/truemail/auditor.rb +1 -1
- data/lib/truemail/configuration.rb +38 -30
- data/lib/truemail/core.rb +11 -15
- data/lib/truemail/dns/punycode_representer.rb +16 -0
- data/lib/truemail/dns/resolver.rb +17 -0
- data/lib/truemail/dns/worker.rb +52 -0
- data/lib/truemail/log/serializer/auditor_json.rb +1 -1
- data/lib/truemail/log/serializer/base.rb +2 -1
- data/lib/truemail/log/serializer/validator_base.rb +1 -1
- data/lib/truemail/log/serializer/validator_text.rb +2 -2
- data/lib/truemail/logger.rb +1 -1
- data/lib/truemail/validate/mx.rb +7 -9
- data/lib/truemail/validate/smtp/request.rb +11 -7
- data/lib/truemail/validate/smtp/response.rb +1 -1
- data/lib/truemail/validator.rb +2 -2
- data/lib/truemail/version.rb +1 -1
- data/lib/truemail/wrapper.rb +3 -3
- data/truemail.gemspec +11 -9
- metadata +59 -40
data/bin/console
CHANGED
@@ -3,14 +3,7 @@
|
|
3
3
|
# frozen_string_literal: true
|
4
4
|
|
5
5
|
require 'bundler/setup'
|
6
|
-
require '
|
6
|
+
require 'pry'
|
7
|
+
require_relative '../lib/truemail'
|
7
8
|
|
8
|
-
|
9
|
-
# with your gem easier. You can also use a different console, if you like.
|
10
|
-
|
11
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
12
|
-
# require "pry"
|
13
|
-
# Pry.start
|
14
|
-
|
15
|
-
require 'irb'
|
16
|
-
IRB.start(__FILE__)
|
9
|
+
Pry.start
|
data/lib/truemail.rb
CHANGED
@@ -46,7 +46,7 @@ module Truemail
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def check_argument_type(argument)
|
49
|
-
raise_unless(argument.is_a?(String), Truemail::INVALID_TYPE, Truemail::TypeError)
|
49
|
+
raise_unless(argument.is_a?(::String), Truemail::INVALID_TYPE, Truemail::TypeError)
|
50
50
|
end
|
51
51
|
|
52
52
|
def determine_configuration(custom_configuration)
|
data/lib/truemail/audit/base.rb
CHANGED
data/lib/truemail/audit/dns.rb
CHANGED
data/lib/truemail/audit/ip.rb
CHANGED
data/lib/truemail/audit/ptr.rb
CHANGED
@@ -15,13 +15,14 @@ module Truemail
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def current_host_reverse_lookup
|
18
|
-
IPAddr.new(current_host_ip).reverse
|
18
|
+
::IPAddr.new(current_host_ip).reverse
|
19
19
|
end
|
20
20
|
|
21
21
|
def ptr_records
|
22
22
|
@ptr_records ||= Truemail::Wrapper.call(configuration: configuration) do
|
23
|
-
|
24
|
-
current_host_reverse_lookup,
|
23
|
+
Truemail::Dns::Resolver.ptr_records(
|
24
|
+
current_host_reverse_lookup,
|
25
|
+
configuration: configuration
|
25
26
|
).map { |ptr_record| ptr_record.name.to_s }
|
26
27
|
end || []
|
27
28
|
end
|
data/lib/truemail/auditor.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Truemail
|
4
4
|
class Auditor < Truemail::Executor
|
5
|
-
Result = Struct.new(:current_host_ip, :warnings, :configuration, keyword_init: true) do
|
5
|
+
Result = ::Struct.new(:current_host_ip, :warnings, :configuration, keyword_init: true) do
|
6
6
|
def initialize(warnings: {}, **args)
|
7
7
|
super
|
8
8
|
end
|
@@ -7,19 +7,23 @@ module Truemail
|
|
7
7
|
DEFAULT_CONNECTION_ATTEMPTS = 2
|
8
8
|
DEFAULT_VALIDATION_TYPE = :smtp
|
9
9
|
DEFAULT_LOGGER_OPTIONS = { tracking_event: :error, stdout: false, log_absolute_path: nil }.freeze
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
SETTERS = %i[
|
11
|
+
email_pattern
|
12
|
+
smtp_error_body_pattern
|
13
|
+
connection_timeout
|
14
|
+
response_timeout
|
15
|
+
connection_attempts
|
16
|
+
whitelisted_domains
|
17
|
+
blacklisted_domains
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
attr_reader :verifier_email,
|
14
21
|
:verifier_domain,
|
15
|
-
:connection_timeout,
|
16
|
-
:response_timeout,
|
17
|
-
:connection_attempts,
|
18
22
|
:default_validation_type,
|
19
23
|
:validation_type_by_domain,
|
20
|
-
:
|
21
|
-
:
|
22
|
-
|
24
|
+
:dns,
|
25
|
+
:logger,
|
26
|
+
*Truemail::Configuration::SETTERS
|
23
27
|
|
24
28
|
attr_accessor :whitelist_validation, :not_rfc_mx_lookup_flow, :smtp_fail_fast, :smtp_safe_check
|
25
29
|
|
@@ -30,13 +34,6 @@ module Truemail
|
|
30
34
|
tap(&block) if block
|
31
35
|
end
|
32
36
|
|
33
|
-
%i[email_pattern smtp_error_body_pattern].each do |method|
|
34
|
-
define_method("#{method}=") do |argument|
|
35
|
-
raise_unless(argument, __method__, argument.is_a?(Regexp))
|
36
|
-
instance_variable_set(:"@#{method}", argument)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
37
|
def verifier_email=(email)
|
41
38
|
validate_arguments(email, __method__)
|
42
39
|
@verifier_email = email.downcase
|
@@ -48,15 +45,8 @@ module Truemail
|
|
48
45
|
@verifier_domain = domain.downcase
|
49
46
|
end
|
50
47
|
|
51
|
-
%i[connection_timeout response_timeout connection_attempts].each do |method|
|
52
|
-
define_method("#{method}=") do |argument|
|
53
|
-
raise_unless(argument, __method__, argument.is_a?(Integer) && argument.positive?)
|
54
|
-
instance_variable_set(:"@#{method}", argument)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
48
|
def default_validation_type=(argument)
|
59
|
-
raise_unless(argument, __method__, argument.is_a?(Symbol) && Truemail::Validator::VALIDATION_TYPES.include?(argument))
|
49
|
+
raise_unless(argument, __method__, argument.is_a?(::Symbol) && Truemail::Validator::VALIDATION_TYPES.include?(argument))
|
60
50
|
@default_validation_type = argument
|
61
51
|
end
|
62
52
|
|
@@ -65,18 +55,31 @@ module Truemail
|
|
65
55
|
validation_type_by_domain.merge!(settings)
|
66
56
|
end
|
67
57
|
|
68
|
-
|
58
|
+
def argument_consistent?(argument)
|
59
|
+
case argument
|
60
|
+
when ::Array then check_domain_list(argument)
|
61
|
+
when ::Integer then argument.positive?
|
62
|
+
when ::Regexp then true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Truemail::Configuration::SETTERS.each do |method|
|
69
67
|
define_method("#{method}=") do |argument|
|
70
|
-
raise_unless(argument, __method__,
|
68
|
+
raise_unless(argument, __method__, argument_consistent?(argument))
|
71
69
|
instance_variable_set(:"@#{method}", argument)
|
72
70
|
end
|
73
71
|
end
|
74
72
|
|
73
|
+
def dns=(argument)
|
74
|
+
raise_unless(argument, __method__, argument.is_a?(::Array) && check_dns_settings(argument))
|
75
|
+
@dns = argument
|
76
|
+
end
|
77
|
+
|
75
78
|
def logger=(options)
|
76
79
|
tracking_event, stdout, log_absolute_path = logger_options(options)
|
77
80
|
valid_event = Truemail::Log::Event::TRACKING_EVENTS.key?(tracking_event)
|
78
81
|
stdout_only = stdout && log_absolute_path.nil?
|
79
|
-
file_only = log_absolute_path.is_a?(String)
|
82
|
+
file_only = log_absolute_path.is_a?(::String)
|
80
83
|
both_types = stdout && file_only
|
81
84
|
argument_info = valid_event ? log_absolute_path : tracking_event
|
82
85
|
raise_unless(argument_info, __method__, valid_event && (stdout_only || file_only || both_types))
|
@@ -89,7 +92,7 @@ module Truemail
|
|
89
92
|
|
90
93
|
private
|
91
94
|
|
92
|
-
def instance_initializer
|
95
|
+
def instance_initializer # rubocop:disable Metrics/MethodLength
|
93
96
|
{
|
94
97
|
email_pattern: Truemail::RegexConstant::REGEX_EMAIL_PATTERN,
|
95
98
|
smtp_error_body_pattern: Truemail::RegexConstant::REGEX_SMTP_ERROR_BODY_PATTERN,
|
@@ -101,6 +104,7 @@ module Truemail
|
|
101
104
|
whitelisted_domains: [],
|
102
105
|
whitelist_validation: false,
|
103
106
|
blacklisted_domains: [],
|
107
|
+
dns: [],
|
104
108
|
not_rfc_mx_lookup_flow: false,
|
105
109
|
smtp_fail_fast: false,
|
106
110
|
smtp_safe_check: false
|
@@ -137,13 +141,17 @@ module Truemail
|
|
137
141
|
end
|
138
142
|
|
139
143
|
def validate_validation_type(settings)
|
140
|
-
raise_unless(settings, 'hash with settings', settings.is_a?(Hash))
|
144
|
+
raise_unless(settings, 'hash with settings', settings.is_a?(::Hash))
|
141
145
|
settings.each do |domain, validation_type|
|
142
146
|
check_domain(domain)
|
143
147
|
check_validation_type(validation_type)
|
144
148
|
end
|
145
149
|
end
|
146
150
|
|
151
|
+
def check_dns_settings(dns_servers)
|
152
|
+
dns_servers.all? { |dns_server| Truemail::RegexConstant::REGEX_DNS_SERVER_ADDRESS_PATTERN.match?(dns_server.to_s) }
|
153
|
+
end
|
154
|
+
|
147
155
|
def logger_options(current_options)
|
148
156
|
Truemail::Configuration::DEFAULT_LOGGER_OPTIONS.merge(current_options).values
|
149
157
|
end
|
data/lib/truemail/core.rb
CHANGED
@@ -10,32 +10,28 @@ module Truemail
|
|
10
10
|
require_relative '../truemail/validator'
|
11
11
|
require_relative '../truemail/logger'
|
12
12
|
|
13
|
-
ConfigurationError = Class.new(StandardError)
|
14
|
-
TypeError = Class.new(StandardError)
|
15
|
-
|
16
|
-
ArgumentError = Class.new(StandardError) do
|
13
|
+
ConfigurationError = ::Class.new(::StandardError)
|
14
|
+
TypeError = ::Class.new(::StandardError)
|
15
|
+
ArgumentError = ::Class.new(::StandardError) do
|
17
16
|
def initialize(arg_value, arg_name)
|
18
17
|
super("#{arg_value} is not a valid #{arg_name}")
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
22
|
-
PunycodeRepresenter = Class.new do
|
23
|
-
require 'simpleidn'
|
24
|
-
|
25
|
-
def self.call(email)
|
26
|
-
return unless email.is_a?(String)
|
27
|
-
return email if email.ascii_only?
|
28
|
-
user, domain = email.split('@')
|
29
|
-
"#{user}@#{SimpleIDN.to_ascii(domain.downcase)}"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
21
|
module RegexConstant
|
34
22
|
REGEX_DOMAIN = /[\p{L}0-9]+([\-.]{1}[\p{L}0-9]+)*\.\p{L}{2,63}/i.freeze
|
35
23
|
REGEX_EMAIL_PATTERN = /(?=\A.{6,255}\z)(\A([\p{L}0-9]+[\w|\-.+]*)@(#{REGEX_DOMAIN})\z)/.freeze
|
36
24
|
REGEX_DOMAIN_PATTERN = /(?=\A.{4,255}\z)(\A#{REGEX_DOMAIN}\z)/.freeze
|
37
25
|
REGEX_DOMAIN_FROM_EMAIL = /\A.+@(.+)\z/.freeze
|
38
26
|
REGEX_SMTP_ERROR_BODY_PATTERN = /(?=.*550)(?=.*(user|account|customer|mailbox)).*/i.freeze
|
27
|
+
REGEX_PORT_NUMBER = /(6553[0-5]|655[0-2][0-9]\d|65[0-4](\d){2}|6[0-4](\d){3}|[1-5](\d){4}|[1-9](\d){0,3})/.freeze
|
28
|
+
REGEX_DNS_SERVER_ADDRESS_PATTERN = /\A((1\d|[1-9]|2[0-4])?\d|25[0-5])(\.\g<1>){3}(:#{REGEX_PORT_NUMBER})?\z/.freeze
|
29
|
+
end
|
30
|
+
|
31
|
+
module Dns
|
32
|
+
require_relative '../truemail/dns/punycode_representer'
|
33
|
+
require_relative '../truemail/dns/worker'
|
34
|
+
require_relative '../truemail/dns/resolver'
|
39
35
|
end
|
40
36
|
|
41
37
|
module Audit
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Truemail
|
4
|
+
module Dns
|
5
|
+
PunycodeRepresenter = Class.new do
|
6
|
+
require 'simpleidn'
|
7
|
+
|
8
|
+
def self.call(email)
|
9
|
+
return unless email.is_a?(::String)
|
10
|
+
return email if email.ascii_only?
|
11
|
+
user, domain = email.split('@')
|
12
|
+
"#{user}@#{SimpleIDN.to_ascii(domain.downcase)}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Truemail
|
4
|
+
module Dns
|
5
|
+
class Resolver
|
6
|
+
WORKER_ACTIONS = %i[dns_lookup a_record a_records cname_records mx_records ptr_records].freeze
|
7
|
+
|
8
|
+
class << self
|
9
|
+
Truemail::Dns::Resolver::WORKER_ACTIONS.each do |worker_action|
|
10
|
+
define_method(worker_action) do |argument, configuration:|
|
11
|
+
Truemail::Dns::Worker.new(configuration.dns).public_send(worker_action, argument)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Truemail
|
4
|
+
module Dns
|
5
|
+
require 'resolv'
|
6
|
+
|
7
|
+
class Worker < ::Resolv::DNS
|
8
|
+
DEFAULT_DNS_PORT = 53
|
9
|
+
|
10
|
+
attr_reader :dns_gateway
|
11
|
+
|
12
|
+
def initialize(dns_servers)
|
13
|
+
super(dns_servers.empty? ? nil : config_info(dns_servers))
|
14
|
+
end
|
15
|
+
|
16
|
+
def dns_lookup(host_address)
|
17
|
+
getname(host_address).to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def a_record(host_name)
|
21
|
+
getaddress(host_name).to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def a_records(host_name)
|
25
|
+
getaddresses(host_name).map(&:to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
def cname_records(host_name)
|
29
|
+
getresources(host_name, ::Resolv::DNS::Resource::IN::CNAME)
|
30
|
+
end
|
31
|
+
|
32
|
+
def mx_records(host_name)
|
33
|
+
getresources(host_name, ::Resolv::DNS::Resource::IN::MX)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ptr_records(host_address)
|
37
|
+
getresources(host_address, ::Resolv::DNS::Resource::IN::PTR)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def nameserver_port(server)
|
43
|
+
server_address, server_port = server.split(':')
|
44
|
+
[server_address, server_port ? server_port.to_i : Truemail::Dns::Worker::DEFAULT_DNS_PORT]
|
45
|
+
end
|
46
|
+
|
47
|
+
def config_info(dns_servers)
|
48
|
+
@dns_gateway = { nameserver_port: dns_servers.map { |server| nameserver_port(server) } }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -30,7 +30,7 @@ module Truemail
|
|
30
30
|
|
31
31
|
alias warnings errors
|
32
32
|
|
33
|
-
%i[validation_type_by_domain whitelisted_domains blacklisted_domains].each do |method|
|
33
|
+
%i[validation_type_by_domain whitelisted_domains blacklisted_domains dns].each do |method|
|
34
34
|
define_method(method) do
|
35
35
|
value = executor_configuration.public_send(method)
|
36
36
|
return if value.empty?
|
@@ -55,6 +55,7 @@ module Truemail
|
|
55
55
|
whitelist_validation: executor_configuration.whitelist_validation,
|
56
56
|
whitelisted_domains: whitelisted_domains,
|
57
57
|
blacklisted_domains: blacklisted_domains,
|
58
|
+
dns: dns,
|
58
59
|
not_rfc_mx_lookup_flow: executor_configuration.not_rfc_mx_lookup_flow,
|
59
60
|
smtp_fail_fast: executor_configuration.smtp_fail_fast,
|
60
61
|
smtp_safe_check: executor_configuration.smtp_safe_check,
|
@@ -20,8 +20,8 @@ module Truemail
|
|
20
20
|
enumerable_object.inject([]) do |formatted_data, (key, value)|
|
21
21
|
data =
|
22
22
|
case
|
23
|
-
when value.is_a?(Hash) then "\n#{printer(value)}"
|
24
|
-
when value.is_a?(Array) then value.join(', ')
|
23
|
+
when value.is_a?(::Hash) then "\n#{printer(value)}"
|
24
|
+
when value.is_a?(::Array) then value.join(', ')
|
25
25
|
else value
|
26
26
|
end
|
27
27
|
formatted_data << "#{key.to_s.tr('_', ' ')}: #{data}".chomp << "\n"
|
data/lib/truemail/logger.rb
CHANGED
@@ -23,7 +23,7 @@ module Truemail
|
|
23
23
|
def init_log_file
|
24
24
|
output_file = Pathname(file)
|
25
25
|
return output_file if output_file.exist?
|
26
|
-
output_file.parent.mkpath && FileUtils.touch(output_file)
|
26
|
+
output_file.parent.mkpath && ::FileUtils.touch(output_file)
|
27
27
|
output_file
|
28
28
|
end
|
29
29
|
|
data/lib/truemail/validate/mx.rb
CHANGED
@@ -3,8 +3,6 @@
|
|
3
3
|
module Truemail
|
4
4
|
module Validate
|
5
5
|
class Mx < Truemail::Validate::Base
|
6
|
-
require 'resolv'
|
7
|
-
|
8
6
|
ERROR = 'target host(s) not found'
|
9
7
|
NULL_MX_RECORD = 'null_mx_record'
|
10
8
|
|
@@ -43,11 +41,11 @@ module Truemail
|
|
43
41
|
end
|
44
42
|
|
45
43
|
def mx_records(hostname)
|
46
|
-
domain_mx_records =
|
44
|
+
domain_mx_records = Truemail::Dns::Resolver.mx_records(hostname, configuration: configuration)
|
47
45
|
return [Truemail::Validate::Mx::NULL_MX_RECORD] if null_mx?(domain_mx_records)
|
48
|
-
domain_mx_records.sort_by(&:preference).
|
49
|
-
|
50
|
-
end
|
46
|
+
domain_mx_records.sort_by(&:preference).flat_map do |mx_record|
|
47
|
+
Truemail::Dns::Resolver.a_records(mx_record.exchange.to_s, configuration: configuration)
|
48
|
+
end
|
51
49
|
end
|
52
50
|
|
53
51
|
def mail_servers_found?
|
@@ -64,15 +62,15 @@ module Truemail
|
|
64
62
|
end
|
65
63
|
|
66
64
|
def a_record(hostname)
|
67
|
-
|
65
|
+
Truemail::Dns::Resolver.a_record(hostname, configuration: configuration)
|
68
66
|
end
|
69
67
|
|
70
68
|
def hosts_from_cname_records?
|
71
|
-
cname_records =
|
69
|
+
cname_records = Truemail::Dns::Resolver.cname_records(domain, configuration: configuration)
|
72
70
|
return if cname_records.empty?
|
73
71
|
cname_records.each do |cname_record|
|
74
72
|
host = a_record(cname_record.name.to_s)
|
75
|
-
hostname =
|
73
|
+
hostname = Truemail::Dns::Resolver.dns_lookup(host, configuration: configuration)
|
76
74
|
found_hosts = mx_records(hostname)
|
77
75
|
fetch_target_hosts(found_hosts.empty? ? [host] : found_hosts)
|
78
76
|
end
|