class FastRejection

Public Class Methods

new(env_file) click to toggle source
Calls superclass method MailReceiverBase::new
# File lib/mail_receiver/fast_rejection.rb, line 14
def initialize(env_file)
  super(env_file)

  @disabled = @env['DISCOURSE_FAST_REJECTION_DISABLED'] || !@env['DISCOURSE_BASE_URL']

  @blacklisted_sender_domains = @env.fetch('BLACKLISTED_SENDER_DOMAINS', "").split(" ").map(&:downcase).to_set
end

Public Instance Methods

disabled?() click to toggle source
# File lib/mail_receiver/fast_rejection.rb, line 22
def disabled?
  !!@disabled
end
endpoint() click to toggle source
# File lib/mail_receiver/fast_rejection.rb, line 103
def endpoint
  "#{@env['DISCOURSE_BASE_URL']}/admin/email/smtp_should_reject.json"
end
maybe_reject_email(from, to) click to toggle source
# File lib/mail_receiver/fast_rejection.rb, line 63
def maybe_reject_email(from, to)
  uri = URI.parse(endpoint)
  fromarg = CGI::escape(from)
  toarg = CGI::escape(to)

  qs = "from=#{fromarg}&to=#{toarg}"
  if uri.query && !uri.query.empty?
    uri.query += "&#{qs}"
  else
    uri.query = qs
  end

  begin
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == "https"
    get = Net::HTTP::Get.new(uri.request_uri)
    get["Api-Username"] = username
    get["Api-Key"] = key
    response = http.request(get)
  rescue StandardError => ex
    logger.err "Failed to GET smtp_should_reject answer from %s: %s (%s)", endpoint, ex.message, ex.class
    logger.err ex.backtrace.map { |l| "  #{l}" }.join("\n")
    return "defer_if_permit Internal error, API request preparation failed"
  ensure
    http.finish if http && http.started?
  end

  if Net::HTTPSuccess === response
    reply = JSON.parse(response.body)
    if reply['reject']
      return "reject #{reply['reason']}"
    end
  else
    logger.err "Failed to GET smtp_should_reject answer from %s: %s", endpoint, response.code
    return "defer_if_permit Internal error, API request failed"
  end

  "dunno"  # let future tests also be allowed to reject this one.
end
process() click to toggle source
# File lib/mail_receiver/fast_rejection.rb, line 26
def process
  $stdout.sync = true   # unbuffered output

  args = {}
  while line = gets
    # Fill up args with the request details.
    line = line.chomp
    if line.empty?
      puts "action=#{process_single_request(args)}"
      puts ''

      args = {}  # reset for next request.
    else
      k, v = line.chomp.split('=', 2)
      args[k] = v
    end
  end
end
process_single_request(args) click to toggle source
# File lib/mail_receiver/fast_rejection.rb, line 45
def process_single_request(args)
  return 'dunno' if disabled?

  if args['request'] != 'smtpd_access_policy'
    return 'defer_if_permit Internal error, Request type invalid'
  elsif args['protocol_state'] != 'RCPT'
    return 'dunno'
  elsif args['sender'].nil?
    # Note that while this key should always exist, its value may be the empty
    # string.  Postfix will convert the "<>" null sender to "".
    return 'defer_if_permit No sender specified'
  elsif args['recipient'].nil?
    return 'defer_if_permit No recipient specified'
  end

  run_filters(args)
end

Private Instance Methods

maybe_reject_by_api(args) click to toggle source
# File lib/mail_receiver/fast_rejection.rb, line 141
def maybe_reject_by_api(args)
  maybe_reject_email(args['sender'], args['recipient'])
end
maybe_reject_by_sender_domain(args) click to toggle source
# File lib/mail_receiver/fast_rejection.rb, line 123
def maybe_reject_by_sender_domain(args)
  sender = args['sender']

  return "dunno" if sender.empty?

  domain = domain_from_addrspec(sender)
  if domain.empty?
    logger.info("deferred mail with domainless sender #{sender}")
    return 'defer_if_permit Invalid sender'
  end
  if @blacklisted_sender_domains.include? domain
    logger.info("rejected mail from blacklisted sender domain #{domain} (from #{sender})")
    return 'reject Invalid sender'
  end

  "dunno"
end
run_filters(args) click to toggle source
# File lib/mail_receiver/fast_rejection.rb, line 109
def run_filters(args)
  filters = [
    :maybe_reject_by_sender_domain,
    :maybe_reject_by_api,
  ]

  filters.each do |f|
    action = send(f, args)
    return action if action != "dunno"
  end

  "dunno"
end