From b2e1f9678f3b052494083d7935fdfc5d5832116d Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 15 Aug 2015 05:12:49 -0700 Subject: [PATCH 01/16] plugins/admin: make command sending loop via hooks This should allow plugins like relay to relay messages, kicks, etc. sent from these admin commands to actually work, preventing desyncs. --- plugins/admin.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/admin.py b/plugins/admin.py index 8362a10..57a2b3d 100644 --- a/plugins/admin.py +++ b/plugins/admin.py @@ -62,6 +62,7 @@ def quit(irc, source, args): u = utils.nickToUid(irc, nick) quitmsg = ' '.join(args[1:]) or 'Client quit' irc.proto.quitClient(irc, u, quitmsg) + irc.callHooks([u, 'PYLINK_ADMIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}]) def joinclient(irc, source, args): """ ,[], etc. @@ -82,6 +83,9 @@ def joinclient(irc, source, args): utils.msg(irc, source, "Error: Invalid channel name %r." % channel) return irc.proto.joinClient(irc, u, channel) + irc.callHooks([u, 'PYLINK_ADMIN_JOIN', {'channel': channel, 'users': [u], + 'modes': irc.channels[channel].modes, + 'parse_as': 'JOIN'}]) utils.add_cmd(joinclient, name='join') @utils.add_cmd @@ -103,6 +107,7 @@ def nick(irc, source, args): utils.msg(irc, source, 'Error: Invalid nickname %r.' % newnick) return irc.proto.nickClient(irc, u, newnick) + irc.callHooks([u, 'PYLINK_ADMIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}]) @utils.add_cmd def part(irc, source, args): @@ -123,6 +128,7 @@ def part(irc, source, args): utils.msg(irc, source, "Error: Invalid channel name %r." % channel) return irc.proto.partClient(irc, u, channel, reason) + irc.callHooks([u, 'PYLINK_ADMIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}]) @utils.add_cmd def kick(irc, source, args): @@ -144,6 +150,7 @@ def kick(irc, source, args): utils.msg(irc, source, "Error: Invalid channel name %r." % channel) return irc.proto.kickClient(irc, u, channel, targetu, reason) + irc.callHooks([u, 'PYLINK_ADMIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}]) @utils.add_cmd def showuser(irc, source, args): @@ -205,9 +212,11 @@ def mode(irc, source, args): return if utils.isInternalServer(irc, modesource): irc.proto.modeServer(irc, modesource, target, parsedmodes) + irc.callHooks([modesource, 'PYLINK_ADMIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}]) else: sourceuid = utils.nickToUid(irc, modesource) irc.proto.modeClient(irc, sourceuid, target, parsedmodes) + irc.callHooks([sourceuid, 'PYLINK_ADMIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}]) @utils.add_cmd def msg(irc, source, args): @@ -235,3 +244,4 @@ def msg(irc, source, args): utils.msg(irc, source, 'Error: no text given.') return irc.proto.messageClient(irc, sourceuid, real_target, text) + irc.callHooks([sourceuid, 'PYLINK_ADMIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}]) From cc7e52a19075ca353b2da0f9994d335aa3e0b30b Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 15 Aug 2015 05:20:17 -0700 Subject: [PATCH 02/16] relayJoins: remove check for whether the sender internal client This is already done by the .remote attribute check. --- plugins/relay.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 39356fc..a783522 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -657,10 +657,6 @@ def relayJoins(irc, channel, users, ts, modes): continue log.debug('(%s) relayJoins: got %r for users', irc.name, users) for user in users.copy(): - if utils.isInternalClient(irc, user) or user not in irc.users: - # We don't need to clone PyLink pseudoclients... That's - # meaningless. - continue try: if irc.users[user].remote: # Is the .remote attribute set? If so, don't relay already From f63d17f94536ad31c4760ae1cc47d31cc43fb63d Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 15 Aug 2015 05:53:44 -0700 Subject: [PATCH 03/16] relay: don't relay kicks if the target has no client on the remote network? --- plugins/relay.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/relay.py b/plugins/relay.py index a783522..0cbc85f 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -409,6 +409,8 @@ def handle_kick(irc, source, command, args): "(half)opped." % channel, notice=True) return + if not real_target: + return # Propogate the kick! if real_kicker: log.debug('(%s) Relay kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan,real_kicker, kicker, irc.name) From 6ebe6e78670b8654e3e4a7361c991455bdce9d47 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 15 Aug 2015 05:54:18 -0700 Subject: [PATCH 04/16] relay: check for whether clients are relay clients, instead of just internal clients This prevents things like non-relay PyLink clients being quit for "Left all shared channels", and allows kicks between internal clients to relay properly. --- plugins/relay.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 0cbc85f..66f12d4 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -61,7 +61,7 @@ def normalizeNick(irc, netname, nick, separator=None, oldnick=''): nick += suffix # FIXME: factorize while utils.nickToUid(irc, nick) or utils.nickToUid(irc, oldnick) and not \ - utils.isInternalClient(irc, utils.nickToUid(irc, nick)): + isRelayClient(irc, utils.nickToUid(irc, nick)): # The nick we want exists? Darn, create another one then, but only if # the target isn't an internal client! # Increase the separator length by 1 if the user was already tagged, @@ -381,7 +381,7 @@ def handle_kick(irc, source, command, args): continue real_kicker = getRemoteUser(irc, remoteirc, kicker, spawnIfMissing=False) log.debug('(%s) Relay kick: real kicker for %s on %s is %s', irc.name, kicker, name, real_kicker) - if not utils.isInternalClient(irc, target): + if not isRelayClient(irc, target): log.debug('(%s) Relay kick: target %s is NOT an internal client', irc.name, target) # Both the target and kicker are external clients; i.e. # they originate from the same network. We won't have @@ -431,7 +431,7 @@ def handle_kick(irc, source, command, args): remoteirc.proto.kickServer(remoteirc, remoteirc.sid, remotechan, real_target, text) - if target != irc.pseudoclient.uid and not irc.users[target].channels: + if isRelayClient(irc, target) and not irc.users[target].channels: irc.proto.quitClient(irc, target, 'Left all shared channels.') remoteuser = getLocalUser(irc, target) del relayusers[remoteuser][irc.name] @@ -646,6 +646,16 @@ def handle_kill(irc, numeric, command, args): utils.add_hook(handle_kill, 'KILL') +def isRelayClient(irc, user): + try: + if irc.users[user].remote: + # Is the .remote attribute set? If so, don't relay already + # relayed clients; that'll trigger an endless loop! + return True + except (KeyError, AttributeError): # Nope, it isn't. + pass + return False + def relayJoins(irc, channel, users, ts, modes): for name, remoteirc in utils.networkobjects.items(): queued_users = [] @@ -659,13 +669,10 @@ def relayJoins(irc, channel, users, ts, modes): continue log.debug('(%s) relayJoins: got %r for users', irc.name, users) for user in users.copy(): - try: - if irc.users[user].remote: - # Is the .remote attribute set? If so, don't relay already - # relayed clients; that'll trigger an endless loop! - continue - except AttributeError: # Nope, it isn't. - pass + if isRelayClient(irc, user): + # Don't clone relay clients; that'll cause some bad, bad + # things to happen. + return log.debug('Okay, spawning %s/%s everywhere', user, irc.name) assert user in irc.users, "(%s) How is this possible? %r isn't in our user database." % (irc.name, user) u = getRemoteUser(irc, remoteirc, user) @@ -697,7 +704,7 @@ def relayPart(irc, channel, user): if remotechan is None or remoteuser is None: continue remoteirc.proto.partClient(remoteirc, remoteuser, remotechan, 'Channel delinked.') - if not remoteirc.users[remoteuser].channels: + if isRelayClient(remoteirc, remoteuser) and not remoteirc.users[remoteuser].channels: remoteirc.proto.quitClient(remoteirc, remoteuser, 'Left all shared channels.') del relayusers[(irc.name, user)][remoteirc.name] @@ -709,7 +716,7 @@ def removeChannel(irc, channel): relay = findRelay((irc.name, channel)) if relay: for user in irc.channels[channel].users.copy(): - if not utils.isInternalClient(irc, user): + if not isRelayClient(irc, user): relayPart(irc, channel, user) # Don't ever part the main client from any of its autojoin channels. else: @@ -904,7 +911,7 @@ def handle_save(irc, numeric, command, args): realuser = getLocalUser(irc, target) log.debug('(%s) relay handle_save: %r got in a nick collision! Real user: %r', irc.name, target, realuser) - if utils.isInternalClient(irc, target) and realuser: + if isRelayClient(irc, target) and realuser: # Nick collision! # It's one of our relay clients; try to fix our nick to the next # available normalized nick. From a1db4932e1ce16cdb59c1d2794fe5ae946b52ad2 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 15 Aug 2015 06:05:12 -0700 Subject: [PATCH 05/16] relay: Don't allow kicks/parts to the PyLink client to be relayed. --- plugins/relay.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 66f12d4..d62c8ed 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -293,6 +293,9 @@ utils.add_hook(handle_nick, 'NICK') def handle_part(irc, numeric, command, args): channels = args['channels'] text = args['text'] + # Don't allow the PyLink client PARTing to be relayed. + if numeric == irc.pseudoclient.uid: + return for channel in channels: for netname, user in relayusers[(irc.name, numeric)].copy().items(): remoteirc = utils.networkobjects[netname] @@ -370,7 +373,8 @@ def handle_kick(irc, source, command, args): kicker = source kicker_modes = getPrefixModes(irc, irc, channel, kicker) relay = findRelay((irc.name, channel)) - if relay is None: + # Don't allow kicks to the PyLink client to be relayed. + if relay is None or target == irc.pseudoclient.uid: return for name, remoteirc in utils.networkobjects.items(): if irc.name == name or not remoteirc.connected.is_set(): From aec29d2aaed3ccf6601553192d2e7a10e4b97faa Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 15 Aug 2015 19:23:41 -0700 Subject: [PATCH 06/16] relay: continue, not return (fix SQUIT not rejoining relay users properly) --- plugins/relay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 5f8eca7..00229cf 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -665,7 +665,7 @@ def relayJoins(irc, channel, users, ts, modes): if isRelayClient(irc, user): # Don't clone relay clients; that'll cause some bad, bad # things to happen. - return + continue log.debug('Okay, spawning %s/%s everywhere', user, irc.name) assert user in irc.users, "(%s) How is this possible? %r isn't in our user database." % (irc.name, user) u = getRemoteUser(irc, remoteirc, user) From d6e6e56e032793a048c0744fdc1a48e26d250305 Mon Sep 17 00:00:00 2001 From: James Lu Date: Mon, 17 Aug 2015 21:15:44 -0700 Subject: [PATCH 07/16] relay: hotfix to fix KILL handling of relay clients --- plugins/relay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 006e97b..4309457 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -607,7 +607,7 @@ utils.add_hook(handle_topic, 'TOPIC') def handle_kill(irc, numeric, command, args): target = args['target'] userdata = args['userdata'] - realuser = getLocalUser(irc, target) + realuser = getLocalUser(irc, target) or userdata.__dict__.get('remote') log.debug('(%s) relay handle_kill: realuser is %r', irc.name, realuser) # Target user was remote: if realuser and realuser[0] != irc.name: From 0d2fbc330d13af3697ee0e98be48cb2573d3ebd0 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 18 Aug 2015 02:44:27 -0700 Subject: [PATCH 08/16] relay: fix "Left all shared channels" quits for KICK handling --- plugins/relay.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 4309457..1c6abf7 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -364,6 +364,7 @@ def handle_kick(irc, source, command, args): # Don't allow kicks to the PyLink client to be relayed. if relay is None or target == irc.pseudoclient.uid: return + origuser = getLocalUser(irc, target) for name, remoteirc in utils.networkobjects.items(): if irc.name == name or not remoteirc.connected.is_set(): continue @@ -402,7 +403,7 @@ def handle_kick(irc, source, command, args): return if not real_target: - return + continue # Propogate the kick! if real_kicker: log.debug('(%s) Relay kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan,real_kicker, kicker, irc.name) @@ -411,7 +412,7 @@ def handle_kick(irc, source, command, args): else: # Kick originated from a server, or the kicker isn't in any # common channels with the target relay network. - log.debug('(%s) Relay kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan,remoteirc.sid, kicker, irc.name) + log.debug('(%s) Relay kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan, remoteirc.sid, kicker, irc.name) try: if kicker in irc.servers: kname = irc.servers[kicker].name @@ -423,9 +424,13 @@ def handle_kick(irc, source, command, args): remoteirc.proto.kickServer(remoteirc, remoteirc.sid, remotechan, real_target, text) - if isRelayClient(irc, target) and not irc.users[target].channels: - remoteuser = getLocalUser(irc, target) - del relayusers[remoteuser][irc.name] + # If the target isn't on any channels, quit them. + if origuser and origuser[0] != remoteirc.name and not remoteirc.users[real_target].channels: + del relayusers[origuser][remoteirc.name] + remoteirc.proto.quitClient(remoteirc, real_target, 'Left all shared channels.') + + if origuser and not irc.users[target].channels: + del relayusers[origuser][irc.name] irc.proto.quitClient(irc, target, 'Left all shared channels.') utils.add_hook(handle_kick, 'KICK') From 5704fddfb4c0c47a6a4a250cfd4dfa099d0f5013 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 18 Aug 2015 02:57:40 -0700 Subject: [PATCH 09/16] relay: ignore internal clients for CLAIM checks --- plugins/relay.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 1c6abf7..bc89461 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -387,7 +387,8 @@ def handle_kick(irc, source, command, args): real_target = getLocalUser(irc, target, targetirc=remoteirc) log.debug('(%s) Relay kick: kicker_modes are %r', irc.name, kicker_modes) if irc.name not in db[relay]['claim'] and not \ - any([mode in kicker_modes for mode in ('y', 'q', 'a', 'o', 'h')]): + (any([mode in kicker_modes for mode in ('y', 'q', 'a', 'o', 'h')]) \ + or utils.isInternalClient(irc, kicker)): log.debug('(%s) Relay kick: kicker %s is not opped... We should rejoin the target user %s', irc.name, kicker, real_target) # Home network is not in the channel's claim AND the kicker is not # opped. We won't propograte the kick then. From f93349ae1f603379bafbfe3dff111c251bb37b86 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 18 Aug 2015 02:59:31 -0700 Subject: [PATCH 10/16] relay: when restoring a user after KICK, join them to the *right* channel --- plugins/relay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index bc89461..a76646e 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -396,7 +396,7 @@ def handle_kick(irc, source, command, args): # kick ops, admins can't kick owners, etc. modes = getPrefixModes(remoteirc, irc, remotechan, real_target) # Join the kicked client back with its respective modes. - irc.proto.sjoinServer(irc, irc.sid, remotechan, [(modes, target)]) + irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)]) if kicker in irc.users: utils.msg(irc, kicker, "This channel is claimed; your kick to " "%s has been blocked because you are not " From 821f546f12b53fef9cf469d2bb2d114e4ff8de64 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 18 Aug 2015 05:44:36 -0700 Subject: [PATCH 11/16] Make sure the PyLink client rejoins all relay channels on KILL This adds a new internal hook, 'PYLINK_SPAWNMAIN', which is triggered whenever Irc().spawnMain() is called. --- coreplugin.py | 4 ++-- main.py | 8 +++++++- plugins/relay.py | 20 +++++++++++++------- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/coreplugin.py b/coreplugin.py index 5f46403..d51a7d6 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -7,7 +7,7 @@ from log import log def handle_kill(irc, source, command, args): if args['target'] == irc.pseudoclient.uid: irc.spawnMain() -utils.add_hook(handle_kill, 'KILL') +utils.add_hook(handle_kill, 'KILL') # Handle KICKs to the PyLink client, rejoining the channels def handle_kick(irc, source, command, args): @@ -15,7 +15,7 @@ def handle_kick(irc, source, command, args): channel = args['channel'] if kicked == irc.pseudoclient.uid: irc.proto.joinClient(irc, irc.pseudoclient.uid, channel) -utils.add_hook(handle_kick, 'KICK') +utils.add_hook(handle_kick, 'KICK') # Handle commands sent to the PyLink client (PRIVMSG) def handle_commands(irc, source, command, args): diff --git a/main.py b/main.py index bdf34e3..f649ff2 100755 --- a/main.py +++ b/main.py @@ -19,9 +19,10 @@ import coreplugin class Irc(): def initVars(self): - # Server, channel, and user indexes to be populated by our protocol module + self.pseudoclient = None self.connected = threading.Event() self.lastping = time.time() + # Server, channel, and user indexes to be populated by our protocol module self.servers = {self.sid: classes.IrcServer(None, self.serverdata['hostname'], internal=True)} self.users = {} self.channels = defaultdict(classes.IrcChannel) @@ -162,6 +163,7 @@ class Irc(): self.pingTimer.cancel() except: # Socket timed out during creation; ignore pass + # Internal hook signifying that a network has disconnected. self.callHooks([None, 'PYLINK_DISCONNECT', {}]) def run(self): @@ -244,9 +246,13 @@ class Irc(): ident = self.botdata.get('ident') or 'pylink' host = self.serverdata["hostname"] log.info('(%s) Connected! Spawning main client %s.', self.name, nick) + olduserobj = self.pseudoclient self.pseudoclient = self.proto.spawnClient(self, nick, ident, host, modes={("+o", None)}) for chan in self.serverdata['channels']: self.proto.joinClient(self, self.pseudoclient.uid, chan) + # PyLink internal hook called when spawnMain is called and the + # contents of Irc().pseudoclient change. + self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}]) if __name__ == '__main__': log.info('PyLink starting...') diff --git a/plugins/relay.py b/plugins/relay.py index a76646e..14c331d 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -256,11 +256,10 @@ def handle_join(irc, numeric, command, args): utils.add_hook(handle_join, 'JOIN') def handle_quit(irc, numeric, command, args): - ouruser = numeric for netname, user in relayusers[(irc.name, numeric)].copy().items(): remoteirc = utils.networkobjects[netname] remoteirc.proto.quitClient(remoteirc, user, args['text']) - del relayusers[(irc.name, ouruser)] + del relayusers[(irc.name, numeric)] utils.add_hook(handle_quit, 'QUIT') def handle_squit(irc, numeric, command, args): @@ -621,13 +620,13 @@ def handle_kill(irc, numeric, command, args): # client and rejoin it to its channels. del relayusers[realuser][irc.name] remoteirc = utils.networkobjects[realuser[0]] - for channel in remoteirc.channels: - remotechan = findRemoteChan(remoteirc, irc, channel) - if remotechan: - modes = getPrefixModes(remoteirc, irc, remotechan, realuser[1]) + for remotechan in remoteirc.channels: + localchan = findRemoteChan(remoteirc, irc, remotechan) + if localchan: + modes = getPrefixModes(remoteirc, irc, localchan, realuser[1]) log.debug('(%s) relay handle_kill: userpair: %s, %s', irc.name, modes, realuser) client = getRemoteUser(remoteirc, irc, realuser[1]) - irc.proto.sjoinServer(irc, irc.sid, remotechan, [(modes, client)]) + irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)]) if userdata and numeric in irc.users: utils.msg(irc, numeric, "Your kill to %s has been blocked " "because PyLink does not allow killing" @@ -947,3 +946,10 @@ def handle_away(irc, numeric, command, args): remoteirc = utils.networkobjects[netname] remoteirc.proto.awayClient(remoteirc, user, args['text']) utils.add_hook(handle_away, 'AWAY') + +def handle_spawnmain(irc, numeric, command, args): + if args['olduser']: + # Kills to the main PyLink client force reinitialization; this makes sure + # it joins all the relay channels like it's supposed to. + initializeAll(irc) +utils.add_hook(handle_spawnmain, 'PYLINK_SPAWNMAIN') From 3fc5896f825b6ea4b8cf9e29c987349ef6d0915c Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 18 Aug 2015 05:49:27 -0700 Subject: [PATCH 12/16] Strip leading/trailing spaces when handling PM commands --- coreplugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coreplugin.py b/coreplugin.py index d51a7d6..4872723 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -20,7 +20,8 @@ utils.add_hook(handle_kick, 'KICK') # Handle commands sent to the PyLink client (PRIVMSG) def handle_commands(irc, source, command, args): if args['target'] == irc.pseudoclient.uid: - cmd_args = args['text'].split(' ') + text = args['text'].strip() + cmd_args = text.split(' ') cmd = cmd_args[0].lower() cmd_args = cmd_args[1:] try: From 1d245bf0012ba8976023889ce1aceae62d1cafa0 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 20 Aug 2015 01:32:30 -0700 Subject: [PATCH 13/16] protoocols.sjoinServer: only add prefix modes to channel state if our TS < theirs --- protocols/inspircd.py | 6 +++++- protocols/ts6.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 2144caf..652ca1a 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -74,6 +74,8 @@ def sjoinServer(irc, server, channel, users, ts=None): orig_ts = irc.channels[channel].ts ts = ts or orig_ts if ts < orig_ts: + # If the TS we're sending is lower than the one that existing, clear the + # mode lists from our channel state and reset the timestamp. log.debug('(%s) sjoinServer: resetting TS of %r from %s to %s (clearing modes)', irc.name, channel, orig_ts, ts) irc.channels[channel].ts = ts @@ -99,7 +101,9 @@ def sjoinServer(irc, server, channel, users, ts=None): irc.users[user].channels.add(channel) except KeyError: # Not initialized yet? log.debug("(%s) sjoinServer: KeyError trying to add %r to %r's channel list?", irc.name, channel, user) - utils.applyModes(irc, channel, changedmodes) + if ts < orig_ts: + # Only save our prefix modes in the channel state if our TS is lower than theirs. + utils.applyModes(irc, channel, changedmodes) namelist = ' '.join(namelist) _send(irc, server, "FJOIN {channel} {ts} {modes} :{users}".format( ts=ts, users=namelist, channel=channel, diff --git a/protocols/ts6.py b/protocols/ts6.py index 745f47b..25ffa1b 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -80,6 +80,8 @@ def sjoinServer(irc, server, channel, users, ts=None): orig_ts = irc.channels[channel].ts ts = ts or orig_ts if ts < orig_ts: + # If the TS we're sending is lower than the one that existing, clear the + # mode lists from our channel state and reset the timestamp. log.debug('(%s) sjoinServer: resetting TS of %r from %s to %s (clearing modes)', irc.name, channel, orig_ts, ts) irc.channels[channel].ts = ts @@ -115,7 +117,9 @@ def sjoinServer(irc, server, channel, users, ts=None): ts=ts, users=namelist, channel=channel, modes=utils.joinModes(modes))) irc.channels[channel].users.update(uids) - utils.applyModes(irc, channel, changedmodes) + if ts < orig_ts: + # Only save our prefix modes in the channel state if our TS is lower than theirs. + utils.applyModes(irc, channel, changedmodes) def _sendModes(irc, numeric, target, modes, ts=None): utils.applyModes(irc, target, modes) From dde9539e77f2b3c85c1170247f1b542c34c4499e Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 20 Aug 2015 03:04:40 -0700 Subject: [PATCH 14/16] relay: fix incorrect logging in normalizeNick --- plugins/relay.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 14c331d..5f2e03d 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -34,7 +34,6 @@ utils.whois_handlers.append(relayWhoisHandlers) def normalizeNick(irc, netname, nick, separator=None, oldnick=''): separator = separator or irc.serverdata.get('separator') or "/" log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator) - orig_nick = nick protoname = irc.proto.__name__ maxnicklen = irc.maxnicklen @@ -68,7 +67,7 @@ def normalizeNick(irc, netname, nick, separator=None, oldnick=''): # but couldn't be created due to a nick conflict. # This can happen when someone steals a relay user's nick. new_sep = separator + separator[-1] - log.debug('(%s) normalizeNick: using %r as new_sep.', irc.name, separator) + log.debug('(%s) normalizeNick: nick %r is in use; using %r as new_sep.', irc.name, nick, new_sep) nick = normalizeNick(irc, netname, orig_nick, separator=new_sep) finalLength = len(nick) assert finalLength <= maxnicklen, "Normalized nick %r went over max " \ From bd2bd55e89f5018a4559f6e59fd48da1b2b4295f Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 20 Aug 2015 22:13:28 -0700 Subject: [PATCH 15/16] relay: fix mode relaying when sender is a client but isn't in any shared channels --- plugins/relay.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 5f2e03d..5b1d641 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -538,10 +538,9 @@ def relayModes(irc, remoteirc, sender, channel, modes=None): # Don't send anything if there are no supported modes left after filtering. if supported_modes: # Check if the sender is a user; remember servers are allowed to set modes too. - if sender in irc.users: - u = getRemoteUser(irc, remoteirc, sender, spawnIfMissing=False) - if u: - remoteirc.proto.modeClient(remoteirc, u, remotechan, supported_modes) + u = getRemoteUser(irc, remoteirc, sender, spawnIfMissing=False) + if u: + remoteirc.proto.modeClient(remoteirc, u, remotechan, supported_modes) else: remoteirc.proto.modeServer(remoteirc, remoteirc.sid, remotechan, supported_modes) From c1d98838d7497a9d8ddcff365934d9213f628e00 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 21 Aug 2015 00:39:58 -0700 Subject: [PATCH 16/16] plugins/admin: allow sending KICK from servers (#90) --- plugins/admin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/admin.py b/plugins/admin.py index 57a2b3d..a834222 100644 --- a/plugins/admin.py +++ b/plugins/admin.py @@ -144,12 +144,15 @@ def kick(irc, source, args): except IndexError: utils.msg(irc, source, "Error: not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).") return - u = utils.nickToUid(irc, nick) + u = utils.nickToUid(irc, nick) or nick targetu = utils.nickToUid(irc, target) if not utils.isChannel(channel): utils.msg(irc, source, "Error: Invalid channel name %r." % channel) return - irc.proto.kickClient(irc, u, channel, targetu, reason) + if utils.isInternalServer(irc, u): + irc.proto.kickServer(irc, u, channel, targetu, reason) + else: + irc.proto.kickClient(irc, u, channel, targetu, reason) irc.callHooks([u, 'PYLINK_ADMIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}]) @utils.add_cmd