Commit 44a61e49 authored by ng's avatar ng
Browse files

implement #282 - allow external loading of post & pre filters

This introduces the loading of post & pre filters from
/etc/schleuder/filters/{pre,post}.
parent 88c2a052
......@@ -29,10 +29,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
* Error messages are converted into human readable text now, instead of giving their class-name. (#338)
* Require mail-gpg >= 0.3.3, which fixes a bug that let some equal-signs disappear under specific circumstances. (#287)
### Known issues
* With the current used mail library version schleuder uses, there are certain malformed emails that can't be parsed. See #334 for background. This will be fixed in future releases of the mail library.
### Added
* Enable to load external filters, similar to how we allow external plugins. (#282)
### Changed
* Use schleuder.org as website and team@schleuder.org as contact email.
......
......@@ -7,6 +7,17 @@ listlogs_dir: /var/lib/schleuder/lists
# Schleuder reads plugins also from this directory.
plugins_dir: /etc/schleuder/plugins
# Schleuder reads filters also from this directory path,
# in the specific pre_decryption or post_decryption subdirectory.
# Filter files must follow the following convention for the
# filename: \d+_a_name.rb
# Where \d+ is any number, that defines the place in the
# list of filters and a_name must match the method name
# of the filter.
# The built-in filters are using round numbers for their
# positioning within the list. Increased by ten.
filters_dir: /etc/schleuder/filters
# How verbose should Schleuder log to syslog? (list-specific messages are written to the list's log-file).
log_level: warn
......
......@@ -46,9 +46,6 @@ Dir["#{libdir}/schleuder/plugins/*.rb"].each do |file|
require file
end
require 'schleuder/filters_runner'
Dir["#{libdir}/schleuder/filters/*.rb"].each do |file|
require file
end
Dir["#{libdir}/schleuder/validators/*.rb"].each do |file|
require file
end
......
......@@ -11,6 +11,7 @@ module Schleuder
'lists_dir' => '/var/lib/schleuder/lists',
'listlogs_dir' => '/var/lib/schleuder/lists',
'plugins_dir' => '/etc/schleuder/plugins',
'filters_dir' => '/etc/schleuder/filters',
'log_level' => 'warn',
'superadmin' => 'root@localhost',
'keyserver' => 'hkp://pool.sks-keyservers.net',
......@@ -58,6 +59,10 @@ module Schleuder
instance.config['plugins_dir']
end
def self.filters_dir
instance.config['filters_dir']
end
def self.database
instance.config['database'][ENV['SCHLEUDER_ENV']]
end
......
module Schleuder
module Filters
def self.receive_encrypted_only(list, mail)
if list.receive_encrypted_only? && ! mail.was_encrypted?
list.logger.info "Rejecting mail as unencrypted"
return Errors::MessageUnencrypted.new
end
end
def self.receive_signed_only(list, mail)
if list.receive_signed_only? && ! mail.was_validly_signed?
list.logger.info "Rejecting mail as unsigned"
return Errors::MessageUnsigned.new
end
end
def self.receive_authenticated_only(list, mail)
if list.receive_authenticated_only? && ( ! mail.was_encrypted? || ! mail.was_validly_signed? )
list.logger.info "Rejecting mail as unauthenticated"
return Errors::MessageUnauthenticated.new
end
end
def self.receive_from_subscribed_emailaddresses_only(list, mail)
if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first).blank?
list.logger.info "Rejecting mail as not from subscribed address."
return Errors::MessageSenderNotSubscribed.new
end
end
def self.receive_admin_only(list, mail)
if list.receive_admin_only? && ( ! mail.was_validly_signed? || ! mail.signer.admin? )
list.logger.info "Rejecting mail as not from admin."
return Errors::MessageNotFromAdmin.new
end
end
end
end
module Schleuder
module Filters
def self.receive_admin_only(list, mail)
if list.receive_admin_only? && ( ! mail.was_validly_signed? || ! mail.signer.admin? )
list.logger.info "Rejecting mail as not from admin."
return Errors::MessageNotFromAdmin.new
end
end
end
end
module Schleuder
module Filters
def self.receive_authenticated_only(list, mail)
if list.receive_authenticated_only? && ( ! mail.was_encrypted? || ! mail.was_validly_signed? )
list.logger.info "Rejecting mail as unauthenticated"
return Errors::MessageUnauthenticated.new
end
end
end
end
module Schleuder
module Filters
def self.receive_signed_only(list, mail)
if list.receive_signed_only? && ! mail.was_validly_signed?
list.logger.info "Rejecting mail as unsigned"
return Errors::MessageUnsigned.new
end
end
end
end
module Schleuder
module Filters
def self.receive_encrypted_only(list, mail)
if list.receive_encrypted_only? && ! mail.was_encrypted?
list.logger.info "Rejecting mail as unencrypted"
return Errors::MessageUnencrypted.new
end
end
end
end
module Schleuder
module Filters
def self.receive_from_subscribed_emailaddresses_only(list, mail)
if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first).blank?
list.logger.info "Rejecting mail as not from subscribed address."
return Errors::MessageSenderNotSubscribed.new
end
end
end
end
......@@ -9,8 +9,8 @@ module Schleuder
# This problem seems to be in fact related to the use of Microsoft
# Exchange. Accordingly, check if the headers contain 'X-MS-Exchange'.
# See #211, #246, #331 and #333 for background.
def self.fix_exchange_messages!(list, mail)
if mail.header_fields.any?{|f| f.name =~ /^X-MS-Exchange-/i } &&
def self.fix_exchange_messages(list, mail)
if mail.header_fields.any?{|f| f.name =~ /^X-MS-Exchange-/i } &&
!mail[:content_type].blank? &&
mail[:content_type].content_type == 'multipart/mixed' && mail.parts.size > 2 &&
mail.parts[0][:content_type].content_type == 'text/plain' &&
......
module Schleuder
module Filters
def self.strip_html_from_alternative!(list, mail)
def self.strip_html_from_alternative(list, mail)
if mail[:content_type].blank? ||
mail[:content_type].content_type != 'multipart/alternative' ||
! mail.to_s.include?('BEGIN PGP ')
......
module Schleuder
module Filters
class Runner
# To define priority sort this.
# The method `setup` parses, decrypts etc.
# the mail sent to the list. So before
# calling setup we do all the things
# that won't require e.g. validation of
# the sender.
PRE_SETUP_FILTERS = %w[
forward_bounce_to_admins
forward_all_incoming_to_admins
send_key
fix_exchange_messages!
strip_html_from_alternative!
]
# message size must be checked after
# decryption as gpg heavily compresses
# messages.
POST_SETUP_FILTERS = %w[
request
max_message_size
forward_to_owner
receive_admin_only
receive_authenticated_only
receive_signed_only
receive_encrypted_only
receive_from_subscribed_emailaddresses_only
]
attr_reader :list, :filter_type
attr_reader :list
def initialize(list)
def initialize(list, filter_type)
@list = list
@filter_type = filter_type
end
def run(mail, filters)
def run(mail)
filters.map do |cmd|
list.logger.debug "Calling filter #{cmd}"
response = Filters.send(cmd, list, mail)
......@@ -48,8 +22,12 @@ module Schleuder
end
nil
end
private
def filters
@filters ||= load_filters
end
private
def stop?(response)
response.kind_of?(StandardError)
end
......@@ -78,6 +56,38 @@ module Schleuder
list.logger.notify_admin reason, original_message, I18n.t('notice')
end
end
def load_filters
list.logger.debug "Loading #{filter_type}_decryption filters"
sorted_filters.map do |filter_name|
require all_filter_files[filter_name]
filter_name.split('_',2).last
end
end
def sorted_filters
@sorted_filters ||= all_filter_files.keys.sort do |a,b|
a.split('_',2).first.to_i <=> b.split('_',2).first.to_i
end
end
def all_filter_files
@all_filter_files ||= begin
files_in_filter_dirs = Dir[*filter_dirs]
files_in_filter_dirs.inject({}) do |res,file|
filter_name = File.basename(file,'.rb')
res[filter_name] = file
res
end
end
end
def filter_dirs
@filter_dirs ||= [File.join(File.dirname(__FILE__),"filters"),
Schleuder::Conf.filters_dir].map do |d|
File.join(d,"#{filter_type}_decryption/[0-9]*_*.rb")
end
end
end
end
end
......@@ -7,7 +7,7 @@ module Schleuder
logger.info "Parsing incoming email."
@mail = Mail.create_message_to_list(msg, recipient, list)
error = run_filters(Filters::Runner::PRE_SETUP_FILTERS)
error = run_filters('pre')
return error if error
begin
......@@ -18,7 +18,7 @@ module Schleuder
return Errors::DecryptionFailed.new(list)
end
error = run_filters(Filters::Runner::POST_SETUP_FILTERS)
error = run_filters('post')
return error if error
if ! @mail.was_validly_signed?
......@@ -56,8 +56,8 @@ module Schleuder
@list
end
def run_filters(filters)
error = filters_runner.run(@mail, filters)
def run_filters(filter_type)
error = filters_runner(filter_type).run(@mail)
if error
if list.bounces_notify_admins?
text = "#{I18n.t('.bounces_notify_admins')}\n\n#{error}"
......@@ -68,8 +68,19 @@ module Schleuder
end
end
def filters_runner
@filters_runner ||= Filters::Runner.new(list)
def filters_runner(filter_type)
if filter_type == 'pre'
filters_runner_pre_decryption
else
filters_runner_post_decryption
end
end
def filters_runner_pre_decryption
@filters_runner_pre_decryption ||= Filters::Runner.new(list,'pre')
end
def filters_runner_post_decryption
@filters_runner_post_decryption ||= Filters::Runner.new(list,'post')
end
def logger
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment