def test_recv_command(self): self.sock.recv(IsA(int)).AndReturn('CMD\r\n') self.mox.ReplayAll() io = IO(self.sock) command, arg = io.recv_command() assert_equal('CMD', command) assert_equal(None, arg)
def test_recv_reply(self): self.sock.recv(IsA(int)).AndReturn('250 Ok\r\n') self.mox.ReplayAll() io = IO(self.sock) code, message = io.recv_reply() self.assertEqual('250', code) self.assertEqual('Ok', message)
def test_recv_nonutf8(self): self.sock.recv(IsA(int)).AndReturn(b'250 \xff\r\n') self.mox.ReplayAll() io = IO(self.sock) code, message = io.recv_reply() self.assertEqual('250', code) self.assertEqual('�', message)
def test_recv_reply_multiline(self): self.sock.recv(IsA(int)).AndReturn(b'250-One\r\n250 Two\r\n') self.mox.ReplayAll() io = IO(self.sock) code, message = io.recv_reply() self.assertEqual('250', code) self.assertEqual('One\r\nTwo', message)
def test_recv_command_nonutf8(self): self.sock.recv(IsA(int)).AndReturn(b'cmd\xffr\n') self.mox.ReplayAll() io = IO(self.sock) command, arg = io.recv_command() self.assertEqual(None, command) self.assertEqual(None, arg)
def test_recv_reply(self): self.sock.recv(IsA(int)).AndReturn(b'250 Ok\r\n') self.mox.ReplayAll() io = IO(self.sock) code, message = io.recv_reply() self.assertEqual('250', code) self.assertEqual('Ok', message)
def test_recv_utf8(self): self.sock.recv(IsA(int)).AndReturn(b'250 \xc3\xbf\r\n') self.mox.ReplayAll() io = IO(self.sock) code, message = io.recv_reply() self.assertEqual('250', code) self.assertEqual(u'\xff', message)
def test_recv_command_arg(self): self.sock.recv(IsA(int)).AndReturn(b'cmd arg \r\n') self.mox.ReplayAll() io = IO(self.sock) command, arg = io.recv_command() self.assertEqual('CMD', command) self.assertEqual('arg', arg)
def test_recv_command_arg(self): self.sock.recv(IsA(int)).AndReturn(b'cmd arg \r\n') self.mox.ReplayAll() io = IO(self.sock) command, arg = io.recv_command() self.assertEqual(b'CMD', command) self.assertEqual(b'arg', arg)
def test_recv_reply_multipart(self): self.sock.recv(IsA(int)).AndReturn('250 ') self.sock.recv(IsA(int)).AndReturn('Ok\r\n') self.mox.ReplayAll() io = IO(self.sock) code, message = io.recv_reply() assert_equal('250', code) assert_equal('Ok', message)
def test_recv(self): self.sock.recv(IsA(int)).AndReturn("\r\nthree\r\n") self.sock.recv(IsA(int)).AndReturn(".\r\nstuff\r\n") self.mox.ReplayAll() io = IO(self.sock) io.recv_buffer = "one\r\ntwo" dr = DataReader(io) self.assertEqual("one\r\ntwo\r\nthree\r\n", dr.recv())
def test_recv_line(self): self.sock.recv(IsA(int)).AndReturn(b'one') self.sock.recv(IsA(int)).AndReturn(b'\r\ntwo') self.mox.ReplayAll() io = IO(self.sock) line = io.recv_line() self.assertEqual(b'one', line) self.assertEqual(b'two', io.recv_buffer)
def test_recv(self): self.sock.recv(IsA(int)).AndReturn(b'\r\nthree\r\n') self.sock.recv(IsA(int)).AndReturn(b'.\r\nstuff\r\n') self.mox.ReplayAll() io = IO(self.sock) io.recv_buffer = b'one\r\ntwo' dr = DataReader(io) self.assertEqual(b'one\r\ntwo\r\nthree\r\n', dr.recv())
def setUp(self): super(TestSmtpAuth, self).setUp() self.sock = self.mox.CreateMock(SSLSocket) self.sock.fileno = lambda: -1 self.sock.getpeername = lambda: ('test', 0) self.io = IO(self.sock) self.make_msgid = email.utils.make_msgid = lambda: '<*****@*****.**>'
def test_return_all(self): io = IO(None) dr = DataReader(io) dr.lines = [b'one\r\n', b'two\r\n', b'.\r\n', b'three\r\n'] dr.EOD = 2 self.assertEqual(b'one\r\ntwo\r\n', dr.return_all()) self.assertEqual(b'three\r\n', io.recv_buffer)
def test_return_all(self): io = IO(None) dr = DataReader(io) dr.lines = ['one\r\n', 'two\r\n', '.\r\n', 'three\r\n'] dr.EOD = 2 assert_equal('one\r\ntwo\r\n', dr.return_all()) assert_equal('three\r\n', io.recv_buffer)
def test_plain(self): self.mox.ReplayAll() io = IO(self.sock) auth = FakeAuth(FakeSession(True)) identity = auth.server_attempt( io, 'PLAIN dGVzdHppZAB0ZXN0dXNlcgB0ZXN0cGFzc3dvcmQ=') self.assertEqual('testidentity', identity)
def __init__(self, socket, handlers, auth_class=None, tls=None, tls_immediately=False, tls_wrapper=None, command_timeout=None, data_timeout=None): self.handlers = handlers self.extensions = Extensions() self.have_mailfrom = None self.have_rcptto = None self.ehlo_as = None self.auth_result = None self.encrypted = False self.extensions.add('8BITMIME') self.extensions.add('PIPELINING') self.extensions.add('ENHANCEDSTATUSCODES') if tls and not tls_immediately: self.extensions.add('STARTTLS') if auth_class: self.extensions.add('AUTH', auth_class(self)) self.io = IO(socket, tls_wrapper) if tls: self.tls = tls.copy() self.tls.setdefault('server_side', True) else: self.tls = None self.tls_immediately = tls_immediately self.command_timeout = command_timeout self.data_timeout = data_timeout or command_timeout
def test_login(self): self.sock.sendall('334 UGFzc3dvcmQ6\r\n') self.sock.recv(IsA(int)).AndReturn('dGVzdHBhc3N3b3Jk\r\n') self.mox.ReplayAll() io = IO(self.sock) auth = FakeAuth(FakeSession(True)) identity = auth.server_attempt(io, 'LOGIN dGVzdHVzZXI=') self.assertEqual('testidentity', identity)
def test_crammd5_malformed(self): self.sock.sendall('334 PHRlc3RAZXhhbXBsZS5jb20+\r\n') self.sock.recv(IsA(int)).AndReturn('bWFsZm9ybWVk\r\n') self.mox.ReplayAll() io = IO(self.sock) auth = FakeAuth(FakeSession(True)) with self.assertRaises(ServerAuthError): auth.server_attempt(io, 'CRAM-MD5 dGVzdHVzZXI=')
def test_client_crammd5_bad_mech(self): self.sock.sendall('AUTH CRAM-MD5\r\n') self.sock.recv(IsA(int)).AndReturn('535 Nope!\r\n') self.mox.ReplayAll() io = IO(self.sock) reply = CramMd5.client_attempt(io, '*****@*****.**', 'asdf', None) self.assertEqual('535', reply.code) self.assertEqual('5.0.0 Nope!', reply.message)
def test_crammd5_badcreds(self): self.sock.sendall('334 PHRlc3RAZXhhbXBsZS5jb20+\r\n') self.sock.recv(IsA(int)).AndReturn('dGVzdHVzZXIgMTIzNDU2Nzg5MA==\r\n') self.mox.ReplayAll() io = IO(self.sock) auth = FakeAuth(FakeSession(True)) with self.assertRaises(CredentialsInvalidError): auth.server_attempt(io, 'CRAM-MD5 dGVzdHVzZXI=')
def test_plain_noarg(self): self.sock.sendall('334 \r\n') self.sock.recv( IsA(int)).AndReturn('dGVzdHppZAB0ZXN0dXNlcgB0ZXN0cGFzc3dvcmQ=\r\n') self.mox.ReplayAll() io = IO(self.sock) auth = FakeAuth(FakeSession(True)) identity = auth.server_attempt(io, 'PLAIN') self.assertEqual('testidentity', identity)
def test_crammd5(self): self.sock.sendall('334 PHRlc3RAZXhhbXBsZS5jb20+\r\n') self.sock.recv(IsA(int)).AndReturn( 'dGVzdHVzZXIgNDkzMzA1OGU2ZjgyOTRkZTE0NDJkMTYxOTI3ZGI5NDQ=\r\n') self.mox.ReplayAll() io = IO(self.sock) auth = FakeAuth(FakeSession(True)) identity = auth.server_attempt(io, 'CRAM-MD5 dGVzdHVzZXI=') self.assertEqual('testidentity', identity)
def test_client_plain(self): self.sock.sendall( 'AUTH PLAIN amtsAHRlc3RAZXhhbXBsZS5jb20AYXNkZg==\r\n') self.sock.recv(IsA(int)).AndReturn('235 Ok\r\n') self.mox.ReplayAll() io = IO(self.sock) reply = Plain.client_attempt(io, '*****@*****.**', 'asdf', 'jkl') self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message)
def test_plain_badcreds(self): self.mox.ReplayAll() io = IO(self.sock) auth = FakeAuth(FakeSession(True)) with self.assertRaises(CredentialsInvalidError): auth.server_attempt( io, 'PLAIN dGVzdHppZAB0ZXN0dXNlcgBiYWRwYXNzd29yZA==') with self.assertRaises(ServerAuthError): auth.server_attempt(io, 'PLAIN dGVzdGluZw==')
def test_client_xoauth2(self): self.sock.sendall( 'AUTH XOAUTH2 dXNlcj10ZXN0QGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyYXNkZgEB\r\n' ) self.sock.recv(IsA(int)).AndReturn('235 Ok\r\n') self.mox.ReplayAll() io = IO(self.sock) reply = OAuth2.client_attempt(io, '*****@*****.**', 'asdf', None) self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message)
def test_client_login_bad_username(self): self.sock.sendall('AUTH LOGIN\r\n') self.sock.recv(IsA(int)).AndReturn('334 VXNlcm5hbWU6\r\n') self.sock.sendall('dGVzdEBleGFtcGxlLmNvbQ==\r\n') self.sock.recv(IsA(int)).AndReturn('535 Nope!\r\n') self.mox.ReplayAll() io = IO(self.sock) reply = Login.client_attempt(io, '*****@*****.**', 'asdf', None) self.assertEqual('535', reply.code) self.assertEqual('5.0.0 Nope!', reply.message)
def test_plain_canceled(self): self.sock.sendall('334 \r\n') self.sock.recv(IsA(int)).AndReturn('*\r\n') self.mox.ReplayAll() io = IO(self.sock) auth = FakeAuth(FakeSession(True)) with self.assertRaises(AuthenticationCanceled): auth.server_attempt(io, 'PLAIN') with self.assertRaises(AuthenticationCanceled): auth.server_attempt(io, 'PLAIN *')
def test_recv_piece(self): self.sock.recv(IsA(int)).AndReturn(b'one\r\ntwo') self.sock.recv(IsA(int)).AndReturn(b'\r\nthree\r\n.\r\nstuff\r\n') self.mox.ReplayAll() dr = DataReader(IO(self.sock)) self.assertTrue(dr.recv_piece()) self.assertFalse(dr.recv_piece()) self.assertEqual([b'one\r\n', b'two\r\n', b'three\r\n', b'.\r\n', b'stuff\r\n', b''], dr.lines) self.assertEqual(3, dr.EOD) self.assertEqual(5, dr.i)
def test_recv_piece(self): self.sock.recv(IsA(int)).AndReturn('one\r\ntwo') self.sock.recv(IsA(int)).AndReturn('\r\nthree\r\n.\r\nstuff\r\n') self.mox.ReplayAll() dr = DataReader(IO(self.sock)) assert_true(dr.recv_piece()) assert_false(dr.recv_piece()) assert_equal(['one\r\n', 'two\r\n', 'three\r\n', '.\r\n', 'stuff\r\n', ''], dr.lines) assert_equal(3, dr.EOD) assert_equal(5, dr.i)
def test_client_crammd5(self): self.sock.sendall('AUTH CRAM-MD5\r\n') self.sock.recv(IsA(int)).AndReturn('334 dGVzdCBjaGFsbGVuZ2U=\r\n') self.sock.sendall( 'dGVzdEBleGFtcGxlLmNvbSA1Yzk1OTBjZGE3ZTgxMDY5Mzk2ZjhiYjlkMzU1MzE1Yg==\r\n' ) self.sock.recv(IsA(int)).AndReturn('235 Ok\r\n') self.mox.ReplayAll() io = IO(self.sock) reply = CramMd5.client_attempt(io, '*****@*****.**', 'asdf', None) self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message)
def test_client_login(self): self.sock.sendall('AUTH LOGIN\r\n') self.sock.recv(IsA(int)).AndReturn('334 VXNlcm5hbWU6\r\n') self.sock.sendall('dGVzdEBleGFtcGxlLmNvbQ==\r\n') self.sock.recv(IsA(int)).AndReturn('334 UGFzc3dvcmQ6\r\n') self.sock.sendall('YXNkZg==\r\n') self.sock.recv(IsA(int)).AndReturn('235 Ok\r\n') self.mox.ReplayAll() io = IO(self.sock) reply = Login.client_attempt(io, '*****@*****.**', 'asdf', None) self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message)
def test_client_xoauth2_error(self): self.sock.sendall( 'AUTH XOAUTH2 dXNlcj10ZXN0QGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyYXNkZgEB\r\n' ) self.sock.recv(IsA(int)).AndReturn( '334 eyJzdGF0dXMiOiI0MDEiLCJzY2hlbWVzIjoiYmVhcmVyIG1hYyIsInNjb3BlIjoiaHR0cHM6Ly9tYWlsLmdvb2dsZS5jb20vIn0K\r\n' ) self.sock.sendall('\r\n') self.sock.recv(IsA(int)).AndReturn('535 Nope!\r\n') self.mox.ReplayAll() io = IO(self.sock) reply = OAuth2.client_attempt(io, '*****@*****.**', 'asdf', None) self.assertEqual('535', reply.code) self.assertEqual('5.0.0 Nope!', reply.message)
def test_from_recv_buffer(self): io = IO(None) io.recv_buffer = b'test\r\ndata' dr = DataReader(io) dr.from_recv_buffer() self.assertEqual([b'test\r\n', b'data'], dr.lines)
def test_buffered_send(self): self.mox.ReplayAll() io = IO(self.sock) io.buffered_send(b'some data') self.assertEqual(b'some data', io.send_buffer.getvalue())
def test_send_command_nonascii(self): self.mox.ReplayAll() io = IO(self.sock) io.send_command(b'CMD\xff') self.assertEqual(b'CMD\xff\r\n', io.send_buffer.getvalue())
def test_from_recv_buffer(self): io = IO(None) io.recv_buffer = "test\r\ndata" dr = DataReader(io) dr.from_recv_buffer() self.assertEqual(["test\r\n", "data"], dr.lines)
def test_send_command(self): self.mox.ReplayAll() io = IO(self.sock) io.send_command('CMD') assert_equal('CMD\r\n', io.send_buffer.getvalue())
class Server(object): """Class that implements an SMTP server given a connected socket. This object has an ``extensions`` attribute that is an |Extensions| object that contains the SMTP extensions the server supports. :param socket: Connected socket for the session. :type socket: :class:`gevent.socket.socket` :param handlers: Object with methods that will be called when corresponding SMTP commands are received. These methods can modify the |Reply| before the command response is sent. :param auth_class: Optional |Auth| sub-class to enable authentication. :param tls: Optional dictionary of TLS settings passed directly as keyword arguments to :class:`gevent.ssl.SSLSocket`. :param tls_immediately: If True, the socket will be encrypted immediately. :param tls_wrapper: Optional function that takes a socket and the ``tls`` dictionary, creates a new encrypted socket, performs the TLS handshake, and returns it. The default uses :class:`~gevent.ssl.SSLSocket`. :type tls_immediately: True or False :param command_timeout: Optional timeout waiting for a command to be sent from the client. :param data_timeout: Optional timeout waiting for data to be sent from the client. """ def __init__(self, socket, handlers, auth_class=None, tls=None, tls_immediately=False, tls_wrapper=None, command_timeout=None, data_timeout=None): self.handlers = handlers self.extensions = Extensions() self.have_mailfrom = None self.have_rcptto = None self.ehlo_as = None self.auth_result = None self.encrypted = False self.extensions.add('8BITMIME') self.extensions.add('PIPELINING') self.extensions.add('ENHANCEDSTATUSCODES') if tls and not tls_immediately: self.extensions.add('STARTTLS') if auth_class: self.extensions.add('AUTH', auth_class(self)) self.io = IO(socket, tls_wrapper) if tls: self.tls = tls.copy() self.tls.setdefault('server_side', True) else: self.tls = None self.tls_immediately = tls_immediately self.command_timeout = command_timeout self.data_timeout = data_timeout or command_timeout def _recv_command(self): timeout = Timeout(self.command_timeout) timeout.start() try: return self.io.recv_command() finally: timeout.cancel() def _get_message_data(self): max_size = self.extensions.getparam('SIZE', filter=int) reader = DataReader(self.io, max_size) err = None timeout = Timeout(self.data_timeout) timeout.start() try: data = reader.recv() except ConnectionLost: raise except SmtpError, e: data = None err = e finally:
def test_send_reply(self): self.mox.ReplayAll() io = IO(self.sock) io.send_reply(Reply('100', 'Ok')) self.assertEqual(b'100 Ok\r\n', io.send_buffer.getvalue())
def test_flush_send(self): self.sock.sendall(b'some data') self.mox.ReplayAll() io = IO(self.sock) io.buffered_send(b'some data') io.flush_send()
def test_buffered_recv(self): self.sock.recv(IsA(int)).AndReturn(b'some data') self.mox.ReplayAll() io = IO(self.sock) io.buffered_recv() self.assertEqual(b'some data', io.recv_buffer)
def test_send_reply_multiline(self): self.mox.ReplayAll() io = IO(self.sock) io.send_reply(Reply('100', 'One\r\nTwo')) self.assertEqual(b'100-One\r\n100 Two\r\n', io.send_buffer.getvalue())
def test_recv_reply_bad_multiline(self): self.sock.recv(IsA(int)).AndReturn(b'250-One\r\n500 Two\r\n') self.mox.ReplayAll() io = IO(self.sock) self.assertRaises(BadReply, io.recv_reply)
def test_recv_reply_bad_code(self): self.sock.recv(IsA(int)).AndReturn(b'bad\r\n') self.mox.ReplayAll() io = IO(self.sock) self.assertRaises(BadReply, io.recv_reply)
def test_send_reply_nonascii(self): self.mox.ReplayAll() io = IO(self.sock) io.send_reply(Reply('100', u'Ok\xff')) self.assertEqual(b'100 Ok\xc3\xbf\r\n', io.send_buffer.getvalue())
def test_flush_send_empty(self): self.mox.ReplayAll() io = IO(self.sock) io.flush_send()
def test_send_reply_nonascii(self): # be strict on what we send self.mox.ReplayAll() io = IO(self.sock) io.send_reply(Reply('100', 'Oké')) self.assertEqual(b'100 Ok?\r\n', io.send_buffer.getvalue())
class Server(object): """Class that implements an SMTP server given a connected socket. This object has an ``extensions`` attribute that is an |Extensions| object that contains the SMTP extensions the server supports. :param socket: Connected socket for the session. :type socket: :class:`gevent.socket.socket` :param handlers: Object with methods that will be called when corresponding SMTP commands are received. These methods can modify the |Reply| before the command response is sent. :param auth_class: Optional |Auth| sub-class to enable authentication. :param tls: Optional dictionary of TLS settings passed directly as keyword arguments to :class:`gevent.ssl.SSLSocket`. :param tls_immediately: If True, the socket will be encrypted immediately. :param tls_wrapper: Optional function that takes a socket and the ``tls`` dictionary, creates a new encrypted socket, performs the TLS handshake, and returns it. The default uses :class:`~gevent.ssl.SSLSocket`. :type tls_immediately: True or False :param command_timeout: Optional timeout waiting for a command to be sent from the client. :param data_timeout: Optional timeout waiting for data to be sent from the client. """ def __init__(self, socket, handlers, auth_class=None, tls=None, tls_immediately=False, tls_wrapper=None, command_timeout=None, data_timeout=None): self.handlers = handlers self.extensions = Extensions() self.have_mailfrom = None self.have_rcptto = None self.ehlo_as = None self.auth_result = None self.encrypted = False self.extensions.add('8BITMIME') self.extensions.add('PIPELINING') self.extensions.add('ENHANCEDSTATUSCODES') if tls and not tls_immediately: self.extensions.add('STARTTLS') if auth_class: self.extensions.add('AUTH', auth_class(self)) self.io = IO(socket, tls_wrapper) if tls: self.tls = tls.copy() self.tls.setdefault('server_side', True) else: self.tls = None self.tls_immediately = tls_immediately self.command_timeout = command_timeout self.data_timeout = data_timeout or command_timeout def _recv_command(self): timeout = Timeout(self.command_timeout) timeout.start() try: return self.io.recv_command() finally: timeout.cancel() def _get_message_data(self): max_size = self.extensions.getparam('SIZE', filter=int) reader = DataReader(self.io, max_size) err = None timeout = Timeout(self.data_timeout) timeout.start() try: data = reader.recv() except ConnectionLost: raise except SmtpError as e: data = None err = e finally: timeout.cancel() reply = Reply('250', '2.6.0 Message Accepted for Delivery') self._call_custom_handler('HAVE_DATA', reply, data, err) self.io.send_reply(reply) self.io.flush_send() self.have_mailfrom = None self.have_rcptto = None def _encrypt_session(self): if not self.io.encrypt_socket(self.tls): return False self._call_custom_handler('TLSHANDSHAKE') self.encrypted = True return True def _handle_command(self, which, arg): method = '_command_'+which if hasattr(self, method): return getattr(self, method)(arg) else: return self._command_custom(which, arg) def _call_custom_handler(self, which, *args): if hasattr(self.handlers, which): return getattr(self.handlers, which)(*args) def handle(self): """Runs through the SMTP session, receiving commands, calling handlers, and sending responses. :raises: :class:`~slimta.smtp.ConnectionLost` or unhandled exceptions. """ if self.tls and self.tls_immediately: if not self._encrypt_session(): tls_failure.send(self.io, flush=True) return command, arg = 'BANNER_', None while True: try: if command: self._handle_command(command, arg) else: unknown_command.send(self.io) except StopIteration: break except ConnectionLost: raise except TimeoutHappened: timed_out.send(self.io) self.io.flush_send() break except Exception as e: unhandled_error.send(self.io) raise finally: self.io.flush_send() try: command, arg = self._recv_command() except TimeoutHappened: timed_out.send(self.io) self.io.flush_send() break def _command_BANNER_(self, arg): reply = Reply('220', 'ESMTP server') reply.enhanced_status_code = False self._call_custom_handler('BANNER_', reply) reply.send(self.io) if reply.code != '220': self._call_custom_handler('CLOSE') raise StopIteration() def _command_EHLO(self, ehlo_as): reply = Reply('250', 'Hello {0}'.format(ehlo_as)) reply.enhanced_status_code = False self._call_custom_handler('EHLO', reply, ehlo_as) # Add extension list to message, if successful. if reply.code == '250': reply.message = self.extensions.build_string(reply.message) self.have_mailfrom = None self.have_rcptto = None self.ehlo_as = ehlo_as reply.send(self.io) def _command_HELO(self, ehlo_as): reply = Reply('250', 'Hello {0}'.format(ehlo_as)) reply.enhanced_status_code = False self._call_custom_handler('HELO', reply, ehlo_as) if reply.code == '250': self.have_mailfrom = None self.have_rcptto = None self.ehlo_as = ehlo_as self.extensions.reset() reply.send(self.io) def _command_STARTTLS(self, arg): if 'STARTTLS' not in self.extensions: unknown_command.send(self.io) return if arg: bad_arguments.send(self.io) return if not self.ehlo_as: bad_sequence.send(self.io) return reply = Reply('220', '2.7.0 Go ahead') self._call_custom_handler('STARTTLS', reply, self.extensions) reply.send(self.io, flush=True) if reply.code == '220': if not self._encrypt_session(): tls_failure.send(self.io) raise StopIteration() self.ehlo_as = None self.extensions.drop('STARTTLS') def _command_AUTH(self, arg): if 'AUTH' not in self.extensions: unknown_command.send(self.io) return if not self.ehlo_as or self.auth_result or self.have_mailfrom: bad_sequence.send(self.io) return auth = self.extensions.getparam('AUTH') reply = Reply('235', '2.7.0 Authentication successful') try: result = auth.server_attempt(self.io, arg) except ServerAuthError as e: reply.copy(e.reply) result = None reply.send(self.io) self._call_custom_handler('AUTH', reply, result) if reply.code == '235': self.auth_result = result def _command_MAIL(self, arg): match = from_pattern.match(arg) if not match: bad_arguments.send(self.io) return start = match.end(0) end = find_outside_quotes(arg, '>', start) if end == -1: bad_arguments.send(self.io) return address = arg[start:end] if not self.ehlo_as: bad_sequence.send(self.io) return if self.have_mailfrom: bad_sequence.send(self.io) return match = size_pattern.search(arg, end+1) if match: try: size = int(match.group(1)) except ValueError: bad_arguments.send(self.io) return max_size = self.extensions.getparam('SIZE', filter=int) if max_size is not None: if size > max_size: m = '5.3.4 Message size exceeds {0} limit'.format(max_size) Reply('552', m).send(self.io) return else: unknown_parameter.send(self.io) return reply = Reply('250', '2.1.0 Sender <{0}> Ok'.format(address)) self._call_custom_handler('MAIL', reply, address) reply.send(self.io) self.have_mailfrom = self.have_mailfrom or (reply.code == '250') def _command_RCPT(self, arg): match = to_pattern.match(arg) if not match: bad_arguments.send(self.io) return start = match.end(0) end = find_outside_quotes(arg, '>', start) if end == -1: bad_arguments.send(self.io) return address = arg[start:end] if not self.have_mailfrom: bad_sequence.send(self.io) return reply = Reply('250', '2.1.5 Recipient <{0}> Ok'.format(address)) self._call_custom_handler('RCPT', reply, address) reply.send(self.io) self.have_rcptto = self.have_rcptto or (reply.code == '250') def _command_DATA(self, arg): if arg: bad_arguments.send(self.io) return if not self.have_mailfrom or not self.have_rcptto: bad_sequence.send(self.io) return reply = Reply('354', 'Start mail input; end with <CRLF>.<CRLF>') self._call_custom_handler('DATA', reply) reply.send(self.io) self.io.flush_send() if reply.code == '354': self._get_message_data() def _command_RSET(self, arg): if arg: bad_arguments.send(self.io) return reply = Reply('250', 'Ok') self._call_custom_handler('RSET', reply) if reply.code == '250': self.have_mailfrom = None self.have_rcptto = None reply.send(self.io) def _command_NOOP(self, arg): reply = Reply('250', 'Ok') self._call_custom_handler('NOOP', reply) reply.send(self.io) def _command_QUIT(self, arg): if arg: bad_arguments.send(self.io) return reply = Reply('221', 'Bye') self._call_custom_handler('QUIT', reply) reply.send(self.io) if reply.code == '221': self._call_custom_handler('CLOSE') raise StopIteration() def _command_custom(self, command, arg): reply = Reply() reply.copy(unknown_command) self._call_custom_handler(command, reply, arg, self) reply.send(self.io)