Update and integrate Suelta.
This commit is contained in:
@@ -6,12 +6,11 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from sleekxmpp.thirdparty import suelta
|
||||
from sleekxmpp.thirdparty.suelta.exceptions import SASLCancelled, SASLError
|
||||
from sleekxmpp.thirdparty.suelta.exceptions import SASLPrepFailure
|
||||
|
||||
from sleekxmpp.util import sasl
|
||||
from sleekxmpp.util.stringprep_profiles import StringPrepError
|
||||
from sleekxmpp.stanza import StreamFeatures
|
||||
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
@@ -31,7 +30,15 @@ class FeatureMechanisms(BasePlugin):
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'use_mech': None,
|
||||
'use_mechs': None,
|
||||
'min_mech': None,
|
||||
'sasl_callback': None,
|
||||
'security_callback': None,
|
||||
'encrypted_plain': True,
|
||||
'unencrypted_plain': False,
|
||||
'unencrypted_digest': False,
|
||||
'unencrypted_cram': False,
|
||||
'unencrypted_scram': True,
|
||||
'order': 100
|
||||
}
|
||||
|
||||
@@ -39,34 +46,13 @@ class FeatureMechanisms(BasePlugin):
|
||||
if not self.use_mech and not self.xmpp.boundjid.user:
|
||||
self.use_mech = 'ANONYMOUS'
|
||||
|
||||
def tls_active():
|
||||
return 'starttls' in self.xmpp.features
|
||||
|
||||
def basic_callback(mech, values):
|
||||
creds = self.xmpp.credentials
|
||||
for value in values:
|
||||
if value == 'username':
|
||||
values['username'] = self.xmpp.boundjid.user
|
||||
elif value == 'password':
|
||||
values['password'] = creds['password']
|
||||
elif value == 'email':
|
||||
jid = self.xmpp.boundjid.bare
|
||||
values['email'] = creds.get('email', jid)
|
||||
elif value in creds:
|
||||
values[value] = creds[value]
|
||||
mech.fulfill(values)
|
||||
|
||||
if self.sasl_callback is None:
|
||||
self.sasl_callback = basic_callback
|
||||
self.sasl_callback = self._default_credentials
|
||||
|
||||
if self.security_callback is None:
|
||||
self.security_callback = self._default_security
|
||||
|
||||
self.mech = None
|
||||
self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp',
|
||||
username=self.xmpp.boundjid.user,
|
||||
sec_query=suelta.sec_query_allow,
|
||||
request_values=self.sasl_callback,
|
||||
tls_active=tls_active,
|
||||
mech=self.use_mech)
|
||||
|
||||
self.mech_list = set()
|
||||
self.attempted_mechs = set()
|
||||
|
||||
@@ -99,6 +85,44 @@ class FeatureMechanisms(BasePlugin):
|
||||
restart=True,
|
||||
order=self.order)
|
||||
|
||||
def _default_credentials(self, required_values, optional_values):
|
||||
creds = self.xmpp.credentials
|
||||
result = {}
|
||||
values = required_values.union(optional_values)
|
||||
for value in values:
|
||||
if value == 'username':
|
||||
result[value] = self.xmpp.boundjid.user
|
||||
elif value == 'password':
|
||||
result[value] = creds['password']
|
||||
elif value == 'email':
|
||||
jid = self.xmpp.boundjid.bare
|
||||
result[value] = creds.get('email', jid)
|
||||
elif value == 'channel_binding':
|
||||
if sys.version_info >= (3, 3):
|
||||
result[value] = self.xmpp.socket.channel_binding()
|
||||
else:
|
||||
result[value] = None
|
||||
elif value == 'host':
|
||||
result[value] = self.xmpp.boundjid.domain
|
||||
elif value == 'realm':
|
||||
result[value] = self.xmpp.boundjid.domain
|
||||
elif value == 'service-name':
|
||||
result[value] = self.xmpp.address[0]
|
||||
elif value == 'service':
|
||||
result[value] = 'xmpp'
|
||||
elif value in creds:
|
||||
result[value] = creds[value]
|
||||
return result
|
||||
|
||||
def _default_security(self, values):
|
||||
result = {}
|
||||
for value in values:
|
||||
if value == 'encrypted':
|
||||
result[value] = 'starttls' in self.xmpp.features
|
||||
else:
|
||||
result[value] = self.config.get(value, False)
|
||||
return result
|
||||
|
||||
def _handle_sasl_auth(self, features):
|
||||
"""
|
||||
Handle authenticating using SASL.
|
||||
@@ -111,37 +135,61 @@ class FeatureMechanisms(BasePlugin):
|
||||
# server has incorrectly offered it again.
|
||||
return False
|
||||
|
||||
if not self.use_mech:
|
||||
self.mech_list = set(features['mechanisms'])
|
||||
else:
|
||||
self.mech_list = set([self.use_mech])
|
||||
enforce_limit = False
|
||||
limited_mechs = self.use_mechs
|
||||
|
||||
if limited_mechs is None:
|
||||
limited_mechs = set()
|
||||
elif limited_mechs and not isinstance(limited_mechs, set):
|
||||
limited_mechs = set(limited_mechs)
|
||||
enforce_limit = True
|
||||
|
||||
if self.use_mech:
|
||||
limited_mechs.add(self.use_mech)
|
||||
enforce_limit = True
|
||||
|
||||
if enforce_limit:
|
||||
self.use_mechs = limited_mechs
|
||||
|
||||
self.mech_list = set(features['mechanisms'])
|
||||
|
||||
return self._send_auth()
|
||||
|
||||
def _send_auth(self):
|
||||
mech_list = self.mech_list - self.attempted_mechs
|
||||
self.mech = self.sasl.choose_mechanism(mech_list)
|
||||
|
||||
if mech_list and self.mech is not None:
|
||||
resp = stanza.Auth(self.xmpp)
|
||||
resp['mechanism'] = self.mech.name
|
||||
try:
|
||||
resp['value'] = self.mech.process()
|
||||
except SASLCancelled:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except SASLError:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except SASLPrepFailure:
|
||||
log.exception("A credential value did not pass SASLprep.")
|
||||
self.xmpp.disconnect()
|
||||
else:
|
||||
resp.send(now=True)
|
||||
else:
|
||||
try:
|
||||
self.mech = sasl.choose(mech_list,
|
||||
self.sasl_callback,
|
||||
self.security_callback,
|
||||
limit=self.use_mechs,
|
||||
min_mech=self.min_mech)
|
||||
except sasl.SASLNoAppropriateMechanism:
|
||||
log.error("No appropriate login method.")
|
||||
self.xmpp.event("no_auth", direct=True)
|
||||
self.attempted_mechs = set()
|
||||
return self.xmpp.disconnect()
|
||||
|
||||
resp = stanza.Auth(self.xmpp)
|
||||
resp['mechanism'] = self.mech.name
|
||||
try:
|
||||
resp['value'] = self.mech.process()
|
||||
except sasl.SASLCancelled:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLFailed:
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self._send_auth()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
except StringPrepError:
|
||||
log.exception("A credential value did not pass SASLprep.")
|
||||
self.xmpp.disconnect()
|
||||
else:
|
||||
resp.send(now=True)
|
||||
|
||||
return True
|
||||
|
||||
def _handle_challenge(self, stanza):
|
||||
@@ -149,20 +197,33 @@ class FeatureMechanisms(BasePlugin):
|
||||
resp = self.stanza.Response(self.xmpp)
|
||||
try:
|
||||
resp['value'] = self.mech.process(stanza['value'])
|
||||
except SASLCancelled:
|
||||
except sasl.SASLCancelled:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except SASLError:
|
||||
except sasl.SASLFailed:
|
||||
self.stanza.Abort(self.xmpp).send()
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
else:
|
||||
resp.send(now=True)
|
||||
|
||||
def _handle_success(self, stanza):
|
||||
"""SASL authentication succeeded. Restart the stream."""
|
||||
self.attempted_mechs = set()
|
||||
self.xmpp.authenticated = True
|
||||
self.xmpp.features.add('mechanisms')
|
||||
self.xmpp.event('auth_success', stanza, direct=True)
|
||||
raise RestartStream()
|
||||
try:
|
||||
final = self.mech.process(stanza['value'])
|
||||
except sasl.SASLMutualAuthFailed:
|
||||
log.error("Mutual authentication failed! " + \
|
||||
"A security breach is possible.")
|
||||
self.attempted_mechs.add(self.mech.name)
|
||||
self.xmpp.disconnect()
|
||||
else:
|
||||
self.attempted_mechs = set()
|
||||
self.xmpp.authenticated = True
|
||||
self.xmpp.features.add('mechanisms')
|
||||
self.xmpp.event('auth_success', stanza, direct=True)
|
||||
raise RestartStream()
|
||||
|
||||
def _handle_fail(self, stanza):
|
||||
"""SASL authentication failed. Disconnect and shutdown."""
|
||||
|
||||
Reference in New Issue
Block a user