aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar JustAnotherArchivist2020-05-13 02:59:43 +0000
committerGravatar JustAnotherArchivist2020-05-13 02:59:49 +0000
commit609829bf5534dc8e3ddbfd6e8fd0a3c4ccc0224c (patch)
tree50da3284bab319e3fe79871cbe08344caa754bb2
parentTighten length limit for channel names according to RFC 1459 (diff)
signature
Track usermask and account for it in the message splitting
Since the ircd will prefix each message with the origin usermask when broadcasting to the other users, it will have to split or truncate the message sent by the user. Charybdis and ratbox silently truncate it.
-rw-r--r--http2irc.py43
1 files changed, 40 insertions, 3 deletions
diff --git a/http2irc.py b/http2irc.py
index 14822eb..5a98c25 100644
--- a/http2irc.py
+++ b/http2irc.py
@@ -288,6 +288,7 @@ class IRCClientProtocol(asyncio.Protocol):
self.pongReceivedEvent = asyncio.Event()
self.sasl = bool(self.config['irc']['certfile'] and self.config['irc']['certkeyfile'])
self.authenticated = False
+ self.usermask = None
@staticmethod
def nick_command(nick: str):
@@ -298,6 +299,11 @@ class IRCClientProtocol(asyncio.Protocol):
nickb = nick.encode('utf-8')
return b'USER ' + nickb + b' ' + nickb + b' ' + nickb + b' :' + real.encode('utf-8')
+ def _maybe_set_usermask(self, usermask):
+ if b'@' in usermask and b'!' in usermask.split(b'@')[0] and all(x not in usermask for x in (b' ', b'*', b'#', b'&')):
+ self.usermask = usermask
+ self.logger.debug(f'Usermask is now {usermask!r}')
+
def connection_made(self, transport):
self.logger.info('IRC connected')
self.transport = transport
@@ -386,11 +392,12 @@ class IRCClientProtocol(asyncio.Protocol):
break
channelB = channel.encode('utf-8')
messageB = message.encode('utf-8')
- if len(b'PRIVMSG ' + channelB + b' :' + messageB) > 510:
+ usermaskPrefixLength = 1 + (len(self.usermask) if self.usermask else 100) + 1
+ if usermaskPrefixLength + len(b'PRIVMSG ' + channelB + b' :' + messageB) > 510:
self.logger.debug(f'Splitting up into smaller messages')
# Message too long, need to split. First try to split on spaces, then on codepoints. Ideally, would use graphemes between, but that's too complicated.
prefix = b'PRIVMSG ' + channelB + b' :'
- prefixLength = len(prefix)
+ prefixLength = usermaskPrefixLength + len(prefix) # Need to account for the origin prefix included by the ircd when sending to others
maxMessageLength = 510 - prefixLength # maximum length of the message part within each line
messages = []
while message:
@@ -463,7 +470,8 @@ class IRCClientProtocol(asyncio.Protocol):
def message_received(self, message):
self.logger.debug(f'Message received: {message!r}')
- if message.startswith(b':'):
+ rawMessage = message
+ if message.startswith(b':') and b' ' in message:
# Prefixed message, extract command + parameters (the prefix cannot contain a space)
message = message.split(b' ', 1)[1]
@@ -482,6 +490,13 @@ class IRCClientProtocol(asyncio.Protocol):
self.transport.close()
elif message == b'AUTHENTICATE +':
self.send(b'AUTHENTICATE +')
+ elif message.startswith(b'900 '): # "You are now logged in", includes the usermask
+ words = message.split(b' ')
+ if len(words) >= 3 and b'!' in words[2] and b'@' in words[2]:
+ if b'!~' not in words[2]:
+ # At least Charybdis seems to always return the user without a tilde, even if identd failed. Assume no identd and account for that extra tilde.
+ words[2] = words[2].replace(b'!', b'!~', 1)
+ self._maybe_set_usermask(words[2])
elif message.startswith(b'903 '): # SASL auth successful
self.authenticated = True
self.send(b'CAP END')
@@ -527,6 +542,28 @@ class IRCClientProtocol(asyncio.Protocol):
asyncio.create_task(self.send_messages())
asyncio.create_task(self.confirm_messages())
+ # JOIN success
+ elif message.startswith(b'JOIN ') and not self.usermask:
+ # If this is my own join message, it should contain the usermask in the prefix
+ if rawMessage.startswith(b':' + self.config['irc']['nick'].encode('utf-8') + b'!') and b' ' in rawMessage:
+ usermask = rawMessage.split(b' ', 1)[0][1:]
+ self._maybe_set_usermask(usermask)
+
+ # Services host change
+ elif message.startswith(b'396 '):
+ words = message.split(b' ')
+ if len(words) >= 3:
+ # Sanity check inspired by irssi src/irc/core/irc-servers.c
+ if not any(x in words[2] for x in (b'*', b'?', b'!', b'#', b'&', b' ')) and not any(words[2].startswith(x) for x in (b'@', b':', b'-')) and words[2][-1:] != b'-':
+ if b'@' in words[2]: # user@host
+ self._maybe_set_usermask(self.config['irc']['nick'].encode('utf-8') + b'!' + words[2])
+ else: # host (get user from previous mask or settings)
+ if self.usermask:
+ user = self.usermask.split(b'@')[0].split(b'!')[1]
+ else:
+ user = b'~' + self.config['irc']['nick'].encode('utf-8')
+ self._maybe_set_usermask(self.config['irc']['nick'].encode('utf-8') + b'!' + user + b'@' + words[2])
+
def connection_lost(self, exc):
self.logger.info('IRC connection lost')
self.connected = False