Skip to content

Commit

Permalink
Feature/verifier domain audit, #18 (#31)
Browse files Browse the repository at this point in the history
* Implement auditor feature, #18
  • Loading branch information
bestwebua authored Apr 17, 2019
1 parent d61e0d0 commit 42e0b7e
Show file tree
Hide file tree
Showing 19 changed files with 335 additions and 66 deletions.
7 changes: 5 additions & 2 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,21 @@ detectors:
Attribute:
exclude:
- Truemail::Configuration#smtp_safe_check
- Truemail::Validate::ResolverExecutionWrapper#attempts
- Truemail::Wrapper#attempts

UtilityFunction:
exclude:
- Truemail::Validate::Smtp::Request#compose_from
- Truemail::Validator#select_validation_type
- Truemail::Validate::Mx#null_mx?
- Truemail::Validate::Mx#a_record
- Truemail::Audit::Ptr#current_host_address

ControlParameter:
exclude:
- Truemail::GenerateEmailHelper#calculate_email_size
- Truemail::Validate::Base#success
- Truemail::Worker#success
- Truemail#raise_unless

FeatureEnvy:
exclude:
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
truemail (0.1.6)
truemail (0.1.7)

GEM
remote: https://rubygems.org/
Expand Down
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![Maintainability](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/maintainability)](https://codeclimate.com/github/rubygarage/truemail/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/657aa241399927dcd2e2/test_coverage)](https://codeclimate.com/github/rubygarage/truemail/test_coverage) [![Gem Version](https://badge.fury.io/rb/truemail.svg)](https://badge.fury.io/rb/truemail) [![CircleCI](https://circleci.com/gh/rubygarage/truemail/tree/master.svg?style=svg)](https://circleci.com/gh/rubygarage/truemail/tree/master) [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v1.4%20adopted-ff69b4.svg)](CODE_OF_CONDUCT.md)

The Truemail gem helps you validate emails by regex pattern, presence of domain mx-records, and real existence of email account on a current email server.
The Truemail gem helps you validate emails by regex pattern, presence of domain mx-records, and real existence of email account on a current email server. Also Truemail gem allows performing an audit of the host in which runs.

## Features

Expand Down Expand Up @@ -137,7 +137,7 @@ Truemail.configuration

#### Regex validation

Validation with regex pattern is the first validation level. By default this validation not performs strictly following RFC 5322 standart, so you can override Truemail default regex pattern if you want.
Validation with regex pattern is the first validation level. By default this validation not performs strictly following RFC 5322 standard, so you can override Truemail default regex pattern if you want.

Example of usage:

Expand Down Expand Up @@ -383,6 +383,30 @@ Truemail.validate('[email protected]')
@validation_type=:smtp>
```

### Host audit features

Truemail gem allows performing an audit of the host in which runs. Only PTR record audit performs for today.

#### PTR audit

So what is a PTR record? A PTR record, or pointer record, enables someone to perform a reverse DNS lookup. This allows them to determine your domain name based on your IP address. Because generic domain names without a PTR are often associated with spammers, incoming mail servers identify email from hosts without PTR records as spam and you can't verify yours emails qualitatively.

```ruby
Truemail.host_audit
# Everything is good
=> #<Truemail::Auditor:0x00005580df358828
@result=
#<struct Truemail::Auditor::Result
warnings={}>>

# Has PTR warning
=> #<Truemail::Auditor:0x00005580df358828
@result=
#<struct Truemail::Auditor::Result
warnings=
{:ptr=>"ptr record does not reference to current verifier domain"}>>
```

### Truemail helpers

#### .valid?
Expand Down
18 changes: 13 additions & 5 deletions lib/truemail.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# frozen_string_literal: true

require 'truemail/version'
require 'truemail/core'
require 'truemail/configuration'
require 'truemail/validator'

module Truemail
INCOMPLETE_CONFIG = 'verifier_email is required parameter'
Expand All @@ -15,7 +12,7 @@ def configuration
return unless block_given?
configuration = Truemail::Configuration.new
yield(configuration)
raise ConfigurationError, INCOMPLETE_CONFIG unless configuration.complete?
raise_unless(configuration.complete?, INCOMPLETE_CONFIG)
configuration
end
end
Expand All @@ -29,12 +26,23 @@ def reset_configuration!
end

def validate(email, **options)
raise ConfigurationError, NOT_CONFIGURED unless configuration
raise_unless(configuration, NOT_CONFIGURED)
Truemail::Validator.new(email, **options).run
end

def valid?(email, **options)
validate(email, **options).result.valid?
end

def host_audit
raise_unless(configuration, NOT_CONFIGURED)
Truemail::Auditor.run
end

private

def raise_unless(condition, message)
raise ConfigurationError, message unless condition
end
end
end
13 changes: 13 additions & 0 deletions lib/truemail/audit/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

module Truemail
module Audit
class Base < Truemail::Worker
private

def add_warning(message)
result.warnings[self.class.name.split('::').last.downcase.to_sym] = message
end
end
end
end
41 changes: 41 additions & 0 deletions lib/truemail/audit/ptr.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module Truemail
module Audit
class Ptr < Truemail::Audit::Base
require 'ipaddr'
require 'resolv'

NOT_FOUND = 'ptr record for current host address was not found'
NOT_REFERENCES = 'ptr record does not reference to current verifier domain'

def run
return if ptr_records.empty? && add_warning(Truemail::Audit::Ptr::NOT_FOUND)
return if ptr_references_to_verifier_domain?
add_warning(Truemail::Audit::Ptr::NOT_REFERENCES)
end

private

def current_host_address
Resolv.getaddress(Socket.gethostname)
end

def current_host_reverse_lookup
IPAddr.new(current_host_address).reverse
end

def ptr_records
@ptr_records ||= Truemail::Wrapper.call do
Resolv::DNS.new.getresources(
current_host_reverse_lookup, Resolv::DNS::Resource::IN::PTR
).map { |ptr_record| ptr_record.name.to_s }
end || []
end

def ptr_references_to_verifier_domain?
ptr_records.include?(Truemail.configuration.verifier_domain)
end
end
end
end
24 changes: 24 additions & 0 deletions lib/truemail/auditor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Truemail
class Auditor
Result = Struct.new(:warnings, keyword_init: true) do
def initialize(warnings: {}, **args)
super
end
end

def self.run
new.run
end

def result
@result ||= Truemail::Auditor::Result.new
end

def run
Truemail::Audit::Ptr.check(result)
self
end
end
end
13 changes: 12 additions & 1 deletion lib/truemail/core.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# frozen_string_literal: true

module Truemail
require 'truemail/version'
require 'truemail/configuration'
require 'truemail/worker'
require 'truemail/wrapper'
require 'truemail/auditor'
require 'truemail/validator'

class ConfigurationError < StandardError; end

class ArgumentError < StandardError
Expand All @@ -16,10 +23,14 @@ module RegexConstant
REGEX_DOMAIN_FROM_EMAIL = /\A.+@(.+)\z/
end

module Audit
require 'truemail/audit/base'
require 'truemail/audit/ptr'
end

module Validate
require 'truemail/validate/base'
require 'truemail/validate/regex'
require 'truemail/validate/resolver_execution_wrapper'
require 'truemail/validate/mx'
require 'truemail/validate/smtp'
require 'truemail/validate/smtp/response'
Expand Down
16 changes: 1 addition & 15 deletions lib/truemail/validate/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,9 @@

module Truemail
module Validate
class Base
attr_reader :result

def self.check(result)
new(result).run
end

def initialize(result)
@result = result
end

class Base < Truemail::Worker
private

def success(condition)
result.success = condition || false
end

def add_error(message)
result.errors[self.class.name.split('::').last.downcase.to_sym] = message
end
Expand Down
22 changes: 15 additions & 7 deletions lib/truemail/validate/mx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def host_extractor_methods

def mx_lookup
host_extractor_methods.any? do |method|
Truemail::Validate::ResolverExecutionWrapper.call { send(method) }
Truemail::Wrapper.call { send(method) }
end
end

Expand All @@ -41,8 +41,8 @@ def domain_not_include_null_mx
!mail_servers.include?(Truemail::Validate::Mx::NULL_MX_RECORD)
end

def mx_records(domain)
domain_mx_records = Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::MX)
def mx_records(hostname)
domain_mx_records = Resolv::DNS.new.getresources(hostname, Resolv::DNS::Resource::IN::MX)
return [Truemail::Validate::Mx::NULL_MX_RECORD] if null_mx?(domain_mx_records)
domain_mx_records.sort_by(&:preference).map do |mx_record|
Resolv.getaddresses(mx_record.exchange.to_s)
Expand All @@ -53,16 +53,24 @@ def mail_servers_found?
!mail_servers.empty?
end

def domain
result.domain
end

def hosts_from_mx_records?
fetch_target_hosts(mx_records(result.domain))
fetch_target_hosts(mx_records(domain))
mail_servers_found?
end

def a_record(hostname)
Resolv.getaddress(hostname)
end

def hosts_from_cname_records?
cname_records = Resolv::DNS.new.getresources(result.domain, Resolv::DNS::Resource::IN::CNAME)
cname_records = Resolv::DNS.new.getresources(domain, Resolv::DNS::Resource::IN::CNAME)
return if cname_records.empty?
cname_records.each do |cname_record|
host = Resolv.getaddress(cname_record.name.to_s)
host = a_record(cname_record.name.to_s)
hostname = Resolv.getname(host)
found_hosts = mx_records(hostname)
fetch_target_hosts(found_hosts.empty? ? [host] : found_hosts)
Expand All @@ -71,7 +79,7 @@ def hosts_from_cname_records?
end

def host_from_a_record?
fetch_target_hosts([Resolv.getaddress(result.domain)])
fetch_target_hosts([a_record(domain)])
mail_servers_found?
end
end
Expand Down
26 changes: 0 additions & 26 deletions lib/truemail/validate/resolver_execution_wrapper.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/truemail/validate/smtp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Truemail
module Validate
class Smtp < Truemail::Validate::Base
ERROR = 'smtp error'
ERROR_BODY = /(?=.*550)(?=.*(user|account)).*/i
ERROR_BODY = /(?=.*550)(?=.*(user|account|customer|mailbox)).*/i

attr_reader :smtp_results

Expand Down
2 changes: 1 addition & 1 deletion lib/truemail/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Truemail
VERSION = '0.1.6'
VERSION = '0.1.7'
end
21 changes: 21 additions & 0 deletions lib/truemail/worker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

module Truemail
class Worker
attr_reader :result

def self.check(result)
new(result).run
end

def initialize(result)
@result = result
end

private

def success(condition)
result.success = condition || false
end
end
end
Loading

0 comments on commit 42e0b7e

Please sign in to comment.