Track which emails are "user generated" for different antispam treatment
authorMagnus Hagander <magnus@hagander.net>
Sat, 11 Jan 2014 19:44:57 +0000 (20:44 +0100)
committerMagnus Hagander <magnus@hagander.net>
Sat, 11 Jan 2014 19:46:48 +0000 (20:46 +0100)
Basically, user generated email (bug report form) will be sent to the mail
frontends for antispam. Any errors generated there will be ignored and
the mails "dropped on the floor". Other emails keep entering the system
through localhost and delivered there.

pgweb/mailqueue/models.py
pgweb/mailqueue/util.py
pgweb/misc/views.py
pgweb/settings.py
pgweb/util/misc.py
tools/mailqueue/send_queued_mail.py

index f2e7d198e2bb6dce56eb24d0d7461cec6c3b5ae1..36d73eee308e8f0994d7f72cb07e039419ce79cf 100644 (file)
@@ -6,6 +6,9 @@ class QueuedMail(models.Model):
        # We store the raw MIME message, so if there are any attachments or
        # anything, we just push them right in there!
        fullmsg = models.TextField(null=False, blank=False)
+       # Flag if the message is "user generated", so we can treat those
+       # separately from an antispam and delivery perspective.
+       usergenerated = models.BooleanField(null=False, blank=False, default=False)
 
        def __unicode__(self):
                return "%s: %s -> %s" % (self.pk, self.sender, self.receiver)
index 65ad3e1294a78e45281354decef616c7ae837b0a..d4d079f0ab374bd60db8e5be42580d30df27fe24 100644 (file)
@@ -6,7 +6,7 @@ from email import encoders
 
 from models import QueuedMail
 
-def send_simple_mail(sender, receiver, subject, msgtxt, attachments=None):
+def send_simple_mail(sender, receiver, subject, msgtxt, attachments=None, usergenerated=False):
        # attachment format, each is a tuple of (name, mimetype,contents)
        # content should be *binary* and not base64 encoded, since we need to
        # use the base64 routines from the email library to get a properly
@@ -30,8 +30,8 @@ def send_simple_mail(sender, receiver, subject, msgtxt, attachments=None):
 
 
        # Just write it to the queue, so it will be transactionally rolled back
-       QueuedMail(sender=sender, receiver=receiver, fullmsg=msg.as_string()).save()
+       QueuedMail(sender=sender, receiver=receiver, fullmsg=msg.as_string(), usergenerated=usergenerated).save()
 
-def send_mail(sender, receiver, fullmsg):
+def send_mail(sender, receiver, fullmsg, usergenerated=False):
        # Send an email, prepared as the full MIME encoded mail already
-       QueuedMail(sender=sender, receiver=receiver, fullmsg=fullmsg).save()
+       QueuedMail(sender=sender, receiver=receiver, fullmsg=fullmsg, usergenerated=False).save()
index 0c243390eacffc46a9fe83eff10d1e1c92275dce..928388fb483dc3432b4183ebe0e54c7847bf8b32 100644 (file)
@@ -31,7 +31,8 @@ def submitbug(request):
                                {
                                        'bugid': bugid,
                                        'bug': form.cleaned_data,
-                               }
+                               },
+                               usergenerated=True
                        )
 
                        return render_to_response('misc/bug_completed.html', {
index a9b13696e673bc4a054369707712a9441bb18cb3..998e89358beac60b366f815c6752a9e3c8fe8e30 100644 (file)
@@ -158,6 +158,7 @@ FTP_MASTERS=()                                                                                 # A tuple containing the *IP addresses* of all machin
 VARNISH_PURGERS=()                                     # Extra servers that can do varnish purges through our queue
 VARNISH_QUEUE_ID=1                                                                        # pgq queue id used for varnish purging
 ARCHIVES_SEARCH_SERVER="archives.postgresql.org"       # Where to post REST request for archives search
+FRONTEND_SMTP_RELAY="magus.postgresql.org"             # Where to relay user generated email
 
 # Load local settings overrides
 from settings_local import *
index a6745902056dc061cb5437594a9d1b2214305311..0877208fb4998854b28117606119af68886c922a 100644 (file)
@@ -4,9 +4,10 @@ from django.conf import settings
 from pgweb.mailqueue.util import send_simple_mail
 from pgweb.util.helpers import template_to_string
 
-def send_template_mail(sender, receiver, subject, templatename, templateattr={}):
+def send_template_mail(sender, receiver, subject, templatename, templateattr={}, usergenerated=False):
        send_simple_mail(sender, receiver, subject,
-                                        template_to_string(templatename, templateattr))
+                                        template_to_string(templatename, templateattr),
+                                        usergenerated=usergenerated)
 
 def is_behind_cache(request):
        """
index 59a541925f8c4b15ba4c57c7b22831ef7fa0181d..478e05e7b275dfdafa480564aeac4853984da3a6 100755 (executable)
@@ -36,8 +36,20 @@ if __name__ == "__main__":
                # Yes, we do a new connection for each run. Just because we can.
                # If it fails we'll throw an exception and just come back on the
                # next cron job. And local delivery should never fail...
-               smtp = smtplib.SMTP("localhost")
-               smtp.sendmail(m.sender, m.receiver, m.fullmsg.encode('utf-8'))
+               if m.usergenerated:
+                       # User generated email gets relayed directly over a frontend
+                       smtphost = settings.FRONTEND_SMTP_RELAY
+               else:
+                       smtphost = 'localhost'
+               smtp = smtplib.SMTP(smtphost)
+               try:
+                       smtp.sendmail(m.sender, m.receiver, m.fullmsg.encode('utf-8'))
+               except (smtplib.SMTPSenderRefused, smtplib.SMTPRecipientsRefused, smtplib.SMTPDataError):
+                       # If this was user generated, this indicates the antispam
+                       # kicking in, so we just ignore it. If it's anything else,
+                       # we want to let the exception through.
+                       if not m.usergenerated:
+                               raise
                smtp.close()
                m.delete()
                transaction.commit()