This repository has been archived on 2022-05-30. You can view files and clone it, but cannot push or open issues or pull requests.
autonomic.discourse-email/files/discourse-smtp-fast-rejection

119 lines
3.1 KiB
Ruby

#!/usr/bin/env ruby
require 'syslog'
require 'json'
require 'uri'
require 'cgi'
require 'net/http'
ENV_FILE = "/etc/postfix/mail-receiver-environment.json"
def logger
@logger ||= Syslog.open("smtp-reject", Syslog::LOG_PID, Syslog::LOG_MAIL)
end
def fatal(*args)
logger.crit *args
exit 1
end
def main
unless File.exists?(ENV_FILE)
fatal "Config file %s does not exist. Aborting.", ENV_FILE
end
real_env = JSON.parse(File.read(ENV_FILE))
%w{DISCOURSE_BASE_URL DISCOURSE_API_KEY DISCOURSE_API_USERNAME}.each do |kw|
fatal "env var %s is required", kw unless real_env[kw]
end
process_requests(real_env)
end
def process_requests(env)
$stdout.sync = true # unbuffered output
args = {}
while line = gets
# Fill up args with the request details.
# logger.err "KDDEBUG line %s", line
line = line.chomp
if line.empty?
process_single_request(args, env)
args = {} # reset for next request.
else
k,v = line.chomp.split('=', 2)
args[k] = v
end
end
end
def process_single_request(args, env)
# logger.err "KDDEBUG args %s", args
action = 'dunno'
if args['request'] != 'smtpd_access_policy'
action = 'defer_if_permit Internal error, Request type invalid'
elsif args['protocol_state'] != 'RCPT'
action = 'dunno'
elsif args['sender'].nil?
action = 'defer_if_permit No sender specified'
elsif args['recipient'].nil?
action = 'defer_if_permit No recipient specified'
else
action = maybe_reject_email(args['sender'], args['recipient'], env)
end
puts "action=#{action}"
puts ''
end
def maybe_reject_email(from, to, env)
endpoint = "#{env['DISCOURSE_BASE_URL']}/admin/email/smtp_should_reject.json"
key = env["DISCOURSE_API_KEY"]
username = env["DISCOURSE_API_USERNAME"]
# just maker sure we have something in the from field
# so we can test for addresses remotely
if from == ''
from = 'test@example.org'
end
uri = URI.parse(endpoint)
fromarg = CGI::escape(from)
toarg = CGI::escape(to)
api_qs = "from=#{fromarg}&to=#{toarg}"
if uri.query and !uri.query.empty?
uri.query += "&#{api_qs}"
else
uri.query = api_qs
end
begin
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == "https"
# logger.err "KDDEBUG request_uri %s", uri.request_uri
get = Net::HTTP::Get.new(uri.request_uri, "api-key" => "#{key}", "api-username" => "#{username}")
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
return "dunno" # let future tests also be allowed to reject this one.
end
main if __FILE__ == $0