email_address 0.1.13 → 0.1.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/lib/email_address/address.rb +87 -92
- data/lib/email_address/config.rb +47 -45
- data/lib/email_address/exchanger.rb +27 -28
- data/lib/email_address/host.rb +116 -116
- data/lib/email_address/version.rb +1 -1
- data/test/email_address/test_host.rb +29 -31
- metadata +3 -3
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "resolv"
|
4
|
+
require "netaddr"
|
5
|
+
require "socket"
|
6
6
|
|
7
7
|
module EmailAddress
|
8
8
|
class Exchanger
|
9
9
|
include Enumerable
|
10
10
|
|
11
|
-
def self.cached(host, config={})
|
11
|
+
def self.cached(host, config = {})
|
12
12
|
@host_cache ||= {}
|
13
|
-
@cache_size ||= ENV[
|
13
|
+
@cache_size ||= ENV["EMAIL_ADDRESS_CACHE_SIZE"].to_i || 100
|
14
14
|
if @host_cache.has_key?(host)
|
15
15
|
o = @host_cache.delete(host)
|
16
16
|
@host_cache[host] = o # LRU cache, move to end
|
@@ -22,14 +22,16 @@ module EmailAddress
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def initialize(host, config={})
|
25
|
+
def initialize(host, config = {})
|
26
26
|
@host = host
|
27
27
|
@config = config
|
28
|
+
@dns_disabled = @config[:host_validation] == :syntax || @config[:dns_lookup] == :off
|
28
29
|
end
|
29
30
|
|
30
31
|
def each(&block)
|
32
|
+
return if @dns_disabled
|
31
33
|
mxers.each do |m|
|
32
|
-
yield({host:m[0], ip:m[1], priority:m[2]})
|
34
|
+
yield({host: m[0], ip: m[1], priority: m[2]})
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -37,7 +39,7 @@ module EmailAddress
|
|
37
39
|
def provider
|
38
40
|
return @provider if defined? @provider
|
39
41
|
EmailAddress::Config.providers.each do |provider, config|
|
40
|
-
if config[:exchanger_match] &&
|
42
|
+
if config[:exchanger_match] && matches?(config[:exchanger_match])
|
41
43
|
return @provider = provider
|
42
44
|
end
|
43
45
|
end
|
@@ -50,40 +52,37 @@ module EmailAddress
|
|
50
52
|
# may not find provider by MX name or IP. I'm not sure about the "0.0.0.0" ip, it should
|
51
53
|
# be good in this context, but in "listen" context it means "all bound IP's"
|
52
54
|
def mxers
|
53
|
-
return [["example.com", "0.0.0.0", 1]] if @
|
54
|
-
@mxers ||= Resolv::DNS.open
|
55
|
+
return [["example.com", "0.0.0.0", 1]] if @dns_disabled
|
56
|
+
@mxers ||= Resolv::DNS.open { |dns|
|
55
57
|
dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout]
|
56
58
|
|
57
59
|
ress = begin
|
58
60
|
dns.getresources(@host, Resolv::DNS::Resource::IN::MX)
|
59
|
-
|
60
|
-
|
61
|
+
rescue Resolv::ResolvTimeout
|
62
|
+
[]
|
61
63
|
end
|
62
64
|
|
63
|
-
records = ress.map
|
64
|
-
|
65
|
-
|
66
|
-
[r.exchange.to_s, IPSocket::getaddress(r.exchange.to_s), r.preference]
|
67
|
-
else
|
68
|
-
nil
|
69
|
-
end
|
70
|
-
rescue SocketError # not found, but could also mean network not work or it could mean one record doesn't resolve an address
|
71
|
-
nil
|
65
|
+
records = ress.map { |r|
|
66
|
+
if r.exchange.to_s > " "
|
67
|
+
[r.exchange.to_s, IPSocket.getaddress(r.exchange.to_s), r.preference]
|
72
68
|
end
|
73
|
-
|
69
|
+
}
|
74
70
|
records.compact
|
75
|
-
|
71
|
+
}
|
72
|
+
# not found, but could also mean network not work or it could mean one record doesn't resolve an address
|
73
|
+
rescue SocketError
|
74
|
+
[["example.com", "0.0.0.0", 1]]
|
76
75
|
end
|
77
76
|
|
78
77
|
# Returns Array of domain names for the MX'ers, used to determine the Provider
|
79
78
|
def domains
|
80
|
-
@_domains ||= mxers.map {|m| EmailAddress::Host.new(m.first).domain_name }.sort.uniq
|
79
|
+
@_domains ||= mxers.map { |m| EmailAddress::Host.new(m.first).domain_name }.sort.uniq
|
81
80
|
end
|
82
81
|
|
83
82
|
# Returns an array of MX IP address (String) for the given email domain
|
84
83
|
def mx_ips
|
85
|
-
return ["0.0.0.0"] if @
|
86
|
-
mxers.map {|m| m[1] }
|
84
|
+
return ["0.0.0.0"] if @dns_disabled
|
85
|
+
mxers.map { |m| m[1] }
|
87
86
|
end
|
88
87
|
|
89
88
|
# Simple matcher, takes an array of CIDR addresses (ip/bits) and strings.
|
@@ -96,9 +95,9 @@ module EmailAddress
|
|
96
95
|
rules = Array(rules)
|
97
96
|
rules.each do |rule|
|
98
97
|
if rule.include?("/")
|
99
|
-
return rule if
|
98
|
+
return rule if in_cidr?(rule)
|
100
99
|
else
|
101
|
-
|
100
|
+
each { |mx| return rule if mx[:host].end_with?(rule) }
|
102
101
|
end
|
103
102
|
end
|
104
103
|
false
|
data/lib/email_address/host.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
|
-
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require 'net/smtp'
|
1
|
+
require "simpleidn"
|
2
|
+
require "resolv"
|
3
|
+
require "netaddr"
|
4
|
+
require "net/smtp"
|
6
5
|
|
7
6
|
module EmailAddress
|
8
7
|
##############################################################################
|
@@ -34,12 +33,12 @@ module EmailAddress
|
|
34
33
|
class Host
|
35
34
|
attr_reader :host_name
|
36
35
|
attr_accessor :dns_name, :domain_name, :registration_name,
|
37
|
-
|
38
|
-
|
36
|
+
:tld, :tld2, :subdomains, :ip_address, :config, :provider,
|
37
|
+
:comment, :error_message, :reason
|
39
38
|
MAX_HOST_LENGTH = 255
|
40
39
|
|
41
40
|
# Sometimes, you just need a Regexp...
|
42
|
-
DNS_HOST_REGEX
|
41
|
+
DNS_HOST_REGEX = / [\p{L}\p{N}]+ (?: (?: \-{1,2} | \.) [\p{L}\p{N}]+ )*/x
|
43
42
|
|
44
43
|
# The IPv4 and IPv6 were lifted from Resolv::IPv?::Regex and tweaked to not
|
45
44
|
# \A...\z anchor at the edges.
|
@@ -85,46 +84,45 @@ module EmailAddress
|
|
85
84
|
|
86
85
|
# host name -
|
87
86
|
# * host type - :email for an email host, :mx for exchanger host
|
88
|
-
def initialize(host_name, config={})
|
89
|
-
@original
|
87
|
+
def initialize(host_name, config = {})
|
88
|
+
@original = host_name ||= ""
|
90
89
|
config[:host_type] ||= :email
|
91
|
-
@config
|
92
|
-
@error
|
90
|
+
@config = config
|
91
|
+
@error = @error_message = nil
|
93
92
|
parse(host_name)
|
94
93
|
end
|
95
94
|
|
96
95
|
# Returns the String representation of the host name (or IP)
|
97
96
|
def name
|
98
|
-
if
|
99
|
-
"[#{
|
100
|
-
elsif
|
101
|
-
"[IPv6:#{
|
97
|
+
if ipv4?
|
98
|
+
"[#{ip_address}]"
|
99
|
+
elsif ipv6?
|
100
|
+
"[IPv6:#{ip_address}]"
|
102
101
|
elsif @config[:host_encoding] && @config[:host_encoding] == :unicode
|
103
|
-
::SimpleIDN.to_unicode(
|
102
|
+
::SimpleIDN.to_unicode(host_name)
|
104
103
|
else
|
105
|
-
|
104
|
+
dns_name
|
106
105
|
end
|
107
106
|
end
|
108
|
-
alias
|
107
|
+
alias to_s name
|
109
108
|
|
110
109
|
# The canonical host name is the simplified, DNS host name
|
111
110
|
def canonical
|
112
|
-
|
111
|
+
dns_name
|
113
112
|
end
|
114
113
|
|
115
114
|
# Returns the munged version of the name, replacing everything after the
|
116
115
|
# initial two characters with "*****" or the configured "munge_string".
|
117
116
|
def munge
|
118
|
-
|
117
|
+
host_name.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] }
|
119
118
|
end
|
120
119
|
|
121
120
|
############################################################################
|
122
121
|
# Parsing
|
123
122
|
############################################################################
|
124
123
|
|
125
|
-
|
126
124
|
def parse(host) # :nodoc:
|
127
|
-
host =
|
125
|
+
host = parse_comment(host)
|
128
126
|
|
129
127
|
if host =~ /\A\[IPv6:(.+)\]/i
|
130
128
|
self.ip_address = $1
|
@@ -149,34 +147,34 @@ module EmailAddress
|
|
149
147
|
name = fully_qualified_domain_name(name.downcase)
|
150
148
|
@host_name = name
|
151
149
|
if @config[:host_remove_spaces]
|
152
|
-
@host_name = @host_name.
|
150
|
+
@host_name = @host_name.delete(" ")
|
153
151
|
end
|
154
|
-
|
155
|
-
|
152
|
+
@dns_name = if /[^[:ascii:]]/.match?(host_name)
|
153
|
+
::SimpleIDN.to_ascii(host_name)
|
156
154
|
else
|
157
|
-
|
155
|
+
host_name
|
158
156
|
end
|
159
157
|
|
160
158
|
# Subdomain only (root@localhost)
|
161
|
-
if name.index(
|
159
|
+
if name.index(".").nil?
|
162
160
|
self.subdomains = name
|
163
161
|
|
164
162
|
# Split sub.domain from .tld: *.com, *.xx.cc, *.cc
|
165
163
|
elsif name =~ /\A(.+)\.(\w{3,10})\z/ ||
|
166
|
-
|
167
|
-
|
164
|
+
name =~ /\A(.+)\.(\w{1,3}\.\w\w)\z/ ||
|
165
|
+
name =~ /\A(.+)\.(\w\w)\z/
|
168
166
|
|
169
167
|
sub_and_domain, self.tld2 = [$1, $2] # sub+domain, com || co.uk
|
170
|
-
self.tld =
|
168
|
+
self.tld = tld2.sub(/\A.+\./, "") # co.uk => uk
|
171
169
|
if sub_and_domain =~ /\A(.+)\.(.+)\z/ # is subdomain? sub.example [.tld2]
|
172
|
-
self.subdomains
|
170
|
+
self.subdomains = $1
|
173
171
|
self.registration_name = $2
|
174
172
|
else
|
175
173
|
self.registration_name = sub_and_domain
|
176
|
-
#self.domain_name = sub_and_domain + '.' + self.tld2
|
174
|
+
# self.domain_name = sub_and_domain + '.' + self.tld2
|
177
175
|
end
|
178
|
-
self.domain_name =
|
179
|
-
|
176
|
+
self.domain_name = registration_name + "." + tld2
|
177
|
+
find_provider
|
180
178
|
else # Bad format
|
181
179
|
self.subdomains = self.tld = self.tld2 = ""
|
182
180
|
self.domain_name = self.registration_name = name
|
@@ -187,7 +185,7 @@ module EmailAddress
|
|
187
185
|
dn = @config[:address_fqdn_domain]
|
188
186
|
if !dn
|
189
187
|
if (host_part.nil? || host_part <= " ") && @config[:host_local]
|
190
|
-
|
188
|
+
"localhost"
|
191
189
|
else
|
192
190
|
host_part
|
193
191
|
end
|
@@ -205,39 +203,39 @@ module EmailAddress
|
|
205
203
|
return false unless registration_name
|
206
204
|
find_provider
|
207
205
|
return false unless config[:host_match]
|
208
|
-
!
|
206
|
+
!matches?(config[:host_match])
|
209
207
|
end
|
210
208
|
|
211
209
|
def find_provider # :nodoc:
|
212
|
-
return
|
210
|
+
return provider if provider
|
213
211
|
|
214
212
|
EmailAddress::Config.providers.each do |provider, config|
|
215
|
-
if config[:host_match] &&
|
216
|
-
return
|
213
|
+
if config[:host_match] && matches?(config[:host_match])
|
214
|
+
return set_provider(provider, config)
|
217
215
|
end
|
218
216
|
end
|
219
217
|
|
220
|
-
return
|
218
|
+
return set_provider(:default) unless dns_enabled?
|
221
219
|
|
222
|
-
provider =
|
220
|
+
provider = exchangers.provider
|
223
221
|
if provider != :default
|
224
|
-
|
222
|
+
set_provider(provider,
|
225
223
|
EmailAddress::Config.provider(provider))
|
226
224
|
end
|
227
225
|
|
228
|
-
self.provider ||=
|
226
|
+
self.provider ||= set_provider(:default)
|
229
227
|
end
|
230
228
|
|
231
|
-
def set_provider(name, provider_config={}) # :nodoc:
|
229
|
+
def set_provider(name, provider_config = {}) # :nodoc:
|
232
230
|
self.config = EmailAddress::Config.all_settings(provider_config, @config)
|
233
231
|
self.provider = name
|
234
232
|
end
|
235
233
|
|
236
234
|
# Returns a hash of the parts of the host name after parsing.
|
237
235
|
def parts
|
238
|
-
{
|
239
|
-
|
240
|
-
|
236
|
+
{host_name: host_name, dns_name: dns_name, subdomain: subdomains,
|
237
|
+
registration_name: registration_name, domain_name: domain_name,
|
238
|
+
tld2: tld2, tld: tld, ip_address: ip_address}
|
241
239
|
end
|
242
240
|
|
243
241
|
############################################################################
|
@@ -246,19 +244,19 @@ module EmailAddress
|
|
246
244
|
|
247
245
|
# Is this a fully-qualified domain name?
|
248
246
|
def fqdn?
|
249
|
-
|
247
|
+
tld ? true : false
|
250
248
|
end
|
251
249
|
|
252
250
|
def ip?
|
253
|
-
|
251
|
+
ip_address.nil? ? false : true
|
254
252
|
end
|
255
253
|
|
256
254
|
def ipv4?
|
257
|
-
|
255
|
+
ip? && ip_address.include?(".")
|
258
256
|
end
|
259
257
|
|
260
258
|
def ipv6?
|
261
|
-
|
259
|
+
ip? && ip_address.include?(":")
|
262
260
|
end
|
263
261
|
|
264
262
|
############################################################################
|
@@ -276,25 +274,25 @@ module EmailAddress
|
|
276
274
|
rules = Array(rules)
|
277
275
|
return false if rules.empty?
|
278
276
|
rules.each do |rule|
|
279
|
-
return rule if rule ==
|
277
|
+
return rule if rule == domain_name || rule == dns_name
|
280
278
|
return rule if registration_name_matches?(rule)
|
281
279
|
return rule if tld_matches?(rule)
|
282
280
|
return rule if domain_matches?(rule)
|
283
281
|
return rule if self.provider && provider_matches?(rule)
|
284
|
-
return rule if
|
282
|
+
return rule if ip_matches?(rule)
|
285
283
|
end
|
286
284
|
false
|
287
285
|
end
|
288
286
|
|
289
287
|
# Does "example." match any tld?
|
290
288
|
def registration_name_matches?(rule)
|
291
|
-
"#{
|
289
|
+
"#{registration_name}." == rule
|
292
290
|
end
|
293
291
|
|
294
292
|
# Does "sub.example.com" match ".com" and ".example.com" top level names?
|
295
293
|
# Matches TLD (uk) or TLD2 (co.uk)
|
296
294
|
def tld_matches?(rule)
|
297
|
-
rule.match(/\A\.(.+)\z/) && ($1 ==
|
295
|
+
rule.match(/\A\.(.+)\z/) && ($1 == tld || $1 == tld2) ? true : false
|
298
296
|
end
|
299
297
|
|
300
298
|
def provider_matches?(rule)
|
@@ -305,20 +303,20 @@ module EmailAddress
|
|
305
303
|
# Requires optionally starts with a "@".
|
306
304
|
def domain_matches?(rule)
|
307
305
|
rule = $1 if rule =~ /\A@(.+)/
|
308
|
-
return rule if File.fnmatch?(rule,
|
309
|
-
return rule if File.fnmatch?(rule,
|
306
|
+
return rule if domain_name && File.fnmatch?(rule, domain_name)
|
307
|
+
return rule if dns_name && File.fnmatch?(rule, dns_name)
|
310
308
|
false
|
311
309
|
end
|
312
310
|
|
313
311
|
# True if the host is an IP Address form, and that address matches
|
314
312
|
# the passed CIDR string ("10.9.8.0/24" or "2001:..../64")
|
315
313
|
def ip_matches?(cidr)
|
316
|
-
return false unless
|
317
|
-
return cidr if !cidr.include?("/") && cidr ==
|
318
|
-
if cidr.include?(":") &&
|
319
|
-
return cidr if NetAddr::IPv6Net.parse(cidr).contains(NetAddr::IPv6.parse(
|
320
|
-
elsif cidr.include?(".") &&
|
321
|
-
return cidr if NetAddr::IPv4Net.parse(cidr).contains(NetAddr::IPv4.parse(
|
314
|
+
return false unless ip_address
|
315
|
+
return cidr if !cidr.include?("/") && cidr == ip_address
|
316
|
+
if cidr.include?(":") && ip_address.include?(":")
|
317
|
+
return cidr if NetAddr::IPv6Net.parse(cidr).contains(NetAddr::IPv6.parse(ip_address))
|
318
|
+
elsif cidr.include?(".") && ip_address.include?(".")
|
319
|
+
return cidr if NetAddr::IPv4Net.parse(cidr).contains(NetAddr::IPv4.parse(ip_address))
|
322
320
|
end
|
323
321
|
false
|
324
322
|
end
|
@@ -329,13 +327,15 @@ module EmailAddress
|
|
329
327
|
|
330
328
|
# True if the :dns_lookup setting is enabled
|
331
329
|
def dns_enabled?
|
332
|
-
[:
|
330
|
+
return false if @config[:dns_lookup] == :off
|
331
|
+
return false if @config[:host_validation] == :syntax
|
332
|
+
true
|
333
333
|
end
|
334
334
|
|
335
335
|
# Returns: [official_hostname, alias_hostnames, address_family, *address_list]
|
336
336
|
def dns_a_record
|
337
337
|
@_dns_a_record = "0.0.0.0" if @config[:dns_lookup] == :off
|
338
|
-
@_dns_a_record ||= Socket.gethostbyname(
|
338
|
+
@_dns_a_record ||= Socket.gethostbyname(dns_name)
|
339
339
|
rescue SocketError # not found, but could also mean network not work
|
340
340
|
@_dns_a_record ||= []
|
341
341
|
end
|
@@ -343,19 +343,20 @@ module EmailAddress
|
|
343
343
|
# Returns an array of EmailAddress::Exchanger hosts configured in DNS.
|
344
344
|
# The array will be empty if none are configured.
|
345
345
|
def exchangers
|
346
|
-
#return nil if @config[:host_type] != :email || !self.dns_enabled?
|
347
|
-
@_exchangers ||= EmailAddress::Exchanger.cached(
|
346
|
+
# return nil if @config[:host_type] != :email || !self.dns_enabled?
|
347
|
+
@_exchangers ||= EmailAddress::Exchanger.cached(dns_name, @config)
|
348
348
|
end
|
349
349
|
|
350
350
|
# Returns a DNS TXT Record
|
351
|
-
def txt(alternate_host=nil)
|
351
|
+
def txt(alternate_host = nil)
|
352
|
+
return nil unless dns_enabled?
|
352
353
|
Resolv::DNS.open do |dns|
|
353
354
|
dns.timeouts = @config[:dns_timeout] if @config[:dns_timeout]
|
354
355
|
records = begin
|
355
|
-
dns.getresources(alternate_host ||
|
356
|
-
|
357
|
-
|
358
|
-
|
356
|
+
dns.getresources(alternate_host || dns_name,
|
357
|
+
Resolv::DNS::Resource::IN::TXT)
|
358
|
+
rescue Resolv::ResolvTimeout
|
359
|
+
[]
|
359
360
|
end
|
360
361
|
|
361
362
|
records.empty? ? nil : records.map(&:data).join(" ")
|
@@ -363,13 +364,13 @@ module EmailAddress
|
|
363
364
|
end
|
364
365
|
|
365
366
|
# Parses TXT record pairs into a hash
|
366
|
-
def txt_hash(alternate_host=nil)
|
367
|
+
def txt_hash(alternate_host = nil)
|
367
368
|
fields = {}
|
368
|
-
record =
|
369
|
+
record = txt(alternate_host)
|
369
370
|
return fields unless record
|
370
371
|
|
371
372
|
record.split(/\s*;\s*/).each do |pair|
|
372
|
-
(n,v) = pair.split(/\s*=\s*/)
|
373
|
+
(n, v) = pair.split(/\s*=\s*/)
|
373
374
|
fields[n.to_sym] = v
|
374
375
|
end
|
375
376
|
fields
|
@@ -378,7 +379,7 @@ module EmailAddress
|
|
378
379
|
# Returns a hash of the domain's DMARC (https://en.wikipedia.org/wiki/DMARC)
|
379
380
|
# settings.
|
380
381
|
def dmarc
|
381
|
-
|
382
|
+
dns_name ? txt_hash("_dmarc." + dns_name) : {}
|
382
383
|
end
|
383
384
|
|
384
385
|
############################################################################
|
@@ -386,13 +387,13 @@ module EmailAddress
|
|
386
387
|
############################################################################
|
387
388
|
|
388
389
|
# Returns true if the host name is valid according to the current configuration
|
389
|
-
def valid?(rules={})
|
390
|
+
def valid?(rules = {})
|
390
391
|
host_validation = rules[:host_validation] || @config[:host_validation] || :mx
|
391
|
-
dns_lookup
|
392
|
+
dns_lookup = rules[:dns_lookup] || host_validation
|
392
393
|
self.error_message = nil
|
393
|
-
if
|
394
|
+
if ip_address
|
394
395
|
valid_ip?
|
395
|
-
elsif !
|
396
|
+
elsif !valid_format?
|
396
397
|
false
|
397
398
|
elsif dns_lookup == :connect
|
398
399
|
valid_mx? && connect
|
@@ -407,8 +408,9 @@ module EmailAddress
|
|
407
408
|
|
408
409
|
# True if the host name has a DNS A Record
|
409
410
|
def valid_dns?
|
411
|
+
return true unless dns_enabled?
|
410
412
|
bool = dns_a_record.size > 0 || set_error(:domain_unknown)
|
411
|
-
if
|
413
|
+
if localhost? && !@config[:host_local]
|
412
414
|
bool = set_error(:domain_no_localhost)
|
413
415
|
end
|
414
416
|
bool
|
@@ -416,10 +418,11 @@ module EmailAddress
|
|
416
418
|
|
417
419
|
# True if the host name has valid MX servers configured in DNS
|
418
420
|
def valid_mx?
|
419
|
-
|
421
|
+
return true unless dns_enabled?
|
422
|
+
if exchangers.nil?
|
420
423
|
set_error(:domain_unknown)
|
421
|
-
elsif
|
422
|
-
if
|
424
|
+
elsif exchangers.mx_ips.size > 0
|
425
|
+
if localhost? && !@config[:host_local]
|
423
426
|
set_error(:domain_no_localhost)
|
424
427
|
else
|
425
428
|
true
|
@@ -433,9 +436,9 @@ module EmailAddress
|
|
433
436
|
|
434
437
|
# True if the host_name passes Regular Expression match and size limits.
|
435
438
|
def valid_format?
|
436
|
-
if
|
439
|
+
if host_name =~ CANONICAL_HOST_REGEX && to_s.size <= MAX_HOST_LENGTH
|
437
440
|
return true if localhost?
|
438
|
-
return true if
|
441
|
+
return true if host_name.include?(".") # require FQDN
|
439
442
|
end
|
440
443
|
set_error(:domain_invalid)
|
441
444
|
end
|
@@ -444,12 +447,12 @@ module EmailAddress
|
|
444
447
|
# is a potentially valid IP address. It does not check if the address
|
445
448
|
# is reachable.
|
446
449
|
def valid_ip?
|
447
|
-
if
|
450
|
+
if !@config[:host_allow_ip]
|
448
451
|
bool = set_error(:ip_address_forbidden)
|
449
|
-
elsif
|
450
|
-
bool =
|
451
|
-
elsif
|
452
|
-
bool =
|
452
|
+
elsif ip_address.include?(":")
|
453
|
+
bool = Resolv::IPv6::Regex.match?(ip_address) ? true : set_error(:ipv6_address_invalid)
|
454
|
+
elsif ip_address.include?(".")
|
455
|
+
bool = Resolv::IPv4::Regex.match?(ip_address) ? true : set_error(:ipv4_address_invalid)
|
453
456
|
end
|
454
457
|
if bool && (localhost? && !@config[:host_local])
|
455
458
|
bool = set_error(:ip_address_no_localhost)
|
@@ -458,20 +461,20 @@ module EmailAddress
|
|
458
461
|
end
|
459
462
|
|
460
463
|
def localhost?
|
461
|
-
if
|
464
|
+
if ip_address
|
462
465
|
rel =
|
463
|
-
if
|
464
|
-
NetAddr::IPv6Net.parse(""+"::1").rel(
|
465
|
-
NetAddr::IPv6Net.parse(
|
466
|
+
if ip_address.include?(":")
|
467
|
+
NetAddr::IPv6Net.parse("" + "::1").rel(
|
468
|
+
NetAddr::IPv6Net.parse(ip_address)
|
466
469
|
)
|
467
470
|
else
|
468
|
-
NetAddr::IPv4Net.parse(""+"127.0.0.0/8").rel(
|
469
|
-
NetAddr::IPv4Net.parse(
|
471
|
+
NetAddr::IPv4Net.parse("" + "127.0.0.0/8").rel(
|
472
|
+
NetAddr::IPv4Net.parse(ip_address)
|
470
473
|
)
|
471
474
|
end
|
472
475
|
!rel.nil? && rel >= 0
|
473
476
|
else
|
474
|
-
|
477
|
+
host_name == "localhost"
|
475
478
|
end
|
476
479
|
end
|
477
480
|
|
@@ -479,33 +482,30 @@ module EmailAddress
|
|
479
482
|
# as an email address check, but is provided to assist in problem resolution.
|
480
483
|
# If you abuse this, you *could* be blocked by the ESP.
|
481
484
|
def connect
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
+
smtp = Net::SMTP.new(host_name || ip_address)
|
486
|
+
smtp.start(@config[:helo_name] || "localhost")
|
487
|
+
smtp.finish
|
488
|
+
true
|
489
|
+
rescue Net::SMTPFatalError => e
|
490
|
+
set_error(:server_not_available, e.to_s)
|
491
|
+
rescue SocketError => e
|
492
|
+
set_error(:server_not_available, e.to_s)
|
493
|
+
ensure
|
494
|
+
if smtp&.started?
|
485
495
|
smtp.finish
|
486
|
-
true
|
487
|
-
rescue Net::SMTPFatalError => e
|
488
|
-
set_error(:server_not_available, e.to_s)
|
489
|
-
rescue SocketError => e
|
490
|
-
set_error(:server_not_available, e.to_s)
|
491
|
-
ensure
|
492
|
-
if smtp && smtp.started?
|
493
|
-
smtp.finish
|
494
|
-
end
|
495
496
|
end
|
496
497
|
end
|
497
498
|
|
498
|
-
def set_error(err, reason=nil)
|
499
|
-
@error
|
500
|
-
@reason
|
499
|
+
def set_error(err, reason = nil)
|
500
|
+
@error = err
|
501
|
+
@reason = reason
|
501
502
|
@error_message = EmailAddress::Config.error_message(err)
|
502
503
|
false
|
503
504
|
end
|
504
505
|
|
505
506
|
# The inverse of valid? -- Returns nil (falsey) if valid, otherwise error message
|
506
507
|
def error
|
507
|
-
|
508
|
+
valid? ? nil : @error_message
|
508
509
|
end
|
509
|
-
|
510
510
|
end
|
511
511
|
end
|