diff --git a/protocols/hybrid.py b/protocols/hybrid.py index c9a4766..6d2fccc 100644 --- a/protocols/hybrid.py +++ b/protocols/hybrid.py @@ -9,17 +9,15 @@ import utils from log import log from classes import * -from ts6_common import TS6BaseProtocol +from ts6 import TS6Protocol -class HybridProtocol(TS6BaseProtocol): +class HybridProtocol(TS6Protocol): def __init__(self, irc): - super(HybridProtocol, self).__init__(irc) + # This protocol module inherits from the TS6 protocol. + super().__init__(irc) + self.casemapping = 'ascii' - self.sidgen = utils.TS6SIDGenerator(self.irc) - self.uidgen = {} - self.caps = {} - self.hook_map = {'EOB': 'ENDBURST', 'TBURST': 'TOPIC'} def connect(self): @@ -105,7 +103,6 @@ class HybridProtocol(TS6BaseProtocol): server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None, manipulatable=False): """Spawns a client with nick on the given IRC connection. - Note: No nick collision / valid nickname checks are done here; it is up to plugins to make sure they don't introduce anything invalid.""" server = server or self.irc.sid @@ -146,119 +143,7 @@ class HybridProtocol(TS6BaseProtocol): else: raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field) - def join(self, client, channel): - """Joins a PyLink client to a channel.""" - channel = utils.toLower(self.irc, channel) - if not self.irc.isInternalClient(client): - raise LookupError('No such PyLink client exists.') - self._send(client, "JOIN 0 %s +" % channel) - self.irc.channels[channel].users.add(client) - self.irc.users[client].channels.add(channel) - - def invite(self, numeric, target, channel): - """Sends an INVITE from a PyLink client..""" - if not self.irc.isInternalClient(numeric): - raise LookupError('No such PyLink client exists.') - self._send(numeric, 'INVITE %s %s %d' % (target, channel, int(time.time()))) - - def mode(self, numeric, target, modes, ts=None): - """Sends mode changes from a PyLink client/server.""" - # c <- :0UYAAAAAA TMODE 0 #a +o 0T4AAAAAC - # u <- :0UYAAAAAA MODE 0UYAAAAAA :-Facdefklnou - - if (not self.irc.isInternalClient(numeric)) and \ - (not self.irc.isInternalServer(numeric)): - raise LookupError('No such PyLink client/server exists.') - - utils.applyModes(self.irc, target, modes) - modes = list(modes) - - if utils.isChannel(target): - ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts - # TMODE: - # parameters: channelTS, channel, cmode changes, opt. cmode parameters... - - # On output, at most ten cmode parameters should be sent; if there are more, - # multiple TMODE messages should be sent. - # TODO(dan): this may not be required on Hybrid? - while modes[:9]: - # Seriously, though. If you send more than 10 mode parameters in - # a line, charybdis will silently REJECT the entire command! - joinedmodes = utils.joinModes(modes = [m for m in modes[:9] if m[0] not in self.irc.cmodes['*A']]) - modes = modes[9:] - self._send(numeric, 'TMODE %s %s %s' % (ts, target, joinedmodes)) - else: - joinedmodes = utils.joinModes(modes) - self._send(numeric, 'MODE %s %s' % (target, joinedmodes)) - - def ping(self, source=None, target=None): - """Sends a PING to a target server. Periodic PINGs are sent to our uplink - automatically by the Irc() internals; plugins shouldn't have to use this.""" - source = source or self.irc.sid - target = target or self.irc.uplink - if not (target is None or source is None): - self._send(source, 'PING :%s' % (source,)) - - def handle_events(self, data): - """Event handler for the Hybrid protocol. - - This passes most commands to the various handle_ABCD() functions - elsewhere defined protocol modules, coersing various sender prefixes - from nicks and server names to UIDs and SIDs respectively, - whenever possible. - - Commands sent without an explicit sender prefix will have them set to - the SID of the uplink server. - """ - data = data.split(" ") - try: # Message starts with a SID/UID prefix. - args = self.parseTS6Args(data) - sender = args[0] - command = args[1] - args = args[2:] - # If the sender isn't in UID format, try to convert it automatically. - # Unreal's protocol, for example, isn't quite consistent with this yet! - sender_server = self._getSid(sender) - if sender_server in self.irc.servers: - # Sender is a server when converted from name to SID. - numeric = sender_server - else: - # Sender is a user. - numeric = self._getNick(sender) - - # parseTS6Args() will raise IndexError if the TS6 sender prefix is missing. - except IndexError: - # Raw command without an explicit sender; assume it's being sent by our uplink. - args = self.parseArgs(data) - numeric = self.irc.uplink - command = args[0] - args = args[1:] - - try: - command = self.hook_map.get(command.upper(), command) - func = getattr(self, 'handle_'+command.lower()) - except AttributeError: # unhandled command - # self._send(self.irc.sid, 'ERROR', 'Unknown Command') - print('Unknown command.\nOffending line: {}'.format(data)) - exit(1) - else: - log.debug('(%s) Handling event %s - %s - %s', self.irc.name, numeric, command, str(args)) - parsed_args = func(numeric, command, args) - if parsed_args is not None: - return [numeric, command, parsed_args] - # command handlers - def handle_pass(self, numeric, command, args): - # <- PASS $somepassword TS 6 42X - if args[0] != self.irc.serverdata['recvpass']: - raise ProtocolError("Error: RECVPASS from uplink does not match configuration!") - ver = args[args.index('TS') + 1] - if ver != '6': - raise ProtocolError("Remote protocol version {} is too old! Is this even TS6?".format()) - numeric = args[3] - log.debug('(%s) Found uplink SID as %r', self.irc.name, numeric) - self.irc.servers[numeric] = IrcServer(None, 'unknown') - self.irc.uplink = numeric def handle_capab(self, numeric, command, args): # We only get a list of keywords here. Hybrid obviously assumes that @@ -272,16 +157,6 @@ class HybridProtocol(TS6BaseProtocol): log.debug('(%s) self.irc.connected set!', self.irc.name) self.irc.connected.set() - def handle_server(self, numeric, command, args): - # <- SERVER charybdis.midnight.vpn 1 :charybdis test server - sname = args[0].lower() - log.debug('(%s) Found uplink server name as %r', self.irc.name, sname) - self.irc.servers[self.irc.uplink].name = sname - self.irc.servers[self.irc.uplink].desc = args[2] - # According to the TS6 protocol documentation, we should send SVINFO - # when we get our uplink's SERVER command. - self.irc.send('SVINFO 6 6 0 :%s' % int(time.time())) - def handle_uid(self, numeric, command, args): """Handles incoming UID commands (user introduction).""" # <- :0UY UID dan 1 1451041551 +Facdeiklosuw ~ident localhost 127.0.0.1 0UYAAAAAB * :realname @@ -303,66 +178,6 @@ class HybridProtocol(TS6BaseProtocol): self.irc.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC_Operator'}]) return {'uid': uid, 'ts': ts, 'nick': nick, 'realname': realname, 'host': host, 'ident': ident, 'ip': ip} - def handle_join(self, numeric, command, args): - """Handles incoming channel JOINs.""" - # parameters: channelTS, channel, '+' (a plus sign) - # <- :0UYAAAAAF JOIN 0 #channel + - ts = int(args[0]) - uid = numeric - channel = args[1] - if channel == '0': - # /join 0; part the user from all channels - oldchans = self.irc.users[uid].channels.copy() - log.debug('(%s) Got /join 0 from %r, channel list is %r', - self.irc.name, uid, oldchans) - for channel in oldchans: - self.irc.channels[channel].users.discard(uid) - self.irc.users[uid].channels.discard(channel) - else: - channel = utils.toLower(self.irc, args[1]) - self.updateTS(channel, ts) - return {'channel': channel, 'users': [uid], 'modes': - self.irc.channels[channel].modes, 'ts': ts} - - def handle_sjoin(self, numeric, command, args): - """Handles incoming channel SJOINs.""" - # parameters: channelTS, channel, modes, prefixed uids - # <- :0UY SJOIN 1451041566 #channel +nt :@0UYAAAAAB - ts = int(args[0]) - uids = args[3].split() - users = {} - umodes = ['+'] - modeprefixes = {v: k for k, v in self.irc.prefixmodes.items()} - for uid in uids: - modes = '' - while uid and uid[0] in modeprefixes: - modes += uid[0] - uid = uid[1:] - for mode in modes: - umodes[0] += modeprefixes[mode] - umodes.append(uid) - if args[1] == '0': - # /join 0; part the user from all channels - for uid in users: - oldchans = self.irc.users[uid].channels.copy() - log.debug('(%s) Got /join 0 from %r, channel list is %r', - self.irc.name, uid, oldchans) - for channel in oldchans: - self.irc.channels[channel].users.discard(uid) - self.irc.users[uid].channels.discard(channel) - # return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'} - else: - channel = utils.toLower(self.irc, args[1]) - self.updateTS(channel, ts) - parsedmodes = utils.parseModes(self.irc, channel, [args[2]]) - utils.applyModes(self.irc, channel, parsedmodes) - parsedmodes = utils.parseModes(self.irc, channel, umodes) - utils.applyModes(self.irc, channel, parsedmodes) - # We send users and modes here because SJOIN and JOIN both use one hook, - # for simplicity's sake (with plugins). - return {'channel': channel, 'users': uids, 'modes': - self.irc.channels[channel].modes, 'ts': ts} - def handle_tburst(self, numeric, command, args): """Handles incoming topic burst (TBURST) commands.""" # <- :0UY TBURST 1459308205 #testchan 1459309379 dan!~d@localhost :sdf @@ -374,77 +189,6 @@ class HybridProtocol(TS6BaseProtocol): self.irc.channels[channel].topicset = True return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic} - def handle_whois(self, numeric, command, args): - """Handles incoming WHOIS commands. - - Note: The core of WHOIS handling is done by coreplugin.py - (IRCd-independent), and not here.""" - # <- :0UYAAAAAH WHOIS 0T4AAAAAA :pylink - return {'target': args[0]} - - def handle_ping(self, source, command, args): - """Handles incoming PING commands.""" - # PING: - # source: any - # parameters: origin, opt. destination server - # PONG: - # source: server - # parameters: origin, destination - - # Sends a PING to the destination server, which will reply with a PONG. If the - # destination server parameter is not present, the server receiving the message - # must reply. - try: - destination = args[1] - except IndexError: - destination = self.irc.sid - if self.irc.isInternalServer(destination): - self._send(destination, 'PONG %s :%s' % (self.irc.servers[destination].name, source)) - - def handle_pong(self, source, command, args): - """Handles incoming PONG commands.""" - log.debug('(%s) Ping received from %s for %s.', self.irc.name, source, args[1]) - if source == self.irc.uplink and args[1] == self.irc.sid: - log.debug('(%s) Set self.irc.lastping.', self.irc.name) - self.irc.lastping = time.time() - - def handle_mode(self, numeric, command, args): - # <- :0UYAAAAAD MODE 0UYAAAAAD :-i - target = args[0] - modestrings = args[1:] - changedmodes = utils.parseModes(self.irc, target, modestrings) - utils.applyModes(self.irc, target, changedmodes) - # Call the OPERED UP hook if +o is being set. - if ('+o', None) in changedmodes: - otype = 'IRC Operator' - self.irc.callHooks([target, 'CLIENT_OPERED', {'text': otype}]) - return {'target': target, 'modes': changedmodes} - - def handle_tmode(self, numeric, command, args): - # <- :0UYAAAAAD TMODE 0 #a +h 0UYAAAAAD - channel = utils.toLower(self.irc, args[1]) - oldobj = self.irc.channels[channel].deepcopy() - modes = args[2:] - changedmodes = utils.parseModes(self.irc, channel, modes) - utils.applyModes(self.irc, channel, changedmodes) - ts = int(args[0]) - if ts > 0: - self.updateTS(channel, ts) - return {'target': channel, 'modes': changedmodes, 'ts': ts, - 'oldchan': oldobj} - - def handle_invite(self, numeric, command, args): - """Handles incoming INVITEs.""" - # <- :0UYAAAAAA INVITE 0T4AAAAAA #testchan 1459316899 - target = args[0] - channel = args[1].lower() - try: - ts = args[3] - except IndexError: - ts = int(time.time()) - # We don't actually need to process this; it's just something plugins/hooks can use - return {'target': target, 'channel': channel, 'ts': ts} - def handle_svstag(self, numeric, command, args): tag = args[2] if tag in ['313']: @@ -455,12 +199,5 @@ class HybridProtocol(TS6BaseProtocol): log.debug('(%s) end of burst received', self.irc.name) return {} - # empty handlers - # TODO: there's a better way to do this - def handle_globops(self, numeric, command, args): - pass - - def handle_svinfo(self, numeric, command, args): - pass Class = HybridProtocol