From f5f031e41b9b78d6d5275134a8a94cfc06bc9e8d Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 22 Jul 2015 11:46:38 -0700 Subject: [PATCH 01/38] config.yml.example: add header and note about comments Apparently this isn't obvious enough for some. Figures. :| --- config.yml.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.yml.example b/config.yml.example index 3af93b8..7aae0b4 100644 --- a/config.yml.example +++ b/config.yml.example @@ -1,3 +1,8 @@ +# This is a sample configuration file for PyLink. You'll likely want to rename it to config.yml +# and begin your configuration there. + +# Note: lines starting with a "#" are comments and will be ignored. + bot: # Sets nick, user/ident, and real name. nick: pylink From 3eb54c479a958819afbbfd794026d35a560bfd84 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 22 Jul 2015 13:18:11 -0700 Subject: [PATCH 02/38] admin: clearer command help? --- plugins/admin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/admin.py b/plugins/admin.py index 7394c01..892d4f3 100644 --- a/plugins/admin.py +++ b/plugins/admin.py @@ -49,7 +49,7 @@ def spawnclient(irc, source, args): def quit(irc, source, args): """ [] - Admin-only. Quits the PyLink client , if it exists.""" + Admin-only. Quits the PyLink client with nick , if one exists.""" checkauthenticated(irc, source) try: nick = args[0] @@ -66,7 +66,7 @@ def quit(irc, source, args): def joinclient(irc, source, args): """ ,[], etc. - Admin-only. Joins , a PyLink client, to a comma-separated list of channels.""" + Admin-only. Joins , the nick of a PyLink client, to a comma-separated list of channels.""" checkauthenticated(irc, source) try: nick = args[0] @@ -108,7 +108,7 @@ def nick(irc, source, args): def part(irc, source, args): """ ,[],... [] - Admin-only. Parts , a PyLink client, from a comma-separated list of channels.""" + Admin-only. Parts , the nick of a PyLink client, from a comma-separated list of channels.""" checkauthenticated(irc, source) try: nick = args[0] @@ -128,7 +128,7 @@ def part(irc, source, args): def kick(irc, source, args): """ [] - Admin-only. Kicks from via , where is a PyLink client.""" + Admin-only. Kicks from via , where is the nick of a PyLink client.""" checkauthenticated(irc, source) try: nick = args[0] @@ -186,7 +186,7 @@ def showchan(irc, source, args): def mode(irc, source, args): """ - Admin-only. Sets modes on .""" + Admin-only. Sets modes on from , where is the nick of a PyLink client.""" checkauthenticated(irc, source) try: modesource, target, modes = args[0], args[1], args[2:] From 8c1e1c18f14b4e87a140d4854d4fceebceffb7be Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 22 Jul 2015 19:29:58 -0700 Subject: [PATCH 03/38] relay: Factorize relayJoins usage in initializeChannel and truly fix #74. Squashed commit of the following: commit 4e481f15db372d5c07f30e92f6581ea93692695b Author: James Lu Date: Wed Jul 22 19:28:34 2015 -0700 relay: Factorize relayJoins usage in initializeChannel and truly fix #74. The real error was that queued_users was being defined in the wrong spot (outside of the for loop), so the user list would grow larger and larger with every network initialized. This reverts parts of the previous commit which weren't actually necessary. commit 76cc6bfbc71439880f01891f944600a26ff81130 Author: James Lu Date: Wed Jul 22 13:34:47 2015 -0700 Mark users as internal at the IrcUser level (attempt to fix #74) --- plugins/relay.py | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 43a6493..56eaac7 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -97,12 +97,11 @@ def save(irc, source, args): def getPrefixModes(irc, remoteirc, channel, user): modes = '' for pmode in ('owner', 'admin', 'op', 'halfop', 'voice'): - if pmode not in remoteirc.cmodes: # Mode not supported by IRCd - continue - mlist = irc.channels[channel].prefixmodes[pmode+'s'] - log.debug('(%s) getPrefixModes: checking if %r is in %r', irc.name, user, mlist) - if user in mlist: - modes += remoteirc.cmodes[pmode] + if pmode in remoteirc.cmodes: # Mode supported by IRCd + mlist = irc.channels[channel].prefixmodes[pmode+'s'] + log.debug('(%s) getPrefixModes: checking if %r is in %r', irc.name, user, mlist) + if user in mlist: + modes += remoteirc.cmodes[pmode] return modes def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True): @@ -218,6 +217,7 @@ def initializeChannel(irc, channel): all_links = db[relay]['links'].copy() all_links.update((relay,)) log.debug('(%s) initializeChannel: all_links: %s', irc.name, all_links) + # Iterate over all the remote channels linked in this relay. for link in all_links: modes = [] remotenet, remotechan = link @@ -229,20 +229,9 @@ def initializeChannel(irc, channel): rc = remoteirc.channels[remotechan] if not (remoteirc.connected and findRemoteChan(remoteirc, irc, remotechan)): continue # They aren't connected, don't bother! - for user in remoteirc.channels[remotechan].users: - # Don't spawn our pseudoclients again. - if not utils.isInternalClient(remoteirc, user): - log.debug('(%s) initializeChannel: should be joining %s/%s to %s', irc.name, user, remotenet, channel) - localuser = getRemoteUser(remoteirc, irc, user) - if localuser is None: - log.warning('(%s) got None for local user for %s/%s', irc.name, user, remotenet) - continue - userpair = (getPrefixModes(remoteirc, irc, remotechan, user), localuser) - log.debug('(%s) initializeChannel: adding %s to queued_users for %s', irc.name, userpair, channel) - queued_users.append(userpair) - if queued_users: - irc.proto.sjoinServer(irc, irc.sid, channel, queued_users, ts=rc.ts) - relayModes(remoteirc, irc, remoteirc.sid, remotechan) + # Join their (remote) users and set their modes. + relayJoins(remoteirc, remotechan, rc.users, + rc.ts, rc.modes) relayModes(irc, remoteirc, irc.sid, channel) topic = remoteirc.channels[relay[1]].topic # Only update the topic if it's different from what we already have, @@ -251,6 +240,7 @@ def initializeChannel(irc, channel): irc.proto.topicServer(irc, irc.sid, channel, topic) log.debug('(%s) initializeChannel: joining our users: %s', irc.name, c.users) + # After that's done, we'll send our users to them. relayJoins(irc, channel, c.users, c.ts, c.modes) irc.proto.joinClient(irc, irc.pseudoclient.uid, channel) @@ -262,7 +252,6 @@ def handle_join(irc, numeric, command, args): modes = args['modes'] ts = args['ts'] users = set(args['users']) - # users.update(irc.channels[channel].users) relayJoins(irc, channel, users, ts, modes) utils.add_hook(handle_join, 'JOIN') @@ -597,8 +586,8 @@ def handle_kill(irc, numeric, command, args): utils.add_hook(handle_kill, 'KILL') def relayJoins(irc, channel, users, ts, modes): - queued_users = [] for name, remoteirc in utils.networkobjects.items(): + queued_users = [] if name == irc.name: # Don't relay things to their source network... continue @@ -607,13 +596,14 @@ def relayJoins(irc, channel, users, ts, modes): # If there is no link on our network for the user, don't # bother spawning it. 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, KeyError): # Nope, it isn't. + except AttributeError: # Nope, it isn't. pass if utils.isInternalClient(irc, user) or user not in irc.users: # We don't need to clone PyLink pseudoclients... That's From 35cdfbf7e61022c48f35cd4acf149d6deaa8fce6 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 22 Jul 2015 20:31:45 -0700 Subject: [PATCH 04/38] Declare IRCd casemapping in protocol modules, and respect these in utils.nickToUid This adds a new utils.toLower(irc, text) function which returns the lowercased version of based on 's declared case mapping. Closes #75. --- protocols/inspircd.py | 2 ++ protocols/ts6.py | 1 + utils.py | 11 ++++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index d33a0cf..b8916a0 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -10,6 +10,8 @@ from log import log from classes import * +casemapping = 'ascii' + # Raw commands sent from servers vary from protocol to protocol. Here, we map # non-standard names to our hook handlers, so plugins get the information they need. diff --git a/protocols/ts6.py b/protocols/ts6.py index 40175c6..f50532c 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -17,6 +17,7 @@ from inspircd import handle_privmsg, handle_kill, handle_kick, handle_error, \ handle_quit, handle_nick, handle_save, handle_squit, handle_mode, handle_topic, \ handle_notice +casemapping = 'rfc1459' hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE'} def _send(irc, sid, msg): diff --git a/utils.py b/utils.py index 14986f1..61baf89 100644 --- a/utils.py +++ b/utils.py @@ -122,9 +122,18 @@ def add_hook(func, command): command = command.upper() command_hooks[command].append(func) +def toLower(irc, text): + if irc.proto.casemapping == 'rfc1459': + text = text.replace('{', '[') + text = text.replace('}', ']') + text = text.replace('|', '\\') + text = text.replace('~', '^') + return text.lower() + def nickToUid(irc, nick): + nick = toLower(irc, nick) for k, v in irc.users.items(): - if v.nick == nick: + if toLower(irc, v.nick) == nick: return k def clientToServer(irc, numeric): From 686467ffa6a3d471832c514bd04a8eef0c408b08 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 22 Jul 2015 20:39:38 -0700 Subject: [PATCH 05/38] Remove endburst toggling in spawnServer, has_bursted IrcServer flag Neither are being used at all; they're essentially dead code. --- classes.py | 1 - protocols/inspircd.py | 9 ++------- protocols/ts6.py | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/classes.py b/classes.py index 0e956d1..18e5fb4 100644 --- a/classes.py +++ b/classes.py @@ -39,7 +39,6 @@ class IrcServer(): self.users = [] self.internal = internal self.name = name.lower() - self.has_bursted = False def __repr__(self): return repr(self.__dict__) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index b8916a0..c514198 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -561,7 +561,7 @@ def handle_events(irc, data): if parsed_args is not None: return [numeric, command, parsed_args] -def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server', endburst=True): +def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server'): # -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver uplink = uplink or irc.sid name = name.lower() @@ -580,13 +580,8 @@ def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server', endburst raise ValueError('Invalid server name %r' % name) _send(irc, uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc)) irc.servers[sid] = IrcServer(uplink, name, internal=True) - if endburst: - endburstServer(irc, sid) - return sid - -def endburstServer(irc, sid): _send(irc, sid, 'ENDBURST') - irc.servers[sid].has_bursted = True + return sid def handle_ftopic(irc, numeric, command, args): # <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic diff --git a/protocols/ts6.py b/protocols/ts6.py index f50532c..75b9cf2 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -527,7 +527,7 @@ def handle_events(irc, data): if parsed_args is not None: return [numeric, command, parsed_args] -def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server', endburst=True): +def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server'): # -> :0AL SERVER test.server 1 0XY :some silly pseudoserver uplink = uplink or irc.sid name = name.lower() From 8a1f96530384469a467f0f9516f59b158169474f Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 22 Jul 2015 21:14:22 -0700 Subject: [PATCH 06/38] Consistently use applyModes in spawnClient, so user modes are stored like ('o', None) instead of ('+o', None) Drop the 'modes' argument in IrcUser, for this is incorrect. --- classes.py | 4 ++-- main.py | 2 +- protocols/inspircd.py | 3 ++- protocols/ts6.py | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/classes.py b/classes.py index 18e5fb4..a649944 100644 --- a/classes.py +++ b/classes.py @@ -9,7 +9,7 @@ import time class IrcUser(): def __init__(self, nick, ts, uid, ident='null', host='null', realname='PyLink dummy client', realhost='null', - ip='0.0.0.0', modes=set()): + ip='0.0.0.0'): self.nick = nick self.ts = ts self.uid = uid @@ -18,7 +18,7 @@ class IrcUser(): self.realhost = realhost self.ip = ip self.realname = realname - self.modes = modes + self.modes = set() self.identified = False self.channels = set() diff --git a/main.py b/main.py index 04b3b46..056addf 100755 --- a/main.py +++ b/main.py @@ -183,7 +183,7 @@ class Irc(): nick = self.botdata.get('nick') or 'PyLink' ident = self.botdata.get('ident') or 'pylink' host = self.serverdata["hostname"] - self.pseudoclient = self.proto.spawnClient(self, nick, ident, host, modes={("o", None)}) + 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) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index c514198..b095cd0 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -39,7 +39,8 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set() realhost = realhost or host raw_modes = utils.joinModes(modes) u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname, - realhost=realhost, ip=ip, modes=modes) + realhost=realhost, ip=ip) + utils.applyModes(irc, uid, modes) irc.servers[server].users.append(uid) _send(irc, server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}" " {ts} {modes} + :{realname}".format(ts=ts, host=host, diff --git a/protocols/ts6.py b/protocols/ts6.py index 75b9cf2..668c2be 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -41,7 +41,8 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set() realhost = realhost or host raw_modes = utils.joinModes(modes) u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname, - realhost=realhost, ip=ip, modes=modes) + realhost=realhost, ip=ip) + utils.applyModes(irc, uid, modes) irc.servers[server].users.append(uid) _send(irc, server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} " ":{realname}".format(ts=ts, host=host, From d52fba37b8c759d3b834beee960bb2bc2435de07 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 22 Jul 2015 21:15:34 -0700 Subject: [PATCH 07/38] add numericServer for sending raw numerics from servers, and WHOIS handling in coreplugin Basic WHOIS handling (user information, server information, IRCop access) are sent. #72 TODO: send channel lists, an extra note for relay clients, user modes, and idle time --- coreplugin.py | 22 ++++++++++++++++++++++ protocols/inspircd.py | 6 ++++++ protocols/ts6.py | 7 +++++++ 3 files changed, 35 insertions(+) diff --git a/coreplugin.py b/coreplugin.py index 32871e3..d06d4b8 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -36,3 +36,25 @@ def handle_commands(irc, source, command, args): utils.msg(irc, source, 'Uncaught exception in command %r: %s: %s' % (cmd, type(e).__name__, str(e))) return utils.add_hook(handle_commands, 'PRIVMSG') + +# Return WHOIS replies to IRCds that use them. +def handle_whois(irc, source, command, args): + target = args['target'] + user = irc.users.get(target) + if user is None: + log.warning('(%s) Got a WHOIS request for %r from %r, but the target doesn\'t exist in irc.users!', irc.name, target, source) + f = irc.proto.numericServer + server = utils.clientToServer(irc, target) or irc.sid + nick = user.nick + # https://www.alien.net.au/irc/irc2numerics.html + # 311: sends nick!user@host information + f(irc, server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname)) + # 312: sends the server the target is on, and the name + f(irc, server, 312, source, "%s %s :PyLink Server" % (nick, irc.serverdata['hostname'])) + # 313: sends a string denoting the target's operator privilege; + # we'll only send it if the user has umode +o. + if ('o', None) in user.modes: + f(irc, server, 313, source, "%s :is an IRC Operator" % nick) + # 318: End of WHOIS. + f(irc, server, 318, source, "%s :End of WHOIS" % nick.lower()) +utils.add_hook(handle_whois, 'WHOIS') diff --git a/protocols/inspircd.py b/protocols/inspircd.py index b095cd0..aa82579 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -293,6 +293,12 @@ def pingServer(irc, source=None, target=None): if not (target is None or source is None): _send(irc, source, 'PING %s %s' % (source, target)) +def numericServer(irc, source, numeric, text): + raise NotImplementedError("Numeric sending is not yet implemented by this " + "protocol module. WHOIS requests are handled " + "locally by InspIRCd servers, so there is no " + "need for PyLink to send numerics directly yet.") + def connect(irc): ts = irc.start_ts irc.uidgen = {} diff --git a/protocols/ts6.py b/protocols/ts6.py index 668c2be..1529ba0 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -230,6 +230,9 @@ def pingServer(irc, source=None, target=None): else: _send(irc, source, 'PING %s' % source) +def numericServer(irc, source, numeric, target, text): + _send(irc, source, '%s %s %s' % (numeric, target, text)) + def connect(irc): ts = irc.start_ts irc.uidgen = {} @@ -586,3 +589,7 @@ def handle_bmask(irc, numeric, command, args): modes.append(('+%s' % mode, ban)) utils.applyModes(irc, channel, modes) return {'target': channel, 'modes': modes, 'ts': ts} + +def handle_whois(irc, numeric, command, args): + # <- :42XAAAAAB WHOIS 5PYAAAAAA :pylink-devel + return {'target': args[0]} From a7f977aa3b67dfe15b1471e214d73c5056c287a8 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 00:01:51 -0700 Subject: [PATCH 08/38] Add whois handlers for channel lists, user modes, and signon time, and relay user information New API: utils.whois_handlers allows one to add functions taking (irc, target) and returning (irc numeric, reply text) Closes #72. --- coreplugin.py | 36 ++++++++++++++++++++++++++++++++++-- plugins/relay.py | 9 +++++++++ utils.py | 1 + 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/coreplugin.py b/coreplugin.py index d06d4b8..e565533 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -55,6 +55,38 @@ def handle_whois(irc, source, command, args): # we'll only send it if the user has umode +o. if ('o', None) in user.modes: f(irc, server, 313, source, "%s :is an IRC Operator" % nick) - # 318: End of WHOIS. - f(irc, server, 318, source, "%s :End of WHOIS" % nick.lower()) + # 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd + f(irc, server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes))) + # 319: RPL_WHOISCHANNELS, shows channel list + public_chans = [] + for chan in user.channels: + # Here, we'll want to hide secret/private channels from non-opers + # who are not in them. + c = irc.channels[chan] + if ((irc.cmodes.get('secret'), None) in c.modes or \ + (irc.cmodes.get('private'), None) in c.modes) \ + and not (('o', None) in irc.users[source].modes or \ + source in c.users): + continue + # TODO: show prefix modes like a regular IRCd does. + public_chans.append(chan) + if public_chans: + f(irc, server, 319, source, '%s :%s' % (nick, ' '.join(public_chans))) + # 317: shows idle and signon time. Though we don't track the user's real + # idle time; we just return 0. + # 317 GL GL 15 1437632859 :seconds idle, signon time + f(irc, server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts)) + try: + # Iterate over plugin-created WHOIS handlers. They return a tuple + # or list with two arguments: the numeric, and the text to send. + for func in utils.whois_handlers: + num, text = func(irc, target) + f(irc, server, num, source, text) + except Exception as e: + # Again, we wouldn't want this to crash our service, in case + # something goes wrong! + log.exception('Error caught in WHOIS handler: %s', e) + finally: + # 318: End of WHOIS. + f(irc, server, 318, source, "%s :End of WHOIS" % nick) utils.add_hook(handle_whois, 'WHOIS') diff --git a/plugins/relay.py b/plugins/relay.py index 56eaac7..c82e28d 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -15,6 +15,15 @@ from log import log dbname = "pylinkrelay.db" relayusers = defaultdict(dict) +def relayWhoisHandlers(irc, target): + user = irc.users[target] + network, remoteuid = getLocalUser(irc, target) + remotenick = utils.networkobjects[network].users[remoteuid].nick + return [320, "%s :is a remote user connected via PyLink Relay. Home " + "network: %s; Home nick: %s" % (user.nick, network, + remotenick)] +utils.whois_handlers.append(relayWhoisHandlers) + def normalizeNick(irc, netname, nick, separator=None): # Block until we know the IRC network's nick length (after capabilities # are sent) diff --git a/utils.py b/utils.py index 61baf89..1b16bb1 100644 --- a/utils.py +++ b/utils.py @@ -12,6 +12,7 @@ command_hooks = defaultdict(list) networkobjects = {} schedulers = {} plugins = [] +whois_handlers = [] started = threading.Event() class TS6UIDGenerator(): From 88c85c847503308e6c42399edabeb485fd06e642 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 00:05:05 -0700 Subject: [PATCH 09/38] coreplugin/whois: only show user modes to opers --- coreplugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/coreplugin.py b/coreplugin.py index e565533..54e5dd7 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -46,6 +46,7 @@ def handle_whois(irc, source, command, args): f = irc.proto.numericServer server = utils.clientToServer(irc, target) or irc.sid nick = user.nick + sourceisOper = ('o', None) in irc.users[source].modes # https://www.alien.net.au/irc/irc2numerics.html # 311: sends nick!user@host information f(irc, server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname)) @@ -55,8 +56,10 @@ def handle_whois(irc, source, command, args): # we'll only send it if the user has umode +o. if ('o', None) in user.modes: f(irc, server, 313, source, "%s :is an IRC Operator" % nick) - # 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd - f(irc, server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes))) + # 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd. + # Only shown to opers! + if sourceisOper: + f(irc, server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes))) # 319: RPL_WHOISCHANNELS, shows channel list public_chans = [] for chan in user.channels: @@ -65,8 +68,7 @@ def handle_whois(irc, source, command, args): c = irc.channels[chan] if ((irc.cmodes.get('secret'), None) in c.modes or \ (irc.cmodes.get('private'), None) in c.modes) \ - and not (('o', None) in irc.users[source].modes or \ - source in c.users): + and not (sourceisOper or source in c.users): continue # TODO: show prefix modes like a regular IRCd does. public_chans.append(chan) From 44e07b0c2fd5d0bb1a3bc276e04cfc48bf8dd18b Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 00:10:54 -0700 Subject: [PATCH 10/38] relay: don't error if the WHOIS target isn't a relay user... oops --- coreplugin.py | 6 ++++-- plugins/relay.py | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/coreplugin.py b/coreplugin.py index 54e5dd7..32d85a1 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -82,8 +82,10 @@ def handle_whois(irc, source, command, args): # Iterate over plugin-created WHOIS handlers. They return a tuple # or list with two arguments: the numeric, and the text to send. for func in utils.whois_handlers: - num, text = func(irc, target) - f(irc, server, num, source, text) + res = func(irc, target) + if res: + num, text = res + f(irc, server, num, source, text) except Exception as e: # Again, we wouldn't want this to crash our service, in case # something goes wrong! diff --git a/plugins/relay.py b/plugins/relay.py index c82e28d..6affcc9 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -17,11 +17,13 @@ relayusers = defaultdict(dict) def relayWhoisHandlers(irc, target): user = irc.users[target] - network, remoteuid = getLocalUser(irc, target) - remotenick = utils.networkobjects[network].users[remoteuid].nick - return [320, "%s :is a remote user connected via PyLink Relay. Home " - "network: %s; Home nick: %s" % (user.nick, network, - remotenick)] + orig = getLocalUser(irc, target) + if orig: + network, remoteuid = orig + remotenick = utils.networkobjects[network].users[remoteuid].nick + return [320, "%s :is a remote user connected via PyLink Relay. Home " + "network: %s; Home nick: %s" % (user.nick, network, + remotenick)] utils.whois_handlers.append(relayWhoisHandlers) def normalizeNick(irc, netname, nick, separator=None): From 8799e1ccc84567f7afd7f023981c13ce2dc71de6 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 00:11:24 -0700 Subject: [PATCH 11/38] spawnClient: add ts as opt. argument; relay: spawn pseudoclients w/ TS of original user --- plugins/relay.py | 2 +- protocols/inspircd.py | 4 ++-- protocols/ts6.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 6affcc9..fed7ebe 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -143,7 +143,7 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True): modes = getSupportedUmodes(irc, remoteirc, userobj.modes) u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident, host=host, realname=realname, - modes=modes).uid + modes=modes, ts=userobj.ts).uid remoteirc.users[u].remote = irc.name relayusers[(irc.name, user)][remoteirc.name] = u remoteirc.users[u].remote = irc.name diff --git a/protocols/inspircd.py b/protocols/inspircd.py index aa82579..1fe935b 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -25,7 +25,7 @@ def _send(irc, sid, msg): irc.send(':%s %s' % (sid, msg)) def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(), - server=None, ip='0.0.0.0', realname=None): + server=None, ip='0.0.0.0', realname=None, ts=None): server = server or irc.sid if not utils.isInternalServer(irc, server): raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server) @@ -34,7 +34,7 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set() if server not in irc.uidgen: irc.uidgen[server] = utils.TS6UIDGenerator(server) uid = irc.uidgen[server].next_uid() - ts = int(time.time()) + ts = ts or int(time.time()) realname = realname or irc.botdata['realname'] realhost = realhost or host raw_modes = utils.joinModes(modes) diff --git a/protocols/ts6.py b/protocols/ts6.py index 1529ba0..00cfbc6 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -24,7 +24,7 @@ def _send(irc, sid, msg): irc.send(':%s %s' % (sid, msg)) def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(), - server=None, ip='0.0.0.0', realname=None): + server=None, ip='0.0.0.0', realname=None, ts=None): server = server or irc.sid if not utils.isInternalServer(irc, server): raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server) @@ -36,7 +36,7 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set() # UID: # parameters: nickname, hopcount, nickTS, umodes, username, # visible hostname, IP address, UID, gecos - ts = int(time.time()) + ts = ts or int(time.time()) realname = realname or irc.botdata['realname'] realhost = realhost or host raw_modes = utils.joinModes(modes) From 2e34d9e85d80cab21d49ce74a7bea6fd465e3868 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 10:22:55 -0700 Subject: [PATCH 12/38] ts6: Use EUID in spawnClient, and error on attempts to use plain UID The latter isn't handled and thus would lead to pretty bad desyncs. Closes #79. --- protocols/ts6.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/protocols/ts6.py b/protocols/ts6.py index 00cfbc6..8a5063b 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -33,9 +33,9 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set() if server not in irc.uidgen: irc.uidgen[server] = utils.TS6UIDGenerator(server) uid = irc.uidgen[server].next_uid() - # UID: + # EUID: # parameters: nickname, hopcount, nickTS, umodes, username, - # visible hostname, IP address, UID, gecos + # visible hostname, IP address, UID, real hostname, account name, gecos ts = ts or int(time.time()) realname = realname or irc.botdata['realname'] realhost = realhost or host @@ -44,10 +44,11 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set() realhost=realhost, ip=ip) utils.applyModes(irc, uid, modes) irc.servers[server].users.append(uid) - _send(irc, server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} " - ":{realname}".format(ts=ts, host=host, - nick=nick, ident=ident, uid=uid, - modes=raw_modes, ip=ip, realname=realname)) + _send(irc, server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} " + "{realhost} * :{realname}".format(ts=ts, host=host, + nick=nick, ident=ident, uid=uid, + modes=raw_modes, ip=ip, realname=realname, + realhost=realhost)) return u def joinClient(irc, client, channel): @@ -380,6 +381,11 @@ def handle_euid(irc, numeric, command, args): irc.servers[numeric].users.append(uid) return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip} +def handle_uid(irc, numeric, command, args): + raise ProtocolError("Servers must use EUID to send users! This is a " + "requested capability; plain UID (received) is not " + "handled by us at all!") + def handle_server(irc, numeric, command, args): # SERVER is sent by our uplink or any other server to introduce others. # parameters: server name, hopcount, sid, server description @@ -452,6 +458,10 @@ def handle_events(irc, data): # https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L80 chary_cmodes = { # TS6 generic modes: + # Note: charybdis +p has the effect of being both + # noknock AND private. Surprisingly, mapping it twice + # works pretty well: setting +p on a charybdis relay + # server sets +pK on an InspIRCd network. 'op': 'o', 'voice': 'v', 'ban': 'b', 'key': 'k', 'limit': 'l', 'moderated': 'm', 'noextmsg': 'n', 'noknock': 'p', 'secret': 's', 'topiclock': 't', From 1efc9018f3a7cf9dfbf32ca787364bc18ef97264 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 10:28:28 -0700 Subject: [PATCH 13/38] main: raise fallback ping frequency to 30 --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 056addf..7736c99 100755 --- a/main.py +++ b/main.py @@ -61,7 +61,7 @@ class Irc(): self.sid = self.serverdata["sid"] self.botdata = conf['bot'] self.proto = proto - self.pingfreq = self.serverdata.get('pingfreq') or 10 + self.pingfreq = self.serverdata.get('pingfreq') or 30 self.pingtimeout = self.pingfreq * 2 self.initVars() From 58a8d7134c7b2850d7193488db2a26e099158c2c Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 11:01:12 -0700 Subject: [PATCH 14/38] Fix handling of inbound CHG* --- plugins/relay.py | 5 +++-- protocols/inspircd.py | 8 ++++---- protocols/ts6.py | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index fed7ebe..dbba8be 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -434,8 +434,9 @@ def handle_chgclient(irc, source, command, args): remoteirc = utils.networkobjects[netname] try: remoteirc.proto.updateClient(remoteirc, user, field, text) - except ValueError: # IRCd doesn't support changing the field we want - logging.debug('(%s) Error raised changing field %r of %s on %s (for %s/%s)', irc.name, field, user, target, remotenet, irc.name) + except NotImplementedError: # IRCd doesn't support changing the field we want + log.debug('(%s) Ignoring changing field %r of %s on %s (for %s/%s);' + 'remote IRCd doesn\'t support it', irc.name, field, user, target, remotenet, irc.name) continue for c in ('CHGHOST', 'CHGNAME', 'CHGIDENT'): diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 1fe935b..207eb61 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -276,16 +276,16 @@ def updateClient(irc, numeric, field, text): Changes the field of PyLink PseudoClient .""" field = field.upper() if field == 'IDENT': - handle_fident(irc, numeric, 'PYLINK_UPDATECLIENT_IDENT', [text]) + irc.users[numeric].ident = text _send(irc, numeric, 'FIDENT %s' % text) elif field == 'HOST': - handle_fhost(irc, numeric, 'PYLINK_UPDATECLIENT_HOST', [text]) + irc.users[numeric].host = text _send(irc, numeric, 'FHOST %s' % text) elif field in ('REALNAME', 'GECOS'): - handle_fname(irc, numeric, 'PYLINK_UPDATECLIENT_GECOS', [text]) + irc.users[numeric].realname = text _send(irc, numeric, 'FNAME :%s' % text) else: - raise ValueError("Changing field %r of a client is unsupported by this protocol." % field) + raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field) def pingServer(irc, source=None, target=None): source = source or irc.sid diff --git a/protocols/ts6.py b/protocols/ts6.py index 8a5063b..0411ab6 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -217,7 +217,7 @@ def updateClient(irc, numeric, field, text): Changes the field of PyLink PseudoClient .""" field = field.upper() if field == 'HOST': - handle_chghost(irc, numeric, 'PYLINK_UPDATECLIENT_HOST', [text]) + irc.users[numeric].host = text _send(irc, irc.sid, 'CHGHOST %s :%s' % (numeric, text)) else: raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field) From 42593ae4313ca27ae3f0f46bbb9b2da0a4200ce7 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 11:17:57 -0700 Subject: [PATCH 15/38] ts6: add missing handler for SID --- protocols/ts6.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/protocols/ts6.py b/protocols/ts6.py index 0411ab6..565e219 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -387,7 +387,6 @@ def handle_uid(irc, numeric, command, args): "handled by us at all!") def handle_server(irc, numeric, command, args): - # SERVER is sent by our uplink or any other server to introduce others. # parameters: server name, hopcount, sid, server description servername = args[0].lower() try: @@ -401,6 +400,8 @@ def handle_server(irc, numeric, command, args): irc.servers[sid] = IrcServer(numeric, servername) return {'name': servername, 'sid': sid, 'text': sdesc} +handle_sid = handle_server + def handle_tmode(irc, numeric, command, args): # <- :42XAAAAAB TMODE 1437450768 #endlessvoid -c+lkC 3 agte4 channel = args[1].lower() From cbe7fa539f460bb1a2f5825e77bd2727c0711290 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 11:41:33 -0700 Subject: [PATCH 16/38] relay: remove more spurious "you must be in channel" messages --- plugins/relay.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index dbba8be..935eae7 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -310,7 +310,10 @@ def handle_privmsg(irc, numeric, command, args): return sent = 0 relay = findRelay((irc.name, target)) - if utils.isChannel(target) and relay and not db[relay]['links']: + # Don't send any "you must be in common channels" if we're not part + # of a relay, or we are but there are no links! + if utils.isChannel(target) and ((relay and not db[relay]['links']) or \ + relay is None): return for netname, user in relayusers[(irc.name, numeric)].items(): remoteirc = utils.networkobjects[netname] From 83494482a2a80993d20567656c6e50db5459da04 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 11:44:38 -0700 Subject: [PATCH 17/38] relay: don't ever quit the main client or part it from autojoin channels --- plugins/relay.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 935eae7..90d6f6e 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -663,9 +663,14 @@ def removeChannel(irc, channel): for user in irc.channels[channel].users.copy(): if not utils.isInternalClient(irc, user): relayPart(irc, channel, user) + # Don't ever part the main client from any of its autojoin channels. else: + if user == irc.pseudoclient.uid and channel in \ + irc.serverdata['channels']: + continue irc.proto.partClient(irc, user, channel, 'Channel delinked.') - if not irc.users[user].channels: + # Don't ever quit it either... + if user != irc.pseudoclient.uid and not irc.users[user].channels: irc.proto.quitClient(irc, user, 'Left all shared channels.') remoteuser = getLocalUser(irc, user) del relayusers[remoteuser][irc.name] From 254ccea0a3c4c8e5e35c62d9cbced1251afa841b Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 11:46:45 -0700 Subject: [PATCH 18/38] remove some debugging cruft and unused imports --- classes.py | 1 - plugins/commands.py | 1 - plugins/relay.py | 1 - protocols/ts6.py | 1 - tests/test_proto_inspircd.py | 2 -- utils.py | 2 -- 6 files changed, 8 deletions(-) diff --git a/classes.py b/classes.py index a649944..729250f 100644 --- a/classes.py +++ b/classes.py @@ -1,4 +1,3 @@ -from collections import defaultdict import threading from random import randint diff --git a/plugins/commands.py b/plugins/commands.py index a7b7b75..baa910f 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -1,7 +1,6 @@ # commands.py: base PyLink commands import sys import os -import logging sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import utils diff --git a/plugins/relay.py b/plugins/relay.py index 90d6f6e..dac12f1 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -5,7 +5,6 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import pickle import sched import threading -import time import string from collections import defaultdict diff --git a/protocols/ts6.py b/protocols/ts6.py index 565e219..9bff525 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -2,7 +2,6 @@ import time import sys import os import re -from copy import copy curdir = os.path.dirname(__file__) sys.path += [curdir, os.path.dirname(curdir)] diff --git a/tests/test_proto_inspircd.py b/tests/test_proto_inspircd.py index 931351a..01eac64 100644 --- a/tests/test_proto_inspircd.py +++ b/tests/test_proto_inspircd.py @@ -2,7 +2,6 @@ import sys import os sys.path += [os.getcwd(), os.path.join(os.getcwd(), 'protocols')] import unittest -import time from collections import defaultdict import inspircd @@ -72,7 +71,6 @@ class TestProtoInspIRCd(unittest.TestCase): self.assertNotIn(u, self.irc.channels['#channel'].users) self.assertNotIn(u, self.irc.users) self.assertNotIn(u, self.irc.servers[self.irc.sid].users) - pass def testKickClient(self): target = self.proto.spawnClient(self.irc, 'soccerball', 'soccerball', 'abcd').uid diff --git a/utils.py b/utils.py index 1b16bb1..389a840 100644 --- a/utils.py +++ b/utils.py @@ -98,8 +98,6 @@ class TS6SIDGenerator(): self.iters[pos] = iter(self.allowedchars[pos]) next(self.iters[pos]) self.increment(pos-1) - else: - print('NEXT') def next_sid(self): sid = ''.join(self.output) From a8b16d9724c53dfc6b004ac546967acdc00809bb Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 13:24:18 -0700 Subject: [PATCH 19/38] utils.parseModes: fix handling of mode "-k *" on TS6 Charybdis allows unsetting +k without actually knowing the key by faking the argument when unsetting as a single "*". We'd need to know the real argument of the +k being removed, in order to remove the mode pair from the mode list. --- utils.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/utils.py b/utils.py index 389a840..eebd440 100644 --- a/utils.py +++ b/utils.py @@ -178,9 +178,11 @@ def parseModes(irc, target, args): if usermodes: log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes) supported_modes = irc.umodes + oldmodes = irc.users[target].modes else: log.debug('(%s) Using irc.cmodes for this query: %s', irc.name, irc.cmodes) supported_modes = irc.cmodes + oldmodes = irc.channels[target].modes res = [] for mode in modestring: if mode in '+-': @@ -195,6 +197,17 @@ def parseModes(irc, target, args): # Must have parameter. log.debug('Mode %s: This mode must have parameter.', mode) arg = args.pop(0) + if prefix == '-' and mode in supported_modes['*B'] and arg == '*': + # Charybdis allows unsetting +k without actually + # knowing the key by faking the argument when unsetting + # as a single "*". + # We'd need to know the real argument of +k for us to + # be able to unset the mode. + oldargs = [m[1] for m in oldmodes if m[0] == mode] + if oldargs: + # Set the arg to the old one on the channel. + arg = oldargs[0] + log.debug("Mode %s: coersing argument of '*' to %r.", mode, arg) elif mode in irc.prefixmodes and not usermodes: # We're setting a prefix mode on someone (e.g. +o user1) log.debug('Mode %s: This mode is a prefix mode.', mode) From 4ce377944c8e4e02174acf9c3a0fd0b4effff29a Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 13:36:40 -0700 Subject: [PATCH 20/38] relay: when filtering modes, ignore internal mode-type lists (modenames starting with *) --- plugins/relay.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index dac12f1..291deb5 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -475,10 +475,14 @@ def relayModes(irc, remoteirc, sender, channel, modes=None): for name, m in irc.cmodes.items(): supported_char = None if modechar == m: + if name.startswith('*'): + # This internally is just used for storing which modes + # are which type; ignore them. + continue if name not in whitelisted_cmodes: - log.debug("(%s) Relay mode: skipping mode (%r, %r) because " - "it isn't a whitelisted (safe) mode for relay.", - irc.name, modechar, arg) + log.debug("(%s) Relay mode: skipping mode %r (%r, %r) because " + "it isn't a whitelisted (safe) cmode for relay.", + irc.name, name, modechar, arg) break if modechar in irc.prefixmodes: # This is a prefix mode (e.g. +o). We must coerse the argument @@ -523,11 +527,15 @@ def getSupportedUmodes(irc, remoteirc, modes): arg = modepair[1] for name, m in irc.umodes.items(): supported_char = None + if name.startswith('*'): + # This internally is just used for storing which modes + # are which type; ignore them. + continue if modechar == m: if name not in whitelisted_umodes: - log.debug("(%s) getSupportedUmodes: skipping mode (%r, %r) because " - "it isn't a whitelisted (safe) mode for relay.", - irc.name, modechar, arg) + log.debug("(%s) getSupportedUmodes: skipping mode %r (%r, %r) because " + "it isn't a whitelisted (safe) umode for relay.", + irc.name, name, modechar, arg) break supported_char = remoteirc.umodes.get(name) if supported_char: From 0575de1face3bee5425bc852672bf2e776a4d30f Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 13:45:38 -0700 Subject: [PATCH 21/38] Support noctcp (+C) on charybdis, and wallops (+w) in relay --- plugins/relay.py | 3 ++- protocols/ts6.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 291deb5..1df02e2 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -452,7 +452,8 @@ whitelisted_cmodes = {'admin', 'allowinvite', 'autoop', 'ban', 'banexception', 'regmoderated', 'secret', 'sslonly', 'stripcolor', 'topiclock', 'voice'} whitelisted_umodes = {'bot', 'hidechans', 'hideoper', 'invisible', 'oper', - 'regdeaf', 'u_stripcolor', 'servprotect', 'u_noctcp'} + 'regdeaf', 'u_stripcolor', 'servprotect', 'u_noctcp', + 'wallops'} def relayModes(irc, remoteirc, sender, channel, modes=None): remotechan = findRemoteChan(irc, remoteirc, channel) log.debug('(%s) Relay mode: remotechan for %s on %s is %s', irc.name, channel, irc.name, remotechan) diff --git a/protocols/ts6.py b/protocols/ts6.py index 9bff525..2100c1e 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -469,7 +469,7 @@ def handle_events(irc, data): 'quiet': 'q', 'redirect': 'f', 'freetarget': 'F', 'joinflood': 'j', 'largebanlist': 'L', 'permanent': 'P', 'c_noforwards': 'Q', 'stripcolor': 'c', 'allowinvite': - 'g', 'opmoderated': 'z', + 'g', 'opmoderated': 'z', 'noctcp': 'C', # Now, map all the ABCD type modes: '*A': 'beI', '*B': 'k', '*C': 'l', '*D': 'mnprst'} if 'EX' in caps: From a77427ded9f4dd721f18be6b83e8e75d769e060f Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 13:45:55 -0700 Subject: [PATCH 22/38] ts6: fix SQUIT handling These come in a different syntax? Strange. --- protocols/ts6.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/protocols/ts6.py b/protocols/ts6.py index 2100c1e..6ec42c5 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -439,6 +439,10 @@ def handle_events(irc, data): # According to the TS6 protocol documentation, we should send SVINFO # when we get our uplink's SERVER command. irc.send('SVINFO 6 6 0 :%s' % int(time.time())) + elif args[0] == 'SQUIT': + # What? Charybdis send this in a different format! + # <- SQUIT 00A :Remote host closed the connection + handle_squit(irc, args[1], 'SQUIT', [args[1]]) elif args[0] == 'CAPAB': # We only get a list of keywords here. Charybdis obviously assumes that # we know what modes it supports (indeed, this is a standard list). From 024cf9d8780df550fd8852eb79ee04ee5c8a6a43 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 14:19:12 -0700 Subject: [PATCH 23/38] main: better INFO logging... --- main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.py b/main.py index 7736c99..e7e89c5 100755 --- a/main.py +++ b/main.py @@ -84,7 +84,9 @@ class Irc(): self.socket.settimeout(self.pingtimeout) self.proto.connect(self) self.spawnMain() + log.info('(%s) Starting ping schedulers....', self.name) self.schedulePing() + log.info('(%s) Server ready; listening for data.', self.name) self.run() except (socket.error, classes.ProtocolError, ConnectionError) as e: log.warning('(%s) Disconnected from IRC: %s: %s', @@ -183,6 +185,7 @@ class Irc(): nick = self.botdata.get('nick') or 'PyLink' ident = self.botdata.get('ident') or 'pylink' host = self.serverdata["hostname"] + log.info('(%s) Connected! Spawning main client %s.', self.name, nick) 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) From d2134295597c493500da0c104f55e288ff022d71 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 14:30:48 -0700 Subject: [PATCH 24/38] Revert "relay: when filtering modes, ignore internal mode-type lists (modenames starting with *)" This reverts commit 4ce377944c8e4e02174acf9c3a0fd0b4effff29a. --- plugins/relay.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 1df02e2..a0b7cc1 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -476,14 +476,10 @@ def relayModes(irc, remoteirc, sender, channel, modes=None): for name, m in irc.cmodes.items(): supported_char = None if modechar == m: - if name.startswith('*'): - # This internally is just used for storing which modes - # are which type; ignore them. - continue if name not in whitelisted_cmodes: - log.debug("(%s) Relay mode: skipping mode %r (%r, %r) because " - "it isn't a whitelisted (safe) cmode for relay.", - irc.name, name, modechar, arg) + log.debug("(%s) Relay mode: skipping mode (%r, %r) because " + "it isn't a whitelisted (safe) mode for relay.", + irc.name, modechar, arg) break if modechar in irc.prefixmodes: # This is a prefix mode (e.g. +o). We must coerse the argument @@ -528,15 +524,11 @@ def getSupportedUmodes(irc, remoteirc, modes): arg = modepair[1] for name, m in irc.umodes.items(): supported_char = None - if name.startswith('*'): - # This internally is just used for storing which modes - # are which type; ignore them. - continue if modechar == m: if name not in whitelisted_umodes: - log.debug("(%s) getSupportedUmodes: skipping mode %r (%r, %r) because " - "it isn't a whitelisted (safe) umode for relay.", - irc.name, name, modechar, arg) + log.debug("(%s) getSupportedUmodes: skipping mode (%r, %r) because " + "it isn't a whitelisted (safe) mode for relay.", + irc.name, modechar, arg) break supported_char = remoteirc.umodes.get(name) if supported_char: From dc7edf542aed997309c7e29fb3168c7fbe1acc1d Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 14:34:56 -0700 Subject: [PATCH 25/38] relay: fix error on handle_part if remote network has no link --- plugins/relay.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/relay.py b/plugins/relay.py index a0b7cc1..9932a17 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -295,6 +295,8 @@ def handle_part(irc, numeric, command, args): for netname, user in relayusers[(irc.name, numeric)].copy().items(): remoteirc = utils.networkobjects[netname] remotechan = findRemoteChan(irc, remoteirc, channel) + if remotechan is None: + continue remoteirc.proto.partClient(remoteirc, user, remotechan, text) if not remoteirc.users[user].channels: remoteirc.proto.quitClient(remoteirc, user, 'Left all shared channels.') From 09772680704df759c9256f8017d31c1fe598d996 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 15:04:57 -0700 Subject: [PATCH 26/38] utils.parseModes: don't error if prefix isn't given, assume + --- utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.py b/utils.py index eebd440..78c96f5 100644 --- a/utils.py +++ b/utils.py @@ -189,7 +189,7 @@ def parseModes(irc, target, args): prefix = mode else: if not prefix: - raise ValueError('Invalid query %r: mode char given without preceding prefix.' % modestring) + prefix = '+' arg = None log.debug('Current mode: %s%s; args left: %s', prefix, mode, args) try: From 57c3a04cda30f3e645e6e431a5772046a2292b45 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 15:05:10 -0700 Subject: [PATCH 27/38] ts6: call SQUIT hooks upon receving it... --- protocols/ts6.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/protocols/ts6.py b/protocols/ts6.py index 6ec42c5..edfd60e 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -442,7 +442,9 @@ def handle_events(irc, data): elif args[0] == 'SQUIT': # What? Charybdis send this in a different format! # <- SQUIT 00A :Remote host closed the connection - handle_squit(irc, args[1], 'SQUIT', [args[1]]) + split_server = args[1] + res = handle_squit(irc, split_server, 'SQUIT', [split_server]) + irc.callHooks([split_server, 'SQUIT', res]) elif args[0] == 'CAPAB': # We only get a list of keywords here. Charybdis obviously assumes that # we know what modes it supports (indeed, this is a standard list). From 254797dcfd6de515430786b406fe8a8d5dc013a5 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 18:05:13 -0700 Subject: [PATCH 28/38] relay: remove servprotect from umode whitelist for now.. --- plugins/relay.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 9932a17..be36ff8 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -454,8 +454,7 @@ whitelisted_cmodes = {'admin', 'allowinvite', 'autoop', 'ban', 'banexception', 'regmoderated', 'secret', 'sslonly', 'stripcolor', 'topiclock', 'voice'} whitelisted_umodes = {'bot', 'hidechans', 'hideoper', 'invisible', 'oper', - 'regdeaf', 'u_stripcolor', 'servprotect', 'u_noctcp', - 'wallops'} + 'regdeaf', 'u_stripcolor', 'u_noctcp', 'wallops'} def relayModes(irc, remoteirc, sender, channel, modes=None): remotechan = findRemoteChan(irc, remoteirc, channel) log.debug('(%s) Relay mode: remotechan for %s on %s is %s', irc.name, channel, irc.name, remotechan) From 868b4503e602692b3cf26518819fcde75da7f5d6 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 23 Jul 2015 19:09:19 -0700 Subject: [PATCH 29/38] protocols: Fix #81 - that was easy! Split data by only one space at a time, not as many spaces as possible. Thanks to @nathan0. --- protocols/inspircd.py | 2 +- protocols/ts6.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 207eb61..46543cc 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -476,7 +476,7 @@ def handle_events(irc, data): # Each server message looks something like this: # :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :v,1SRAAESWE # : ... :final multi word argument - args = data.split() + args = data.split(" ") if not args: # No data?? return diff --git a/protocols/ts6.py b/protocols/ts6.py index edfd60e..0b986d5 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -414,7 +414,7 @@ def handle_events(irc, data): # TS6 messages: # :42X COMMAND arg1 arg2 :final long arg # :42XAAAAAA PRIVMSG #somewhere :hello! - args = data.split() + args = data.split(" ") if not args: # No data?? return From 5c3466bf8235a555cd17713e043760c3134e20b5 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 10:50:39 -0700 Subject: [PATCH 30/38] relay: briefly workaround #74 by reordering our user checks --- plugins/relay.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 9932a17..186e352 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -615,6 +615,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(): + 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 @@ -622,10 +626,6 @@ def relayJoins(irc, channel, users, ts, modes): continue except AttributeError: # Nope, it isn't. pass - if utils.isInternalClient(irc, user) or user not in irc.users: - # We don't need to clone PyLink pseudoclients... That's - # meaningless. - 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 71a3464e8a34967dbaa17e82fcbbcf96b8ea1567 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 10:51:16 -0700 Subject: [PATCH 31/38] relay: also quit users who aren't on any shared channels after KICK --- plugins/relay.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/relay.py b/plugins/relay.py index 186e352..733c9fc 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -420,6 +420,12 @@ def handle_kick(irc, source, command, args): text = "(@%s) %s" % (irc.name, text) remoteirc.proto.kickServer(remoteirc, remoteirc.sid, remotechan, real_target, text) + + if target != irc.pseudoclient.uid and not irc.users[target].channels: + irc.proto.quitClient(irc, target, 'Left all shared channels.') + remoteuser = getLocalUser(irc, target) + del relayusers[remoteuser][irc.name] + utils.add_hook(handle_kick, 'KICK') def handle_chgclient(irc, source, command, args): From a5d71ffb5633a359819921a7fd9ff56d0f1b8a08 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 10:56:23 -0700 Subject: [PATCH 32/38] README: mark TS6 support as experimental Currently, #71 makes this near unusable because of mode flooding whenever anything happens on a slightly large network. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55599ff..f41ef08 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Dependencies currently include: #### Supported IRCds * InspIRCd 2.0.x - module: `inspircd` -* charybdis (3.5.x / git master) - module: `ts6` +* charybdis (3.5.x / git master) - module: `ts6` (**experimental**) ### Installation From 01220b3024d278233afb2ef42219d66dacdb3c02 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 10:59:04 -0700 Subject: [PATCH 33/38] correction: InspIRCd uses RFC1459 case mapping by default --- protocols/inspircd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index 207eb61..b832060 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -10,7 +10,7 @@ from log import log from classes import * -casemapping = 'ascii' +casemapping = 'rfc1459' # Raw commands sent from servers vary from protocol to protocol. Here, we map # non-standard names to our hook handlers, so plugins get the information they need. From 3b67ddfee692a59df96e65e3d69579de144cb151 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 11:13:53 -0700 Subject: [PATCH 34/38] coreplugin: use a more standard "End of WHOIS" message "End of /WHOIS list" seems relatively standard: it's used by InspIRCd, charybdis, and UnrealIRCd. --- coreplugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coreplugin.py b/coreplugin.py index 32d85a1..5f46403 100644 --- a/coreplugin.py +++ b/coreplugin.py @@ -92,5 +92,5 @@ def handle_whois(irc, source, command, args): log.exception('Error caught in WHOIS handler: %s', e) finally: # 318: End of WHOIS. - f(irc, server, 318, source, "%s :End of WHOIS" % nick) + f(irc, server, 318, source, "%s :End of /WHOIS list" % nick) utils.add_hook(handle_whois, 'WHOIS') From a4da9b5324094f63352a5db96e65718f53c2e2e5 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 18:26:31 -0700 Subject: [PATCH 35/38] protocol/relay: fix handling of KILLs sent to non-relay users --- plugins/relay.py | 46 ++++++++++++++++++++++++++----------------- protocols/inspircd.py | 5 +++-- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 2a22bdb..81235dd 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -586,25 +586,35 @@ utils.add_hook(handle_topic, 'TOPIC') def handle_kill(irc, numeric, command, args): target = args['target'] userdata = args['userdata'] - if numeric not in irc.users: - # A server's sending kill? Uh oh, this can't be good. - return - # We don't allow killing over the relay, so we must spawn the client. - # all over again and rejoin it to its channels. realuser = getLocalUser(irc, target) - 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]) - log.debug('(%s) handle_kill: userpair: %s, %s', irc.name, modes, realuser) - client = getRemoteUser(remoteirc, irc, realuser[1]) - irc.proto.sjoinServer(irc, irc.sid, remotechan, [(modes, client)]) - utils.msg(irc, numeric, "Your kill has to %s been blocked " - "because PyLink does not allow killing" - " users over the relay at this time." % \ - userdata.nick, notice=True) + log.debug('(%s) relay handle_kill: realuser is %r', irc.name, realuser) + # Target user was remote: + if realuser and realuser[0] != irc.name: + # We don't allow killing over the relay, so we must respawn the affected + # 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]) + 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)]) + if userdata and numeric in irc.users: + utils.msg(irc, numeric, "Your kill has to %s been blocked " + "because PyLink does not allow killing" + " users over the relay at this time." % \ + userdata.nick, notice=True) + # Target user was local. + else: + # IMPORTANT: some IRCds (charybdis) don't send explicit QUIT messages + # for locally killed clients, while others (inspircd) do! + # If we receive a user object in 'userdata' instead of None, it means + # that the KILL hasn't been handled by a preceding QUIT message. + if userdata: + handle_quit(irc, target, 'KILL', {'text': args['text']}) + utils.add_hook(handle_kill, 'KILL') def relayJoins(irc, channel, users, ts, modes): diff --git a/protocols/inspircd.py b/protocols/inspircd.py index d112eae..4984c1a 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -327,8 +327,9 @@ def handle_privmsg(irc, source, command, args): def handle_kill(irc, source, command, args): killed = args[0] - data = irc.users[killed] - removeClient(irc, killed) + data = irc.users.get(killed) + if data: + removeClient(irc, killed) return {'target': killed, 'text': args[1], 'userdata': data} def handle_kick(irc, source, command, args): From e4da670ae01150adcaca21e2e5de31934a8d52c5 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 18:26:54 -0700 Subject: [PATCH 36/38] relay: propagate SAVE as NICK changes for non-relay users! --- plugins/relay.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/plugins/relay.py b/plugins/relay.py index 81235dd..8944b08 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -865,18 +865,24 @@ def handle_disconnect(irc, numeric, command, args): utils.add_hook(handle_disconnect, "PYLINK_DISCONNECT") def handle_save(irc, numeric, command, args): - # Nick collision! Try to change our nick to the next available normalized - # nick. target = args['target'] - if utils.isInternalClient(irc, target): - realuser = getLocalUser(irc, target) - if realuser is None: - return + 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: + # Nick collision! + # It's one of our relay clients; try to fix our nick to the next + # available normalized nick. remotenet, remoteuser = realuser remoteirc = utils.networkobjects[remotenet] nick = remoteirc.users[remoteuser].nick newnick = normalizeNick(irc, remotenet, nick) irc.proto.nickClient(irc, target, newnick) + else: + # Somebody else on the network (not a PyLink client) had a nick collision; + # relay this as a nick change appropriately. + handle_nick(irc, target, 'SAVE', {'oldnick': None, 'newnick': target}) + utils.add_hook(handle_save, "SAVE") @utils.add_cmd From 146ab5e444a1484758667cd0d34d554ee580843d Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 20:55:48 -0700 Subject: [PATCH 37/38] ts6: support +AOS charybdis extension modes, warning if the IRCd doesn't support them Also, add 'adminonly' (+A) to relay's whitelist. --- plugins/relay.py | 2 +- protocols/ts6.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index 8944b08..e0081ae 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -457,7 +457,7 @@ whitelisted_cmodes = {'admin', 'allowinvite', 'autoop', 'ban', 'banexception', 'limit', 'moderated', 'noctcp', 'noextmsg', 'nokick', 'noknock', 'nonick', 'nonotice', 'op', 'operonly', 'opmoderated', 'owner', 'private', 'regonly', - 'regmoderated', 'secret', 'sslonly', + 'regmoderated', 'secret', 'sslonly', 'adminonly', 'stripcolor', 'topiclock', 'voice'} whitelisted_umodes = {'bot', 'hidechans', 'hideoper', 'invisible', 'oper', 'regdeaf', 'u_stripcolor', 'u_noctcp', 'wallops'} diff --git a/protocols/ts6.py b/protocols/ts6.py index 0b986d5..9b2e300 100644 --- a/protocols/ts6.py +++ b/protocols/ts6.py @@ -476,6 +476,8 @@ def handle_events(irc, data): 'joinflood': 'j', 'largebanlist': 'L', 'permanent': 'P', 'c_noforwards': 'Q', 'stripcolor': 'c', 'allowinvite': 'g', 'opmoderated': 'z', 'noctcp': 'C', + # charybdis-specific modes provided by EXTENSIONS + 'operonly': 'O', 'adminonly': 'A', 'sslonly': 'S', # Now, map all the ABCD type modes: '*A': 'beI', '*B': 'k', '*C': 'l', '*D': 'mnprst'} if 'EX' in caps: @@ -609,3 +611,19 @@ def handle_bmask(irc, numeric, command, args): def handle_whois(irc, numeric, command, args): # <- :42XAAAAAB WHOIS 5PYAAAAAA :pylink-devel return {'target': args[0]} + +def handle_472(irc, numeric, command, args): + # <- :charybdis.midnight.vpn 472 GL|devel O :is an unknown mode char to me + # 472 is sent to us when one of our clients tries to set a mode the server + # doesn't support. In this case, we'll raise a warning to alert the user + # about it. + badmode = args[1] + reason = args[-1] + setter = args[0] + charlist = {'A': 'chm_adminonly', 'O': 'chm_operonly', 'S': 'chm_sslonly'} + if badmode in charlist: + log.warning('(%s) User %r attempted to set channel mode %r, but the ' + 'extension providing it isn\'t loaded! To prevent possible' + ' desyncs, try adding the line "loadmodule "extensions/%s.so";" to ' + 'your IRCd configuration.', irc.name, setter, badmode, + charlist[badmode]) From ee1267f06efdd1fa78c1c19fb2df88ba41016261 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 24 Jul 2015 21:12:33 -0700 Subject: [PATCH 38/38] relay: fix use of incorrect variable in logging --- plugins/relay.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/relay.py b/plugins/relay.py index e0081ae..8c9f911 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -446,7 +446,8 @@ def handle_chgclient(irc, source, command, args): remoteirc.proto.updateClient(remoteirc, user, field, text) except NotImplementedError: # IRCd doesn't support changing the field we want log.debug('(%s) Ignoring changing field %r of %s on %s (for %s/%s);' - 'remote IRCd doesn\'t support it', irc.name, field, user, target, remotenet, irc.name) + ' remote IRCd doesn\'t support it', irc.name, field, + user, target, netname, irc.name) continue for c in ('CHGHOST', 'CHGNAME', 'CHGIDENT'):