123 lines
2.8 KiB
Python
123 lines
2.8 KiB
Python
# Copyright (c) Twisted Matrix Laboratories.
|
|
# See LICENSE for details.
|
|
|
|
"""
|
|
Credential managers for L{twisted.mail}.
|
|
"""
|
|
|
|
from __future__ import absolute_import, division
|
|
|
|
import hmac
|
|
import hashlib
|
|
|
|
from zope.interface import implementer
|
|
|
|
from twisted.cred import credentials
|
|
from twisted.python.compat import nativeString
|
|
from twisted.mail._except import IllegalClientResponse
|
|
from twisted.mail.interfaces import IClientAuthentication, IChallengeResponse
|
|
|
|
|
|
@implementer(IClientAuthentication)
|
|
class CramMD5ClientAuthenticator:
|
|
def __init__(self, user):
|
|
self.user = user
|
|
|
|
|
|
def getName(self):
|
|
return b"CRAM-MD5"
|
|
|
|
|
|
def challengeResponse(self, secret, chal):
|
|
response = hmac.HMAC(secret, chal, digestmod=hashlib.md5).hexdigest()
|
|
return self.user + b' ' + response.encode('ascii')
|
|
|
|
|
|
|
|
@implementer(IClientAuthentication)
|
|
class LOGINAuthenticator:
|
|
def __init__(self, user):
|
|
self.user = user
|
|
self.challengeResponse = self.challengeUsername
|
|
|
|
|
|
def getName(self):
|
|
return b"LOGIN"
|
|
|
|
|
|
def challengeUsername(self, secret, chal):
|
|
# Respond to something like "Username:"
|
|
self.challengeResponse = self.challengeSecret
|
|
return self.user
|
|
|
|
|
|
def challengeSecret(self, secret, chal):
|
|
# Respond to something like "Password:"
|
|
return secret
|
|
|
|
|
|
|
|
@implementer(IClientAuthentication)
|
|
class PLAINAuthenticator:
|
|
def __init__(self, user):
|
|
self.user = user
|
|
|
|
|
|
def getName(self):
|
|
return b"PLAIN"
|
|
|
|
|
|
def challengeResponse(self, secret, chal):
|
|
return b'\0' + self.user + b'\0' + secret
|
|
|
|
|
|
|
|
@implementer(IChallengeResponse)
|
|
class LOGINCredentials(credentials.UsernamePassword):
|
|
def __init__(self):
|
|
self.challenges = [b'Password\0', b'User Name\0']
|
|
self.responses = [b'password', b'username']
|
|
credentials.UsernamePassword.__init__(self, None, None)
|
|
|
|
|
|
def getChallenge(self):
|
|
return self.challenges.pop()
|
|
|
|
|
|
def setResponse(self, response):
|
|
setattr(self, nativeString(self.responses.pop()), response)
|
|
|
|
|
|
def moreChallenges(self):
|
|
return bool(self.challenges)
|
|
|
|
|
|
|
|
@implementer(IChallengeResponse)
|
|
class PLAINCredentials(credentials.UsernamePassword):
|
|
def __init__(self):
|
|
credentials.UsernamePassword.__init__(self, None, None)
|
|
|
|
|
|
def getChallenge(self):
|
|
return b''
|
|
|
|
|
|
def setResponse(self, response):
|
|
parts = response.split(b'\0')
|
|
if len(parts) != 3:
|
|
raise IllegalClientResponse(
|
|
"Malformed Response - wrong number of parts")
|
|
useless, self.username, self.password = parts
|
|
|
|
|
|
def moreChallenges(self):
|
|
return False
|
|
|
|
|
|
__all__ = [
|
|
"CramMD5ClientAuthenticator",
|
|
"LOGINCredentials", "LOGINAuthenticator",
|
|
"PLAINCredentials", "PLAINAuthenticator",
|
|
]
|