Skip to content

Commit

Permalink
feat: support for using rspamd for spam filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
adamcooke committed Aug 2, 2021
1 parent 724325a commit a1277ba
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 1 deletion.
8 changes: 8 additions & 0 deletions config/postal.defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ rails:
environment: production
secret_key:

rspamd:
enabled: false
host: 127.0.0.1
port: 11334
ssl: false
password: null
flags: null

spamd:
enabled: false
host: 127.0.0.1
Expand Down
4 changes: 3 additions & 1 deletion lib/postal/message_inspector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ class << self
def inspectors
Array.new.tap do |inspectors|

if Postal.config.spamd&.enabled
if Postal.config.rspamd&.enabled
inspectors << MessageInspectors::Rspamd.new(Postal.config.rspamd)
elsif Postal.config.spamd&.enabled
inspectors << MessageInspectors::SpamAssassin.new(Postal.config.spamd)
end

Expand Down
1 change: 1 addition & 0 deletions lib/postal/message_inspectors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module MessageInspectors
extend ActiveSupport::Autoload
eager_autoload do
autoload :Clamav
autoload :Rspamd
autoload :SpamAssassin
end
end
Expand Down
74 changes: 74 additions & 0 deletions lib/postal/message_inspectors/rspamd.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
require 'net/http'

module Postal
module MessageInspectors
class Rspamd < MessageInspector

class Error < StandardError
end

def inspect_message(inspection)
response = request(inspection.message, inspection.scope)
response = JSON.parse(response.body)
return unless response['symbols'].is_a?(Hash)

response['symbols'].values.each do |symbol|
next if symbol['description'].blank?

inspection.spam_checks << SpamCheck.new(symbol['name'], symbol['score'], symbol['description'])
end
rescue Error => e
inspection.spam_checks << SpamCheck.new("ERROR", 0, e.message)
end

private

def request(message, scope)
http = Net::HTTP.new(@config.host, @config.port)
http.use_ssl = true if @config.ssl
http.read_timeout = 10
http.open_timeout = 10

raw_message = message.raw_message

request = Net::HTTP::Post.new('/checkv2')
request.body = raw_message
request['Content-Length'] = raw_message.bytesize.to_s
request['Password'] = @config.password if @config.password
request['Flags'] = @config.flags if @config.flags
request['User-Agent'] = 'Postal'
request['Deliver-To'] = message.rcpt_to
request['From'] = message.mail_from
request['Rcpt'] = message.rcpt_to
request['Queue-Id'] = message.token

if scope == :outgoing
request['User'] = ''
# We don't actually know the IP but an empty input here will
# still trigger rspamd to treat this as an outbound email
# and disable certain checks.
# https://rspamd.com/doc/tutorials/scanning_outbound.html
request['Ip'] = ''
end

response = nil
begin
response = http.request(request)
rescue Exception => e
logger.error "Error talking to rspamd: #{e.class} (#{e.message})"
logger.error e.backtrace[0,5]

raise Error, "Error when scanning with rspamd (#{e.class})"
end

unless response.is_a?(Net::HTTPOK)
logger.info "Got #{response.code} status from rspamd, wanted 200"
raise Error, "Error when scanning with rspamd (got #{response.code})"
end

response
end

end
end
end

3 comments on commit a1277ba

@dragoangel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is here soft rejects are supported? It could be in 2 cases:

  1. timeout of scan and rescan in background with caching
  2. graylisting

@adamcooke
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will simply provide the score back to Postal in the same way that happens with SpamAssassin. If rspamd is unavailable, we don't make any decisions about a message and it will be assumed to not be spam.

Mail is not routed through rspamd, it is simply used to inspect messages therefore graylisting is still handled by Postal.

@dragoangel
Copy link
Contributor

@dragoangel dragoangel commented on a1277ba Aug 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean rspamd provide actions, not only score. hard fail and reply "X", soft fail and reply "Y", accept.

Please sign in to comment.