diff --git a/src/conf.py b/src/conf.py index f38dfba33..fb90e17b1 100644 --- a/src/conf.py +++ b/src/conf.py @@ -258,7 +258,7 @@ class SpaceSeparatedSetOfChannels(registry.SpaceSeparatedListOf): else: return ircmsgs.join(channel) -def registerNetwork(name, password='', ssl=False): +def registerNetwork(name, username='', password='', ssl=False): network = registerGroup(supybot.networks, name) registerGlobalValue(network, 'password', registry.String(password, _("""Determines what password will be used on %s. Yes, we know that @@ -277,6 +277,15 @@ def registerNetwork(name, password='', ssl=False): registerChannelValue(network.channels, 'key', registry.String('', _("""Determines what key (if any) will be used to join the channel."""))) + sasl = registerGroup(network, 'sasl') + registerGlobalValue(sasl, 'username', registry.String(username, + _("""Determines what SASL username will be used on %s. This should + be the bot's account name. Due to the way SASL works, you can't use + any grouped nick.""") % name, private=False)) + registerGlobalValue(sasl, 'password', registry.String(password, + _("""Determines what SASL password will be used on %s. Yes, we know + that technically passwords are server-specific and not network-specific, + but this is the best we can do right now.""") % name, private=True)) return network # Let's fill our networks. diff --git a/src/irclib.py b/src/irclib.py index 0efa1abc8..b7dc0f648 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -31,6 +31,7 @@ import re import copy import time import random +import base64 import supybot.log as log import supybot.conf as conf @@ -862,6 +863,8 @@ class Irc(IrcCommandDispatcher): self.ident = conf.supybot.ident() self.alternateNicks = conf.supybot.nick.alternates()[:] self.password = conf.supybot.networks.get(self.network).password() + self.sasl_username = conf.supybot.networks.get(self.network).sasl.username() + self.sasl_password = conf.supybot.networks.get(self.network).sasl.password() self.prefix = '%s!%s@%s' % (self.nick, self.ident, 'unset.domain') # The rest. self.lastTake = 0 @@ -875,6 +878,18 @@ class Irc(IrcCommandDispatcher): self.driver.die() self._reallyDie() else: + if self.sasl_password: + if not self.sasl_username: + log.error('SASL username is not set, unable to identify.') + else: + auth_string = base64.b64encode('%s\x00%s\x00%s' % (self.sasl_username, + self.sasl_username, self.sasl_password)) + log.debug('Sending CAP REQ command, requesting capability \'sasl\'.') + self.queueMsg(ircmsgs.IrcMsg(command="CAP", args=('REQ', 'sasl'))) + log.debug('Sending AUTHENTICATE command, using mechanism PLAIN.') + self.queueMsg(ircmsgs.IrcMsg(command="AUTHENTICATE", args=('PLAIN',))) + log.info('Sending AUTHENTICATE command, not logging the password.') + self.queueMsg(ircmsgs.IrcMsg(command="AUTHENTICATE", args=(auth_string,))) if self.password: log.info('Sending PASS command, not logging the password.') self.queueMsg(ircmsgs.password(self.password)) @@ -884,6 +899,17 @@ class Irc(IrcCommandDispatcher): self.ident, self.user) self.queueMsg(ircmsgs.user(self.ident, self.user)) + def do903(self, msg): + log.info('SASL authentication successful') + log.debug('Sending CAP END command.') + self.queueMsg(ircmsgs.IrcMsg(command="CAP", args=('END',))) + + def do904(self, msg): + log.warning('SASL authentication failed') + log.debug('Aborting authentication.') + log.debug('Sending CAP END command.') + self.queueMsg(ircmsgs.IrcMsg(command="CAP", args=('END',))) + def _getNextNick(self): if self.alternateNicks: nick = self.alternateNicks.pop(0)