diff --git a/example-conf.yml b/example-conf.yml index 80eeaa4..011db49 100644 --- a/example-conf.yml +++ b/example-conf.yml @@ -305,6 +305,9 @@ servers: #sasl_username: "mIRCsKripterz" #sasl_password: "DownLoaditn00b!!!" + # Defines the SASL timeout - this defaults to 15 seconds. + #sasl_timeout: 15 + # If this option is enabled, the bot will attempt SASL authentication even after it's # connected, as services become available throughout netsplits and reconnects. # Note: This requires an IRC server capable of IRCv3.2 cap-notify and sasl: diff --git a/protocols/clientbot.py b/protocols/clientbot.py index 4516d79..c227fea 100644 --- a/protocols/clientbot.py +++ b/protocols/clientbot.py @@ -73,6 +73,17 @@ class ClientbotWrapperProtocol(Protocol): if sendpass: f('PASS %s' % sendpass) + f('CAP LS 302') + + # Start a timer to call CAP END if registration freezes (e.g. if AUTHENTICATE for SASL is + # never replied to). + def capEnd(): + log.info('(%s) Skipping SASL due to timeout; are the IRCd and services configured ' + 'properly?', self.irc.name) + self.capEnd() + self._cap_timer = threading.Timer(self.irc.serverdata.get('sasl_timeout') or 15, capEnd) + self._cap_timer.start() + # This is a really gross hack to get the defined NICK/IDENT/HOST/GECOS. # But this connection stuff is done before any of the spawnClient stuff in # services_support fires. @@ -82,8 +93,6 @@ class ClientbotWrapperProtocol(Protocol): f('USER %s 8 * :%s' % (ident, # TODO: per net realnames or hostnames aren't implemented yet. conf.conf["bot"].get("realname", "PyLink Clientbot"))) - f('CAP LS 302') - # Note: clientbot clients are initialized with umode +i by default def spawnClient(self, nick, ident='unknown', host='unknown.host', realhost=None, modes={('i', None)}, server=None, ip='0.0.0.0', realname='', ts=None, opertype=None, @@ -413,6 +422,14 @@ class ClientbotWrapperProtocol(Protocol): parsed_args['tags'] = tags # Add message tags to this dict. return [idsource, command, parsed_args] + def capEnd(self): + """ + Abort SASL login by sending CAP END. + """ + self.irc.send('CAP END') + log.debug("(%s) Stopping CAP END timer.", self.irc.name) + self._cap_timer.cancel() + def saslAuth(self): """ Starts an authentication attempt via SASL. This returns True if SASL @@ -483,7 +500,7 @@ class ClientbotWrapperProtocol(Protocol): logfunc = log.info if command == '903' else log.warning logfunc('(%s) %s', self.irc.name, args[-1]) if not self.has_eob: - self.irc.send('CAP END') + self.capEnd() handle_903 = handle_902 = handle_905 = handle_906 = handle_907 = handle_904 def requestNewCaps(self): @@ -521,12 +538,12 @@ class ClientbotWrapperProtocol(Protocol): # to do so. if not self.saslAuth(): if not self.has_eob: - self.irc.send('CAP END') + self.capEnd() elif subcmd == 'NAK': log.warning('(%s) Got NAK for IRCv3 capabilities %s, even though they were supposedly available', self.irc.name, args[-1]) if not self.has_eob: - self.irc.send('CAP END') + self.capEnd() elif subcmd == 'NEW': # :irc.example.com CAP modernclient NEW :batch # :irc.example.com CAP tester NEW :away-notify extended-join