module Schleuder::Filters
Public Class Methods
Outlook / Hotmail seems to dismantle multipart/encrypted messages and put them again together as multipart/mixed, which is wrong and makes it problematic to correctly detect the message as a valid pgp/mime-mail. Here we fix the mail to be a proper pgp/mime aka. multipart/encrypted message, so further processing will detect it properly. 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.
# File lib/schleuder/filters/pre_decryption/40_fix_exchange_messages.rb, line 12 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' && mail.parts[0].body.to_s.blank? && mail.parts[1][:content_type].content_type == 'application/pgp-encrypted' && mail.parts[2][:content_type].content_type == 'application/octet-stream' mail.parts.delete_at(0) mail.content_type = [:multipart, :encrypted, {protocol: 'application/pgp-encrypted', boundary: mail.boundary}] end end
# File lib/schleuder/filters/pre_decryption/20_forward_all_incoming_to_admins.rb, line 4 def self.forward_all_incoming_to_admins(list, mail) if list.forward_all_incoming_to_admins list.logger.notify_admin I18n.t(:forward_all_incoming_to_admins), mail.original_message, I18n.t('incoming_message') end end
# File lib/schleuder/filters/pre_decryption/10_forward_bounce_to_admins.rb, line 3 def self.forward_bounce_to_admins(list, mail) if mail.automated_message? list.logger.info 'Forwarding automated message to admins' list.logger.notify_admin I18n.t(:forward_automated_message_to_admins), mail.original_message, I18n.t('automated_message_subject') exit end end
# File lib/schleuder/filters/post_decryption/30_forward_to_owner.rb, line 3 def self.forward_to_owner(list, mail) return if ! mail.to_owner? list.logger.debug 'Forwarding addressed to -owner' mail.add_pseudoheader(:note, I18n.t(:owner_forward_prefix)) cleanmail = mail.clean_copy(true) list.admins.each do |admin| list.logger.debug "Forwarding message to #{admin}" admin.send_mail(cleanmail) end exit end
# File lib/schleuder/filters/post_decryption/35_key_auto_import_from_attachments.rb, line 3 def self.key_auto_import_from_attachments(list, mail) # Don't run if not enabled. return if ! list.key_auto_import_from_email imported_fingerprints = EmailKeyImporter.import_from_attachments(list, mail) if imported_fingerprints.size > 0 # If the message's signature could not be validated before, re-run the # validation, because after having imported new or updated keys the # validation now might work. if mail.signature.present? && ! mail.signature.valid? # Re-validate the signature validation, now that a new key was # imported that might be the previously unknown signing key. mail.repeat_validation! end end end
# File lib/schleuder/filters/pre_decryption/60_key_auto_import_from_autocrypt_header.rb, line 3 def self.key_auto_import_from_autocrypt_header(list, mail) if list.key_auto_import_from_email EmailKeyImporter.import_from_autocrypt_header(list, mail) end end
# File lib/schleuder/filters/post_decryption/20_max_message_size.rb, line 4 def self.max_message_size(list, mail) if (mail.raw_source.size / 1024) > list.max_message_size_kb list.logger.info 'Rejecting mail as too big' return Errors::MessageTooBig.new(list) end end
# File lib/schleuder/filters/post_decryption/40_receive_admin_only.rb, line 3 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
# File lib/schleuder/filters/post_decryption/50_receive_authenticated_only.rb, line 3 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
# File lib/schleuder/filters/post_decryption/70_receive_encrypted_only.rb, line 3 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
# File lib/schleuder/filters/post_decryption/80_receive_from_subscribed_emailaddresses_only.rb, line 3 def self.receive_from_subscribed_emailaddresses_only(list, mail) if list.receive_from_subscribed_emailaddresses_only? && list.subscriptions.where(email: mail.from.first.downcase).blank? list.logger.info 'Rejecting mail as not from subscribed address.' return Errors::MessageSenderNotSubscribed.new end end
# File lib/schleuder/filters/post_decryption/60_receive_signed_only.rb, line 3 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
# File lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb, line 15 def self.recursively_strip_html_from_alternative_if_keywords_present(list, mail) if mail[:content_type].blank? then return false end content_type = mail[:content_type].content_type # The multipart/alternative could hide inside an arbitrary number of # levels of multipart/mixed encapsulation. # see also: https://www.rfc-editor.org/rfc/rfc2046#section-5.1.3 if content_type == 'multipart/mixed' mail.parts.each do |part| self.recursively_strip_html_from_alternative_if_keywords_present(list, part) end return false end # inside the mutlipart/mixed, we only care about multipart/mixed and # mutlipart/alternative if content_type != 'multipart/alternative' then return false end # Inside multipart/alternative, there could be a text/html-part, or there # could be a multipart/related-part which contains the text/html-part. # Everything inside the multipart/alternative that is not text/plain # should be deleted, since it will contain keywords and we only strip # keywords from text/plain-parts. Schleuder.logger.debug 'Stripping html-part from multipart/alternative-message because it contains keywords' mail.parts.delete_if do |part| content_type = part[:content_type].content_type content_type != 'text/plain' end # NOTE: We could instead unencapsulate it. mail.content_type = 'multipart/mixed' mail.add_pseudoheader(:note, I18n.t('pseudoheaders.stripped_html_from_multialt_with_keywords')) end
# File lib/schleuder/filters/post_decryption/10_request.rb, line 3 def self.request(list, mail) return if ! mail.request? list.logger.debug 'Request-message' if ! mail.was_encrypted? || ! mail.was_validly_signed? list.logger.debug 'Error: Message was not encrypted and validly signed' return Errors::MessageUnauthenticated.new end if mail.keywords.empty? output = I18n.t(:no_keywords_error) else output = KeywordHandlersRunner.run(type: :request, list: list, mail: mail) output = output.flatten.map(&:presence).compact if output.blank? output = I18n.t(:no_output_result) end end mail.reply_to_signer(output) exit end
# File lib/schleuder/filters/pre_decryption/30_send_key.rb, line 3 def self.send_key(list, mail) return if ! mail.sendkey_request? list.logger.debug 'Sending public key as reply.' out = mail.reply out.from = list.email # We're not sending to a subscribed address, so we need to specify a envelope-sender manually. out.sender = list.bounce_address out.body = I18n.t(:list_public_key_attached) out.attach_list_key!(list) # TODO: find out why the gpg-module puts all the headers into the first mime-part, too out.gpg list.gpg_sign_options out.deliver exit end
# File lib/schleuder/filters/pre_decryption/50_strip_html_from_alternative.rb, line 4 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 ') return false end Schleuder.logger.debug 'Stripping html-part from multipart/alternative-message' mail.parts.delete_if do |part| part[:content_type].content_type == 'text/html' end mail.content_type = 'multipart/mixed' mail.add_pseudoheader(:note, I18n.t('pseudoheaders.stripped_html_from_multialt')) end
If keywords are present, recurse into arbitrary levels of multipart/mixed encapsulation. If multipart/alternative is found, remove all sub-parts but the text/plain part (assuming that every multipart/alternative contains exactly one text/plain). Change the content_type from mutlipart/alternative to mutlipart/mixed.
# File lib/schleuder/filters/post_decryption/90_strip_html_from_alternative_if_keywords_present.rb, line 9 def self.strip_html_from_alternative_if_keywords_present(list, mail) # Only strip the text/html-part if keywords are present if mail.keywords.blank? then return false end return self.recursively_strip_html_from_alternative_if_keywords_present(list, mail) end