From b082f459c9b7c55401d6fb724b10838a30af9363 Mon Sep 17 00:00:00 2001 From: Bianca Nenciu Date: Thu, 18 Aug 2022 18:18:58 +0300 Subject: [PATCH] FEATURE: Limit maximum recipients for group emails (#17971) New maximum_recipients_per_new_group_email site setting can be used to prevent spam group emails with many recipients. --- config/locales/server.en.yml | 11 +++++++++++ config/site_settings.yml | 3 +++ lib/email/processor.rb | 6 ++++++ lib/email/receiver.rb | 30 ++++++++++++++++++++++++++++++ spec/lib/email/processor_spec.rb | 22 ++++++++++++++++++++++ spec/lib/email/receiver_spec.rb | 5 +++++ 6 files changed, 77 insertions(+) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index b2b180f86bf..3e774e16f52 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2043,6 +2043,7 @@ en: max_emails_per_day_per_user: "Maximum number of emails to send users per day. 0 to disable the limit" enable_staged_users: "Automatically create staged users when processing incoming emails." maximum_staged_users_per_email: "Maximum number of staged users created when processing an incoming email." + maximum_recipients_per_new_group_email: "Block incoming emails with too many recipients." auto_generated_allowlist: "List of email addresses that won't be checked for auto-generated content. Example: foo@bar.com|discourse@bar.com" block_auto_generated_emails: "Block incoming emails identified as being auto generated." ignore_by_title: "Ignore incoming emails based on their title." @@ -3359,6 +3360,16 @@ en: If you believe this is an error, [contact a staff member](%{base_url}/about). + email_reject_too_many_recipients: + title: "Email Reject Too Many Recipients" + subject_template: "[%{email_prefix}] Email issue -- Too Many Recipients" + text_body_template: | + We're sorry, but your email message to %{destination} (titled %{former_title}) didn't work. + + You attempted to email more than %{max_recipients_count} people and our system automatically tagged your email as spam. + + If you believe this is an error, [contact a staff member](%{base_url}/about). + email_error_notification: title: "Email Error Notification" subject_template: "[%{email_prefix}] Email issue -- POP authentication error" diff --git a/config/site_settings.yml b/config/site_settings.yml index 02a5094bd0b..0d1ec4f1592 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1202,6 +1202,9 @@ email: max_emails_per_day_per_user: 100 enable_staged_users: true maximum_staged_users_per_email: 10 + maximum_recipients_per_new_group_email: + default: 10 + min: 1 auto_generated_allowlist: default: "" type: list diff --git a/lib/email/processor.rb b/lib/email/processor.rb index d0a266eaaf4..4d9f5085d40 100644 --- a/lib/email/processor.rb +++ b/lib/email/processor.rb @@ -67,6 +67,7 @@ module Email when Email::Receiver::OldDestinationError then :email_reject_old_destination when Email::Receiver::ReplyNotAllowedError then :email_reject_reply_not_allowed when Email::Receiver::ReplyToDigestError then :email_reject_reply_to_digest + when Email::Receiver::TooManyRecipientsError then :email_reject_too_many_recipients else :email_reject_unrecognized_error end @@ -96,6 +97,11 @@ module Email template_args[:number_of_days] = SiteSetting.disallow_reply_by_email_after_days end + if message_template == :email_reject_too_many_recipients + template_args[:recipients_count] = e.recipients_count + template_args[:max_recipients_count] = SiteSetting.maximum_recipients_per_new_group_email + end + if message_template # inform the user about the rejection message = Mail::Message.new(mail_string) diff --git a/lib/email/receiver.rb b/lib/email/receiver.rb index ff01667ef19..a3a6a1d118c 100644 --- a/lib/email/receiver.rb +++ b/lib/email/receiver.rb @@ -33,6 +33,14 @@ module Email class OldDestinationError < ProcessingError; end class ReplyToDigestError < ProcessingError; end + class TooManyRecipientsError < ProcessingError + attr_reader :recipients_count + + def initialize(recipients_count:) + @recipients_count = recipients_count + end + end + attr_reader :incoming_email attr_reader :raw_email attr_reader :mail @@ -156,6 +164,11 @@ module Email raise UserNotFoundError unless SiteSetting.enable_staged_users end + recipients = get_all_recipients(@mail) + if recipients.size > SiteSetting.maximum_recipients_per_new_group_email + raise TooManyRecipientsError.new(recipients_count: recipients.size) + end + body, elided = select_body body ||= "" @@ -230,6 +243,23 @@ module Email raise SilencedUserError if user.silenced? end + def get_all_recipients(mail) + recipients = Set.new + + %i(to cc bcc).each do |field| + next if mail[field].blank? + + mail[field].each do |address_field| + begin + address_field.decoded + recipients << address_field.address.downcase + end + end + end + + recipients + end + def is_bounce? @mail.bounced? || bounce_key end diff --git a/spec/lib/email/processor_spec.rb b/spec/lib/email/processor_spec.rb index 4fd1c0c1121..01a0dc61612 100644 --- a/spec/lib/email/processor_spec.rb +++ b/spec/lib/email/processor_spec.rb @@ -193,4 +193,26 @@ RSpec.describe Email::Processor do )) end end + + describe 'when group email recipients exceeds maximum_recipients_per_new_group_email site setting' do + let(:mail) { file_from_fixtures("cc.eml", "emails").read } + + it 'rejects the email with the right response' do + SiteSetting.maximum_recipients_per_new_group_email = 3 + + processor = Email::Processor.new(mail) + processor.process! + + rejection_raw = ActionMailer::Base.deliveries.first.body.to_s + + expect(rejection_raw).to eq( + I18n.t("system_messages.email_reject_too_many_recipients.text_body_template", + destination: '["someone@else.com"]', + former_title: 'The more, the merrier', + max_recipients_count: 3, + base_url: Discourse.base_url, + ) + ) + end + end end diff --git a/spec/lib/email/receiver_spec.rb b/spec/lib/email/receiver_spec.rb index 306afa83e4e..b12aea30caa 100644 --- a/spec/lib/email/receiver_spec.rb +++ b/spec/lib/email/receiver_spec.rb @@ -883,6 +883,11 @@ RSpec.describe Email::Receiver do expect(Topic.last.ordered_posts[-1].post_type).to eq(Post.types[:moderator_action]) end + it "rejects messages with too many recipients" do + SiteSetting.maximum_recipients_per_new_group_email = 3 + expect { process(:cc) }.to raise_error(Email::Receiver::TooManyRecipientsError) + end + describe "reply-to header" do before do SiteSetting.block_auto_generated_emails = false