Skip to content

Commit

Permalink
Adds BATV prvs capabilities
Browse files Browse the repository at this point in the history
  • Loading branch information
afair committed Jan 8, 2018
1 parent c57ef0e commit 19bbc36
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 20 deletions.
20 changes: 13 additions & 7 deletions lib/email_address/address.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,26 @@ class Address
# instance, and initializes the address to the "normalized" format of the
# address. The original string is available in the #original method.
def initialize(email_address, config={})
email_address = email_address.strip if email_address
@config = config # This needs refactoring!
email_address = (email_address || "").strip
@original = email_address
email_address||= ""
email_address = parse_rewritten(email_address) unless config[:skip_rewrite]
if lh = email_address.match(/(.+)@(.+)/)
(_, local, host) = lh.to_a
else
(local, host) = [email_address, '']
end
local, host = EmailAddress::Address.split_local_host(email_address)

@host = EmailAddress::Host.new(host, config)
@config = @host.config
@local = EmailAddress::Local.new(local, @config, @host)
end

# Given an email address, this returns an array of [local, host] parts
def self.split_local_host(email)
if lh = email.match(/(.+)@(.+)/)
lh.to_a[1,2]
else
[email, '']
end
end

############################################################################
# Local Part (left of @) access
# * local: Access full local part instance
Expand Down
74 changes: 61 additions & 13 deletions lib/email_address/rewriter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,34 +61,82 @@ def srs_tt(t=Time.now.utc)
end

def srs_hash(email, options={}, &block)
key = options[:key] || @config[:key] || email.reverse
if block_given?
block.call(email)[0,4]
elsif options[:secret]
Base64.encode64(Digest::SHA1.digest(email + options[:secret].to_s))[0,4]
else
# Non-Secure signing. Please give a secret
Base64.encode64(Digest::SHA1.digest(email.reverse))[0,4]
Base64.encode64(Digest::SHA1.digest(email + key))[0,4]
end
end

# BATV - Returns the Bounce Address Tag Validation format
#---------------------------------------------------------------------------
# Returns a BATV form email address with "Private Signature" (prvs).
# Options: key: 0-9 key digit to use
# key_0..key_9: secret key used to sign/verify
# prvs_days: number of days before address "expires"
#
# BATV - Bounce Address Tag Validation
# PRVS - Simple Private Signature
# Ex: prvs=KDDDSSSS=pat@example.com
# Ex: prvs=KDDDSSSS=user@example.com
# * K: Digit for Key rotation
# * DDD: Expiry date, since 1970, low 3 digits
# * SSSSSS: sha1( KDDD + orig-mailfrom + key)[0,6]
# Source: https://tools.ietf.org/html/draft-levine-smtp-batv-01
def batv_prvs()
raise "Not yet implemented"
# See: https://tools.ietf.org/html/draft-levine-smtp-batv-01
#---------------------------------------------------------------------------
def batv_prvs(options={})
k = options[:prvs_key_id] || "0"
prvs_days = options[:prvs_days] || @config[:prvs_days] || 30
ddd = prvs_day(prvs_days)
ssssss = prvs_sign(k, ddd, self.to_s, options={})
["prvs=", k, ddd, ssssss, '=', self.to_s].join('')
end

PRVS_REGEX = /\Aprvs=(\d)(\d{3})(\w{6})=(.+)\z/

def parse_prvs(email, options={})
if email.match(PRVS_REGEX)
@rewrite_scheme = :prvs
k, ddd, ssssss, email = [$1, $2, $3, $4]

unless ssssss == prvs_sign(k, ddd, email, options)
@rewrite_error = "Invalid BATV Address: Signature unverified"
end
exp = ddd.to_i
roll = 1000 - exp # rolling 1000 day window
today = prvs_day(0)
# I'm sure this is wrong
if exp > today && exp < roll
@rewrite_error = "Invalid SRS Email Address: Address expired"
elsif exp < today && (today - exp) > 0
@rewrite_error = "Invalid SRS Email Address: Address expired"
end
[local, domain].join("@")
else
email
end
end

def prvs_day(days)
((Time.now.to_i + (days*24*60*60)) / (24*60*60)).to_s[-3,3]
end

def prvs_sign(k, ddd, email, options={})
str = [ddd, ssssss, '=', self.to_s].join('')
key = options["key_#{k}".to_i] || @config["key_#{k}".to_i] || str.reverse
Digest::SHA1.hexdigest([k,ddd, email, key].join(''))[0,6]
end

#---------------------------------------------------------------------------
# VERP Embeds a recipient email address into the bounce address
# Bounce Address: [email protected]
# Recipient Email: [email protected]
# VERP : [email protected]
def verp(recipient, split_char='+', verp_at='=')
# Recipient Email: [email protected]
# VERP : [email protected]
# To handle incoming verp, the "tag" is the recipient email address,
# remember to convert the last '=' into a '@' to reconstruct it.
#---------------------------------------------------------------------------
def verp(recipient, split_char='+')
self.local.to_s +
split_char + recipient.gsub("@",verp_at) +
split_char + recipient.gsub("@","=") +
"@" + self.hostname
end

Expand Down

0 comments on commit 19bbc36

Please sign in to comment.