def test_esc_set_false(self): r = Reply('250', 'Ok') self.assertEqual('2.0.0 Ok', r.message) r.enhanced_status_code = None self.assertEqual('2.0.0 Ok', r.message) r.enhanced_status_code = False self.assertEqual('Ok', r.message)
def test_message_set_clear_esc(self): r = Reply('250', '2.3.4 Ok') self.assertEqual('2.3.4 Ok', r.message) self.assertEqual('2.3.4', r.enhanced_status_code) r.message = None self.assertEqual(None, r.message) self.assertEqual('2.0.0', r.enhanced_status_code)
def test_copy(self): r1 = Reply('250', '2.1.0 Ok') r2 = Reply(command='RCPT') r2.copy(r1) self.assertEqual('250', r2.code) self.assertEqual('2.1.0', r2.enhanced_status_code) self.assertEqual('2.1.0 Ok', r2.message) self.assertEqual('RCPT', r2.command)
def test_enqueue_wait_partial_relay_expired(self): env = Envelope('*****@*****.**', ['*****@*****.**', '*****@*****.**', '*****@*****.**']) bounce_mock = self.mox.CreateMockAnything() bounce_mock(IsA(Envelope), IsA(Reply)).AndReturn(None) bounce_mock(IsA(Envelope), IsA(Reply)).AndReturn(None) self.store.write(env, IsA(float)).AndReturn('1234') self.relay._attempt(env, 0).AndReturn([TransientRelayError('transient', Reply('450', 'transient 1')), TransientRelayError('transient', Reply('450', 'transient 1')), TransientRelayError('transient', Reply('450', 'transient 2'))]) self.store.increment_attempts('1234') self.store.remove('1234') self.mox.ReplayAll() queue = Queue(self.store, self.relay, bounce_factory=bounce_mock, relay_pool=5) queue.enqueue(env) queue.relay_pool.join()
def _run(self): result, envelope = self.poll() if not result: return reraise = True try: self._connect() self._handshake() while result: if self._check_server_timeout(): self.queue.appendleft((result, envelope)) break self._deliver(result, envelope) if self.idle_timeout is None: break result, envelope = self.poll() except SmtpRelayError as e: result.set_exception(e) except SmtpError as e: if not result.ready(): if self.client.io.last_error: reply = Reply(self.client.io.last_error_code, self.client.io.last_error_message, command=self.current_command) else: reply = Reply('421', '4.3.0 {0!s}'.format(e), command=self.current_command) relay_error = SmtpRelayError.factory(reply) result.set_exception(relay_error) except Timeout: if not result.ready(): reply = Reply(command=self.current_command).copy(timed_out) relay_error = SmtpRelayError.factory(reply) result.set_exception(relay_error) except Exception as e: if not result.ready(): result.set_exception(e) reraise = False raise finally: try: self._disconnect() except Exception: if reraise: raise
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)
def _handle_encoding(self, envelope): if '8BITMIME' not in self.client.extensions: try: envelope.encode_7bit(self.binary_encoder) except UnicodeError: reply = Reply('554', '5.6.3 Conversion not allowed', command=b'[data conversion]', address=self.address) raise SmtpRelayError.factory(reply)
def _try_pipe(self, envelope): try: status, stdout, stderr = self._exec_process(envelope) except Timeout: msg = 'Delivery timed out' reply = Reply('450', msg) raise TransientRelayError(msg, reply) if status != 0: self.raise_error(status, stdout, stderr)
def test_send_empty_data(self): self.sock.sendall(b'.\r\n') self.sock.recv(IsA(int)).AndReturn(b'250 2.0.0 Ok\r\n' b'550 5.0.0 Not Ok\r\n') self.mox.ReplayAll() client = LmtpClient(self.sock) client.rcpttos = [('test1', Reply('250')), ('test2', Reply('250')), ('test3', Reply('550'))] replies = client.send_empty_data() self.assertEqual(2, len(replies)) self.assertEqual('test1', replies[0][0]) self.assertEqual('250', replies[0][1].code) self.assertEqual('2.0.0 Ok', replies[0][1].message) self.assertEqual(b'[SEND_DATA]', replies[0][1].command) self.assertEqual('test2', replies[1][0]) self.assertEqual('550', replies[1][1].code) self.assertEqual('5.0.0 Not Ok', replies[1][1].message) self.assertEqual(b'[SEND_DATA]', replies[1][1].command)
def test_process_response_200(self): http_res = self.mox.CreateMockAnything() http_res.status = '200' http_res.reason = 'OK' http_res.getheader('X-Smtp-Reply', '').AndReturn('250; message="2.0.0 Ok"') http_res.getheaders() self.result.set(Reply('250', '2.0.0 Ok')) self.mox.ReplayAll() self.client._process_response(http_res, self.result)
def test_run_multiple(self): result1 = AsyncResult() result2 = AsyncResult() env1 = Envelope('*****@*****.**', ['*****@*****.**']) env1.parse(b'From: [email protected]\r\n\r\ntest test\r\n') env2 = Envelope('*****@*****.**', ['*****@*****.**']) env2.parse(b'From: [email protected]\r\n\r\ntest test\r\n') queue = BlockingDeque() queue.append((result1, env1)) queue.append((result2, env2)) self.sock.recv(IsA(int)).AndReturn(b'220 Welcome\r\n') self.sock.sendall(b'EHLO there\r\n') self.sock.recv(IsA(int)).AndReturn(b'250-Hello\r\n250 PIPELINING\r\n') self.sock.sendall( b'MAIL FROM:<*****@*****.**>\r\nRCPT TO:<*****@*****.**>\r\nDATA\r\n' ) self.sock.recv( IsA(int)).AndReturn(b'250 Ok\r\n250 Ok\r\n354 Go ahead\r\n') self.sock.sendall( b'From: [email protected]\r\n\r\ntest test\r\n.\r\n') self.sock.recv(IsA(int)).AndReturn(b'250 Ok\r\n') self.sock.sendall( b'MAIL FROM:<*****@*****.**>\r\nRCPT TO:<*****@*****.**>\r\nDATA\r\n' ) self.sock.recv( IsA(int)).AndReturn(b'250 Ok\r\n250 Ok\r\n354 Go ahead\r\n') self.sock.sendall( b'From: [email protected]\r\n\r\ntest test\r\n.\r\n') self.sock.recv(IsA(int)).AndReturn(b'250 Ok\r\n') self.sock.sendall(b'QUIT\r\n') self.sock.recv(IsA(int)).AndReturn(b'221 Goodbye\r\n') self.sock.close() self.mox.ReplayAll() client = SmtpRelayClient(('addr', 0), queue, socket_creator=self._socket_creator, ehlo_as='there', idle_timeout=0.0) client._run() self.assertEqual({'*****@*****.**': Reply('250', 'Ok')}, result1.get_nowait()) self.assertEqual({'*****@*****.**': Reply('250', 'Ok')}, result2.get_nowait())
def _handle_encoding(self, result, envelope): if '8BITMIME' not in self.client.extensions: try: envelope.encode_7bit(self.binary_encoder) except UnicodeDecodeError: reply = Reply('554', '5.6.3 Conversion not allowed') e = SmtpRelayError.factory(reply) result.set_exception(e) return False return True
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', [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()
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()
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 _get_error_reply(self, exc): try: if self.client.last_error.code == '421': return self.client.last_error except Exception: pass return Reply('421', '4.3.0 ' + str(exc), command=self.current_command, address=self.address)
def attempt(self, envelope, attempts): """Overrides the |Relay| :meth:`~slimta.relay.Relay.attempt` method to silently discard attempted messages. The |Queue| will see the attempt as a successful delivery. :param envelope: |Envelope| to attempt delivery for. :param attempts: Number of times the envelope has attempted delivery. """ msg = '2.0.0 Message Delivered; {0!s}'.format(uuid.uuid4()) return Reply('250', msg)
def test_have_data(self): env = Envelope() handoff = self.mox.CreateMockAnything() handoff(env).AndReturn([(env, 'testid')]) self.mox.ReplayAll() h = SmtpSession(('127.0.0.1', 0), None, handoff) h.envelope = env reply = Reply('250') h.HAVE_DATA(reply, b'', None) self.assertEqual('250', reply.code) self.assertEqual('2.6.0 Message accepted for delivery', reply.message)
def object_hook(obj): if '__datetime__' in obj: obj = datetime.datetime.strptime( obj['as_str'], "%Y%m%dT%H:%M:%S.%f").replace(tzinfo=pytz.utc) elif '__slimta.Reply__' in obj: obj = Reply(code=obj['as_dict'].get('code'), message=obj['as_dict'].get('message'), command=obj['as_dict'].get('command'), address=obj['as_dict'].get('address')) return obj
def test_enqueue_wait_transientfail(self): self.store.write(self.env, IsA(float)).AndReturn('1234') self.relay._attempt(self.env, 0).AndRaise(TransientRelayError('transient', Reply('450', 'transient'))) self.store.increment_attempts('1234') self.store.set_timestamp('1234', IsA(float)) self.mox.ReplayAll() def backoff(envelope, attempts): return 0 queue = Queue(self.store, self.relay, backoff=backoff, relay_pool=5) queue.enqueue(self.env) queue.relay_pool.join()
def test_have_data_queueerror(self): env = Envelope() handoff = self.mox.CreateMockAnything() handoff(env).AndReturn([(env, QueueError())]) self.mox.ReplayAll() h = SmtpSession(('127.0.0.1', 0), None, handoff) h.envelope = env reply = Reply('250') h.HAVE_DATA(reply, b'', None) self.assertEqual('451', reply.code) self.assertEqual('4.3.0 Error queuing message', reply.message)
def _connect(self): try: with Timeout(self.connect_timeout): self.socket = self.socket_creator(self.address) except socket_error: reply = Reply('451', '4.3.0 Connection failed', command=self.current_command) raise SmtpRelayError.factory(reply) else: log.connect(self.socket, self.address) self.client = self._client_class(self.socket, self.tls_wrapper)
def attempt_delivery(self, envelope, attempts): try: self.relay.attempt(envelope, attempts) except TransientRelayError as exc: self._handle_transient_failure(envelope, attempts, exc.reply) except PermanentRelayError as exc: self.enqueue_bounce(envelope, exc.reply) except Exception as exc: log_exception(__name__) reply = Reply('450', '4.0.0 Unhandled delivery error: ' + str(exc)) self._handle_transient_failure(envelope, attempts, reply) raise
def test_extended_handshake(self): creds = self.mox.CreateMockAnything() creds.authcid = 'testuser' creds.authzid = 'testzid' mock = self.mox.CreateMockAnything() mock.__call__(IsA(SmtpSession)).AndReturn(mock) mock.handle_banner(IsA(Reply), ('127.0.0.1', 0)) mock.handle_ehlo(IsA(Reply), 'there') mock.handle_tls() mock.handle_auth(IsA(Reply), creds) self.mox.ReplayAll() h = SmtpSession(('127.0.0.1', 0), mock, None) h.BANNER_(Reply('220')) h.EHLO(Reply('250'), 'there') h.TLSHANDSHAKE() h.AUTH(Reply('235'), creds) self.assertEqual('there', h.ehlo_as) self.assertTrue(h.extended_smtp) self.assertEqual('TLS', h.security) self.assertEqual(('testuser', 'testzid'), h.auth) self.assertEqual('ESMTPSA', h.protocol)
def _try_pipe_one_rcpt(self, envelope): header_data, message_data = envelope.flatten() stdin = b''.join((header_data, message_data)) rcpt = envelope.recipients[0] try: with Timeout(self.timeout): args = self._process_args(envelope, rcpt) return self._exec_process(args, stdin) except Timeout: msg = 'Delivery timed out' reply = Reply('450', '4.4.2 ' + msg) raise TransientRelayError(msg, reply)
def _parse_smtp_reply_header(self, http_res): raw_reply = http_res.getheader('X-Smtp-Reply', '') match = re.match(self.reply_code_pattern, raw_reply) if not match: return None code = match.group(1) message = '' command = None for match in re.finditer(self.reply_param_pattern, raw_reply): if match.group(1).lower() == 'message': message = match.group(2) elif match.group(1).lower() == 'command': command = match.group(2) return Reply(code, message, command)
def _attempt(self, id, envelope, attempts): try: self.relay._attempt(envelope, attempts) except TransientRelayError as e: self._pool_spawn('store', self._retry_later, id, envelope, e.reply) except PermanentRelayError as e: self._perm_fail(id, envelope, e.reply) except Exception as e: log_exception(__name__) reply = Reply('450', '4.0.0 Unhandled delivery error: ' + str(e)) self._pool_spawn('store', self._retry_later, id, envelope, reply) raise else: self._pool_spawn('store', self.store.remove, id)
def attempt(self, envelope, attempts): domain = self._get_rcpt_domain(envelope) if domain in self._force_mx: dest, port = self._force_mx[domain] else: record = self._mx_records.setdefault(domain, MxRecord(domain)) try: dest = self.choose_mx(record.get(), attempts) except ValueError as exc: msg = str(exc) reply = Reply('550', '5.1.2 ' + msg) raise PermanentRelayError(msg, reply) except DNSException: log_exception(__name__) msg = 'DNS lookup failed' reply = Reply('451', '4.4.3 ' + msg) raise TransientRelayError(msg, reply) port = 25 try: relayer = self._relayers[(dest, port)] except KeyError: relayer = self.new_static_relay(dest, port) self._relayers[(dest, port)] = relayer return relayer.attempt(envelope, attempts)
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 _try_pipe_all_rcpts(self, envelope): header_data, message_data = envelope.flatten() stdin = b''.join((header_data, message_data)) results = {} try: with Timeout(self.timeout): for rcpt in envelope.recipients: args = self._process_args(envelope, rcpt) results[rcpt] = self._exec_process(args, stdin) except Timeout: for rcpt in envelope.recipients: if rcpt not in results: msg = 'Delivery timed out' reply = Reply('450', '4.4.2 ' + msg) results[rcpt] = TransientRelayError(msg, reply) return results
def test_have_data(self): class PtrLookup(object): def finish(self, *args): return 'localhost' env = Envelope() handoff = self.mox.CreateMockAnything() handoff(env).AndReturn([(env, 'testid')]) self.mox.ReplayAll() h = SmtpSession(('127.0.0.1', 0), None, handoff) h.envelope = env h._ptr_lookup = PtrLookup() reply = Reply('250') h.HAVE_DATA(reply, b'', None) self.assertEqual('250', reply.code) self.assertEqual('2.6.0 Message accepted for delivery', reply.message) self.assertEqual('localhost', env.client['host'])
def _attempt(self, id, envelope, attempts): try: results = self.relay._attempt(envelope, attempts) except TransientRelayError as e: self._pool_spawn('store', self._retry_later, id, envelope, e.reply) except PermanentRelayError as e: self._perm_fail(id, envelope, e.reply) except Exception as e: log_exception(__name__) reply = Reply('450', '4.0.0 Unhandled delivery error: ' + str(e)) self._pool_spawn('store', self._retry_later, id, envelope, reply) raise else: if isinstance(results, collections.Sequence): self._handle_partial_relay(id, envelope, attempts, results) else: self._remove(id)
def test_attempt_delivery_transientrelayerror(self): task = self.mox.CreateMockAnything() subtask = self.mox.CreateMockAnything() result = self.mox.CreateMockAnything() result.id = '12345' self.relay.attempt(self.env, 0).AndRaise(TransientRelayError('transient', Reply('450', 'transient error'))) self.celery.task(IgnoreArg()).AndReturn(task) task.s(self.env, 1).AndReturn(subtask) subtask.set(countdown=60) subtask.apply_async().AndReturn(result) self.mox.ReplayAll() def backoff(envelope, attempts): self.assertEqual(self.env, envelope) self.assertEqual(1, attempts) return 60 queue = CeleryQueue(self.celery, self.relay, backoff=backoff) queue.attempt_delivery(self.env, 0)
def test_esc_set(self): r = Reply('250') r.enhanced_status_code = None self.assertEqual('2.0.0', r.enhanced_status_code) r.enhanced_status_code = '2.3.4' self.assertEqual('2.3.4', r.enhanced_status_code)
def test_code_set(self): r = Reply() r.code = None assert_equal(None, r.code) r.code = '100' assert_equal('100', r.code)
def test_message_set(self): r = Reply() r.message = None assert_equal(None, r.message) r.message = 'Ok' assert_equal('Ok', r.message)
def test_send(self): r = Reply('250', 'Ok') io = IO(None) r.send(io) self.assertEqual(b'250 2.0.0 Ok\r\n', io.send_buffer.getvalue())
def test_code_set(self): r = Reply() r.code = None self.assertEqual(None, r.code) r.code = '100' self.assertEqual('100', r.code)
def test_send_newline_first(self): r = Reply('250', 'Ok') r.newline_first = True io = IO(None) r.send(io) self.assertEqual(b'\r\n250 2.0.0 Ok\r\n', io.send_buffer.getvalue())
def test_code_set_bad_value(self): r = Reply() with self.assertRaises(ValueError): r.code = 'asdf'
def test_esc_without_code(self): r = Reply() r.enhanced_status_code = '2.3.4' self.assertEqual(None, r.enhanced_status_code) r.code = '250' self.assertEqual('2.3.4', r.enhanced_status_code)
def test_esc_set_bad_value(self): r = Reply() with self.assertRaises(ValueError): r.enhanced_status_code = 'abc'
def test_message_set_with_esc(self): r = Reply('250') r.message = '2.3.4 Ok' self.assertEqual('2.3.4 Ok', r.message) self.assertEqual('2.3.4', r.enhanced_status_code)
def test_message_set(self): r = Reply() r.message = None self.assertEqual(None, r.message) r.message = 'Ok' self.assertEqual('Ok', r.message)