Example #1
0
    def raise_error(self, status, stdout, stderr):
        """This method may be over-ridden by sub-classes if you need to control
        how the relay error is generated. By default, the error raised is a
        :class:`~slimta.relay.TransientRelayError` unless the process output
        begins with a ``5.X.X`` enhanced status code. This behavior attempts to
        mimic the postfix pipe_ daemon.

        This method is only called if the subprocess returns a non-zero exit
        status.

        :param status: The non-zero exit status of the subprocess.
        :param stdout: The subprocess's standard output, as received by
                       :py:meth:`~subprocess.Popen.communicate`.
        :type stdout: string
        :param stderr: The subprocess's standard error output, as received by
                       :py:meth:`~subprocess.Popen.communicate`.
        :type stderr: string
        :raises: :class:`~slimta.relay.TransientRelayError`,
                 :class:`~slimta.relay.PermanentRelayError`

        """
        error_msg = stdout.rstrip() or stderr.rstrip() or 'Delivery failed'
        if isinstance(error_msg, bytes):
            error_msg = error_msg.decode('utf-8')
        if self._permanent_error_pattern.match(error_msg):
            reply = Reply('550', error_msg)
            raise PermanentRelayError(error_msg, reply)
        else:
            reply = Reply('450', error_msg)
            raise TransientRelayError(error_msg, reply)
Example #2
0
    def test_enqueue_wait_partial_relay(self):
        env = Envelope(
            '*****@*****.**',
            ['*****@*****.**', '*****@*****.**', '*****@*****.**'])
        self.store.write(env, IsA(float)).AndReturn('1234')
        self.relay._attempt(env, 0).AndReturn({
            '*****@*****.**':
            None,
            '*****@*****.**':
            TransientRelayError('transient', Reply('450', 'transient')),
            '*****@*****.**':
            PermanentRelayError('permanent', Reply('550', 'permanent'))
        })
        self.store.increment_attempts('1234')
        self.store.set_timestamp('1234', IsA(float))
        self.store.set_recipients_delivered('1234', set([0, 2]))
        self.mox.ReplayAll()

        def backoff(envelope, attempts):
            return 0

        def no_bounce(envelope, reply):
            return None

        queue = Queue(self.store,
                      self.relay,
                      backoff=backoff,
                      bounce_factory=no_bounce,
                      relay_pool=5)
        queue.enqueue(env)
        queue.relay_pool.join()
Example #3
0
 def raise_error(self, status, stdout, stderr):
     error_msg = stdout.rstrip() or stderr.rstrip() or 'LDA delivery failed'
     if status == self.EX_TEMPFAIL:
         reply = Reply('450', error_msg)
         raise TransientRelayError(error_msg, reply)
     else:
         reply = Reply('550', error_msg)
         raise PermanentRelayError(error_msg, reply)
 def test_attempt_delivery_permanentrelayerror_nullsender(self):
     task = self.mox.CreateMockAnything()
     self.relay.attempt(self.bounce, 0).AndRaise(PermanentRelayError('permanent', Reply('550', 'permanent error')))
     self.celery.task(IgnoreArg()).AndReturn(task)
     self.mox.ReplayAll()
     def return_bounce(envelope, reply):
         self.fail('Tried to generate a bounce to a NULL sender.')
     queue = CeleryQueue(self.celery, self.relay, bounce_factory=return_bounce)
     queue.attempt_delivery(self.bounce, 0)
Example #5
0
 def test_enqueue_relayerror(self):
     err = PermanentRelayError('msg failure', Reply('550', 'Not Ok'))
     self.relay._attempt(self.env, 0).AndRaise(err)
     self.mox.ReplayAll()
     q = ProxyQueue(self.relay)
     ret = q.enqueue(self.env)
     self.assertEqual(1, len(ret))
     self.assertEqual(2, len(ret[0]))
     self.assertEqual(self.env, ret[0][0])
     self.assertEqual(err, ret[0][1])
 def test_enqueue_wait_permanentfail(self):
     self.store.write(self.env, IsA(float)).AndReturn('1234')
     self.relay._attempt(self.env, 0).AndRaise(PermanentRelayError('permanent', Reply('550', 'permanent')))
     self.store.remove('1234')
     self.mox.ReplayAll()
     def no_bounce(envelope, reply):
         return None
     queue = Queue(self.store, self.relay, bounce_factory=no_bounce, relay_pool=5)
     queue.enqueue(self.env)
     queue.relay_pool.join()
