From 0bb4a35c6ff4c6455f1171e2a95e56f8e33ab67d Mon Sep 17 00:00:00 2001 From: James Lu Date: Mon, 21 Aug 2017 23:05:27 -0700 Subject: [PATCH] Support pre-auth irc.pseudoclient enumeration & configurable altnicks Closes #516. Closes #288. --- coremods/service_support.py | 56 +++++++++++++++++++------------------ protocols/clientbot.py | 50 ++++++++++++++++++--------------- 2 files changed, 56 insertions(+), 50 deletions(-) diff --git a/coremods/service_support.py b/coremods/service_support.py index 2448548..9b8b2bf 100644 --- a/coremods/service_support.py +++ b/coremods/service_support.py @@ -22,34 +22,36 @@ def spawn_service(irc, source, command, args): # Get the ServiceBot object. sbot = world.services[name] - nick = sbot.get_nick(irc) - ident = sbot.get_ident(irc) - host = sbot.get_host(irc) - realname = sbot.get_realname(irc) - - # Spawning service clients with these umodes where supported. servprotect usage is a - # configuration option. - preferred_modes = ['oper', 'hideoper', 'hidechans', 'invisible', 'bot'] - modes = [] - - if conf.conf['pylink'].get('protect_services'): - preferred_modes.append('servprotect') - - for mode in preferred_modes: - mode = irc.umodes.get(mode) - if mode: - modes.append((mode, None)) - - # Track the service's UIDs on each network. - log.debug('(%s) spawn_service: Using nick %s for service %s', irc.name, nick, name) - u = irc.nick_to_uid(nick) - if u and irc.is_internal_client(u): # If an internal client exists, reuse it. - log.debug('(%s) spawn_service: Using existing client %s/%s', irc.name, u, nick) - userobj = irc.users[u] + if name == 'pylink' and irc.pseudoclient: + # irc.pseudoclient already exists, for protocols like clientbot + log.debug('(%s) spawn_service: Using existing nick %r for service %r', irc.name, irc.pseudoclient.nick, name) + userobj = irc.pseudoclient + userobj.opertype = "PyLink Service" + userobj.manipulatable = sbot.manipulatable else: - log.debug('(%s) spawn_service: Spawning new client %s', irc.name, nick) + # No client exists, spawn a new one + nick = sbot.get_nick(irc) + ident = sbot.get_ident(irc) + host = sbot.get_host(irc) + realname = sbot.get_realname(irc) + + # Spawning service clients with these umodes where supported. servprotect usage is a + # configuration option. + preferred_modes = ['oper', 'hideoper', 'hidechans', 'invisible', 'bot'] + modes = [] + + if conf.conf['pylink'].get('protect_services'): + preferred_modes.append('servprotect') + + for mode in preferred_modes: + mode = irc.umodes.get(mode) + if mode: + modes.append((mode, None)) + + # Track the service's UIDs on each network. + log.debug('(%s) spawn_service: Spawning new client %s for service %s', irc.name, nick, name) userobj = irc.spawn_client(nick, ident, host, modes=modes, opertype="PyLink Service", - realname=realname, manipulatable=sbot.manipulatable) + realname=realname, manipulatable=sbot.manipulatable) # Store the service name in the User object for easier access. userobj.service = name @@ -58,7 +60,7 @@ def spawn_service(irc, source, command, args): # Special case: if this is the main PyLink client being spawned, # assign this as irc.pseudoclient. - if name == 'pylink': + if name == 'pylink' and not irc.pseudoclient: log.debug('(%s) spawn_service: irc.pseudoclient set to UID %s', irc.name, u) irc.pseudoclient = userobj diff --git a/protocols/clientbot.py b/protocols/clientbot.py index 2883ad1..82a1f3f 100644 --- a/protocols/clientbot.py +++ b/protocols/clientbot.py @@ -53,6 +53,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): self.handle_463 = self.handle_464 = self.handle_465 = self.handle_error self._use_builtin_005_handling = True + self._nick_fails = 0 self.hook_map = {'ACCOUNT': 'CLIENT_SERVICES_LOGIN'} @@ -92,14 +93,19 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): self._cap_timer = threading.Timer(self.serverdata.get('sasl_timeout') or 15, _do_cap_end_wrapper) self._cap_timer.start() - # This is a really gross hack to get the defined NICK/IDENT/HOST/GECOS. - # But this connection stuff is done before any of the spawn_client stuff in - # services_support fires. - self.conf_nick = self.serverdata.get('pylink_nick') or conf.conf["bot"].get("nick", "PyLink") - f('NICK %s' % (self.conf_nick)) - ident = self.serverdata.get('pylink_ident') or conf.conf["bot"].get("ident", "pylink") - f('USER %s 8 * :%s' % (ident, # TODO: per net realnames or hostnames aren't implemented yet. - conf.conf["bot"].get("realname", "PyLink Clientbot"))) + # Log in to IRC and set our irc.pseudoclient object. + sbot = world.services['pylink'] + self._nick_fails = 0 + + nick = sbot.get_nick(self) + ident = sbot.get_ident(self) + realname = sbot.get_realname(self) + + f('NICK %s' % nick) + f('USER %s 8 * :%s' % (ident, realname)) + self.pseudoclient = User(nick, int(time.time()), self.uidgen.next_uid(prefix='@ClientbotInternal'), self.sid, + ident=ident, realname=realname) + self.users[self.pseudoclient.uid] = self.pseudoclient # Note: clientbot clients are initialized with umode +i by default def spawn_client(self, nick, ident='unknown', host='unknown.host', realhost=None, modes={('i', None)}, @@ -116,7 +122,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): log.debug('(%s) spawn_client stub called, saving nick %s as PUID %s', self.name, nick, uid) u = self.users[uid] = User(nick, ts, uid, server, ident=ident, host=host, realname=realname, - manipulatable=manipulatable, realhost=realhost, ip=ip) + manipulatable=manipulatable, realhost=realhost, ip=ip) self.servers[server].users.add(uid) self.apply_modes(uid, modes) @@ -794,12 +800,12 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): def handle_433(self, source, command, args): # <- :millennium.overdrivenetworks.com 433 * ice :Nickname is already in use. - # HACK: I don't like modifying the config entries raw, but this is difficult because - # irc.pseudoclient doesn't exist as an attribute until we get run the ENDBURST stuff - # in service_support (this is mapped to 005 here). - self.conf_nick += '_' - self.serverdata['pylink_nick'] = self.conf_nick - self.send('NICK %s' % self.conf_nick) + + self._nick_fails += 1 + newnick = self.pseudoclient.nick = world.services['pylink'].get_nick(self, fails=self._nick_fails) + log.debug('(%s) _nick_fails = %s, trying new nick %r', self.name, self._nick_fails, newnick) + + self.send('NICK %s' % newnick) handle_432 = handle_437 = handle_433 def handle_account(self, source, command, args): @@ -937,17 +943,15 @@ class ClientbotWrapperProtocol(IRCCommonProtocol): """Handles NICK changes.""" # <- :GL|!~GL@127.0.0.1 NICK :GL_ - if not self.pseudoclient: + if not self.connected.is_set(): # We haven't properly logged on yet, so any initial NICK should be treated as a forced - # nick change for US. For example, this clause is used to handle forced nick changes + # nick change for us. For example, this clause is used to handle forced nick changes # sent by ZNC, when the login nick and the actual IRC nick of the bouncer differ. - - # HACK: change the nick config entry so services_support knows what our main - # pseudoclient is called. - oldnick = self.serverdata['pylink_nick'] - self.serverdata['pylink_nick'] = self.conf_nick = args[0] - log.debug('(%s) Pre-auth FNC: Forcing configured nick to %s from %s', self.name, args[0], oldnick) + self.pseudoclient.nick = args[0] + log.debug('(%s) Pre-auth FNC: Changing our nick to %s', self.name, args[0]) return + elif source == self.pseudoclient.uid: + self._nick_fails = 0 # Our last nick change succeeded. oldnick = self.users[source].nick self.users[source].nick = args[0]