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)
Example #2
0
 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)
Example #3
0
 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)
Example #4
0
 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)
Example #5
0
 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_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_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(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)
Example #10
0
 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_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_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())
Example #15
0
 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 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)
Example #18
0
 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)
Example #21
0
 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)
Example #22
0
    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
Example #23
0
 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)
Example #24
0
 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=')
Example #25
0
 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)
Example #26
0
 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=')
Example #27
0
 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)
Example #28
0
 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)
Example #29
0
 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)
Example #30
0
 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==')
Example #31
0
 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)
Example #32
0
 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)
Example #33
0
 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)
Example #36
0
 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)
Example #37
0
 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)
Example #38
0
 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())
Example #44
0
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())
Example #49
0
 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)
Example #50
0
 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()
Example #53
0
 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())
Example #54
0
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)