Add basic pre-registration SASL authentication.

Unfinished, currently breaks nickserv auto-identify when switching handles.
This commit is contained in:
Dpeta 2023-02-25 04:46:59 +01:00
parent 1ec2d42e31
commit f8c3dd3b35
No known key found for this signature in database
GPG key ID: 51227517CEA0030C
3 changed files with 73 additions and 15 deletions

55
irc.py
View file

@ -115,6 +115,8 @@ class PesterIRC(QtCore.QThread):
"768": self._keynotset,
"769": self._keynopermission,
"770": self._metadatasubok,
"903": self._saslsuccess, # We did a SASL!! woo yeah!! (RPL_SASLSUCCESS)
"904": self._saslfail, # oh no,,, cringe,,, (ERR_SASLFAIL)
"error": self._error,
"join": self._join,
"kick": self._kick,
@ -129,6 +131,7 @@ class PesterIRC(QtCore.QThread):
"metadata": self._metadata, # Metadata specification
"tagmsg": self._tagmsg, # IRCv3 message tags extension
"cap": self._cap, # IRCv3 Client Capability Negotiation
"authenticate": self._authenticate, # IRCv3 SASL authentication
}
def run(self):
@ -190,7 +193,28 @@ class PesterIRC(QtCore.QThread):
if self.password:
self._send_irc.pass_(self.password)
self._send_irc.nick(self.mainwindow.profile().handle)
# Negotiate capabilities
self._send_irc.cap("REQ", "message-tags")
self._send_irc.cap(
"REQ",
"draft/metadata-notify-2", # <--- Not required for the unreal5 module.
)
self._send_irc.cap("REQ", "pesterchum-tag") # <--- Currently not using this
self._send_irc.cap("REQ", "twitch.tv/membership") # Twitch silly
# This should not be here.
profile = self.mainwindow.profile()
# Do SASL!!
if self.mainwindow.userprofile.getAutoIdentify():
self._send_irc.cap("REQ", "sasl")
self._send_irc.authenticate("PLAIN")
else:
# Without SASL, end caps here.
self._send_irc.cap("END")
# Send NICK & USER :3
self._send_irc.nick(profile.handle)
self._send_irc.user("pcc31", "pcc31")
def _conn_generator(self):
@ -827,13 +851,6 @@ class PesterIRC(QtCore.QThread):
)
self.connected.emit() # Alert main thread that we've connected.
profile = self.mainwindow.profile()
# Negotiate capabilities
self._send_irc.cap("REQ", "message-tags")
self._send_irc.cap(
"REQ", "draft/metadata-notify-2"
) # <--- Not required in the unreal5 module implementation
self._send_irc.cap("REQ", "pesterchum-tag") # <--- Currently not using this
self._send_irc.cap("REQ", "twitch.tv/membership") # Twitch silly
# Get mood
mood = profile.mood.value_str()
# Moods via metadata
@ -867,8 +884,9 @@ class PesterIRC(QtCore.QThread):
See: https://ircv3.net/specs/extensions/capability-negotiation
"""
PchumLog.info("CAP %s %s %s %s", server, nick, subcommand, tag)
# if tag == "message-tags":
# if subcommand == "ACK":
if subcommand.casefold() == "nak" and tag.casefold() == "sasl":
# SASL isn't supported, end CAP negotation.
self._send_irc.cap("END")
def _umodeis(self, _server, _handle, modes):
"""Numeric reply 221 RPL_UMODEIS, shows us our user modes."""
@ -1013,6 +1031,23 @@ class PesterIRC(QtCore.QThread):
""" "METADATA DRAFT numeric reply 770 RPL_METADATASUBOK, we subbed to a key."""
PchumLog.info("_metadatasubok: %s", params)
def _authenticate(self, _, token):
"""Handle IRCv3 SASL authneticate command from server."""
if token == "+":
# Try to send password now
self._send_irc.authenticate(
nick=self.mainwindow.profile().handle,
password=self.mainwindow.userprofile.getNickServPass(),
)
def _saslfail(self, *_msg):
"""Handle 'RPL_SASLSUCCESS' reply from server, SASL authentication succeeded! woo yeah!!"""
self._send_irc.cap("END")
def _saslsuccess(self, *_msg):
"""Handle 'ERR_SASLFAIL' reply from server, SASL failed somehow."""
self._send_irc.cap("END")
moodUpdated = QtCore.pyqtSignal(str, Mood)
colorUpdated = QtCore.pyqtSignal(str, QtGui.QColor)
messageReceived = QtCore.pyqtSignal(str, str)

View file

@ -2545,10 +2545,11 @@ class PesterWindow(MovingWindow):
self.waitingMessages.answerMessage()
def doAutoIdentify(self):
if self.userprofile.getAutoIdentify():
self.sendMessage.emit(
"identify " + self.userprofile.getNickServPass(), "NickServ"
)
pass
# if self.userprofile.getAutoIdentify():
# self.sendMessage.emit(
# "identify " + self.userprofile.getNickServPass(), "NickServ"
# )
def doAutoJoins(self):
if not self.autoJoinDone:

View file

@ -1,5 +1,6 @@
"""IRC-related functions and classes to be imported by irc.py"""
import logging
import base64
PchumLog = logging.getLogger("pchumLogger")
@ -41,7 +42,7 @@ class SendIRC:
try:
PchumLog.debug("Sending: %s", command)
self.socket.sendall(outgoing_bytes)
self.socket.send(outgoing_bytes)
except OSError:
PchumLog.exception("Error while sending: '%s'", command.strip())
self.socket.close()
@ -157,6 +158,27 @@ class SendIRC:
"""Send QUIT to terminate connection."""
self._send("QUIT", text=reason)
def authenticate(self, token=None, nick=None, password=None):
"""Authenticate command for SASL.
Send either a token like 'PLAIN' or authenticate with nick and password.
Reference: https://ircv3.net/irc/#account-authentication-and-registration
"""
if token:
self._send("AUTHENTICATE", text=token)
return
if nick and password:
# Authentication identity 'nick', authorization identity 'nick' and password 'password'.
sasl_string = f"{nick}\x00{nick}\x00{password}"
# Encode to use base64, then decode since 'text' only takes str.
sasl_string_bytes = sasl_string.encode(encoding="utf-8", errors="replace")
sasl_string_base64 = base64.b64encode(sasl_string_bytes).decode(
encoding="utf-8"
)
# Woo yeah woo yeah
self._send("AUTHENTICATE", text=sasl_string_base64)
def parse_irc_line(line: str):
"""Retrieves tags, prefix, command, and arguments from an unparsed IRC line."""