From c52d542ed8c3f34ddc6dade7646958777b94e140 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 16 Jul 2016 22:42:17 -0700 Subject: [PATCH] Initial Clientbot stub, with very rudimentary user handling (#144) --- protocols/clientbot.py | 121 +++++++++++++++++++++++++++++++++++++++++ utils.py | 18 ++++++ 2 files changed, 139 insertions(+) create mode 100644 protocols/clientbot.py diff --git a/protocols/clientbot.py b/protocols/clientbot.py new file mode 100644 index 0000000..be0e8b0 --- /dev/null +++ b/protocols/clientbot.py @@ -0,0 +1,121 @@ +import time + +from pylinkirc import utils, conf +from pylinkirc.log import log +from pylinkirc.classes import Protocol, IrcUser, IrcServer + +class ClientbotWrapperProtocol(Protocol): + def __init__(self, irc): + super().__init__(irc) + + # FIXME: Grab this from 005 / RPL_ISUPPORT instead of hardcoding. + self.casemapping = 'ascii' + + self.caps = {} + + # Initialize counter-based pseudo UID generators + self.uidgen = utils.PUIDGenerator('PUID') + self.sidgen = utils.PUIDGenerator('PSID') + + def connect(self): + """Initializes a connection to a server.""" + ts = self.irc.start_ts + f = self.irc.send + + # TODO: fetch channel/user/prefix modes from RPL_ISUPPORT. + #self.irc.prefixmodes = {'q': '~', 'a': '&', 'o': '@', 'h': '%', 'v': '+'} + + # HACK: Replace the SID from the config options with our own. + old_sid = self.irc.sid + self.irc.sid = sid = self.sidgen.next_uid() + self.irc.servers[sid] = self.irc.servers[old_sid] + del self.irc.servers[old_sid] + + sendpass = self.irc.serverdata.get("sendpass") + if sendpass: + f('PASS %s' % sendpass) + + # This is a really gross hack to get the defined NICK/IDENT/HOST/GECOS. + # But this connection stuff is done before any of the spawnClient stuff in + # services_support fires. + f('NICK %s' % (self.irc.serverdata.get('pylink_nick') or conf.conf["bot"].get("nick", "PyLink"))) + ident = self.irc.serverdata.get('pylink_ident') or conf.conf["bot"].get("ident", "pylink") + f('USER %s %s 0.0.0.0 %s' % (ident, ident, + # TODO: per net realnames or hostnames aren't implemented yet. + conf.conf["bot"].get("realname", "PyLink Clientbot"))) + + def spawnClient(self, nick, ident='null', host='null', realhost=None, modes=set(), + server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None, + manipulatable=False): + """ + STUB: Pretends to spawn a new client with a subset of the given options. + """ + + uid = self.uidgen.next_uid() + + ts = ts or int(time.time()) + realname = realname or '' + log.debug('(%s) spawnClient stub called, saving nick %s as PUID %s', self.irc.name, nick, uid) + u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname) + log.debug('(%s) self.irc.users: %s', self.irc.name, self.irc.users) + self.irc.servers[self.irc.sid].users.add(uid) + return u + + def spawnServer(self, name, sid=None, uplink=None, desc=None, endburst_delay=0): + """ + STUB: Pretends to spawn a new server with a subset of the given options. + """ + name = name.lower() + sid = self.sidgen.next_sid() + self.irc.servers[sid] = IrcServer(uplink, name) + return sid + + def join(self, *args): + return + + def ping(self, *args): + return + + def handle_events(self, data): + """Event handler for the RFC1459 (clientbot) protocol. + """ + data = data.split(" ") + args = self.parsePrefixedArgs(data) + sender = args[0] + + # PyLink as a services framework expects UIDs and SIDs for everythiung. Since we connect + # as a bot here, there's no explicit user introduction, so we're going to generate + # pseudo-uids and pseudo-sids as we see prefixes. + log.debug('(%s) handle_events: sender is %s', self.irc.name, sender) + if '!' not in sender: + # Sender is a server name. + idsource = self._getSid(sender) + if idsource not in self.irc.servers: + idsource = self.spawnServer(sender) + else: + # Sender is a nick!user@host prefix. Split it into its relevant parts. + nick, identhost = sender.split('!', 1) + idsource = self.irc.nickToUid(nick) + if not idsource: + ident, host = identhost.split('@', 1) + idsource = self.spawnClient(nick, ident, host).uid + log.debug('(%s) handle_events: idsource is %s', self.irc.name, idsource) + + command = args[1] + args = args[2:] + try: + func = getattr(self, 'handle_'+command.lower()) + except AttributeError: # unhandled command + pass + else: + parsed_args = func(idsource, command, args) + if parsed_args is not None: + return [idsource, command, parsed_args] + + def handle_005(self, source, command, args): + """ + Handles 005 / RPL_ISUPPORT. + """ + return {'parse_as': 'ENDBURST'} + +Class = ClientbotWrapperProtocol diff --git a/utils.py b/utils.py index a79f8e8..7d94e31 100644 --- a/utils.py +++ b/utils.py @@ -64,6 +64,24 @@ class IncrementalUIDGenerator(): self.increment() return uid +class PUIDGenerator(): + """ + Pseudo UID Generator module, using a prefix and a simple counter. + """ + + def __init__(self, prefix): + self.prefix = prefix + self.counter = 0 + + def next_uid(self): + """ + Generates the next PUID. + """ + uid = '%s@%s' % (self.prefix, self.counter) + self.counter += 1 + return uid + next_sid = next_uid + def add_cmd(func, name=None, **kwargs): """Binds an IRC command function to the given command name.""" world.services['pylink'].add_cmd(func, name=name, **kwargs)