Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
author_email='[email protected]',
license='GNU',
packages=find_packages(),
install_requires=['six'],
install_requires=['six', 'pyxmpp2_scram>=2.0.2'],
extras_require={
'pysocks': ['pysocks'],
'pysocks:sys_platform=="win32" and python_version == "2.7"': ['win_inet_pton']
Expand Down
1 change: 1 addition & 0 deletions zirc/ext/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import sasl
27 changes: 23 additions & 4 deletions zirc/ext/sasl.py → zirc/ext/sasl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# SASL authentication for zirc

import base64
from ..errors import SASLError
from . import scram
from ...errors import SASLError

class Sasl(object):
name = "sasl"
Expand All @@ -11,18 +12,19 @@ def __init__(self, username, password, method="plain"):
self.password = password
self.method = method
self.retries = 0
self.sasl_scram_state = {'step': 'uninitialized'}

def run(self, bot, args=None):
if args is None:
mechanisms = ["EXTERNAL", "PLAIN"]
mechanisms = ["SCRAM-SHA256-PLUS", "SCRAM-SHA256", "EXTERNAL", "PLAIN"]
else:
mechanisms = args
self.bot = bot
bot.listen(self.on_authenticate, "authenticate")
bot.listen(self.on_saslfailed, "saslfailed")
bot.listen(self.on_saslsuccess, "saslsuccess")
if self.method.upper() in mechanisms:
if self.method in ["plain", "external"]:
if self.method in ["scram-sha256-plus", "scram-sha256", "plain", "external"]:
bot.send("AUTHENTICATE " + self.method.upper())
else:
raise SASLError("Not implemented yet")
Expand All @@ -35,12 +37,29 @@ def on_authenticate(self, event):
password = base64.b64encode("{0}\x00{0}\x00{1}".format(self.username, self.password).encode("UTF-8")).decode("UTF-8")
elif self.method == 'external':
password = "+"
elif self.method.startswith('scram-sha'):
scram.doAuthenticateScramFirst(self, self.method)
self.bot.send("AUTHENTICATE {0}".format(password))
else:
step = self.sasl_scram_state['step']
string = event.arguments[0]
try:
if step == 'first-sent':
scram.doAuthenticateScramChallenge(self, string)
elif step == 'final-sent':
scram.doAuthenticateScramFinish(self, string)
elif step == "authenticated":
self.bot.send("AUTHENTICATE +")
else:
assert False
except scram.ScramException:
bot.send('AUTHENTICATE *')
self.retries += 2

def on_saslfailed(self, event):
self.retries += 1
if self.method == 'external':
if self.retries == 2:
if self.retries >= 2:
self.retries = 1
self.method = 'plain'
self.bot.send("AUTHENTICATE PLAIN")
Expand Down
33 changes: 33 additions & 0 deletions zirc/ext/sasl/scram.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pyxmpp2_scram as scram

def doAuthenticateScramFirst(self, mechanism):
"""Handle sending the client-first message of SCRAM auth."""
hash_name = mechanism[len('scram-'):]
if hash_name.endswith('-plus'):
hash_name = hash_name[:-len('-plus')]
hash_name = hash_name.upper()
if hash_name not in scram.HASH_FACTORIES:
self.retries += 2
return
authenticator = scram.SCRAMClientAuthenticator(hash_name, channel_binding=False)
self.sasl_scram_state['authenticator'] = authenticator
client_first = authenticator.start({
'username': self.sasl_username,
'password': self.sasl_password,
})
self.bot.send("AUTHENTICATE {0}".format(client_first))
self.sasl_scram_state['step'] = 'first-sent'

def doAuthenticateScramChallenge(self, challenge):
client_final = self.sasl_scram_state['authenticator'].challenge(challenge)
self.bot.send("AUTHENTICATE {0}".format(client_final))
self.sasl_scram_state['step'] = 'final-sent'

def doAuthenticateScramFinish(self, data):
try:
res = self.sasl_scram_state['authenticator'].finish(data)
except scram.BadSuccessException:
self.retries += 2
else:
self.sasl_scram_state['step'] = 'authenticated'
self.bot.send("AUTHENTICATE +")