diff --git a/config.example.toml b/config.example.toml index 97cf9d5..6bfa3a1 100644 --- a/config.example.toml +++ b/config.example.toml @@ -5,7 +5,7 @@ #ssl = 'yes' #nick = 'h2ibot' #real = 'I am an http2irc bot.' - # Certificate and key for CertFP authentication with NickServ; certfile is a string containing the path to a .pem file which has the certificate and the key, certkeyfile similarly for one containing only the key; default values are empty (None in Python) to disable CertFP authentication + # Certificate and key for SASL EXTERNAL authentication with NickServ; certfile is a string containing the path to a .pem file which has the certificate and the key, certkeyfile similarly for one containing only the key; default values are empty (None in Python) to disable authentication; the connection is terminated if authentication fails #certfile = #certkeyfile = diff --git a/http2irc.py b/http2irc.py index e568e48..86d0491 100644 --- a/http2irc.py +++ b/http2irc.py @@ -255,12 +255,16 @@ class IRCClientProtocol(asyncio.Protocol): self.channels = channels # Currently joined/supposed-to-be-joined channels; set(str) self.unconfirmedMessages = [] self.pongReceivedEvent = asyncio.Event() + self.sasl = bool(self.config['irc']['certfile'] and self.config['irc']['certkeyfile']) + self.authenticated = False def connection_made(self, transport): self.logger.info('IRC connected') self.transport = transport self.connected = True nickb = self.config['irc']['nick'].encode('utf-8') + if self.sasl: + self.send(b'CAP REQ :sasl') self.send(b'NICK ' + nickb) self.send(b'USER ' + nickb + b' ' + nickb + b' ' + nickb + b' :' + self.config['irc']['real'].encode('utf-8')) @@ -357,8 +361,22 @@ class IRCClientProtocol(asyncio.Protocol): self.send(b'PONG ' + message[5:]) elif message.startswith(b'PONG '): self.pongReceivedEvent.set() + elif message.startswith(b'CAP ') and self.sasl and message[message.find(b' ', 4) + 1:] == b'ACK :sasl': + self.send(b'AUTHENTICATE EXTERNAL') + elif message == b'AUTHENTICATE +': + self.send(b'AUTHENTICATE +') + elif message.startswith(b'903 '): # SASL auth successful + self.authenticated = True + self.send(b'CAP END') + elif any(message.startswith(x) for x in (b'902 ', b'904 ', b'905 ', b'906 ', b'908 ')): + self.logger.error('SASL error, terminating connection') + self.transport.close() elif message.startswith(b'001 '): self.logger.info('IRC connection registered') + if self.sasl and not self.authenticated: + self.logger.error('IRC connection registered but not authenticated, terminating connection') + self.transport.close() + return self.send(b'JOIN ' + ','.join(self.channels).encode('utf-8')) #TODO: Split if too long asyncio.create_task(self.send_messages()) asyncio.create_task(self.confirm_messages())