From 0b75d6c733560aab1522cf5080be99a8c3ba1420 Mon Sep 17 00:00:00 2001 From: Patric Stout Date: Tue, 27 Jun 2023 11:03:52 +0200 Subject: [PATCH] fix(irc): restrict the start of usernames even more Turns out, they have to start with [a-zA-Z0-9]. --- dibridge/irc.py | 28 +++++++++++++++++++--------- dibridge/irc_puppet.py | 11 +++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/dibridge/irc.py b/dibridge/irc.py index 4a55238..c54fca7 100644 --- a/dibridge/irc.py +++ b/dibridge/irc.py @@ -19,6 +19,14 @@ # talk to someone if they are in an active conversation with them. LEFT_WHILE_TALKING_TIMEOUT = 60 * 10 +# By RFC, only these characters are allowed in a nickname. +REGEX_NICKNAME_FILTER = r"[^a-zA-Z0-9_\-\[\]\{\}\|]" +# By RFC, a nickname cannot start with a number or a dash. +REGEX_NICKNAME_START_FILTER = r"^[0-9\-]+" +# By implementation, a username is more strict than a nickname in what +# it can start with. This filter is in addition to the nickname filters. +REGEX_USERNAME_START_FILTER = r"^[_\[\]\{\}\|]+" + class IRCRelay(irc.client_aio.AioSimpleIRCClient): def __init__(self, host, port, nickname, channel, puppet_ip_range, puppet_postfix, ignore_list, idle_timeout): @@ -129,10 +137,8 @@ async def _pinger(self): async def _connect(self): while True: - username = self._nickname - # An additional constraints usernames have over nicknames, that they are - # also not allowed to start with an underscore. - username = re.sub(r"^_+", "", username) + # Additional constraints usernames have over nicknames. + username = re.sub(REGEX_USERNAME_START_FILTER, "", self._nickname) try: await self.connection.connect( @@ -168,11 +174,15 @@ async def _send_message(self, discord_id, discord_username, message, is_action=F sanitized_discord_username = self._sanitize_discord_username(discord_username) ipv6_address = self._puppet_ip_range[self._generate_ipv6_bits(sanitized_discord_username)] + irc_nickname = f"{sanitized_discord_username}{self._puppet_postfix}" + irc_username = re.sub(REGEX_USERNAME_START_FILTER, "", irc_nickname) + self._puppets[discord_id] = IRCPuppet( self._host, self._port, ipv6_address, - f"{sanitized_discord_username}{self._puppet_postfix}", + irc_nickname, + irc_username, self._channel, functools.partial(self._remove_puppet, discord_id), self._idle_timeout, @@ -211,10 +221,10 @@ def _sanitize_discord_username(self, discord_username): original_discord_username = discord_username discord_username = discord_username.strip() - # Remove all characters not allowed in IRC usernames. - discord_username = re.sub(r"[^a-zA-Z0-9_\-\[\]\{\}\|]", "", discord_username) - # Make sure a username doesn't start with a number or "-". - discord_username = re.sub(r"^[0-9\-]", "", discord_username) + # Remove all characters not allowed in IRC nicknames. + discord_username = re.sub(REGEX_NICKNAME_FILTER, "", discord_username) + # Make sure a nicknames doesn't start with an invalid character. + discord_username = re.sub(REGEX_NICKNAME_START_FILTER, "", discord_username) # On Discord you can create usernames that don't contain any character valid # on IRC, leaving an empty username. In that case we have no option but to diff --git a/dibridge/irc_puppet.py b/dibridge/irc_puppet.py index 8b79c49..a86e6df 100644 --- a/dibridge/irc_puppet.py +++ b/dibridge/irc_puppet.py @@ -1,12 +1,11 @@ import asyncio import irc.client_aio import logging -import re import socket class IRCPuppet(irc.client_aio.AioSimpleIRCClient): - def __init__(self, irc_host, irc_port, ipv6_address, nickname, channel, remove_puppet_func, idle_timeout): + def __init__(self, irc_host, irc_port, ipv6_address, nickname, username, channel, remove_puppet_func, idle_timeout): irc.client.SimpleIRCClient.__init__(self) self.loop = asyncio.get_event_loop() @@ -17,6 +16,7 @@ def __init__(self, irc_host, irc_port, ipv6_address, nickname, channel, remove_p self._nickname = nickname self._nickname_original = nickname self._nickname_iteration = 0 + self._username = username self._joined = False self._channel = channel self._pinger_task = None @@ -140,17 +140,12 @@ async def connect(self): local_addr = (str(self._ipv6_address), 0) while self._reconnect: - username = self._nickname - # An additional constraints usernames have over nicknames, that they are - # also not allowed to start with an underscore. - username = re.sub(r"^_+", "", username) - try: await self.connection.connect( self._irc_host, self._irc_port, self._nickname, - username=username, + username=self._username, # We force an IPv6 connection, as we need that for the puppet source address. connect_factory=irc.connection.AioFactory( family=socket.AF_INET6, local_addr=local_addr, ssl=self._irc_port == 6697