From e3d72c43a4dcb10b53e7f472c12014840c12e316 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 15:47:03 -0700 Subject: [PATCH 01/22] inspircd: move proto_ver constants into the class definition --- protocols/inspircd.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 5518cbd..c3b2e61 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -14,6 +14,9 @@ class InspIRCdProtocol(TS6BaseProtocol): S2S_BUFSIZE = 0 # InspIRCd allows infinitely long S2S messages, so set bufsize to infinite + MIN_PROTO_VER = 1202 # anything below is error + MAX_PROTO_VER = 1205 # anything above warns (not officially supported) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -30,9 +33,7 @@ class InspIRCdProtocol(TS6BaseProtocol): 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME', 'SVSTOPIC': 'TOPIC', 'SAKICK': 'KICK'} - self.min_proto_ver = 1202 self.proto_ver = 1202 - self.max_proto_ver = 1202 # Anything above should warn (not officially supported) # Track the modules supported by the uplink. self._modsupport = set() @@ -508,13 +509,13 @@ class InspIRCdProtocol(TS6BaseProtocol): # Check the protocol version self.remote_proto_ver = protocol_version = int(caps['PROTOCOL']) - if protocol_version < self.min_proto_ver: + if protocol_version < self.MIN_PROTO_VER: raise ProtocolError("Remote protocol version is too old! " "At least %s (InspIRCd 2.0.x) is " "needed. (got %s)" % (self.min_proto_ver, protocol_version)) - elif protocol_version > self.max_proto_ver: - log.warning("(%s) PyLink support for InspIRCd 2.2+ is experimental, " + elif protocol_version > self.MAX_PROTO_VER: + log.warning("(%s) PyLink support for InspIRCd > 3.0 is experimental, " "and should not be relied upon for anything important.", self.name) From 44a364df98e061989b64add27a851a607fbd54d3 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 15:54:15 -0700 Subject: [PATCH 02/22] Move message tags code from clientbot to ircs2s_common --- protocols/clientbot.py | 21 --------------------- protocols/ircs2s_common.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/protocols/clientbot.py b/protocols/clientbot.py index 728cdc3..898d9b1 100644 --- a/protocols/clientbot.py +++ b/protocols/clientbot.py @@ -446,27 +446,6 @@ class ClientbotWrapperProtocol(ClientbotBaseProtocol, IRCCommonProtocol): nicks = {self.get_friendly_name(u) for u in puids} self.call_hooks([server, 'CLIENTBOT_SJOIN', {'channel': channel, 'nicks': nicks}]) - def parse_message_tags(self, data): - """ - Parses a message with IRC v3.2 message tags, as described at http://ircv3.net/specs/core/message-tags-3.2.html - """ - # Example query: - # @aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello - if data[0].startswith('@'): - tagdata = data[0].lstrip('@').split(';') - for idx, tag in enumerate(tagdata): - tag = tag.replace(r'\s', ' ') - tag = tag.replace(r'\\', '\\') - tag = tag.replace(r'\r', '\r') - tag = tag.replace(r'\n', '\n') - tag = tag.replace(r'\:', ';') - tagdata[idx] = tag - - results = self.parse_isupport(tagdata, fallback=None) - log.debug('(%s) parsed message tags %s', self.name, results) - return results - return {} - def _set_account_name(self, uid, account): """ Updates the user's account metadata. diff --git a/protocols/ircs2s_common.py b/protocols/ircs2s_common.py index 5a485cf..6e7034c 100644 --- a/protocols/ircs2s_common.py +++ b/protocols/ircs2s_common.py @@ -136,6 +136,29 @@ class IRCCommonProtocol(IRCNetwork): prefixsearch = re.search(r'\(([A-Za-z]+)\)(.*)', args) return dict(zip(prefixsearch.group(1), prefixsearch.group(2))) + def parse_message_tags(self, data): + """ + Parses IRCv3.2 message tags from a message, as described at http://ircv3.net/specs/core/message-tags-3.2.html + + data is a list of command arguments, split by spaces. + """ + # Example query: + # @aaa=bbb;ccc;example.com/ddd=eee :nick!ident@host.com PRIVMSG me :Hello + if data[0].startswith('@'): + tagdata = data[0].lstrip('@').split(';') + for idx, tag in enumerate(tagdata): + tag = tag.replace(r'\s', ' ') + tag = tag.replace(r'\\', '\\') + tag = tag.replace(r'\r', '\r') + tag = tag.replace(r'\n', '\n') + tag = tag.replace(r'\:', ';') + tagdata[idx] = tag + + results = self.parse_isupport(tagdata, fallback=None) + log.debug('(%s) parsed message tags %s', self.name, results) + return results + return {} + def handle_away(self, source, command, args): """Handles incoming AWAY messages.""" # TS6: @@ -270,6 +293,12 @@ class IRCS2SProtocol(IRCCommonProtocol): the SID of the uplink server. """ data = data.split(" ") + + tags = self.parse_message_tags(data) + if tags: + # If we have message tags, split off the first argument. + data = data[1:] + args = self.parse_args(data) sender = args[0] @@ -318,6 +347,8 @@ class IRCS2SProtocol(IRCCommonProtocol): else: parsed_args = func(sender, command, args) if parsed_args is not None: + if tags: + parsed_args['tags'] = tags # Add message tags to this hook payload. return [sender, command, parsed_args] def invite(self, source, target, channel): From 6f617cb068f6c0afe99b6a9cfa9823b0985e3df2 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 16:11:21 -0700 Subject: [PATCH 03/22] inspircd: allow choosing the target IRCd via "target_version" option --- protocols/inspircd.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index c3b2e61..f8a7142 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -13,6 +13,8 @@ from pylinkirc.protocols.ts6_common import * class InspIRCdProtocol(TS6BaseProtocol): S2S_BUFSIZE = 0 # InspIRCd allows infinitely long S2S messages, so set bufsize to infinite + SUPPORTED_IRCDS = ['insp20', 'insp3'] + DEFAULT_IRCD = SUPPORTED_IRCDS[0] MIN_PROTO_VER = 1202 # anything below is error MAX_PROTO_VER = 1205 # anything above warns (not officially supported) @@ -33,7 +35,14 @@ class InspIRCdProtocol(TS6BaseProtocol): 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME', 'SVSTOPIC': 'TOPIC', 'SAKICK': 'KICK'} - self.proto_ver = 1202 + ircd_target = self.serverdata.get('target_version', 'insp20').lower() + if ircd_target == 'insp20': + self.proto_ver = 1202 + elif ircd_target == 'insp3': + self.proto_ver = 1205 + else: + raise ProtocolError("Unsupported target_version %r: supported values include %s" % (ircd_target, self.SUPPORTED_IRCDS)) + log.debug('(%s) inspircd: using protocol version %s for target_version %r', self.name, self.proto_ver, ircd_target) # Track the modules supported by the uplink. self._modsupport = set() From 0fe8a8d51af97f26d3356a55ed5f0079421fd3c8 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 16:11:53 -0700 Subject: [PATCH 04/22] inspircd: move protocol version check into CAPAB START handler InspIRCd 3.0 stopped sending the protocol version in CAPAB CAPABILITIES, but it's always available in CAPAB START. --- protocols/inspircd.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index f8a7142..1f7fa12 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -451,8 +451,26 @@ class InspIRCdProtocol(TS6BaseProtocol): """ # 6 CAPAB commands are usually sent on connect: CAPAB START, MODULES, # MODSUPPORT, CHANMODES, USERMODES, and CAPABILITIES. - # The only ones of interest to us are CHANMODES, USERMODES, - # CAPABILITIES, and MODSUPPORT. + # We check just about everything except MODULES + + if args[0] == 'START': + # Check the protocol version + # insp20: + # <- CAPAB START 1202 + # insp3: + # <- CAPAB START 1205 + self.remote_proto_ver = protocol_version = int(args[1]) + + if protocol_version < self.MIN_PROTO_VER: + raise ProtocolError("Remote protocol version is too old! " + "At least %s (InspIRCd 2.0.x) is " + "needed. (got %s)" % (self.min_proto_ver, + protocol_version)) + elif protocol_version > self.MAX_PROTO_VER: + log.warning("(%s) PyLink support for InspIRCd > 3.0 is experimental, " + "and should not be relied upon for anything important.", + self.name) + log.debug("(%s) inspircd: got remote protocol version %s", self.name, protocol_version) if args[0] == 'CHANMODES': # <- CAPAB CHANMODES :admin=&a allowinvite=A autoop=w ban=b From 4276607ee44770002c51916ba7a297379d9f946e Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 16:51:06 -0700 Subject: [PATCH 05/22] inspircd: rework modelist negotiation to support InspIRCd 3.0 --- protocols/inspircd.py | 156 ++++++++++++++++++++++++++---------------- 1 file changed, 97 insertions(+), 59 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 1f7fa12..55e372c 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -1,5 +1,5 @@ """ -inspircd.py: InspIRCd 2.x protocol module for PyLink. +inspircd.py: InspIRCd 2.0, 3.0 protocol module for PyLink. """ import time @@ -40,10 +40,14 @@ class InspIRCdProtocol(TS6BaseProtocol): self.proto_ver = 1202 elif ircd_target == 'insp3': self.proto_ver = 1205 + log.debug('(%s) inspircd: clearing cmodes, umodes defs %r %r', self.name, self.cmodes, self.umodes) else: raise ProtocolError("Unsupported target_version %r: supported values include %s" % (ircd_target, self.SUPPORTED_IRCDS)) log.debug('(%s) inspircd: using protocol version %s for target_version %r', self.name, self.proto_ver, ircd_target) + # Track prefix mode levels on InspIRCd 3 + self._prefix_levels = {} + # Track the modules supported by the uplink. self._modsupport = set() @@ -472,32 +476,86 @@ class InspIRCdProtocol(TS6BaseProtocol): self.name) log.debug("(%s) inspircd: got remote protocol version %s", self.name, protocol_version) - if args[0] == 'CHANMODES': + if protocol_version >= 1205: + # Clear mode lists, they will be negotiated during burst + self.cmodes = {'*A': '', '*B': '', '*C': '', '*D': ''} + self.umodes = {'*A': '', '*B': '', '*C': '', '*D': ''} + self.prefixmodes.clear() + + if args[0] in {'CHANMODES', 'USERMODES'}: + # insp20: # <- CAPAB CHANMODES :admin=&a allowinvite=A autoop=w ban=b - # banexception=e blockcolor=c c_registered=r exemptchanops=X - # filter=g flood=f halfop=%h history=H invex=I inviteonly=i - # joinflood=j key=k kicknorejoin=J limit=l moderated=m nickflood=F - # noctcp=C noextmsg=n nokick=Q noknock=K nonick=N nonotice=T - # official-join=!Y op=@o operonly=O opmoderated=U owner=~q - # permanent=P private=p redirect=L reginvite=R regmoderated=M - # secret=s sslonly=z stripcolor=S topiclock=t voice=+v + # banexception=e blockcolor=c c_registered=r exemptchanops=X + # filter=g flood=f halfop=%h history=H invex=I inviteonly=i + # joinflood=j key=k kicknorejoin=J limit=l moderated=m nickflood=F + # noctcp=C noextmsg=n nokick=Q noknock=K nonick=N nonotice=T + # official-join=!Y op=@o operonly=O opmoderated=U owner=~q + # permanent=P private=p redirect=L reginvite=R regmoderated=M + # secret=s sslonly=z stripcolor=S topiclock=t voice=+v + # <- CAPAB USERMODES :bot=B callerid=g cloak=x deaf_commonchan=c + # helpop=h hidechans=I hideoper=H invisible=i oper=o regdeaf=R + # servprotect=k showwhois=W snomask=s u_registered=r u_stripcolor=S + # wallops=w + + # insp3: + # <- CAPAB CHANMODES :list:autoop=w list:ban=b list:banexception=e list:filter=g list:invex=I + # list:namebase=Z param-set:anticaps=B param-set:flood=f param-set:joinflood=j param-set:kicknorejoin=J + # param-set:limit=l param-set:nickflood=F param-set:redirect=L param:key=k prefix:10000:voice=+v + # prefix:20000:halfop=%h prefix:30000:op=@o prefix:40000:admin=&a prefix:50000:founder=~q + # prefix:9000000:official-join=!Y simple:allowinvite=A simple:auditorium=u simple:blockcolor=c + # simple:c_registered=r simple:censor=G simple:inviteonly=i simple:moderated=m simple:noctcp=C + # simple:noextmsg=n simple:nokick=Q simple:noknock=K simple:nonick=N simple:nonotice=T + # simple:operonly=O simple:permanent=P simple:private=p simple:reginvite=R simple:regmoderated=M + # simple:secret=s simple:sslonly=z simple:stripcolor=S simple:topiclock=t + # <- CAPAB USERMODES :param-set:snomask=s simple:antiredirect=L simple:bot=B simple:callerid=g simple:cloak=x + # simple:deaf_commonchan=c simple:helpop=h simple:hidechans=I simple:hideoper=H simple:invisible=i + # simple:oper=o simple:regdeaf=R simple:u_censor=G simple:u_registered=r simple:u_stripcolor=S + # simple:wallops=w + + mydict = self.cmodes if args[0] == 'CHANMODES' else self.umodes # Named modes are essential for a cross-protocol IRC service. We # can use InspIRCd as a model here and assign a similar mode map to # our cmodes list. for modepair in args[-1].split(): - name, char = modepair.split('=') + name, char = modepair.rsplit('=', 1) - # Strip c_ prefixes to be consistent with other protocols. + if self.remote_proto_ver >= 1205: + # Detect mode types from the mode type tag + parts = name.split(':') + modetype = parts[0] + name = parts[-1] + + # Modes are divided into A, B, C, and D classes + # See http://www.irc.org/tech_docs/005.html + if modetype == 'simple': # No parameter + mydict['*D'] += char + elif modetype == 'param-set': # Only parameter when setting (e.g. cmode +l) + mydict['*C'] += char + elif modetype == 'param': # Always has parameter (e.g. cmode +k) + mydict['*B'] += char + elif modetype == 'list': # List modes like ban, except, invex, ... + mydict['*A'] += char + elif modetype == 'prefix': # prefix:30000:op=@o + if args[0] != 'CHANMODES': # This should never happen... + log.warning("(%s) Possible desync? Got a prefix type modepair %r but not for channel modes", self.name, modepair) + else: + # We don't do anything with prefix levels yet, let's just store them for future use + self._prefix_levels[name] = int(parts[1]) + + # Map mode names to their prefixes + self.prefixmodes[char[-1]] = char[0] + + # Strip c_, u_ prefixes to be consistent with other protocols. name = name.lstrip('c_') + name = name.lstrip('u_') if name == 'reginvite': # Reginvite? That's an odd name. name = 'regonly' if name == 'founder': # Channel mode +q - # Founder, owner; same thing. m_customprefix allows you to - # name it anything you like. The former is config default, - # but I personally prefer the latter. + # Founder, owner; same thing. m_customprefix allows you to name it anything you like, + # but PyLink uses the latter in its definitions name = 'owner' if name in ('repeat', 'kicknorejoin'): @@ -505,65 +563,45 @@ class InspIRCdProtocol(TS6BaseProtocol): # be safely relayed. name += '_insp' - # We don't care about mode prefixes; just the mode char. - self.cmodes[name] = char[-1] - - - elif args[0] == 'USERMODES': - # <- CAPAB USERMODES :bot=B callerid=g cloak=x deaf_commonchan=c - # helpop=h hidechans=I hideoper=H invisible=i oper=o regdeaf=R - # servprotect=k showwhois=W snomask=s u_registered=r u_stripcolor=S - # wallops=w - - # Ditto above. - for modepair in args[-1].split(): - name, char = modepair.split('=') - # Strip u_ prefixes to be consistent with other protocols. - name = name.lstrip('u_') - self.umodes[name] = char + # Add the mode char to our table + mydict[name] = char[-1] elif args[0] == 'CAPABILITIES': + # Insp 2 # <- CAPAB CAPABILITIES :NICKMAX=21 CHANMAX=64 MAXMODES=20 # IDENTMAX=11 MAXQUIT=255 MAXTOPIC=307 MAXKICK=255 MAXGECOS=128 # MAXAWAY=200 IP6SUPPORT=1 PROTOCOL=1202 PREFIX=(Yqaohv)!~&@%+ # CHANMODES=IXbegw,k,FHJLfjl,ACKMNOPQRSTUcimnprstz # USERMODES=,,s,BHIRSWcghikorwx GLOBOPS=1 SVSPART=1 + # Insp 3 + # CAPAB CAPABILITIES :NICKMAX=30 CHANMAX=64 MAXMODES=20 IDENTMAX=10 MAXQUIT=255 MAXTOPIC=307 + # MAXKICK=255 MAXREAL=128 MAXAWAY=200 MAXHOST=64 CHALLENGE=xxxxxxxxx CASEMAPPING=ascii GLOBOPS=1 + # First, turn the arguments into a dict caps = self.parse_isupport(args[-1]) log.debug("(%s) capabilities list: %s", self.name, caps) - # Check the protocol version - self.remote_proto_ver = protocol_version = int(caps['PROTOCOL']) - - if protocol_version < self.MIN_PROTO_VER: - raise ProtocolError("Remote protocol version is too old! " - "At least %s (InspIRCd 2.0.x) is " - "needed. (got %s)" % (self.min_proto_ver, - protocol_version)) - elif protocol_version > self.MAX_PROTO_VER: - log.warning("(%s) PyLink support for InspIRCd > 3.0 is experimental, " - "and should not be relied upon for anything important.", - self.name) - # Store the max nick and channel lengths - self.maxnicklen = int(caps['NICKMAX']) - self.maxchanlen = int(caps['CHANMAX']) + if 'NICKMAX' in caps: + self.maxnicklen = int(caps['NICKMAX']) + if 'CHANMAX' in caps: + self.maxchanlen = int(caps['CHANMAX']) - # Modes are divided into A, B, C, and D classes - # See http://www.irc.org/tech_docs/005.html - - # FIXME: Find a neater way to assign/store this. - self.cmodes['*A'], self.cmodes['*B'], self.cmodes['*C'], self.cmodes['*D'] \ - = caps['CHANMODES'].split(',') - self.umodes['*A'], self.umodes['*B'], self.umodes['*C'], self.umodes['*D'] \ - = caps['USERMODES'].split(',') - - # Separate the prefixes field (e.g. "(Yqaohv)!~&@%+") into a - # dict mapping mode characters to mode prefixes. - self.prefixmodes = self.parse_isupport_prefixes(caps['PREFIX']) - log.debug('(%s) self.prefixmodes set to %r', self.name, - self.prefixmodes) + # InspIRCd 2 only: mode & prefix definitions are sent as CAPAB CAPABILITIES CHANMODES/USERMODES/PREFIX + if self.remote_proto_ver < 1205: + if 'CHANMODES' in caps: + self.cmodes['*A'], self.cmodes['*B'], self.cmodes['*C'], self.cmodes['*D'] \ + = caps['CHANMODES'].split(',') + if 'USERMODES' in caps: + self.umodes['*A'], self.umodes['*B'], self.umodes['*C'], self.umodes['*D'] \ + = caps['USERMODES'].split(',') + if 'PREFIX' in caps: + # Separate the prefixes field (e.g. "(Yqaohv)!~&@%+") into a + # dict mapping mode characters to mode prefixes. + self.prefixmodes = self.parse_isupport_prefixes(caps['PREFIX']) + log.debug('(%s) self.prefixmodes set to %r', self.name, + self.prefixmodes) elif args[0] == 'MODSUPPORT': # <- CAPAB MODSUPPORT :m_alltime.so m_check.so m_chghost.so m_chgident.so m_chgname.so m_fullversion.so m_gecosban.so m_knock.so m_muteban.so m_nicklock.so m_nopartmsg.so m_opmoderated.so m_sajoin.so m_sanick.so m_sapart.so m_serverban.so m_services_account.so m_showwhois.so m_silence.so m_swhois.so m_uninvite.so m_watch.so From 42e1eda51a9db73de6570095e9b5876b9c374c79 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 17:06:04 -0700 Subject: [PATCH 06/22] inspircd: use NUM to send numerics on insp3 --- protocols/inspircd.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 55e372c..fd16683 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -327,11 +327,12 @@ class InspIRCdProtocol(TS6BaseProtocol): # given user. # <- :70M PUSH 0ALAAAAAA ::midnight.vpn 422 PyLink-devel :Message of the day file is missing. - # Note: InspIRCd 2.2 uses a new NUM command in this format: - # : NUM <3 digit number> - # Take this into consideration if we ever target InspIRCd 2.2, even though m_spanningtree - # does provide backwards compatibility for commands like this. -GLolol - self._send_with_prefix(self.sid, 'PUSH %s ::%s %s %s %s' % (target, source, numeric, target, text)) + # InspIRCd 3 uses a new NUM command in this format: + # -> NUM + if self.remote_proto_ver >= 1205: + self._send('NUM %s %s %s %s' % (source, target, numeric, text)) + else: + self._send_with_prefix(self.sid, 'PUSH %s ::%s %s %s %s' % (target, source, numeric, target, text)) def away(self, source, text): """Sends an AWAY message from a PyLink client. can be an empty string @@ -567,14 +568,14 @@ class InspIRCdProtocol(TS6BaseProtocol): mydict[name] = char[-1] elif args[0] == 'CAPABILITIES': - # Insp 2 + # insp20: # <- CAPAB CAPABILITIES :NICKMAX=21 CHANMAX=64 MAXMODES=20 # IDENTMAX=11 MAXQUIT=255 MAXTOPIC=307 MAXKICK=255 MAXGECOS=128 # MAXAWAY=200 IP6SUPPORT=1 PROTOCOL=1202 PREFIX=(Yqaohv)!~&@%+ # CHANMODES=IXbegw,k,FHJLfjl,ACKMNOPQRSTUcimnprstz # USERMODES=,,s,BHIRSWcghikorwx GLOBOPS=1 SVSPART=1 - # Insp 3 + # insp3: # CAPAB CAPABILITIES :NICKMAX=30 CHANMAX=64 MAXMODES=20 IDENTMAX=10 MAXQUIT=255 MAXTOPIC=307 # MAXKICK=255 MAXREAL=128 MAXAWAY=200 MAXHOST=64 CHALLENGE=xxxxxxxxx CASEMAPPING=ascii GLOBOPS=1 From db6d5d6d05e0368dfa3db6a38e32e1e7518c6c31 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 17:23:00 -0700 Subject: [PATCH 07/22] inspircd: actually read our DEFAULT_IRCD setting --- protocols/inspircd.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index fd16683..4dd12ec 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -35,12 +35,11 @@ class InspIRCdProtocol(TS6BaseProtocol): 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME', 'SVSTOPIC': 'TOPIC', 'SAKICK': 'KICK'} - ircd_target = self.serverdata.get('target_version', 'insp20').lower() + ircd_target = self.serverdata.get('target_version', self.DEFAULT_IRCD).lower() if ircd_target == 'insp20': self.proto_ver = 1202 elif ircd_target == 'insp3': self.proto_ver = 1205 - log.debug('(%s) inspircd: clearing cmodes, umodes defs %r %r', self.name, self.cmodes, self.umodes) else: raise ProtocolError("Unsupported target_version %r: supported values include %s" % (ircd_target, self.SUPPORTED_IRCDS)) log.debug('(%s) inspircd: using protocol version %s for target_version %r', self.name, self.proto_ver, ircd_target) From 08386a8ef7b3c27eea919e784355fba3fdcbb332 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 17:23:20 -0700 Subject: [PATCH 08/22] inspircd: get rid of MIN_PROTO_VER We should always check that our remote has a protocol version >= our own. i.e. support links using PyLink 1202 <-> InspIRCd 1205, PyLink 1205 <-> InspIRCd 1205, but NOT PyLink 1205 <-> InspIRCd 1202 --- protocols/inspircd.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 4dd12ec..a3aaf72 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -16,8 +16,7 @@ class InspIRCdProtocol(TS6BaseProtocol): SUPPORTED_IRCDS = ['insp20', 'insp3'] DEFAULT_IRCD = SUPPORTED_IRCDS[0] - MIN_PROTO_VER = 1202 # anything below is error - MAX_PROTO_VER = 1205 # anything above warns (not officially supported) + MAX_PROTO_VER = 1205 # anything above this warns (not officially supported) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -465,11 +464,10 @@ class InspIRCdProtocol(TS6BaseProtocol): # <- CAPAB START 1205 self.remote_proto_ver = protocol_version = int(args[1]) - if protocol_version < self.MIN_PROTO_VER: + if protocol_version < self.proto_ver: raise ProtocolError("Remote protocol version is too old! " - "At least %s (InspIRCd 2.0.x) is " - "needed. (got %s)" % (self.min_proto_ver, - protocol_version)) + "At least %s is needed. (got %s)" % + (self.proto_ver, protocol_version)) elif protocol_version > self.MAX_PROTO_VER: log.warning("(%s) PyLink support for InspIRCd > 3.0 is experimental, " "and should not be relied upon for anything important.", From ad4cb9561cb6f2b23ac2e73d84eb0a84ed6c4aab Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 17:27:48 -0700 Subject: [PATCH 09/22] inspircd: add FJOIN, IJOIN, KICK handling for InspIRCd 3 IJOIN is new. Strip membership IDs from incoming FJOIN and KICK for now. --- protocols/inspircd.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index a3aaf72..648e2a8 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -30,9 +30,9 @@ class InspIRCdProtocol(TS6BaseProtocol): # non-standard names to our hook handlers, so command handlers' outputs # are called with the right hooks. self.hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE', - 'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST', - 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME', 'SVSTOPIC': 'TOPIC', - 'SAKICK': 'KICK'} + 'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST', + 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME', 'SVSTOPIC': 'TOPIC', + 'SAKICK': 'KICK', 'IJOIN': 'JOIN'} ircd_target = self.serverdata.get('target_version', self.DEFAULT_IRCD).lower() if ircd_target == 'insp20': @@ -605,6 +605,16 @@ class InspIRCdProtocol(TS6BaseProtocol): # <- CAPAB MODSUPPORT :m_alltime.so m_check.so m_chghost.so m_chgident.so m_chgname.so m_fullversion.so m_gecosban.so m_knock.so m_muteban.so m_nicklock.so m_nopartmsg.so m_opmoderated.so m_sajoin.so m_sanick.so m_sapart.so m_serverban.so m_services_account.so m_showwhois.so m_silence.so m_swhois.so m_uninvite.so m_watch.so self._modsupport |= set(args[-1].split()) + def handle_kick(self, source, command, args): + """Handles incoming KICKs.""" + # InspIRCD 3 adds membership IDs to KICK messages when forwarding across servers + # <- :3INAAAAAA KICK #endlessvoid 3INAAAAAA :test (local) + # <- :3INAAAAAA KICK #endlessvoid 7PYAAAAAA 0 :test (remote) + if self.remote_proto_ver >= 1205 and len(args) > 3: + del args[2] + + return super().handle_kick(source, command, args) + def handle_ping(self, source, command, args): """Handles incoming PING commands, so we don't time out.""" # <- :70M PING 70M 0AL @@ -616,7 +626,10 @@ class InspIRCdProtocol(TS6BaseProtocol): def handle_fjoin(self, servernumeric, command, args): """Handles incoming FJOIN commands (InspIRCd equivalent of JOIN/SJOIN).""" - # :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...> + # insp2: + # <- :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...> + # insp3: + # <- :3IN FJOIN #test 1556842195 +nt :o,3INAAAAAA:4 channel = args[0] chandata = self._channels[channel].deepcopy() # InspIRCd sends each channel's users in the form of 'modeprefix(es),UID' @@ -632,6 +645,10 @@ class InspIRCdProtocol(TS6BaseProtocol): for user in userlist: modeprefix, user = user.split(',', 1) + if self.remote_proto_ver >= 1205: + # XXX: we don't handle membership IDs yet + user = user.split(':', 1)[0] + # Don't crash when we get an invalid UID. if user not in self.users: log.debug('(%s) handle_fjoin: tried to introduce user %s not in our user list, ignoring...', @@ -659,6 +676,18 @@ class InspIRCdProtocol(TS6BaseProtocol): return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts, 'channeldata': chandata} + def handle_ijoin(self, source, command, args): + """Handles InspIRCd 3 joins with membership ID.""" + # insp3: + # <- :3INAAAAAA IJOIN #valhalla 6 + # For now we don't care about the membership ID + channel = args[0] + self.users[source].channels.add(channel) + self._channels[channel].users.add(source) + + return {'channel': channel, 'users': [source], 'modes': + self._channels[channel].modes} + def handle_uid(self, numeric, command, args): """Handles incoming UID commands (user introduction).""" # :70M UID 70MAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname From 3d69b7f4e8edcef4e1a1a118dad40051f8b23c07 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 17:36:12 -0700 Subject: [PATCH 10/22] ircs2s_common: fix sending the wrong target in PING --- protocols/ircs2s_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/ircs2s_common.py b/protocols/ircs2s_common.py index 6e7034c..286223a 100644 --- a/protocols/ircs2s_common.py +++ b/protocols/ircs2s_common.py @@ -401,7 +401,7 @@ class IRCS2SProtocol(IRCCommonProtocol): This is mostly used by PyLink internals to check whether the remote link is up.""" if self.sid and self.connected.is_set(): - self._send_with_prefix(self.sid, 'PING %s' % self._expandPUID(self.sid)) + self._send_with_prefix(self.sid, 'PING %s' % self._expandPUID(self.uplink)) def quit(self, numeric, reason): """Quits a PyLink client.""" From 66485ec6a206943829d06c2820cdb0068ff05f9e Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 17:42:45 -0700 Subject: [PATCH 11/22] inspircd: send SINFO instead of VERSION on 1205 --- protocols/inspircd.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 648e2a8..b54da11 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -433,7 +433,11 @@ class InspIRCdProtocol(TS6BaseProtocol): self._send_with_prefix(self.sid, 'BURST %s' % ts) # InspIRCd sends VERSION data on link, instead of whenever requested by a client. - self._send_with_prefix(self.sid, 'VERSION :%s' % self.version()) + if self.proto_ver >= 1205: + for version_type in {'version', 'fullversion', 'rawversion'}: + self._send_with_prefix(self.sid, 'SINFO %s :%s' % (version_type, self.version())) + else: + self._send_with_prefix(self.sid, 'VERSION :%s' % self.version()) self._send_with_prefix(self.sid, 'ENDBURST') # Extban definitions From c43d13ef610c4705f23f8ba672a6123c6fe408d0 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 2 May 2019 18:05:38 -0700 Subject: [PATCH 12/22] inspircd: FTOPIC handling for InspIRCd 3 --- protocols/inspircd.py | 48 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index b54da11..941673a 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -251,13 +251,29 @@ class InspIRCdProtocol(TS6BaseProtocol): self._remove_client(target) - def topic_burst(self, numeric, target, text): + def topic(self, source, target, text): + """Sends a topic change from a PyLink client.""" + if not self.is_internal_client(source): + raise LookupError('No such PyLink client exists.') + + if self.remote_proto_ver >= 1205: + self._send_with_prefix(source, 'FTOPIC %s %s %s :%s' % (target, self._channels[target].ts, int(time.time()), text)) + else: + return super().topic(source, target, text) + + def topic_burst(self, source, target, text): """Sends a topic change from a PyLink server. This is usually used on burst.""" - if not self.is_internal_server(numeric): + if not self.is_internal_server(source): raise LookupError('No such PyLink server exists.') - ts = int(time.time()) - servername = self.servers[numeric].name - self._send_with_prefix(numeric, 'FTOPIC %s %s %s :%s' % (target, ts, servername, text)) + + topic_ts = int(time.time()) + servername = self.servers[source].name + + if self.remote_proto_ver >= 1205: + self._send_with_prefix(source, 'FTOPIC %s %s %s %s :%s' % (target, self._channels[target].ts, topic_ts, servername, text)) + else: + self._send_with_prefix(source, 'FTOPIC %s %s %s :%s' % (target, topic_ts, servername, text)) + self._channels[target].topic = text self._channels[target].topicset = True @@ -769,12 +785,28 @@ class InspIRCdProtocol(TS6BaseProtocol): # First arg = source, second = signon time, third = idle time self._send_with_prefix(target, 'IDLE %s %s 0' % (source, start_time)) - def handle_ftopic(self, numeric, command, args): + def handle_ftopic(self, source, command, args): """Handles incoming FTOPIC (sets topic on burst).""" + # insp2 (only used for server senders): # <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic + + # insp3 (used for server AND user senders): + # <- :3IN FTOPIC #qwerty 1556828864 1556844505 GL!gl@midnight-umk.of4.0.127.IP :1234abcd + # <- :3INAAAAAA FTOPIC #qwerty 1556828864 1556844248 :topic text + # chan creation time ^ ^ topic set time (the one we want) channel = args[0] - ts = args[1] - setter = args[2] + + if self.remote_proto_ver >= 1205: + ts = args[2] + if source in self.users: + setter = source + else: + setter = args[3] + else: + ts = args[1] + setter = args[2] + ts = int(ts) + topic = args[-1] self._channels[channel].topic = topic self._channels[channel].topicset = True From ea753774fd418f4938b01c3775401108b3a5c49c Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 31 May 2019 17:31:11 -0700 Subject: [PATCH 13/22] inspircd: check for local protocol version instead of the remote's We should be speaking the insp20 protocol even to insp3 servers if configured to do so, not some broken hybrid of the two. OPERTYPE handling remains an exception. --- protocols/inspircd.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 941673a..0faa1e1 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -199,10 +199,8 @@ class InspIRCdProtocol(TS6BaseProtocol): self.name, target) userobj.opertype = otype - # InspIRCd 2.x uses _ in OPERTYPE to denote spaces, while InspIRCd 3.x does not. This is not - # backwards compatible: spaces in InspIRCd 2.x will cause the oper type to get cut off at - # the first word, while underscores in InspIRCd 3.x are shown literally as _. - # We can do the underscore fixing based on the version of our uplink: + # InspIRCd 2.x uses _ in OPERTYPE to denote spaces, while InspIRCd 3.x does not. + # This is one of the few things not fixed by 2.0/3.0 link compat, so here's a workaround if self.remote_proto_ver < 1205: otype = otype.replace(" ", "_") else: @@ -256,7 +254,7 @@ class InspIRCdProtocol(TS6BaseProtocol): if not self.is_internal_client(source): raise LookupError('No such PyLink client exists.') - if self.remote_proto_ver >= 1205: + if self.proto_ver >= 1205: self._send_with_prefix(source, 'FTOPIC %s %s %s :%s' % (target, self._channels[target].ts, int(time.time()), text)) else: return super().topic(source, target, text) @@ -269,7 +267,7 @@ class InspIRCdProtocol(TS6BaseProtocol): topic_ts = int(time.time()) servername = self.servers[source].name - if self.remote_proto_ver >= 1205: + if self.proto_ver >= 1205: self._send_with_prefix(source, 'FTOPIC %s %s %s %s :%s' % (target, self._channels[target].ts, topic_ts, servername, text)) else: self._send_with_prefix(source, 'FTOPIC %s %s %s :%s' % (target, topic_ts, servername, text)) @@ -343,7 +341,7 @@ class InspIRCdProtocol(TS6BaseProtocol): # InspIRCd 3 uses a new NUM command in this format: # -> NUM - if self.remote_proto_ver >= 1205: + if self.proto_ver >= 1205: self._send('NUM %s %s %s %s' % (source, target, numeric, text)) else: self._send_with_prefix(self.sid, 'PUSH %s ::%s %s %s %s' % (target, source, numeric, target, text)) @@ -494,7 +492,7 @@ class InspIRCdProtocol(TS6BaseProtocol): self.name) log.debug("(%s) inspircd: got remote protocol version %s", self.name, protocol_version) - if protocol_version >= 1205: + if self.proto_ver >= 1205: # Clear mode lists, they will be negotiated during burst self.cmodes = {'*A': '', '*B': '', '*C': '', '*D': ''} self.umodes = {'*A': '', '*B': '', '*C': '', '*D': ''} @@ -538,7 +536,7 @@ class InspIRCdProtocol(TS6BaseProtocol): for modepair in args[-1].split(): name, char = modepair.rsplit('=', 1) - if self.remote_proto_ver >= 1205: + if self.proto_ver >= 1205: # Detect mode types from the mode type tag parts = name.split(':') modetype = parts[0] @@ -607,7 +605,7 @@ class InspIRCdProtocol(TS6BaseProtocol): self.maxchanlen = int(caps['CHANMAX']) # InspIRCd 2 only: mode & prefix definitions are sent as CAPAB CAPABILITIES CHANMODES/USERMODES/PREFIX - if self.remote_proto_ver < 1205: + if self.proto_ver < 1205: if 'CHANMODES' in caps: self.cmodes['*A'], self.cmodes['*B'], self.cmodes['*C'], self.cmodes['*D'] \ = caps['CHANMODES'].split(',') @@ -630,7 +628,7 @@ class InspIRCdProtocol(TS6BaseProtocol): # InspIRCD 3 adds membership IDs to KICK messages when forwarding across servers # <- :3INAAAAAA KICK #endlessvoid 3INAAAAAA :test (local) # <- :3INAAAAAA KICK #endlessvoid 7PYAAAAAA 0 :test (remote) - if self.remote_proto_ver >= 1205 and len(args) > 3: + if self.proto_ver >= 1205 and len(args) > 3: del args[2] return super().handle_kick(source, command, args) @@ -665,7 +663,7 @@ class InspIRCdProtocol(TS6BaseProtocol): for user in userlist: modeprefix, user = user.split(',', 1) - if self.remote_proto_ver >= 1205: + if self.proto_ver >= 1205: # XXX: we don't handle membership IDs yet user = user.split(':', 1)[0] @@ -796,7 +794,7 @@ class InspIRCdProtocol(TS6BaseProtocol): # chan creation time ^ ^ topic set time (the one we want) channel = args[0] - if self.remote_proto_ver >= 1205: + if self.proto_ver >= 1205: ts = args[2] if source in self.users: setter = source From 12784a4b5b3740e1dd060d3479fb1089f9c683a6 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 31 May 2019 17:46:36 -0700 Subject: [PATCH 14/22] inspircd: handle insp3 IJOIN with TS & flags --- protocols/inspircd.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 0faa1e1..0be3372 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -697,12 +697,25 @@ class InspIRCdProtocol(TS6BaseProtocol): def handle_ijoin(self, source, command, args): """Handles InspIRCd 3 joins with membership ID.""" # insp3: + # EX: regular /join on an existing channel # <- :3INAAAAAA IJOIN #valhalla 6 + + # EX: /ojoin on an existing channel + # <- :3INAAAAAA IJOIN #valhalla 7 1559348434 Yo + + # From insp3 source: + # <- : IJOIN [ []] + # args idx: 0 1 2 3 + # For now we don't care about the membership ID channel = args[0] self.users[source].channels.add(channel) self._channels[channel].users.add(source) + # Apply prefix modes if they exist and the TS check passes + if len(args) >= 4 and int(args[2]) <= self._channels[channel].ts: + self.apply_modes(source, {('+%s' % mode, source) for mode in args[3]}) + return {'channel': channel, 'users': [source], 'modes': self._channels[channel].modes} From b260a28c8fcd4052a7a95a7f588cd06ba3d6ad20 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 31 May 2019 18:12:06 -0700 Subject: [PATCH 15/22] inspircd: handle insp3 SERVER command --- protocols/inspircd.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 0be3372..82a95e1 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -738,32 +738,38 @@ class InspIRCdProtocol(TS6BaseProtocol): self.servers[numeric].users.add(uid) return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip} - def handle_server(self, numeric, command, args): + def handle_server(self, source, command, args): """Handles incoming SERVER commands (introduction of servers).""" - # Initial SERVER command on connect. if self.uplink is None: # <- SERVER whatever.net abcdefgh 0 10X :some server description servername = args[0].lower() - numeric = args[3] + source = args[3] if args[1] != self.serverdata['recvpass']: # Check if recvpass is correct raise ProtocolError('recvpass from uplink server %s does not match configuration!' % servername) sdesc = args[-1] - self.servers[numeric] = Server(self, None, servername, desc=sdesc) - self.uplink = numeric + self.servers[source] = Server(self, None, servername, desc=sdesc) + self.uplink = source + log.debug('(%s) inspircd: found uplink %s', self.name, self.uplink) return # Other server introductions. + # insp20: # <- :00A SERVER test.server * 1 00C :testing raw message syntax + # insp3: + # <- :3IN SERVER services.abc.local 0SV :Some server servername = args[0].lower() - sid = args[3] + if self.proto_ver >= 1205: + sid = args[1] # insp3 + else: + sid = args[3] # insp20 sdesc = args[-1] - self.servers[sid] = Server(self, numeric, servername, desc=sdesc) + self.servers[sid] = Server(self, source, servername, desc=sdesc) - return {'name': servername, 'sid': args[3], 'text': sdesc} + return {'name': servername, 'sid': sid, 'text': sdesc} def handle_fmode(self, numeric, command, args): """Handles the FMODE command, used for channel mode changes.""" From 917543dd12d2a768def5e8334eca05a7f35badf0 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 31 May 2019 18:12:26 -0700 Subject: [PATCH 16/22] inspircd: burst shorter version strings on insp3 These get shown in /map, for example. --- protocols/inspircd.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 82a95e1..2a4c3e2 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -446,10 +446,13 @@ class InspIRCdProtocol(TS6BaseProtocol): sdesc=self.serverdata.get('serverdesc') or conf.conf['pylink']['serverdesc'])) self._send_with_prefix(self.sid, 'BURST %s' % ts) - # InspIRCd sends VERSION data on link, instead of whenever requested by a client. + + # InspIRCd sends VERSION data on link, instead of when requested by a client. if self.proto_ver >= 1205: - for version_type in {'version', 'fullversion', 'rawversion'}: - self._send_with_prefix(self.sid, 'SINFO %s :%s' % (version_type, self.version())) + verstr = self.version() + for version_type in {'version', 'rawversion'}: + self._send_with_prefix(self.sid, 'SINFO %s :%s' % (version_type, verstr.split(' ', 1)[0])) + self._send_with_prefix(self.sid, 'SINFO fullversion :%s' % verstr) else: self._send_with_prefix(self.sid, 'VERSION :%s' % self.version()) self._send_with_prefix(self.sid, 'ENDBURST') From 722881bc33cc75375dd135f71475690aad8f1e9a Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 31 May 2019 18:13:21 -0700 Subject: [PATCH 17/22] inspircd: fix incorrect lstrip() usage when mangling mode names --- protocols/inspircd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 2a4c3e2..ebf37d3 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -566,8 +566,8 @@ class InspIRCdProtocol(TS6BaseProtocol): self.prefixmodes[char[-1]] = char[0] # Strip c_, u_ prefixes to be consistent with other protocols. - name = name.lstrip('c_') - name = name.lstrip('u_') + if name.startswith(('c_', 'u_')): + name = name[2:] if name == 'reginvite': # Reginvite? That's an odd name. name = 'regonly' From 762b47120d27a5f5acfd71284ad59364ad44fe37 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 31 May 2019 18:28:28 -0700 Subject: [PATCH 18/22] inspircd: support insp3 INVITE --- protocols/inspircd.py | 12 ++++++++++++ protocols/ircs2s_common.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index ebf37d3..96907e1 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -346,6 +346,18 @@ class InspIRCdProtocol(TS6BaseProtocol): else: self._send_with_prefix(self.sid, 'PUSH %s ::%s %s %s %s' % (target, source, numeric, target, text)) + def invite(self, source, target, channel): + """Sends an INVITE from a PyLink client.""" + if not self.is_internal_client(source): + raise LookupError('No such PyLink client exists.') + + if self.proto_ver >= 1205: # insp3 + # Note: insp3 supports optionally sending an invite expiration (after the TS argument), + # but we don't use / expose that feature yet. + self._send_with_prefix(source, 'INVITE %s %s %d' % (target, channel, self._channels[channel].ts)) + else: # insp2 + self._send_with_prefix(source, 'INVITE %s %s' % (target, channel)) + def away(self, source, text): """Sends an AWAY message from a PyLink client. can be an empty string to unset AWAY status.""" diff --git a/protocols/ircs2s_common.py b/protocols/ircs2s_common.py index 286223a..da9d80f 100644 --- a/protocols/ircs2s_common.py +++ b/protocols/ircs2s_common.py @@ -352,7 +352,7 @@ class IRCS2SProtocol(IRCCommonProtocol): return [sender, command, parsed_args] def invite(self, source, target, channel): - """Sends an INVITE from a PyLink client..""" + """Sends an INVITE from a PyLink client.""" if not self.is_internal_client(source): raise LookupError('No such PyLink client exists.') From 8d2ae6af5036eeee38bba4c859ad5fb933f1f6db Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 31 May 2019 18:43:53 -0700 Subject: [PATCH 19/22] example-conf: rewrap comments for the first server example --- example-conf.yml | 50 +++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/example-conf.yml b/example-conf.yml index a0ede9c..507f9c7 100644 --- a/example-conf.yml +++ b/example-conf.yml @@ -169,30 +169,29 @@ servers: # Hostname we will use to connect to the remote server hostname: "pylink.yournet.local" - # Sets the server ID (SID) that the main PyLink server should use. - # For TS6-like servers (InspIRCd, Charybdis, UnrealIRCd, etc.), this - # must be three characters: the first char must be a digit [0-9], and - # the remaining two may be either uppercase letters [A-Z] or digits. + # Sets the server ID (SID) that the main PyLink server should use. For TS6-like servers + # (InspIRCd, Charybdis, UnrealIRCd, etc.), this must be three characters: + # the first char must be a digit [0-9], and the remaining two may be either uppercase + # letters [A-Z] or digits. sid: "0PY" - # Server ID range: this specifies the range of server IDs that PyLink - # may use for subservers such as relay. On TS6, this should be a - # combination of digits, letters, and #'s. Each # denotes a range (0-9A-Z) - # of characters that can be used by PyLink to generate appropriate SIDs. + # Server ID range: this specifies the range of server IDs that PyLink# may use for + # subservers such as Relay. On TS6, this should be a combination of digits, letters, and #'s. + # Each # denotes a range (0-9A-Z) of characters that can be used by PyLink to generate SIDs. # You will want to make sure no other servers are using this range. # There must be at least one # in this entry. sidrange: "8##" - # Sets the protocol module to use for this network - see the README for a - # list of supported IRCds. + # Sets the protocol module to use for this network - see the README for a list of supported + # IRCds. protocol: "inspircd" - # Sets the max nick length for the network. It is important that this is - # set correctly, or PyLink might introduce a nick that is too long and - # cause netsplits! This defaults to 30 if not set. + # Sets the max nick length for the network. It is important that this is set correctly, or + # PyLink might introduce a nick that is too long and cause netsplits! + # This defaults to 30 if not set. maxnicklen: 30 - # Toggles SSL for this network - you should seriously consider using TLS in all your links + # Toggles SSL for this network - you should seriously consider using TLS in all server links # for optimal security. Defaults to False if not specified. ssl: true @@ -218,14 +217,13 @@ servers: #ssl_fingerprint: "e0fee1adf795c84eec4735f039503eb18d9c35cc" # This sets the hash type for the fingerprint (md5, sha1, sha256, etc.) - # Valid values include md5 and sha1-sha512, though others may be - # supported depending on your system: see - # https://docs.python.org/3/library/hashlib.html - # This setting defaults to sha256. + # Valid values include md5 and sha1-sha512, though others may be supported depending on + # your system: see https://docs.python.org/3/library/hashlib.html + # This defaults to sha256 if not set. #ssl_fingerprint_type: sha256 - # Sets autoconnect delay - comment this out or set the value below 1 to - # disable autoconnect entirely. + # Sets autoconnect delay - comment this out or set the value below 1 to disable autoconnect + # entirely. autoconnect: 10 # Optional autoconnect settings: @@ -252,18 +250,18 @@ servers: # This setting is EXPERIMENTAL as of PyLink 1.2.x. #encoding: utf-8 - # Sets the ping frequency in seconds (i.e. how long we should wait between - # sending pings to our uplink). When more than two consecutive pings are missed, - # PyLink will disconnect with a ping timeout. This defaults to 90 if not set. + # Sets the ping frequency in seconds (i.e. how long we should wait between sending pings to + # our uplink). When more than two consecutive pings are missed, PyLink will disconnect with + # a ping timeout. This defaults to 90 if not set. #pingfreq: 90 # If relay nick tagging is disabled, this option specifies a list of nick globs to always # tag when introducing remote users *onto* this network. #relay_forcetag_nicks: ["someuser", "Guest*"] - # Sets the suffix that relay subservers on this network should use. - # If not specified per network, this falls back to the value at - # relay::server_suffix, or "relay" if that is also not set. + # Sets the suffix that relay subservers on this network should use. If not specified per + # network, this falls back to the value at relay::server_suffix, or the string "relay" if + # that is also not set. #relay_server_suffix: "relay.yournet.net" # Determines whether relay will tag nicks on this network. This overrides the relay::tag_nicks From 2b04050bf5857670638a4ff1093b336679b982fe Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 31 May 2019 19:01:25 -0700 Subject: [PATCH 20/22] inspircd: minor cleanup --- protocols/inspircd.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 96907e1..ebc5454 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -934,8 +934,7 @@ class InspIRCdProtocol(TS6BaseProtocol): def handle_metadata(self, numeric, command, args): """ - Handles the METADATA command, used by servers to send metadata (services - login name, certfp data, etc.) for clients. + Handles the METADATA command, used by servers to send metadata for various objects. """ uid = args[0] @@ -943,8 +942,8 @@ class InspIRCdProtocol(TS6BaseProtocol): # <- :00A METADATA 1MLAAAJET accountname : # <- :00A METADATA 1MLAAAJET accountname :tester # Sets the services login name of the client. - self.call_hooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': args[-1]}]) + elif args[1] == 'modules' and numeric == self.uplink: # Note: only handle METADATA from our uplink; otherwise leaf servers unloading modules # while shutting down will corrupt the state. From 04d36e93a1d3165794bc9855f523624f1ce774e0 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 6 Jun 2019 23:49:27 -0700 Subject: [PATCH 21/22] inspircd: document target_version variable --- README.md | 6 +++--- example-conf.yml | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4e88f50..daa36b0 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,9 @@ These IRCds (in alphabetical order) are frequently tested and well supported. If * [charybdis](https://github.com/charybdis-ircd/charybdis) (3.5+) - module `ts6` - For KLINE support to work, a `shared{}` block should be added for PyLink on all servers. -* [InspIRCd](http://www.inspircd.org/) 2.0.x - module `inspircd` +* [InspIRCd](http://www.inspircd.org/) 2.0, 3.x [BETA] - module `inspircd` + - Both InspIRCd 2.0 and 3.x are supported by this module. + Set the `target_version` option to `insp3` to target InspIRCd 3.x, or `insp20` to target InspIRCd 2.0 (currently the default). - For vHost setting to work, `m_chghost.so` must be loaded. For ident and realname changing support, `m_chgident.so` and `m_chgname.so` must be loaded respectively. - Supported channel, user, and prefix modes are negotiated on connect, but hotloading modules that change these is not supported. After changing module configuration, it is recommended to SQUIT PyLink to force a protocol renegotiation. * [Nefarious IRCu](https://github.com/evilnet/nefarious2) (2.0.0+) - module `p10` @@ -99,8 +101,6 @@ Support for these IRCds exist, but are not tested as frequently and thoroughly. - For KLINE support to work, a `shared{}` block should be added for PyLink on all servers. * [Elemental-IRCd](https://github.com/Elemental-IRCd/elemental-ircd) (6.6.x / git master) - module `ts6` - For KLINE support to work, a `shared{}` block should be added for PyLink on all servers. -* [InspIRCd](http://www.inspircd.org/) 3.0.x (git master) - module `inspircd` - - The same notes for InspIRCd 2.x apply here as well. * [IRCd-Hybrid](http://www.ircd-hybrid.org/) (8.2.x / svn trunk) - module `hybrid` - For host changing support and optimal functionality, a `service{}` block / U-line should be added for PyLink on every IRCd across your network. - For KLINE support to work, a `shared{}` block should also be added for PyLink on all servers. diff --git a/example-conf.yml b/example-conf.yml index 507f9c7..7b66ff7 100644 --- a/example-conf.yml +++ b/example-conf.yml @@ -152,7 +152,6 @@ servers: # CHANGE THIS to some abbreviation representing your network; usually # something 3-5 characters should be good. inspnet: - # Server IP, port, and passwords. The ip: field also supports resolving # hostnames. ip: 127.0.0.1 @@ -186,6 +185,15 @@ servers: # IRCds. protocol: "inspircd" + # InspIRCd specific option: sets the target InspIRCd protocol version. + # Valid values include: + # "insp20" - InspIRCd 2.0.x (1202) [DEFAULT†] + # "insp3" - InspIRCd 3.x (1205) [BETA] + #target_version: insp20 + #target_version: insp3 + # † InspIRCd 2.0 servers can link to InspIRCd 3.0 via built-in link compatibility, but some + # new features may not work correctly. + # Sets the max nick length for the network. It is important that this is set correctly, or # PyLink might introduce a nick that is too long and cause netsplits! # This defaults to 30 if not set. From dd58dcf377870896d53e1b1c71d493bba6d42915 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 6 Jun 2019 23:50:08 -0700 Subject: [PATCH 22/22] inspircd: show a note when linking to insp3 servers using insp20 compat --- protocols/inspircd.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index ebc5454..3caca62 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -502,9 +502,14 @@ class InspIRCdProtocol(TS6BaseProtocol): "At least %s is needed. (got %s)" % (self.proto_ver, protocol_version)) elif protocol_version > self.MAX_PROTO_VER: - log.warning("(%s) PyLink support for InspIRCd > 3.0 is experimental, " + log.warning("(%s) PyLink support for InspIRCd > 3.x is experimental, " "and should not be relied upon for anything important.", self.name) + elif protocol_version >= 1205 > self.proto_ver: + log.info("(%s) PyLink 2.1 introduces native support for InspIRCd 3.0. " + "You can enable this by setting the 'target_version' option in your " + "InspIRCd server block to 'insp3'.", self.name) + log.info("(%s) Falling back to InspIRCd 2.0 (compatibility) mode.", self.name) log.debug("(%s) inspircd: got remote protocol version %s", self.name, protocol_version) if self.proto_ver >= 1205: