2019-11-26 09:55:22 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Email
|
|
|
|
class AuthenticationResults
|
|
|
|
VERDICT = Enum.new(:gray, :pass, :fail, start: 0)
|
|
|
|
|
|
|
|
def initialize(headers)
|
2020-01-21 11:12:00 -05:00
|
|
|
@authserv_id = SiteSetting.email_in_authserv_id
|
|
|
|
@headers = headers
|
|
|
|
@verdict = :gray if @authserv_id.blank?
|
|
|
|
end
|
|
|
|
|
|
|
|
def results
|
|
|
|
@results ||=
|
|
|
|
Array(@headers)
|
2019-11-26 09:55:22 -05:00
|
|
|
.map { |header| parse_header(header.to_s) }
|
2020-01-21 11:12:00 -05:00
|
|
|
.filter { |result| @authserv_id.blank? || @authserv_id == result[:authserv_id] }
|
2019-11-26 09:55:22 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def action
|
|
|
|
@action ||= calc_action
|
|
|
|
end
|
|
|
|
|
|
|
|
def verdict
|
|
|
|
@verdict ||= calc_verdict
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def calc_action
|
|
|
|
if verdict == :fail
|
2020-01-21 11:12:00 -05:00
|
|
|
:enqueue
|
2019-11-26 09:55:22 -05:00
|
|
|
else
|
|
|
|
:accept
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def calc_verdict
|
|
|
|
VERDICT[calc_dmarc]
|
|
|
|
end
|
|
|
|
|
|
|
|
def calc_dmarc
|
|
|
|
verdict = VERDICT[:gray]
|
2020-01-21 11:12:00 -05:00
|
|
|
results.each do |result|
|
2019-11-26 09:55:22 -05:00
|
|
|
result[:resinfo].each do |resinfo|
|
|
|
|
if resinfo[:method] == "dmarc"
|
|
|
|
v = VERDICT[resinfo[:result].to_sym].to_i
|
|
|
|
verdict = v if v > verdict
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
verdict = VERDICT[:gray] if SiteSetting.email_in_authserv_id.blank? &&
|
|
|
|
verdict == VERDICT[:pass]
|
|
|
|
verdict
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_header(header)
|
|
|
|
# based on https://tools.ietf.org/html/rfc8601#section-2.2
|
|
|
|
cfws = /\s*(\([^()]*\))?\s*/
|
|
|
|
value = /(?:"([^"]*)")|(?:([^\s";]*))/
|
|
|
|
authserv_id = value
|
|
|
|
authres_version = /\d+#{cfws}?/
|
|
|
|
no_result = /#{cfws}?;#{cfws}?none/
|
|
|
|
keyword = /([a-zA-Z0-9-]*[a-zA-Z0-9])/
|
|
|
|
authres_payload =
|
|
|
|
/\A#{cfws}?#{authserv_id}(?:#{cfws}#{authres_version})?(?:#{no_result}|([\S\s]*))/
|
|
|
|
|
|
|
|
method_version = authres_version
|
|
|
|
method = %r{#{keyword}\s*(?:#{cfws}?/#{cfws}?#{method_version})?}
|
|
|
|
result = keyword
|
|
|
|
methodspec = /#{cfws}?#{method}#{cfws}?=#{cfws}?#{result}/
|
|
|
|
reasonspec = /reason#{cfws}?=#{cfws}?#{value}/
|
|
|
|
resinfo = /#{cfws}?;#{methodspec}(?:#{cfws}#{reasonspec})?(?:#{cfws}([^;]*))?/
|
|
|
|
|
|
|
|
ptype = keyword
|
|
|
|
property = value
|
|
|
|
pvalue = /#{cfws}?#{value}#{cfws}?/
|
|
|
|
propspec = /#{ptype}#{cfws}?\.#{cfws}?#{property}#{cfws}?=#{pvalue}/
|
|
|
|
|
|
|
|
authres_payload_match = authres_payload.match(header)
|
|
|
|
parsed_authserv_id = authres_payload_match[2] || authres_payload_match[3]
|
|
|
|
resinfo_val = authres_payload_match[-1]
|
|
|
|
|
|
|
|
if resinfo_val
|
|
|
|
resinfo_scan = resinfo_val.scan(resinfo)
|
|
|
|
parsed_resinfo =
|
|
|
|
resinfo_scan.map do |x|
|
|
|
|
{
|
|
|
|
method: x[2],
|
|
|
|
result: x[8],
|
|
|
|
reason: x[12] || x[13],
|
|
|
|
props:
|
|
|
|
x[-1]
|
|
|
|
.scan(propspec)
|
|
|
|
.map { |y| { ptype: y[0], property: y[4], pvalue: y[8] || y[9] } },
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
{ authserv_id: parsed_authserv_id, resinfo: parsed_resinfo }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|