discourse/lib/email/authentication_results.rb
2023-01-09 12:10:19 +00:00

104 lines
2.8 KiB
Ruby

# frozen_string_literal: true
module Email
class AuthenticationResults
VERDICT = Enum.new(:gray, :pass, :fail, start: 0)
def initialize(headers)
@authserv_id = SiteSetting.email_in_authserv_id
@headers = headers
@verdict = :gray if @authserv_id.blank?
end
def results
@results ||=
Array(@headers)
.map { |header| parse_header(header.to_s) }
.filter { |result| @authserv_id.blank? || @authserv_id == result[:authserv_id] }
end
def action
@action ||= calc_action
end
def verdict
@verdict ||= calc_verdict
end
private
def calc_action
if verdict == :fail
:enqueue
else
:accept
end
end
def calc_verdict
VERDICT[calc_dmarc]
end
def calc_dmarc
verdict = VERDICT[:gray]
results.each do |result|
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