Example #7
0
 def raise_error(self, status, stdout, stderr):
     error_msg = 'Delivery failed'
     if stdout.startswith('maildrop: '):
         error_msg = stdout[10:].rstrip()
     elif stderr.startswith('maildrop: '):
         error_msg = stderr[10:].rstrip()
     if status == self.EX_TEMPFAIL:
         reply = Reply('450', error_msg)
         raise TransientRelayError(error_msg, reply)
     else:
         reply = Reply('550', error_msg)
         raise PermanentRelayError(error_msg, reply)
 def test_attempt_delivery_permanentrelayerror(self):
     task = self.mox.CreateMockAnything()
     subtask = self.mox.CreateMockAnything()
     result = self.mox.CreateMockAnything()
     result.id = '12345'
     self.relay.attempt(self.env, 0).AndRaise(PermanentRelayError('permanent', Reply('550', 'permanent error')))
     self.celery.task(IgnoreArg()).AndReturn(task)
     task.s(self.bounce, 0).AndReturn(subtask)
     subtask.apply_async().AndReturn(result)
     self.mox.ReplayAll()
     def return_bounce(envelope, reply):
         self.assertEqual(self.env, envelope)
         return self.bounce
     queue = CeleryQueue(self.celery, self.relay, bounce_factory=return_bounce)
     queue.attempt_delivery(self.env, 0)
 def test_default_replies(self):
     perm = PermanentRelayError('test msg')
     transient = TransientRelayError('test msg')
     self.assertEqual('550 5.0.0 test msg', str(perm.reply))
     self.assertEqual('450 4.0.0 test msg', str(transient.reply))
Example #10
0
    def apply(self, envelope):
        identifier = envelope.headers.get(
            settings.TRANSACTIONAL['X_MESSAGE_ID_HEADER'])
        if not identifier or not envelope.client.get('auth'):
            # FIXME: log exact error
            raise PermanentRelayError('Internal application error')

        # Process mail content
        munchers_kwargs = {
            'mail_identifier':
            identifier,
            'app_url':
            get_app_url(domain=envelope.sending_domain,
                        organization=envelope.organization),
        }

        msg_links = {}
        mail_kwargs = {}
        track_open = envelope.headers.get(
            settings.TRANSACTIONAL.get('X_MAIL_TRACK_OPEN_HEADER', None))
        track_open_done = False
        if track_open:
            munchers_kwargs.update({'track_open': True})
            mail_kwargs.update({'track_open': True})

        track_clicks = envelope.headers.get(
            settings.TRANSACTIONAL.get('X_MAIL_TRACK_CLICKS_HEADER', None))
        if track_clicks:
            mail_kwargs.update({'track_clicks': True})
            munchers_kwargs.update({'track_clicks': True})

        add_unsubscribe = envelope.headers.get(
            settings.TRANSACTIONAL.get('X_MAIL_UNSUBSCRIBE_HEADER', None))
        if add_unsubscribe:
            munchers_kwargs.update({
                'unsubscribe_url':
                Mail.unsubscribe_url(identifier, envelope.user,
                                     envelope.sending_domain)
            })

        # Retrieve full html to extract links
        html = ''
        message = email.message_from_bytes(b'\n'.join(envelope.flatten()))
        for part in message.walk():
            if part.get_content_type() == 'text/html':
                html += part.get_payload()

        msg_links = get_msg_links(html)
        mail_kwargs.update({'msg_links': msg_links})
        munchers_kwargs.update({'links_map': msg_links})

        # Walk throught every parts to apply munchers on it
        for part in message.walk():
            if part.get_content_type() == 'text/html':
                html = part.get_payload()
                if track_open and not track_open_done:
                    html = add_tracking_image(html, **munchers_kwargs)
                    track_open_done = True
                if track_clicks:
                    html = rewrite_html_links(html, **munchers_kwargs)
                part.set_payload(html)

            content = part.get_payload()

            if add_unsubscribe:
                content = set_unsubscribe_url(content, **munchers_kwargs)

            part.set_payload(content)

        envelope.parse_msg(message)

        batch = envelope.headers.get(
            settings.TRANSACTIONAL.get('X_MAIL_BATCH_HEADER', None))
        if batch:
            batch, created = MailBatch.objects.get_or_create(
                name=batch,
                author=envelope.user,
                defaults={'msg_links': msg_links})
            if not created:
                batch.msg_links = msg_links
                batch.save()
            category = envelope.headers.get(
                settings.TRANSACTIONAL.get('X_MAIL_BATCH_CATEGORY_HEADER',
                                           None))
            if category:
                category, _ = Category.objects.get_or_create(
                    author=envelope.user, name=category)
                batch.category = category
                batch.save()

        raw_mail, _ = RawMail.objects.get_or_create(content=envelope.message)
        mail = Mail.objects.create(
            author=envelope.user,
            batch=batch,
            identifier=envelope.headers.get(
                settings.TRANSACTIONAL['X_MESSAGE_ID_HEADER']),
            headers={
                k: v.encode('utf-8', 'surrogateescape').decode('utf-8')
                for k, v in envelope.headers.raw_items()
            },
            message=raw_mail,
            sender=envelope.sender,
            recipient=envelope.recipients[0],
            **mail_kwargs)
        MailStatus.objects.create(mail=mail,
                                  destination_domain=extract_domain(
                                      envelope.recipients[0]))