def __init__(self, socket, handlers, auth=False, tls=None, tls_immediately=False, tls_wrapper=None, command_timeout=None, data_timeout=None): self.handlers = handlers self.extensions = Extensions() self.io = IO(socket, tls_wrapper) self.bannered = False self.have_mailfrom = None self.have_rcptto = None self.ehlo_as = None self.authed = False self.extensions.add('8BITMIME') self.extensions.add('PIPELINING') self.extensions.add('ENHANCEDSTATUSCODES') self.extensions.add('SMTPUTF8') if tls and not tls_immediately: self.extensions.add('STARTTLS') if auth: if isinstance(auth, list): auth_obj = SASLAuth(auth) else: auth_obj = SASLAuth() auth_session = AuthSession(auth_obj, self.io) self.extensions.add('AUTH', auth_session) self.tls = validate_tls(tls, server_side=True) self.tls_immediately = tls_immediately self.command_timeout = command_timeout self.data_timeout = data_timeout or command_timeout
def test_crammd5_malformed(self): self.sock.sendall(b'334 PHRlc3RAZXhhbXBsZS5jb20+\r\n') self.sock.recv(IsA(int)).AndReturn(b'bWFsZm9ybWVk\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) with self.assertRaises(ServerAuthError): auth.server_attempt('CRAM-MD5')
def auth(self, authcid, secret, authzid=None, mechanism=None): """Negotiates authentication for the current SMTP session. This transaction may involve several back-and-forth packets to the server, depending on the SASL mechanism used, and this function will only return once all have completed. :param authcid: The authentication identity, usually the username. :param secret: The secret (i.e. password) string to send for the given authentication and authorization identities. :param authzid: The authorization identity, if applicable. :param mechanism: SASL mechanism name to use instead of the best available. :type mechanism: str :returns: |Reply| object populated with the response. """ self._flush_pipeline() if 'AUTH' not in self.extensions: return unknown_command advertised = [self._encode(mech_name) for mech_name in self.extensions.getparam('AUTH').split()] auth = AuthSession(SASLAuth(advertised), self.io) if not mechanism and auth.client_mechanisms: mechanism = auth.client_mechanisms[0].name return auth.client_attempt(authcid, secret, authzid, mechanism)
def test_plain(self): self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) result = auth.server_attempt(b'PLAIN dGVzdHppZAB0ZXN0dXNlcgB0ZXN0cGFzc3dvcmQ=') self.assertEqual(u'testuser', result.authcid) self.assertEqual(u'testpassword', result.secret) self.assertEqual(u'testzid', result.authzid)
def test_client_bad_mech(self): self.sock.sendall(b'AUTH LOGIN\r\n') self.sock.recv(IsA(int)).AndReturn(b'535 Nope!\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) reply = auth.client_attempt('*****@*****.**', 'asdf', None, 'LOGIN') self.assertEqual('535', reply.code) self.assertEqual('5.0.0 Nope!', reply.message)
def test_client_xoauth2(self): self.sock.sendall(b'AUTH XOAUTH2 dXNlcj10ZXN0QGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyYXNkZgEB\r\n') self.sock.recv(IsA(int)).AndReturn(b'235 Ok\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth([b'XOAUTH2']), self.io) reply = auth.client_attempt(u'*****@*****.**', u'asdf', None, b'XOAUTH2') self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message)
def test_client_plain(self): self.sock.sendall( b'AUTH PLAIN amtsAHRlc3RAZXhhbXBsZS5jb20AYXNkZg==\r\n') self.sock.recv(IsA(int)).AndReturn(b'235 Ok\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) reply = auth.client_attempt('*****@*****.**', 'asdf', 'jkl', 'PLAIN') self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message)
def test_login(self): self.sock.sendall(b'334 UGFzc3dvcmQ6\r\n') self.sock.recv(IsA(int)).AndReturn(b'dGVzdHBhc3N3b3Jk\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) result = auth.server_attempt('LOGIN dGVzdHVzZXI=') self.assertEqual(u'testuser', result.authcid) self.assertEqual(u'testpassword', result.secret) self.assertEqual(None, result.authzid)
def test_plain_noarg(self): self.sock.sendall(b'334 \r\n') self.sock.recv(IsA(int)).AndReturn(b'dGVzdHppZAB0ZXN0dXNlcgB0ZXN0cGFzc3dvcmQ=\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) result = auth.server_attempt(b'PLAIN') self.assertEqual(u'testuser', result.authcid) self.assertEqual(u'testpassword', result.secret) self.assertEqual(u'testzid', result.authzid)
def test_plain_canceled(self): self.sock.sendall(b'334 \r\n') self.sock.recv(IsA(int)).AndReturn(b'*\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) with self.assertRaises(AuthenticationCanceled): auth.server_attempt('PLAIN') with self.assertRaises(AuthenticationCanceled): auth.server_attempt('PLAIN *')
def test_client_login_bad_username(self): self.sock.sendall(b'AUTH LOGIN\r\n') self.sock.recv(IsA(int)).AndReturn(b'334 VXNlcm5hbWU6\r\n') self.sock.sendall(b'dGVzdEBleGFtcGxlLmNvbQ==\r\n') self.sock.recv(IsA(int)).AndReturn(b'535 Nope!\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) reply = auth.client_attempt('*****@*****.**', 'asdf', None, 'LOGIN') self.assertEqual('535', reply.code) self.assertEqual('5.0.0 Nope!', reply.message)
def test_crammd5(self): self.sock.sendall(b'334 PHRlc3RAZXhhbXBsZS5jb20+\r\n') self.sock.recv(IsA(int)).AndReturn(b'dGVzdHVzZXIgNDkzMzA1OGU2ZjgyOTRkZTE0NDJkMTYxOTI3ZGI5NDQ=\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) result = auth.server_attempt(b'CRAM-MD5') self.assertEqual(u'testuser', result.authcid) self.assertTrue(result.check_secret(u'testpassword')) self.assertFalse(result.check_secret(u'testwrong')) self.assertEqual(None, result.authzid)
def test_client_xoauth2_error(self): self.sock.sendall(b'AUTH XOAUTH2 dXNlcj10ZXN0QGV4YW1wbGUuY29tAWF1dGg9QmVhcmVyYXNkZgEB\r\n') self.sock.recv(IsA(int)).AndReturn(b'334 eyJzdGF0dXMiOiI0MDEiLCJzY2hlbWVzIjoiYmVhcmVyIG1hYyIsInNjb3BlIjoiaHR0cHM6Ly9tYWlsLmdvb2dsZS5jb20vIn0K\r\n') self.sock.sendall(b'\r\n') self.sock.recv(IsA(int)).AndReturn(b'535 Nope!\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth([b'XOAUTH2']), self.io) reply = auth.client_attempt(u'*****@*****.**', u'asdf', None, b'XOAUTH2') self.assertEqual('535', reply.code) self.assertEqual('5.0.0 Nope!', reply.message)
def test_client_crammd5(self): self.sock.sendall(b'AUTH CRAM-MD5\r\n') self.sock.recv(IsA(int)).AndReturn(b'334 dGVzdCBjaGFsbGVuZ2U=\r\n') self.sock.sendall(b'dGVzdEBleGFtcGxlLmNvbSA1Yzk1OTBjZGE3ZTgxMDY5Mzk2ZjhiYjlkMzU1MzE1Yg==\r\n') self.sock.recv(IsA(int)).AndReturn(b'235 Ok\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) reply = auth.client_attempt(u'*****@*****.**', u'asdf', None, b'CRAM-MD5') self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message)
def test_client_login(self): self.sock.sendall(b'AUTH LOGIN\r\n') self.sock.recv(IsA(int)).AndReturn(b'334 VXNlcm5hbWU6\r\n') self.sock.sendall(b'dGVzdEBleGFtcGxlLmNvbQ==\r\n') self.sock.recv(IsA(int)).AndReturn(b'334 UGFzc3dvcmQ6\r\n') self.sock.sendall(b'YXNkZg==\r\n') self.sock.recv(IsA(int)).AndReturn(b'235 Ok\r\n') self.mox.ReplayAll() auth = AuthSession(SASLAuth(), self.io) reply = auth.client_attempt('*****@*****.**', 'asdf', None, 'LOGIN') self.assertEqual('235', reply.code) self.assertEqual('2.0.0 Ok', reply.message)
def test_auth(self): self.sock.sendall(b'220 ESMTP server\r\n') self.sock.recv(IsA(int)).AndReturn(b'EHLO there\r\n') self.sock.sendall(b'250-Hello there\r\n250 AUTH PLAIN\r\n') self.sock.recv(IsA(int)).AndReturn( b'AUTH PLAIN dGVzdHppZAB0ZXN0dXNlcgB0ZXN0cGFzc3dvcmQ=\r\n') self.sock.sendall(b'235 2.7.0 Authentication successful\r\n') self.sock.recv(IsA(int)).AndReturn(b'QUIT\r\n') self.sock.sendall(b'221 2.0.0 Bye\r\n') self.mox.ReplayAll() s = Server(self.sock, None) s.extensions.reset() s.extensions.add('AUTH', AuthSession(SASLAuth([b'PLAIN']), s.io)) s.handle() self.assertTrue(s.authed)
def auth(self, authcid, secret, authzid=None, mechanism='PLAIN'): """Negotiates authentication for the current SMTP session. This transaction may involve several back-and-forth packets to the server, depending on the SASL mechanism used, and this function will only return once all have completed. :param authcid: The authentication identity, usually the username. :param secret: The secret (i.e. password) string to send for the given authentication and authorization identities. :param authzid: The authorization identity, if applicable. :param mechanism: SASL mechanism name to use for authentication. :type mechanism: str :returns: |Reply| object populated with the response. """ self._flush_pipeline() auth = AuthSession(SASLAuth(), self.io) return auth.client_attempt(authcid, secret, authzid, mechanism)
def insecure_auth(self) -> SASLAuth: return SASLAuth.plaintext()
def initial_auth(self) -> SASLAuth: if self._reject_insecure_auth: return SASLAuth([]) else: return self.insecure_auth
def test_bytes(self): auth = AuthSession(SASLAuth(), self.io) self.assertEqual('PLAIN LOGIN CRAM-MD5', str(auth))
def test_str(self): auth = AuthSession(SASLAuth(), self.io) self.assertEqual('CRAM-MD5 LOGIN PLAIN', str(auth))
def test_invalid_mechanism(self): auth = AuthSession(SASLAuth(), self.io) with self.assertRaises(InvalidMechanismError): auth.server_attempt('TEST') with self.assertRaises(InvalidMechanismError): auth.server_attempt('B@D')