From 5710d5d771537d106fbe3488355ded00e514c073 Mon Sep 17 00:00:00 2001
From: Martin Brennan <mjrbrennan@gmail.com>
Date: Tue, 19 Jan 2021 09:49:50 +1000
Subject: [PATCH] FIX: Do not process pop3 mails > 1 week old (#11740)

This adds a safe default to not process pop3 emails when the pop3 polling option is set up that are > 1 week old. This is to avoid the situation where an older mailbox is used, which causes us to go and process all emails in that mailbox, sending out error emails to the senders of emails which cannot be parsed successfully.
---
 app/jobs/scheduled/poll_mailbox.rb | 17 ++++++++++---
 spec/jobs/poll_mailbox_spec.rb     | 39 ++++++++++++++++++++++++++----
 2 files changed, 48 insertions(+), 8 deletions(-)

diff --git a/app/jobs/scheduled/poll_mailbox.rb b/app/jobs/scheduled/poll_mailbox.rb
index a016ec2036c..4a9ee8bf100 100644
--- a/app/jobs/scheduled/poll_mailbox.rb
+++ b/app/jobs/scheduled/poll_mailbox.rb
@@ -19,8 +19,8 @@ module Jobs
       SiteSetting.pop3_polling_enabled?
     end
 
-    def process_popmail(popmail)
-      Email::Processor.process!(popmail.pop)
+    def process_popmail(mail_string)
+      Email::Processor.process!(mail_string)
     end
 
     POLL_MAILBOX_TIMEOUT_ERROR_KEY = "poll_mailbox_timeout_error_key"
@@ -38,7 +38,9 @@ module Jobs
 
       pop3.start(SiteSetting.pop3_polling_username, SiteSetting.pop3_polling_password) do |pop|
         pop.each_mail do |p|
-          process_popmail(p)
+          mail_string = p.pop
+          break if mail_too_old?(mail_string)
+          process_popmail(mail_string)
           p.delete if SiteSetting.pop3_polling_delete_from_server?
         end
       end
@@ -69,6 +71,15 @@ module Jobs
       Discourse.redis.zcard(POLL_MAILBOX_ERRORS_KEY).to_i
     end
 
+    def mail_too_old?(mail_string)
+      mail = Mail.new(mail_string)
+      date_header = mail.header['Date']
+      return false if date_header.blank?
+
+      date = Time.parse(date_header.to_s)
+      date < 1.week.ago
+    end
+
     def mark_as_errored!
       now = Time.now.to_i
       Discourse.redis.zadd(POLL_MAILBOX_ERRORS_KEY, now, now.to_s)
diff --git a/spec/jobs/poll_mailbox_spec.rb b/spec/jobs/poll_mailbox_spec.rb
index 267913034d4..87fecc9dcfd 100644
--- a/spec/jobs/poll_mailbox_spec.rb
+++ b/spec/jobs/poll_mailbox_spec.rb
@@ -72,14 +72,38 @@ describe Jobs::PollMailbox do
     end
 
     context "has emails" do
+      let(:oldmail) { file_from_fixtures("old_destination.eml", "emails").read }
+
+      # the date is dynamic here because there is a 1 week cutoff for
+      # the pop3 polling
+      let(:example_email) do
+        email = <<~EMAIL
+        Return-Path: <one@foo.com>
+        From: One <one@foo.com>
+        To: team@bar.com
+        Subject: Testing email
+        Date: #{1.day.ago.strftime("%a, %d %b %Y")} 03:12:43 +0100
+        Message-ID: <34@foo.bar.mail>
+        Mime-Version: 1.0
+        Content-Type: text/plain
+        Content-Transfer-Encoding: 7bit
+
+        This is an email example.
+        EMAIL
+      end
       before do
-        mail1 = Net::POPMail.new(3, nil, nil, nil)
-        mail2 = Net::POPMail.new(3, nil, nil, nil)
+        mail1 = Net::POPMail.new(1, nil, nil, nil)
+        mail2 = Net::POPMail.new(2, nil, nil, nil)
         mail3 = Net::POPMail.new(3, nil, nil, nil)
+        mail4 = Net::POPMail.new(4, nil, nil, nil)
         Net::POP3.any_instance.stubs(:start).yields(Net::POP3.new(nil, nil))
-        Net::POP3.any_instance.stubs(:mails).returns([mail1, mail2, mail3])
+        Net::POP3.any_instance.stubs(:mails).returns([mail1, mail2, mail3, mail4])
         Net::POP3.any_instance.expects(:delete_all).never
-        poller.stubs(:process_popmail)
+        mail1.stubs(:pop).returns(example_email)
+        mail2.stubs(:pop).returns(example_email)
+        mail3.stubs(:pop).returns(example_email)
+        mail4.stubs(:pop).returns(oldmail)
+        poller.expects(:process_popmail).times(3)
       end
 
       it "deletes emails from server when when deleting emails from server is enabled" do
@@ -93,6 +117,11 @@ describe Jobs::PollMailbox do
         SiteSetting.pop3_polling_delete_from_server = false
         poller.poll_pop3
       end
+
+      it "does not process emails > 1 week old" do
+        SiteSetting.pop3_polling_delete_from_server = false
+        poller.poll_pop3
+      end
     end
   end
 
@@ -100,7 +129,7 @@ describe Jobs::PollMailbox do
     def process_popmail(email_name)
       pop_mail = stub("pop mail")
       pop_mail.expects(:pop).returns(email(email_name))
-      Jobs::PollMailbox.new.process_popmail(pop_mail)
+      Jobs::PollMailbox.new.process_popmail(pop_mail.pop)
     end
 
     it "does not reply to a bounced email" do