class PhisherPhinder::MailParser::AuthenticationHeaders::ReceivedSpfParser

Public Class Methods

new(ip_factory:) click to toggle source
# File lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb, line 7
def initialize(ip_factory:)
  @ip_factory = ip_factory
end

Public Instance Methods

parse(value) click to toggle source
# File lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb, line 11
def parse(value)
  matches = value.match(
    %r{
      \A(?<result>\S+)\s
      \((?<additional_data>[^\)]+)\)
      (?<attributes>.*)
    }x
  )

  {
    result: matches[:result].downcase.to_sym,
  }.merge(parse_additional_data(matches[:additional_data])).merge(parse_attributes(matches[:attributes]))

    # {
    #   result: matches[:result].downcase.to_sym,
    #   authserv_id: extract(matches, :authserv_id),
    #   mailfrom: extract(matches, :mailfrom),
    #   ip: @ip_factory.build(extract(matches, :ip)),
    #   client_ip: expand_ip(extract(matches, :client_ip)),
    #   receiver: extract(matches, :receiver),
    #   helo: expand_ip(extract(matches, :helo)),
    #   envelope_from: extract(matches, :envelope_from)
    # }
end

Private Instance Methods

expand_ip(ip) click to toggle source
# File lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb, line 216
def expand_ip(ip)
  ip ? (@ip_factory.build(ip) || ip) : nil
end
extract(matches, key) click to toggle source
# File lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb, line 212
def extract(matches, key)
  matches.names.include?(key.to_s) ? matches[key] : nil
end
parse_additional_data(data) click to toggle source

def parse(value)

patterns = [
  /\A(?<result>\S+)\s\(domain\sof\s(?<mailfrom>\S+)\sdesignates\s(?<ip>\S+)\sas\spermitted\ssender\)/,
  /\A(?<result>\S+)\s\(domain\sof\s(?<mailfrom>\S+)\sdoes\snot\sdesignate\spermitted\ssender\shosts\)/,
  %r{
    \A(?<result>\S+)\s\(mailfrom\)\s
    identity=(?<identity>[^;]+);\s
    client-ip=(?<client_ip>[^;]+);\s
    helo=(?<helo>[^;]+);\s
    envelope-from=(?<envelope_from>[^;]+);\s
    receiver=(?<receiver>[^;]+)
  }x,
  %r{
    \A(?<result>\S+)\s
    \(
      (?<authserv_id>[^:]+):\s
      transitioning\sdomain\sof\s(?<mailfrom>\S+)\s
      .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    \)\s
    client-ip=(?<client_ip>[^;]+);\s
    envelope-from=(?<envelope_from>[^;]+);\s
    helo=\[?(?<helo>[^;]+?)\]?;
  }x,
  %r{
    \A(?<result>\S+)\s
    \(
      (?<authserv_id>[^:]+):\s
      best\sguess\srecord\sfor\sdomain\sof\s(?<mailfrom>\S+)\s
      .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    \)\s
    client-ip=(?<client_ip>[^;]+);
  }x,
  %r{
    \A(?<result>\S+)\s
    \(
      (?<authserv_id>[^:]+):\s
      domain\sof\stransitioning\s(?<mailfrom>\S+)\s
      .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    \)\s
    client-ip=(?<client_ip>[^;]+);
  }x,
  %r{
    \A(?<result>\S+)\s
    \(
      (?<authserv_id>[^:]+):\s
      domain\sof\s(?<mailfrom>\S+)\s
      .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    \)\s
    receiver=(?<receiver>[^;]+);\s
    client-ip=(?<client_ip>[^;]+);\s
    helo=\[?(?<helo>[^;]+?)\]?;
  }x,
  %r{
    \A(?<result>\S+)\s
    \(
      (?<authserv_id>[^:]+):\s
      domain\sof\s(?<mailfrom>\S+)\s
      .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    \)\s
    client-ip=(?<client_ip>[^;]+);
  }x,
  %r{
    \A(?<result>\S+)\s
    \(
      (?<authserv_id>[^:]+):\s
      (?<ip>\S+)\sis\sneither\s
      .+?
      domain\sof\s(?<mailfrom>\S+)
    \)\s
    client-ip=(?<client_ip>[^;]+);
  }x
]

matches = patterns.inject(nil) do |memo, pattern|
  memo || value.match(pattern)
end

if matches
  {
    result: matches[:result].downcase.to_sym,
    authserv_id: extract(matches, :authserv_id),
    mailfrom: extract(matches, :mailfrom),
    ip: @ip_factory.build(extract(matches, :ip)),
    client_ip: expand_ip(extract(matches, :client_ip)),
    receiver: extract(matches, :receiver),
    helo: expand_ip(extract(matches, :helo)),
    envelope_from: extract(matches, :envelope_from)
  }
end

end

# File lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb, line 134
def parse_additional_data(data)
  patterns = [
    %r{
      (?<authserv_id>[^:]+):\s
      best\sguess\srecord\sfor\sdomain\sof\s(?<mailfrom>\S+)\s
      .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    }x,
    /domain\sof\s(?<mailfrom>\S+)\sdesignates\s(?<ip>\S+)\sas\spermitted\ssender/,
    /domain\sof\s(?<mailfrom>\S+)\sdoes\snot\sdesignate\spermitted\ssender\shosts/,
    %r{
      (?<authserv_id>[^:]+):\s
      transitioning\sdomain\sof\s(?<mailfrom>\S+)\s
      .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    }x,
    %r{
      (?<authserv_id>[^:]+):\s
      domain\sof\stransitioning\s(?<mailfrom>\S+)\s
        .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    }x,
    %r{
      (?<authserv_id>[^:]+):\s
      domain\sof\s(?<mailfrom>\S+)\s
        .+?
      \s(?<ip>\S+)\sas\spermitted\ssender
    }x,
    %r{
      (?<authserv_id>[^:]+):\s
      (?<ip>\S+)\sis\sneither\s
        .+?
      domain\sof\s(?<mailfrom>\S+)
    }x
  ]

  matches = patterns.inject(nil) do |memo, pattern|
    memo || data.match(pattern)
  end

  if matches
    {
      authserv_id: extract(matches, :authserv_id),
      mailfrom: extract(matches, :mailfrom),
      ip: @ip_factory.build(extract(matches, :ip)),
      # client_ip: expand_ip(extract(matches, :client_ip)),
      # receiver: extract(matches, :receiver),
      # helo: expand_ip(extract(matches, :helo)),
      # envelope_from: extract(matches, :envelope_from)
    }
  else
    {
      authserv_id: nil,
      mailfrom: nil,
      ip: nil,
    }
  end
end
parse_attr_value(attr_value_string) click to toggle source
# File lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb, line 207
def parse_attr_value(attr_value_string)
  attribute, value = attr_value_string.strip.gsub(/[;\[\]]/, '').split('=')
  [attribute.gsub(/-/, '_'), value]
end
parse_attributes(attribute_data) click to toggle source
# File lib/phisher_phinder/mail_parser/authentication_headers/received_spf_parser.rb, line 193
def parse_attributes(attribute_data)
  output_template = {
    client_ip: nil, receiver: nil, helo: nil, envelope_from: nil, identity: nil
  }
  attribute_data.scan(/[^=]+=\S+/).inject(output_template) do |memo, attr_string|
    attribute, value = parse_attr_value(attr_string)
    if ['client_ip', 'helo'].include?(attribute)
      memo.merge(attribute.to_sym => expand_ip(value))
    else
      memo.merge(attribute.to_sym => value)
    end
  end
end