3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-12-24 11:42:51 +01:00

classes, protocols: convert self.irc usage to self

This commit is contained in:
James Lu 2017-06-25 02:03:12 -07:00
parent eef7a73ce9
commit 7814914a05
11 changed files with 996 additions and 996 deletions

View File

@ -421,18 +421,18 @@ class PyLinkNetworkCore(utils.DeprecatedAttributesObject, utils.CamelCaseToSnake
def remove_client(self, numeric):
"""Internal function to remove a client from our internal state."""
for c, v in self.irc.channels.copy().items():
for c, v in self.channels.copy().items():
v.removeuser(numeric)
# Clear empty non-permanent channels.
if not (self.irc.channels[c].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[c].modes)):
del self.irc.channels[c]
if not (self.channels[c].users or ((self.cmodes.get('permanent'), None) in self.channels[c].modes)):
del self.channels[c]
assert numeric not in v.users, "IrcChannel's removeuser() is broken!"
sid = self.irc.get_server(numeric)
log.debug('Removing client %s from self.irc.users', numeric)
del self.irc.users[numeric]
log.debug('Removing client %s from self.irc.servers[%s].users', numeric, sid)
self.irc.servers[sid].users.discard(numeric)
sid = self.get_server(numeric)
log.debug('Removing client %s from self.users', numeric)
del self.users[numeric]
log.debug('Removing client %s from self.servers[%s].users', numeric, sid)
self.servers[sid].users.discard(numeric)
class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
@ -1065,46 +1065,46 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
modes = []
def _clear():
log.debug("(%s) Clearing local modes from channel %s due to TS change", self.irc.name,
log.debug("(%s) Clearing local modes from channel %s due to TS change", self.name,
channel)
self.irc.channels[channel].modes.clear()
for p in self.irc.channels[channel].prefixmodes.values():
self.channels[channel].modes.clear()
for p in self.channels[channel].prefixmodes.values():
for user in p.copy():
if not self.irc.is_internal_client(user):
if not self.is_internal_client(user):
p.discard(user)
def _apply():
if modes:
log.debug("(%s) Applying modes on channel %s (TS ok)", self.irc.name,
log.debug("(%s) Applying modes on channel %s (TS ok)", self.name,
channel)
self.irc.apply_modes(channel, modes)
self.apply_modes(channel, modes)
# Use a lock so only one thread can change a channel's TS at once: this prevents race
# conditions from desyncing the channel list.
with self.ts_lock:
our_ts = self.irc.channels[channel].ts
our_ts = self.channels[channel].ts
assert type(our_ts) == int, "Wrong type for our_ts (expected int, got %s)" % type(our_ts)
assert type(their_ts) == int, "Wrong type for their_ts (expected int, got %s)" % type(their_ts)
# Check if we're the mode sender based on the UID / SID given.
our_mode = self.irc.is_internal_client(sender) or self.irc.is_internal_server(sender)
our_mode = self.is_internal_client(sender) or self.is_internal_server(sender)
log.debug("(%s/%s) our_ts: %s; their_ts: %s; is the mode origin us? %s", self.irc.name,
log.debug("(%s/%s) our_ts: %s; their_ts: %s; is the mode origin us? %s", self.name,
channel, our_ts, their_ts, our_mode)
if their_ts == our_ts:
log.debug("(%s/%s) remote TS of %s is equal to our %s; mode query %s",
self.irc.name, channel, their_ts, our_ts, modes)
self.name, channel, their_ts, our_ts, modes)
# Their TS is equal to ours. Merge modes.
_apply()
elif (their_ts < our_ts):
if their_ts < 750000:
log.warning('(%s) Possible desync? Not setting bogus TS %s on channel %s', self.irc.name, their_ts, channel)
log.warning('(%s) Possible desync? Not setting bogus TS %s on channel %s', self.name, their_ts, channel)
else:
log.debug('(%s) Resetting channel TS of %s from %s to %s (remote has lower TS)',
self.irc.name, channel, our_ts, their_ts)
self.irc.channels[channel].ts = their_ts
self.name, channel, our_ts, their_ts)
self.channels[channel].ts = their_ts
# Remote TS was lower and we're receiving modes. Clear the modelist and apply theirs.
@ -1115,7 +1115,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
def _get_SID(self, sname):
"""Returns the SID of a server with the given name, if present."""
name = sname.lower()
for k, v in self.irc.servers.items():
for k, v in self.servers.items():
if v.name.lower() == name:
return k
else:
@ -1125,7 +1125,7 @@ class PyLinkNetworkCoreWithUtils(PyLinkNetworkCore):
def _get_UID(self, target):
"""Converts a nick argument to its matching UID. This differs from irc.nick_to_uid()
in that it returns the original text instead of None, if no matching nick is found."""
target = self.irc.nick_to_uid(target) or target
target = self.nick_to_uid(target) or target
return target
_getUid = _get_UID

View File

@ -45,9 +45,9 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
"""
Returns the real nick for the given PUID.
"""
if uid in self.irc.users:
nick = self.irc.users[uid].nick
log.debug('(%s) Mangling target PUID %s to nick %s', self.irc.name, uid, nick)
if uid in self.users:
nick = self.users[uid].nick
log.debug('(%s) Mangling target PUID %s to nick %s', self.name, uid, nick)
return nick
return uid
@ -58,11 +58,11 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
self.sidgen = utils.PUIDGenerator('PSID')
self.has_eob = False
ts = self.irc.start_ts
f = lambda text: self.irc.send(text, queue=False)
ts = self.start_ts
f = lambda text: self.send(text, queue=False)
# Enumerate our own server
self.irc.sid = self.sidgen.next_sid()
self.sid = self.sidgen.next_sid()
# Clear states from last connect
self.who_received.clear()
@ -71,7 +71,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
self.ircv3_caps.clear()
self.ircv3_caps_available.clear()
sendpass = self.irc.serverdata.get("sendpass")
sendpass = self.serverdata.get("sendpass")
if sendpass:
f('PASS %s' % sendpass)
@ -81,17 +81,17 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# never replied to).
def capEnd():
log.info('(%s) Skipping SASL due to timeout; are the IRCd and services configured '
'properly?', self.irc.name)
'properly?', self.name)
self.capEnd()
self._cap_timer = threading.Timer(self.irc.serverdata.get('sasl_timeout') or 15, capEnd)
self._cap_timer = threading.Timer(self.serverdata.get('sasl_timeout') or 15, capEnd)
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 spawnClient stuff in
# services_support fires.
self.conf_nick = self.irc.serverdata.get('pylink_nick') or conf.conf["bot"].get("nick", "PyLink")
self.conf_nick = self.serverdata.get('pylink_nick') or conf.conf["bot"].get("nick", "PyLink")
f('NICK %s' % (self.conf_nick))
ident = self.irc.serverdata.get('pylink_ident') or conf.conf["bot"].get("ident", "pylink")
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")))
@ -103,17 +103,17 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
STUB: Pretends to spawn a new client with a subset of the given options.
"""
server = server or self.irc.sid
server = server or self.sid
uid = self.uidgen.next_uid(prefix=nick)
ts = ts or int(time.time())
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, server, ident=ident, host=host, realname=realname,
log.debug('(%s) spawnClient stub called, saving nick %s as PUID %s', self.name, nick, uid)
u = self.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
manipulatable=manipulatable, realhost=realhost, ip=ip)
self.irc.servers[server].users.add(uid)
self.servers[server].users.add(uid)
self.irc.applyModes(uid, modes)
self.applyModes(uid, modes)
return u
@ -123,85 +123,85 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
"""
name = name.lower()
sid = self.sidgen.next_sid(prefix=name)
self.irc.servers[sid] = IrcServer(uplink, name, internal=internal)
self.servers[sid] = IrcServer(uplink, name, internal=internal)
return sid
def away(self, source, text):
"""STUB: sets away messages for clients internally."""
log.debug('(%s) away: target is %s, internal client? %s', self.irc.name, source, self.irc.isInternalClient(source))
log.debug('(%s) away: target is %s, internal client? %s', self.name, source, self.isInternalClient(source))
if self.irc.users[source].away != text:
if not self.irc.isInternalClient(source):
log.debug('(%s) away: sending AWAY hook from %s with text %r', self.irc.name, source, text)
self.irc.callHooks([source, 'AWAY', {'text': text}])
if self.users[source].away != text:
if not self.isInternalClient(source):
log.debug('(%s) away: sending AWAY hook from %s with text %r', self.name, source, text)
self.callHooks([source, 'AWAY', {'text': text}])
self.irc.users[source].away = text
self.users[source].away = text
def invite(self, client, target, channel):
"""Invites a user to a channel."""
self.irc.send('INVITE %s %s' % (self.irc.getFriendlyName(target), channel))
self.send('INVITE %s %s' % (self.getFriendlyName(target), channel))
def join(self, client, channel):
"""STUB: Joins a user to a channel."""
channel = self.irc.toLower(channel)
channel = self.toLower(channel)
# Only joins for the main PyLink client are actually forwarded. Others are ignored.
# Note: we do not automatically add our main client to the channel state, as we
# rely on the /NAMES reply to sync it up properly.
if self.irc.pseudoclient and client == self.irc.pseudoclient.uid:
self.irc.send('JOIN %s' % channel)
if self.pseudoclient and client == self.pseudoclient.uid:
self.send('JOIN %s' % channel)
# Send /names and /who requests right after
self.irc.send('MODE %s' % channel)
self.irc.send('NAMES %s' % channel)
self.irc.send('WHO %s' % channel)
self.send('MODE %s' % channel)
self.send('NAMES %s' % channel)
self.send('WHO %s' % channel)
else:
self.irc.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel)
self.channels[channel].users.add(client)
self.users[client].channels.add(channel)
log.debug('(%s) join: faking JOIN of client %s/%s to %s', self.irc.name, client,
self.irc.getFriendlyName(client), channel)
self.irc.callHooks([client, 'CLIENTBOT_JOIN', {'channel': channel}])
log.debug('(%s) join: faking JOIN of client %s/%s to %s', self.name, client,
self.getFriendlyName(client), channel)
self.callHooks([client, 'CLIENTBOT_JOIN', {'channel': channel}])
def kick(self, source, channel, target, reason=''):
"""Sends channel kicks."""
log.debug('(%s) kick: checking if target %s (nick: %s) is an internal client? %s',
self.irc.name, target, self.irc.getFriendlyName(target),
self.irc.isInternalClient(target))
if self.irc.isInternalClient(target):
self.name, target, self.getFriendlyName(target),
self.isInternalClient(target))
if self.isInternalClient(target):
# Target was one of our virtual clients. Just remove them from the state.
self.handle_part(target, 'KICK', [channel, reason])
# Send a KICK hook for message formatting.
self.irc.callHooks([source, 'CLIENTBOT_KICK', {'channel': channel, 'target': target, 'text': reason}])
self.callHooks([source, 'CLIENTBOT_KICK', {'channel': channel, 'target': target, 'text': reason}])
return
self.irc.send('KICK %s %s :<%s> %s' % (channel, self._expandPUID(target),
self.irc.getFriendlyName(source), reason))
self.send('KICK %s %s :<%s> %s' % (channel, self._expandPUID(target),
self.getFriendlyName(source), reason))
# Don't update our state here: wait for the IRCd to send an acknowledgement instead.
# There is essentially a 3 second wait to do this, as we send NAMES with a delay
# to resync any users lost due to kicks being blocked, etc.
if (channel not in self.kick_queue) or (not self.kick_queue[channel][1].is_alive()):
# However, only do this if there isn't a NAMES request scheduled already.
t = threading.Timer(3, lambda: self.irc.send('NAMES %s' % channel))
log.debug('(%s) kick: setting NAMES timer for %s on %s', self.irc.name, target, channel)
t = threading.Timer(3, lambda: self.send('NAMES %s' % channel))
log.debug('(%s) kick: setting NAMES timer for %s on %s', self.name, target, channel)
# Store the channel, target UID, and timer object in the internal kick queue.
self.kick_queue[channel] = ({target}, t)
t.start()
else:
log.debug('(%s) kick: adding %s to kick queue for channel %s', self.irc.name, target, channel)
log.debug('(%s) kick: adding %s to kick queue for channel %s', self.name, target, channel)
self.kick_queue[channel][0].add(target)
def message(self, source, target, text, notice=False):
"""Sends messages to the target."""
command = 'NOTICE' if notice else 'PRIVMSG'
if self.irc.pseudoclient and self.irc.pseudoclient.uid == source:
self.irc.send('%s %s :%s' % (command, self._expandPUID(target), text))
if self.pseudoclient and self.pseudoclient.uid == source:
self.send('%s %s :%s' % (command, self._expandPUID(target), text))
else:
self.irc.callHooks([source, 'CLIENTBOT_MESSAGE', {'target': target, 'is_notice': notice, 'text': text}])
self.callHooks([source, 'CLIENTBOT_MESSAGE', {'target': target, 'is_notice': notice, 'text': text}])
def mode(self, source, channel, modes, ts=None):
"""Sends channel MODE changes."""
@ -211,35 +211,35 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# things that were never banned. This prevents the bot from getting caught in a loop
# with IRCd MODE acknowledgements.
# FIXME: More related safety checks should be added for this.
log.debug('(%s) mode: re-parsing modes %s', self.irc.name, modes)
joined_modes = self.irc.joinModes(modes)
for modepair in self.irc.parseModes(channel, joined_modes):
log.debug('(%s) mode: checking if %s a prefix mode: %s', self.irc.name, modepair, self.irc.prefixmodes)
if modepair[0][-1] in self.irc.prefixmodes:
if self.irc.isInternalClient(modepair[1]):
log.debug('(%s) mode: re-parsing modes %s', self.name, modes)
joined_modes = self.joinModes(modes)
for modepair in self.parseModes(channel, joined_modes):
log.debug('(%s) mode: checking if %s a prefix mode: %s', self.name, modepair, self.prefixmodes)
if modepair[0][-1] in self.prefixmodes:
if self.isInternalClient(modepair[1]):
# Ignore prefix modes for virtual internal clients.
log.debug('(%s) mode: skipping virtual client prefixmode change %s', self.irc.name, modepair)
log.debug('(%s) mode: skipping virtual client prefixmode change %s', self.name, modepair)
continue
else:
# For other clients, change the mode argument to nick instead of PUID.
nick = self.irc.getFriendlyName(modepair[1])
log.debug('(%s) mode: coersing mode %s argument to %s', self.irc.name, modepair, nick)
nick = self.getFriendlyName(modepair[1])
log.debug('(%s) mode: coersing mode %s argument to %s', self.name, modepair, nick)
modepair = (modepair[0], nick)
extmodes.append(modepair)
log.debug('(%s) mode: filtered modes for %s: %s', self.irc.name, channel, extmodes)
log.debug('(%s) mode: filtered modes for %s: %s', self.name, channel, extmodes)
if extmodes:
self.irc.send('MODE %s %s' % (channel, self.irc.joinModes(extmodes)))
self.send('MODE %s %s' % (channel, self.joinModes(extmodes)))
# Don't update the state here: the IRCd sill respond with a MODE reply if successful.
def nick(self, source, newnick):
"""STUB: Sends NICK changes."""
if self.irc.pseudoclient and source == self.irc.pseudoclient.uid:
self.irc.send('NICK :%s' % newnick)
if self.pseudoclient and source == self.pseudoclient.uid:
self.send('NICK :%s' % newnick)
# No state update here: the IRCd will respond with a NICK acknowledgement if the change succeeds.
else:
self.irc.callHooks([source, 'CLIENTBOT_NICK', {'newnick': newnick}])
self.irc.users[source].nick = newnick
self.callHooks([source, 'CLIENTBOT_NICK', {'newnick': newnick}])
self.users[source].nick = newnick
def notice(self, source, target, text):
"""Sends notices to the target."""
@ -250,29 +250,29 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
"""
Sends PING to the uplink.
"""
if self.irc.uplink:
self.irc.send('PING %s' % self.irc.getFriendlyName(self.irc.uplink))
if self.uplink:
self.send('PING %s' % self.getFriendlyName(self.uplink))
# Poll WHO periodically to figure out any ident/host/away status changes.
for channel in self.irc.pseudoclient.channels:
self.irc.send('WHO %s' % channel)
for channel in self.pseudoclient.channels:
self.send('WHO %s' % channel)
def part(self, source, channel, reason=''):
"""STUB: Parts a user from a channel."""
self.irc.channels[channel].removeuser(source)
self.irc.users[source].channels.discard(channel)
self.channels[channel].removeuser(source)
self.users[source].channels.discard(channel)
# Only parts for the main PyLink client are actually forwarded. Others are ignored.
if self.irc.pseudoclient and source == self.irc.pseudoclient.uid:
self.irc.send('PART %s :%s' % (channel, reason))
if self.pseudoclient and source == self.pseudoclient.uid:
self.send('PART %s :%s' % (channel, reason))
else:
self.irc.callHooks([source, 'CLIENTBOT_PART', {'channel': channel, 'text': reason}])
self.callHooks([source, 'CLIENTBOT_PART', {'channel': channel, 'text': reason}])
def quit(self, source, reason):
"""STUB: Quits a client."""
userdata = self.irc.users[source]
userdata = self.users[source]
self.removeClient(source)
self.irc.callHooks([source, 'CLIENTBOT_QUIT', {'text': reason, 'userdata': userdata}])
self.callHooks([source, 'CLIENTBOT_QUIT', {'text': reason, 'userdata': userdata}])
def sjoin(self, server, channel, users, ts=None, modes=set()):
"""STUB: bursts joins from a server."""
@ -280,16 +280,16 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# given. modes and TS are currently ignored.
puids = {u[-1] for u in users}
for user in puids:
if self.irc.pseudoclient and self.irc.pseudoclient.uid == user:
if self.pseudoclient and self.pseudoclient.uid == user:
# If the SJOIN affects our main client, forward it as a regular JOIN.
self.join(user, channel)
else:
# Otherwise, track the state for our virtual clients.
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
self.irc.channels[channel].users |= puids
nicks = {self.irc.getFriendlyName(u) for u in puids}
self.irc.callHooks([server, 'CLIENTBOT_SJOIN', {'channel': channel, 'nicks': nicks}])
self.channels[channel].users |= puids
nicks = {self.getFriendlyName(u) for u in puids}
self.callHooks([server, 'CLIENTBOT_SJOIN', {'channel': channel, 'nicks': nicks}])
def squit(self, source, target, text):
"""STUB: SQUITs a server."""
@ -298,7 +298,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
squit_data = self._squit(source, 'CLIENTBOT_VIRTUAL_SQUIT', [target, text])
if squit_data.get('nicks'):
self.irc.callHooks([source, 'CLIENTBOT_SQUIT', squit_data])
self.callHooks([source, 'CLIENTBOT_SQUIT', squit_data])
def _stub(self, *args):
"""Stub outgoing command function (does nothing)."""
@ -307,28 +307,28 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
def updateClient(self, target, field, text):
"""Updates the known ident, host, or realname of a client."""
if target not in self.irc.users:
log.warning("(%s) Unknown target %s for updateClient()", self.irc.name, target)
if target not in self.users:
log.warning("(%s) Unknown target %s for updateClient()", self.name, target)
return
u = self.irc.users[target]
u = self.users[target]
if field == 'IDENT' and u.ident != text:
u.ident = text
if not self.irc.isInternalClient(target):
if not self.isInternalClient(target):
# We're updating the host of an external client in our state, so send the appropriate
# hook payloads.
self.irc.callHooks([self.irc.sid, 'CHGIDENT',
self.callHooks([self.sid, 'CHGIDENT',
{'target': target, 'newident': text}])
elif field == 'HOST' and u.host != text:
u.host = text
if not self.irc.isInternalClient(target):
self.irc.callHooks([self.irc.sid, 'CHGHOST',
if not self.isInternalClient(target):
self.callHooks([self.sid, 'CHGHOST',
{'target': target, 'newhost': text}])
elif field in ('REALNAME', 'GECOS') and u.realname != text:
u.realname = text
if not self.irc.isInternalClient(target):
self.irc.callHooks([self.irc.sid, 'CHGNAME',
if not self.isInternalClient(target):
self.callHooks([self.sid, 'CHGNAME',
{'target': target, 'newgecos': text}])
else:
return # Nothing changed
@ -340,19 +340,19 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
Limited (internal) nick collision checking is done here to prevent Clientbot users from
being confused with virtual clients, and vice versa."""
self._validateNick(nick)
idsource = self.irc.nickToUid(nick)
is_internal = self.irc.isInternalClient(idsource)
idsource = self.nickToUid(nick)
is_internal = self.isInternalClient(idsource)
# If this sender isn't known or it is one of our virtual clients, spawn a new one.
# This also takes care of any nick collisions caused by new, Clientbot users
# taking the same nick as one of our virtual clients, and will force the virtual client to lose.
if (not idsource) or (is_internal and self.irc.pseudoclient and idsource != self.irc.pseudoclient.uid):
if (not idsource) or (is_internal and self.pseudoclient and idsource != self.pseudoclient.uid):
if idsource:
log.debug('(%s) Nick-colliding virtual client %s/%s', self.irc.name, idsource, nick)
self.irc.callHooks([self.irc.sid, 'CLIENTBOT_NICKCOLLIDE', {'target': idsource, 'parse_as': 'SAVE'}])
log.debug('(%s) Nick-colliding virtual client %s/%s', self.name, idsource, nick)
self.callHooks([self.sid, 'CLIENTBOT_NICKCOLLIDE', {'target': idsource, 'parse_as': 'SAVE'}])
idsource = self.spawnClient(nick, ident or 'unknown', host or 'unknown',
server=self.irc.uplink, realname=FALLBACK_REALNAME).uid
server=self.uplink, realname=FALLBACK_REALNAME).uid
return idsource
@ -373,7 +373,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
tagdata[idx] = tag
results = self.parseCapabilities(tagdata, fallback=None)
log.debug('(%s) parsed message tags %s', self.irc.name, results)
log.debug('(%s) parsed message tags %s', self.name, results)
return results
return {}
@ -395,7 +395,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
except IndexError:
# Raw command without an explicit sender; assume it's being sent by our uplink.
args = self.parseArgs(data)
idsource = sender = self.irc.uplink
idsource = sender = self.uplink
command = args[0]
args = args[1:]
else:
@ -405,7 +405,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
if ('!' not in sender) and '.' in sender:
# Sender is a server name. XXX: make this check more foolproof
idsource = self._get_SID(sender)
if idsource not in self.irc.servers:
if idsource not in self.servers:
idsource = self.spawnServer(sender, internal=False)
else:
# Sender is a either a nick or a nick!user@host prefix. Split it into its relevant parts.
@ -430,8 +430,8 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
"""
Abort SASL login by sending CAP END.
"""
self.irc.send('CAP END')
log.debug("(%s) Stopping CAP END timer.", self.irc.name)
self.send('CAP END')
log.debug("(%s) Stopping CAP END timer.", self.name)
self._cap_timer.cancel()
def saslAuth(self):
@ -440,43 +440,43 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
is enabled and correctly configured, and False otherwise.
"""
if 'sasl' not in self.ircv3_caps:
log.info("(%s) Skipping SASL auth since the IRCd doesn't support it.", self.irc.name)
log.info("(%s) Skipping SASL auth since the IRCd doesn't support it.", self.name)
return
sasl_mech = self.irc.serverdata.get('sasl_mechanism')
sasl_mech = self.serverdata.get('sasl_mechanism')
if sasl_mech:
sasl_mech = sasl_mech.upper()
sasl_user = self.irc.serverdata.get('sasl_username')
sasl_pass = self.irc.serverdata.get('sasl_password')
ssl_cert = self.irc.serverdata.get('ssl_certfile')
ssl_key = self.irc.serverdata.get('ssl_keyfile')
ssl = self.irc.serverdata.get('ssl')
sasl_user = self.serverdata.get('sasl_username')
sasl_pass = self.serverdata.get('sasl_password')
ssl_cert = self.serverdata.get('ssl_certfile')
ssl_key = self.serverdata.get('ssl_keyfile')
ssl = self.serverdata.get('ssl')
if sasl_mech == 'PLAIN':
if not (sasl_user and sasl_pass):
log.warning("(%s) Not attempting PLAIN authentication; sasl_username and/or "
"sasl_password aren't correctly set.", self.irc.name)
"sasl_password aren't correctly set.", self.name)
return False
elif sasl_mech == 'EXTERNAL':
if not ssl:
log.warning("(%s) Not attempting EXTERNAL authentication; SASL external requires "
"SSL, but it isn't enabled.", self.irc.name)
"SSL, but it isn't enabled.", self.name)
return False
elif not (ssl_cert and ssl_key):
log.warning("(%s) Not attempting EXTERNAL authentication; ssl_certfile and/or "
"ssl_keyfile aren't correctly set.", self.irc.name)
"ssl_keyfile aren't correctly set.", self.name)
return False
else:
log.warning('(%s) Unsupported SASL mechanism %s; aborting SASL.', self.irc.name, sasl_mech)
log.warning('(%s) Unsupported SASL mechanism %s; aborting SASL.', self.name, sasl_mech)
return False
self.irc.send('AUTHENTICATE %s' % sasl_mech, queue=False)
self.send('AUTHENTICATE %s' % sasl_mech, queue=False)
return True
return False
def sendAuthChunk(self, data):
"""Send Base64 encoded SASL authentication chunks."""
enc_data = base64.b64encode(data).decode()
self.irc.send('AUTHENTICATE %s' % enc_data, queue=False)
self.send('AUTHENTICATE %s' % enc_data, queue=False)
def handle_authenticate(self, source, command, args):
"""
@ -488,21 +488,21 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
if not args:
return
if args[0] == '+':
sasl_mech = self.irc.serverdata['sasl_mechanism'].upper()
sasl_mech = self.serverdata['sasl_mechanism'].upper()
if sasl_mech == 'PLAIN':
sasl_user = self.irc.serverdata['sasl_username']
sasl_pass = self.irc.serverdata['sasl_password']
sasl_user = self.serverdata['sasl_username']
sasl_pass = self.serverdata['sasl_password']
authstring = '%s\0%s\0%s' % (sasl_user, sasl_user, sasl_pass)
self.sendAuthChunk(authstring.encode('utf-8'))
elif sasl_mech == 'EXTERNAL':
self.irc.send('AUTHENTICATE +')
self.send('AUTHENTICATE +')
def handle_904(self, source, command, args):
"""
Handles SASL authentication status reports.
"""
logfunc = log.info if command == '903' else log.warning
logfunc('(%s) %s', self.irc.name, args[-1])
logfunc('(%s) %s', self.name, args[-1])
if not self.has_eob:
self.capEnd()
handle_903 = handle_902 = handle_905 = handle_906 = handle_907 = handle_904
@ -513,9 +513,9 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# And by the ones we don't already have.
caps_wanted = available_caps - self.ircv3_caps
log.debug('(%s) Requesting IRCv3 capabilities %s (available: %s)', self.irc.name, caps_wanted, available_caps)
log.debug('(%s) Requesting IRCv3 capabilities %s (available: %s)', self.name, caps_wanted, available_caps)
if caps_wanted:
self.irc.send('CAP REQ :%s' % ' '.join(caps_wanted), queue=False)
self.send('CAP REQ :%s' % ' '.join(caps_wanted), queue=False)
def handle_cap(self, source, command, args):
"""
@ -527,7 +527,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# Server: CAP * LS * :multi-prefix extended-join account-notify batch invite-notify tls
# Server: CAP * LS * :cap-notify server-time example.org/dummy-cap=dummyvalue example.org/second-dummy-cap
# Server: CAP * LS :userhost-in-names sasl=EXTERNAL,DH-AES,DH-BLOWFISH,ECDSA-NIST256P-CHALLENGE,PLAIN
log.debug('(%s) Got new capabilities %s', self.irc.name, args[-1])
log.debug('(%s) Got new capabilities %s', self.name, args[-1])
self.ircv3_caps_available.update(self.parseCapabilities(args[-1], None))
if args[2] != '*':
self.requestNewCaps()
@ -535,7 +535,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
elif subcmd == 'ACK':
# Server: CAP * ACK :multi-prefix sasl
newcaps = set(args[-1].split())
log.debug('(%s) Received ACK for IRCv3 capabilities %s', self.irc.name, newcaps)
log.debug('(%s) Received ACK for IRCv3 capabilities %s', self.name, newcaps)
self.ircv3_caps |= newcaps
# Only send CAP END immediately if SASL is disabled. Otherwise, wait for the 90x responses
@ -545,7 +545,7 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
self.capEnd()
elif subcmd == 'NAK':
log.warning('(%s) Got NAK for IRCv3 capabilities %s, even though they were supposedly available',
self.irc.name, args[-1])
self.name, args[-1])
if not self.has_eob:
self.capEnd()
elif subcmd == 'NEW':
@ -553,19 +553,19 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# :irc.example.com CAP tester NEW :away-notify extended-join
# Note: CAP NEW allows capabilities with values (e.g. sasl=mech1,mech2), while CAP DEL
# does not.
log.debug('(%s) Got new capabilities %s', self.irc.name, args[-1])
log.debug('(%s) Got new capabilities %s', self.name, args[-1])
newcaps = self.parseCapabilities(args[-1], None)
self.ircv3_caps_available.update(newcaps)
self.requestNewCaps()
# Attempt SASL auth routines when sasl is added/removed, if doing so is enabled.
if 'sasl' in newcaps and self.irc.serverdata.get('sasl_reauth'):
log.debug('(%s) Attempting SASL reauth due to CAP NEW', self.irc.name)
if 'sasl' in newcaps and self.serverdata.get('sasl_reauth'):
log.debug('(%s) Attempting SASL reauth due to CAP NEW', self.name)
self.saslAuth()
elif subcmd == 'DEL':
# :irc.example.com CAP modernclient DEL :userhost-in-names multi-prefix away-notify
log.debug('(%s) Removing capabilities %s', self.irc.name, args[-1])
log.debug('(%s) Removing capabilities %s', self.name, args[-1])
for cap in args[-1].split():
# Remove the capabilities from the list available, and return None (ignore) if any fail
self.ircv3_caps_available.pop(cap, None)
@ -576,41 +576,41 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
Handles 001 / RPL_WELCOME.
"""
# enumerate our uplink
self.irc.uplink = source
self.uplink = source
def handle_005(self, source, command, args):
"""
Handles 005 / RPL_ISUPPORT.
"""
self.caps.update(self.parseCapabilities(args[1:-1]))
log.debug('(%s) handle_005: self.caps is %s', self.irc.name, self.caps)
log.debug('(%s) handle_005: self.caps is %s', self.name, self.caps)
if 'CHANMODES' in self.caps:
self.irc.cmodes['*A'], self.irc.cmodes['*B'], self.irc.cmodes['*C'], self.irc.cmodes['*D'] = \
self.cmodes['*A'], self.cmodes['*B'], self.cmodes['*C'], self.cmodes['*D'] = \
self.caps['CHANMODES'].split(',')
log.debug('(%s) handle_005: cmodes: %s', self.irc.name, self.irc.cmodes)
log.debug('(%s) handle_005: cmodes: %s', self.name, self.cmodes)
if 'USERMODES' in self.caps:
self.irc.umodes['*A'], self.irc.umodes['*B'], self.irc.umodes['*C'], self.irc.umodes['*D'] = \
self.umodes['*A'], self.umodes['*B'], self.umodes['*C'], self.umodes['*D'] = \
self.caps['USERMODES'].split(',')
log.debug('(%s) handle_005: umodes: %s', self.irc.name, self.irc.umodes)
log.debug('(%s) handle_005: umodes: %s', self.name, self.umodes)
self.casemapping = self.caps.get('CASEMAPPING', self.casemapping)
log.debug('(%s) handle_005: casemapping set to %s', self.irc.name, self.casemapping)
log.debug('(%s) handle_005: casemapping set to %s', self.name, self.casemapping)
if 'PREFIX' in self.caps:
self.irc.prefixmodes = prefixmodes = self.parsePrefixes(self.caps['PREFIX'])
log.debug('(%s) handle_005: prefix modes set to %s', self.irc.name, self.irc.prefixmodes)
self.prefixmodes = prefixmodes = self.parsePrefixes(self.caps['PREFIX'])
log.debug('(%s) handle_005: prefix modes set to %s', self.name, self.prefixmodes)
# Autodetect common prefix mode names.
for char, modename in COMMON_PREFIXMODES:
# Don't overwrite existing named mode definitions.
if char in self.irc.prefixmodes and modename not in self.irc.cmodes:
self.irc.cmodes[modename] = char
log.debug('(%s) handle_005: autodetecting mode %s (%s) as %s', self.irc.name,
char, self.irc.prefixmodes[char], modename)
if char in self.prefixmodes and modename not in self.cmodes:
self.cmodes[modename] = char
log.debug('(%s) handle_005: autodetecting mode %s (%s) as %s', self.name,
char, self.prefixmodes[char], modename)
self.irc.connected.set()
self.connected.set()
def handle_376(self, source, command, args):
"""
@ -618,8 +618,8 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
"""
# Run autoperform commands.
for line in self.irc.serverdata.get("autoperform", []):
self.irc.send(line)
for line in self.serverdata.get("autoperform", []):
self.send(line)
# Virtual endburst hook.
if not self.has_eob:
@ -634,14 +634,14 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# <- :charybdis.midnight.vpn 353 ice = #test :ice @GL
# Mark "@"-type channels as secret automatically, per RFC2812.
channel = self.irc.toLower(args[2])
channel = self.toLower(args[2])
if args[1] == '@':
self.irc.applyModes(channel, [('+s', None)])
self.applyModes(channel, [('+s', None)])
names = set()
modes = set()
prefix_to_mode = {v:k for k, v in self.irc.prefixmodes.items()}
prefixes = ''.join(self.irc.prefixmodes.values())
prefix_to_mode = {v:k for k, v in self.prefixmodes.items()}
prefixes = ''.join(self.prefixmodes.values())
for name in args[-1].split():
nick = name.lstrip(prefixes)
@ -653,41 +653,41 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# Queue these virtual users to be joined if they're not already in the channel,
# or we're waiting for a kick acknowledgment for them.
if (idsource not in self.irc.channels[channel].users) or (idsource in \
if (idsource not in self.channels[channel].users) or (idsource in \
self.kick_queue.get(channel, ([],))[0]):
names.add(idsource)
self.irc.users[idsource].channels.add(channel)
self.users[idsource].channels.add(channel)
# Process prefix modes
for char in name:
if char in self.irc.prefixmodes.values():
if char in self.prefixmodes.values():
modes.add(('+' + prefix_to_mode[char], idsource))
else:
break
# Statekeeping: make sure the channel's user list is updated!
self.irc.channels[channel].users |= names
self.irc.applyModes(channel, modes)
self.channels[channel].users |= names
self.applyModes(channel, modes)
log.debug('(%s) handle_353: adding users %s to %s', self.irc.name, names, channel)
log.debug('(%s) handle_353: adding modes %s to %s', self.irc.name, modes, channel)
log.debug('(%s) handle_353: adding users %s to %s', self.name, names, channel)
log.debug('(%s) handle_353: adding modes %s to %s', self.name, modes, channel)
# Unless /WHO has already been received for the given channel, we generally send the hook
# for JOIN after /who data is received, to enumerate the ident, host, and real names of
# users.
if names and hasattr(self.irc.channels[channel], 'who_received'):
if names and hasattr(self.channels[channel], 'who_received'):
# /WHO *HAS* already been received. Send JOIN hooks here because we use this to keep
# track of any failed KICK attempts sent by the relay bot.
log.debug('(%s) handle_353: sending JOIN hook because /WHO was already received for %s',
self.irc.name, channel)
return {'channel': channel, 'users': names, 'modes': self.irc.channels[channel].modes,
self.name, channel)
return {'channel': channel, 'users': names, 'modes': self.channels[channel].modes,
'parse_as': "JOIN"}
def _validateNick(self, nick):
"""
Checks to make sure a nick doesn't clash with a PUID.
"""
if nick in self.irc.users or nick in self.irc.servers:
if nick in self.users or nick in self.servers:
raise ProtocolError("Got bad nick %s from IRC which clashes with a PUID. Is someone trying to spoof users?" % nick)
def handle_352(self, source, command, args):
@ -705,10 +705,10 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
realname = args[-1].split(' ', 1)[-1]
self._validateNick(nick)
uid = self.irc.nickToUid(nick)
uid = self.nickToUid(nick)
if uid is None:
log.debug("(%s) Ignoring extraneous /WHO info for %s", self.irc.name, nick)
log.debug("(%s) Ignoring extraneous /WHO info for %s", self.name, nick)
return
self.updateClient(uid, 'IDENT', ident)
@ -720,27 +720,27 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# G means away is set (we'll have to fake a message because it's not given)
# * means IRCop.
# The rest are prefix modes. Multiple can be given by the IRCd if multiple are set
log.debug('(%s) handle_352: status string on user %s: %s', self.irc.name, nick, status)
log.debug('(%s) handle_352: status string on user %s: %s', self.name, nick, status)
if status[0] == 'G':
log.debug('(%s) handle_352: calling away() with argument', self.irc.name)
log.debug('(%s) handle_352: calling away() with argument', self.name)
self.away(uid, 'Away')
elif status[0] == 'H':
log.debug('(%s) handle_352: calling away() without argument', self.irc.name)
log.debug('(%s) handle_352: calling away() without argument', self.name)
self.away(uid, '') # Unmark away status
else:
log.warning('(%s) handle_352: got wrong string %s for away status', self.irc.name, status[0])
log.warning('(%s) handle_352: got wrong string %s for away status', self.name, status[0])
if self.irc.serverdata.get('track_oper_statuses'):
if self.serverdata.get('track_oper_statuses'):
if '*' in status: # Track IRCop status
if not self.irc.isOper(uid, allowAuthed=False):
if not self.isOper(uid, allowAuthed=False):
# Don't send duplicate oper ups if the target is already oper.
self.irc.applyModes(uid, [('+o', None)])
self.irc.callHooks([uid, 'MODE', {'target': uid, 'modes': {('+o', None)}}])
self.irc.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
elif self.irc.isOper(uid, allowAuthed=False) and not self.irc.isInternalClient(uid):
self.applyModes(uid, [('+o', None)])
self.callHooks([uid, 'MODE', {'target': uid, 'modes': {('+o', None)}}])
self.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
elif self.isOper(uid, allowAuthed=False) and not self.isInternalClient(uid):
# Track deopers
self.irc.applyModes(uid, [('-o', None)])
self.irc.callHooks([uid, 'MODE', {'target': uid, 'modes': {('-o', None)}}])
self.applyModes(uid, [('-o', None)])
self.callHooks([uid, 'MODE', {'target': uid, 'modes': {('-o', None)}}])
self.who_received.add(uid)
@ -753,8 +753,8 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
users = self.who_received.copy()
self.who_received.clear()
channel = self.irc.toLower(args[1])
c = self.irc.channels[channel]
channel = self.toLower(args[1])
c = self.channels[channel]
c.who_received = True
modes = set(c.modes)
@ -762,13 +762,13 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# Fill in prefix modes of everyone when doing mock SJOIN.
try:
for mode in c.getPrefixModes(user):
modechar = self.irc.cmodes.get(mode)
log.debug('(%s) handle_315: adding mode %s +%s %s', self.irc.name, mode, modechar, user)
modechar = self.cmodes.get(mode)
log.debug('(%s) handle_315: adding mode %s +%s %s', self.name, mode, modechar, user)
if modechar:
modes.add((modechar, user))
except KeyError as e:
log.debug("(%s) Ignoring KeyError (%s) from WHO response; it's probably someone we "
"don't share any channels with", self.irc.name, e)
"don't share any channels with", self.name, e)
return {'channel': channel, 'users': users, 'modes': modes,
'parse_as': "JOIN"}
@ -779,8 +779,8 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# 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.irc.serverdata['pylink_nick'] = self.conf_nick
self.irc.send('NICK %s' % self.conf_nick)
self.serverdata['pylink_nick'] = self.conf_nick
self.send('NICK %s' % self.conf_nick)
handle_432 = handle_437 = handle_433
def handle_join(self, source, command, args):
@ -788,18 +788,18 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
Handles incoming JOINs.
"""
# <- :GL|!~GL@127.0.0.1 JOIN #whatever
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
self.join(source, channel)
return {'channel': channel, 'users': [source], 'modes': self.irc.channels[channel].modes}
return {'channel': channel, 'users': [source], 'modes': self.channels[channel].modes}
def handle_kick(self, source, command, args):
"""
Handles incoming KICKs.
"""
# <- :GL!~gl@127.0.0.1 KICK #whatever GL| :xd
channel = self.irc.toLower(args[0])
target = self.irc.nickToUid(args[1])
channel = self.toLower(args[0])
target = self.nickToUid(args[1])
try:
reason = args[2]
@ -808,29 +808,29 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
if channel in self.kick_queue:
# Remove this client from the kick queue if present there.
log.debug('(%s) kick: removing %s from kick queue for channel %s', self.irc.name, target, channel)
log.debug('(%s) kick: removing %s from kick queue for channel %s', self.name, target, channel)
self.kick_queue[channel][0].discard(target)
if not self.kick_queue[channel][0]:
log.debug('(%s) kick: cancelling kick timer for channel %s (all kicks accounted for)', self.irc.name, channel)
log.debug('(%s) kick: cancelling kick timer for channel %s (all kicks accounted for)', self.name, channel)
# There aren't any kicks that failed to be acknowledged. We can remove the timer now
self.kick_queue[channel][1].cancel()
del self.kick_queue[channel]
# Statekeeping: remove the target from the channel they were previously in.
self.irc.channels[channel].removeuser(target)
self.channels[channel].removeuser(target)
try:
self.irc.users[target].channels.remove(channel)
self.users[target].channels.remove(channel)
except KeyError:
pass
if (not self.irc.isInternalClient(source)) and not self.irc.isInternalServer(source):
if (not self.isInternalClient(source)) and not self.isInternalServer(source):
# Don't repeat hooks if we're the kicker.
self.irc.callHooks([source, 'KICK', {'channel': channel, 'target': target, 'text': reason}])
self.callHooks([source, 'KICK', {'channel': channel, 'target': target, 'text': reason}])
# Delete channels that we were kicked from, for better state keeping.
if self.irc.pseudoclient and target == self.irc.pseudoclient.uid:
del self.irc.channels[channel]
if self.pseudoclient and target == self.pseudoclient.uid:
del self.channels[channel]
def handle_mode(self, source, command, args):
"""Handles MODE changes."""
@ -838,23 +838,23 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# <- :ice MODE ice :+Zi
target = args[0]
if utils.isChannel(target):
target = self.irc.toLower(target)
oldobj = self.irc.channels[target].deepcopy()
target = self.toLower(target)
oldobj = self.channels[target].deepcopy()
else:
target = self.irc.nickToUid(target)
target = self.nickToUid(target)
oldobj = None
modes = args[1:]
changedmodes = self.irc.parseModes(target, modes)
self.irc.applyModes(target, changedmodes)
changedmodes = self.parseModes(target, modes)
self.applyModes(target, changedmodes)
if self.irc.isInternalClient(target):
log.debug('(%s) Suppressing MODE change hook for internal client %s', self.irc.name, target)
if self.isInternalClient(target):
log.debug('(%s) Suppressing MODE change hook for internal client %s', self.name, target)
return
if changedmodes:
# Prevent infinite loops: don't send MODE hooks if the sender is US.
# Note: this is not the only check in Clientbot to prevent mode loops: if our nick
# somehow gets desynced, this may not catch everything it's supposed to.
if (self.irc.pseudoclient and source != self.irc.pseudoclient.uid) or not self.irc.pseudoclient:
if (self.pseudoclient and source != self.pseudoclient.uid) or not self.pseudoclient:
return {'target': target, 'modes': changedmodes, 'channeldata': oldobj}
def handle_324(self, source, command, args):
@ -862,36 +862,36 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# -> MODE #test
# <- :midnight.vpn 324 GL #test +nt
# <- :midnight.vpn 329 GL #test 1491773459
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
modes = args[2:]
log.debug('(%s) Got RPL_CHANNELMODEIS (324) modes %s for %s', self.irc.name, modes, channel)
changedmodes = self.irc.parseModes(channel, modes)
self.irc.applyModes(channel, changedmodes)
log.debug('(%s) Got RPL_CHANNELMODEIS (324) modes %s for %s', self.name, modes, channel)
changedmodes = self.parseModes(channel, modes)
self.applyModes(channel, changedmodes)
def handle_329(self, source, command, args):
"""Handles TS announcements via RPL_CREATIONTIME."""
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
ts = int(args[2])
self.irc.channels[channel].ts = ts
self.channels[channel].ts = ts
def handle_nick(self, source, command, args):
"""Handles NICK changes."""
# <- :GL|!~GL@127.0.0.1 NICK :GL_
if not self.irc.pseudoclient:
if not self.pseudoclient:
# 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
# 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.irc.serverdata['pylink_nick']
self.irc.serverdata['pylink_nick'] = self.conf_nick = args[0]
log.debug('(%s) Pre-auth FNC: Forcing configured nick to %s from %s', self.irc.name, args[0], oldnick)
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)
return
oldnick = self.irc.users[source].nick
self.irc.users[source].nick = args[0]
oldnick = self.users[source].nick
self.users[source].nick = args[0]
return {'newnick': args[0], 'oldnick': oldnick}
@ -900,35 +900,35 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
Handles incoming PARTs.
"""
# <- :GL|!~GL@127.0.0.1 PART #whatever
channels = list(map(self.irc.toLower, args[0].split(',')))
channels = list(map(self.toLower, args[0].split(',')))
try:
reason = args[1]
except IndexError:
reason = ''
for channel in channels:
self.irc.channels[channel].removeuser(source)
self.irc.users[source].channels -= set(channels)
self.channels[channel].removeuser(source)
self.users[source].channels -= set(channels)
self.irc.callHooks([source, 'PART', {'channels': channels, 'text': reason}])
self.callHooks([source, 'PART', {'channels': channels, 'text': reason}])
# Clear channels that are empty, or that we're parting.
for channel in channels:
if (self.irc.pseudoclient and source == self.irc.pseudoclient.uid) or not self.irc.channels[channel].users:
del self.irc.channels[channel]
if (self.pseudoclient and source == self.pseudoclient.uid) or not self.channels[channel].users:
del self.channels[channel]
def handle_ping(self, source, command, args):
"""
Handles incoming PING requests.
"""
self.irc.send('PONG :%s' % args[0], queue=False)
self.send('PONG :%s' % args[0], queue=False)
def handle_pong(self, source, command, args):
"""
Handles incoming PONG.
"""
if source == self.irc.uplink:
self.irc.lastping = time.time()
if source == self.uplink:
self.lastping = time.time()
def handle_privmsg(self, source, command, args):
"""Handles incoming PRIVMSG/NOTICE."""
@ -936,22 +936,22 @@ class ClientbotWrapperProtocol(IRCCommonProtocol):
# <- :sender NOTICE somenick :afasfsa
target = args[0]
if self.irc.isInternalClient(source) or self.irc.isInternalServer(source):
log.warning('(%s) Received %s to %s being routed the wrong way!', self.irc.name, command, target)
if self.isInternalClient(source) or self.isInternalServer(source):
log.warning('(%s) Received %s to %s being routed the wrong way!', self.name, command, target)
return
# We use lowercase channels internally.
if utils.isChannel(target):
target = self.irc.toLower(target)
target = self.toLower(target)
else:
target = self.irc.nickToUid(target)
target = self.nickToUid(target)
if target:
return {'target': target, 'text': args[1]}
handle_notice = handle_privmsg
def handle_quit(self, source, command, args):
"""Handles incoming QUITs."""
if self.irc.pseudoclient and source == self.irc.pseudoclient.uid:
if self.pseudoclient and source == self.pseudoclient.uid:
# Someone faked a quit from us? We should abort.
raise ProtocolError("Received QUIT from uplink (%s)" % args[0])

View File

@ -18,9 +18,9 @@ class HybridProtocol(TS6Protocol):
def post_connect(self):
"""Initializes a connection to a server."""
ts = self.irc.start_ts
ts = self.start_ts
self.has_eob = False
f = self.irc.send
f = self.send
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L80
cmodes = {
@ -37,7 +37,7 @@ class HybridProtocol(TS6Protocol):
'*A': 'beI', '*B': 'k', '*C': 'l', '*D': 'cimnprstCMORS'
}
self.irc.cmodes = cmodes
self.cmodes = cmodes
umodes = {
'oper': 'o', 'invisible': 'i', 'wallops': 'w', 'locops': 'l',
@ -52,13 +52,13 @@ class HybridProtocol(TS6Protocol):
'*A': '', '*B': '', '*C': '', '*D': 'DFGHRSWabcdefgijklnopqrsuwxy'
}
self.irc.umodes = umodes
self.umodes = umodes
# halfops is mandatory on Hybrid
self.irc.prefixmodes = {'o': '@', 'h': '%', 'v': '+'}
self.prefixmodes = {'o': '@', 'h': '%', 'v': '+'}
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L55
f('PASS %s TS 6 %s' % (self.irc.serverdata["sendpass"], self.irc.sid))
f('PASS %s TS 6 %s' % (self.serverdata["sendpass"], self.sid))
# We request the following capabilities (for hybrid):
@ -79,11 +79,11 @@ class HybridProtocol(TS6Protocol):
# EOB: Supports EOB (end of burst) command
f('CAPAB :TBURST DLN KNOCK UNDLN UNKLN KLN ENCAP IE EX HOPS CHW SVS CLUSTER EOB QS')
f('SERVER %s 0 :%s' % (self.irc.serverdata["hostname"],
self.irc.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']))
f('SERVER %s 0 :%s' % (self.serverdata["hostname"],
self.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']))
# send endburst now
self.irc.send(':%s EOB' % (self.irc.sid,))
self.send(':%s EOB' % (self.sid,))
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,
@ -95,8 +95,8 @@ class HybridProtocol(TS6Protocol):
up to plugins to make sure they don't introduce anything invalid.
"""
server = server or self.irc.sid
if not self.irc.isInternalServer(server):
server = server or self.sid
if not self.isInternalServer(server):
raise ValueError('Server %r is not a PyLink server!' % server)
uid = self.uidgen[server].next_uid()
@ -104,11 +104,11 @@ class HybridProtocol(TS6Protocol):
ts = ts or int(time.time())
realname = realname or conf.conf['bot']['realname']
realhost = realhost or host
raw_modes = self.irc.joinModes(modes)
u = self.irc.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
raw_modes = self.joinModes(modes)
u = self.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable)
self.irc.applyModes(uid, modes)
self.irc.servers[server].users.add(uid)
self.applyModes(uid, modes)
self.servers[server].users.add(uid)
self._send_with_prefix(server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
"* :{realname}".format(ts=ts, host=host,
nick=nick, ident=ident, uid=uid,
@ -125,28 +125,28 @@ class HybridProtocol(TS6Protocol):
# parv[4] = optional argument (services account, vhost)
field = field.upper()
ts = self.irc.users[target].ts
ts = self.users[target].ts
if field == 'HOST':
self.irc.users[target].host = text
self.users[target].host = text
# On Hybrid, it appears that host changing is actually just forcing umode
# "+x <hostname>" on the target. -GLolol
self._send_with_prefix(self.irc.sid, 'SVSMODE %s %s +x %s' % (target, ts, text))
self._send_with_prefix(self.sid, 'SVSMODE %s %s +x %s' % (target, ts, text))
else:
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
def topicBurst(self, numeric, target, text):
"""Sends a topic change from a PyLink server. This is usually used on burst."""
# <- :0UY TBURST 1459308205 #testchan 1459309379 dan!~d@localhost :sdf
if not self.irc.isInternalServer(numeric):
if not self.isInternalServer(numeric):
raise LookupError('No such PyLink server exists.')
ts = self.irc.channels[target].ts
servername = self.irc.servers[numeric].name
ts = self.channels[target].ts
servername = self.servers[numeric].name
self._send_with_prefix(numeric, 'TBURST %s %s %s %s :%s' % (ts, target, int(time.time()), servername, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
self.channels[target].topic = text
self.channels[target].topicset = True
# command handlers
@ -154,13 +154,13 @@ class HybridProtocol(TS6Protocol):
# We only get a list of keywords here. Hybrid obviously assumes that
# we know what modes it supports (indeed, this is a standard list).
# <- CAPAB :UNDLN UNKLN KLN TBURST KNOCK ENCAP DLN IE EX HOPS CHW SVS CLUSTER EOB QS
self.irc.caps = caps = args[0].split()
self.caps = caps = args[0].split()
for required_cap in ('EX', 'IE', 'SVS', 'EOB', 'HOPS', 'QS', 'TBURST', 'SVS'):
if required_cap not in caps:
raise ProtocolError('%s not found in TS6 capabilities list; this is required! (got %r)' % (required_cap, caps))
log.debug('(%s) self.irc.connected set!', self.irc.name)
self.irc.connected.set()
log.debug('(%s) self.connected set!', self.name)
self.connected.set()
def handle_uid(self, numeric, command, args):
"""
@ -174,39 +174,39 @@ class HybridProtocol(TS6Protocol):
if account == '*':
account = None
log.debug('(%s) handle_uid: got args nick=%s ts=%s uid=%s ident=%s '
'host=%s realname=%s ip=%s', self.irc.name, nick, ts, uid,
'host=%s realname=%s ip=%s', self.name, nick, ts, uid,
ident, host, realname, ip)
self.irc.users[uid] = IrcUser(nick, ts, uid, numeric, ident, host, realname, host, ip)
self.users[uid] = IrcUser(nick, ts, uid, numeric, ident, host, realname, host, ip)
parsedmodes = self.irc.parseModes(uid, [modes])
log.debug('(%s) handle_uid: Applying modes %s for %s', self.irc.name, parsedmodes, uid)
self.irc.applyModes(uid, parsedmodes)
self.irc.servers[numeric].users.add(uid)
parsedmodes = self.parseModes(uid, [modes])
log.debug('(%s) handle_uid: Applying modes %s for %s', self.name, parsedmodes, uid)
self.applyModes(uid, parsedmodes)
self.servers[numeric].users.add(uid)
# Call the OPERED UP hook if +o is being added to the mode list.
if ('+o', None) in parsedmodes:
self.irc.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC_Operator'}])
self.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC_Operator'}])
# Set the account name if present
if account:
self.irc.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': account}])
self.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': account}])
return {'uid': uid, 'ts': ts, 'nick': nick, 'realname': realname, 'host': host, 'ident': ident, 'ip': ip}
def handle_tburst(self, numeric, command, args):
"""Handles incoming topic burst (TBURST) commands."""
# <- :0UY TBURST 1459308205 #testchan 1459309379 dan!~d@localhost :sdf
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
ts = args[2]
setter = args[3]
topic = args[-1]
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
self.channels[channel].topic = topic
self.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
def handle_eob(self, numeric, command, args):
log.debug('(%s) end of burst received', self.irc.name)
log.debug('(%s) end of burst received', self.name)
if not self.has_eob: # Only call ENDBURST hooks if we haven't already.
return {}
@ -221,7 +221,7 @@ class HybridProtocol(TS6Protocol):
target = args[0]
ts = args[1]
modes = args[2:]
parsedmodes = self.irc.parseModes(target, modes)
parsedmodes = self.parseModes(target, modes)
for modepair in parsedmodes:
if modepair[0] == '+d':
@ -241,7 +241,7 @@ class HybridProtocol(TS6Protocol):
# Send the login hook, and remove this mode from the mode
# list, as it shouldn't be parsed literally.
self.irc.callHooks([target, 'CLIENT_SERVICES_LOGIN', {'text': account}])
self.callHooks([target, 'CLIENT_SERVICES_LOGIN', {'text': account}])
parsedmodes.remove(modepair)
elif modepair[0] == '+x':
@ -250,16 +250,16 @@ class HybridProtocol(TS6Protocol):
# to some.host, for example.
host = args[-1]
self.irc.users[target].host = host
self.users[target].host = host
# Propagate the hostmask change as a hook.
self.irc.callHooks([numeric, 'CHGHOST',
self.callHooks([numeric, 'CHGHOST',
{'target': target, 'newhost': host}])
parsedmodes.remove(modepair)
if parsedmodes:
self.irc.applyModes(target, parsedmodes)
self.applyModes(target, parsedmodes)
return {'target': target, 'modes': parsedmodes}

View File

@ -42,9 +42,9 @@ class InspIRCdProtocol(TS6BaseProtocol):
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
server = server or self.sid
if not self.irc.isInternalServer(server):
if not self.isInternalServer(server):
raise ValueError('Server %r is not a PyLink server!' % server)
uid = self.uidgen[server].next_uid()
@ -52,12 +52,12 @@ class InspIRCdProtocol(TS6BaseProtocol):
ts = ts or int(time.time())
realname = realname or conf.conf['bot']['realname']
realhost = realhost or host
raw_modes = self.irc.joinModes(modes)
u = self.irc.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
raw_modes = self.joinModes(modes)
u = self.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
self.irc.applyModes(uid, modes)
self.irc.servers[server].users.add(uid)
self.applyModes(uid, modes)
self.servers[server].users.add(uid)
self._send_with_prefix(server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
" {ts} {modes} + :{realname}".format(ts=ts, host=host,
@ -73,20 +73,20 @@ class InspIRCdProtocol(TS6BaseProtocol):
# InspIRCd doesn't distinguish between burst joins and regular joins,
# so what we're actually doing here is sending FJOIN from the server,
# on behalf of the clients that are joining.
channel = self.irc.toLower(channel)
channel = self.toLower(channel)
server = self.irc.getServer(client)
if not self.irc.isInternalServer(server):
log.error('(%s) Error trying to join %r to %r (no such client exists)', self.irc.name, client, channel)
server = self.getServer(client)
if not self.isInternalServer(server):
log.error('(%s) Error trying to join %r to %r (no such client exists)', self.name, client, channel)
raise LookupError('No such PyLink client exists.')
# Strip out list-modes, they shouldn't be ever sent in FJOIN.
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']]
modes = [m for m in self.channels[channel].modes if m[0] not in self.cmodes['*A']]
self._send_with_prefix(server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
ts=self.irc.channels[channel].ts, uid=client, channel=channel,
modes=self.irc.joinModes(modes)))
self.irc.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel)
ts=self.channels[channel].ts, uid=client, channel=channel,
modes=self.joinModes(modes)))
self.channels[channel].users.add(client)
self.users[client].channels.add(channel)
def sjoin(self, server, channel, users, ts=None, modes=set()):
"""Sends an SJOIN for a group of users to a channel.
@ -97,29 +97,29 @@ class InspIRCdProtocol(TS6BaseProtocol):
Example uses:
sjoin('100', '#test', [('', '100AAABBC'), ('qo', 100AAABBB'), ('h', '100AAADDD')])
sjoin(self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])
sjoin(self.sid, '#test', [('o', self.pseudoclient.uid)])
"""
channel = self.irc.toLower(channel)
server = server or self.irc.sid
channel = self.toLower(channel)
server = server or self.sid
assert users, "sjoin: No users sent?"
log.debug('(%s) sjoin: got %r for users', self.irc.name, users)
log.debug('(%s) sjoin: got %r for users', self.name, users)
if not server:
raise LookupError('No such PyLink client exists.')
# Strip out list-modes, they shouldn't ever be sent in FJOIN (protocol rules).
modes = modes or self.irc.channels[channel].modes
orig_ts = self.irc.channels[channel].ts
modes = modes or self.channels[channel].modes
orig_ts = self.channels[channel].ts
ts = ts or orig_ts
banmodes = []
regularmodes = []
for mode in modes:
modechar = mode[0][-1]
if modechar in self.irc.cmodes['*A']:
if modechar in self.cmodes['*A']:
# Track bans separately (they are sent as a normal FMODE instead of in FJOIN.
# However, don't reset bans that have already been set.
if (modechar, mode[1]) not in self.irc.channels[channel].modes:
if (modechar, mode[1]) not in self.channels[channel].modes:
banmodes.append(mode)
else:
regularmodes.append(mode)
@ -137,21 +137,21 @@ class InspIRCdProtocol(TS6BaseProtocol):
for m in prefixes:
changedmodes.add(('+%s' % m, user))
try:
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
except KeyError: # Not initialized yet?
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.name, channel, user)
namelist = ' '.join(namelist)
self._send_with_prefix(server, "FJOIN {channel} {ts} {modes} :{users}".format(
ts=ts, users=namelist, channel=channel,
modes=self.irc.joinModes(modes)))
self.irc.channels[channel].users.update(uids)
modes=self.joinModes(modes)))
self.channels[channel].users.update(uids)
if banmodes:
# Burst ban modes if there are any.
# <- :1ML FMODE #test 1461201525 +bb *!*@bad.user *!*@rly.bad.user
self._send_with_prefix(server, "FMODE {channel} {ts} {modes} ".format(
ts=ts, channel=channel, modes=self.irc.joinModes(banmodes)))
ts=ts, channel=channel, modes=self.joinModes(banmodes)))
self.updateTS(server, channel, ts, changedmodes)
@ -163,19 +163,19 @@ class InspIRCdProtocol(TS6BaseProtocol):
recognize ANY non-burst oper ups.
Plugins don't have to call this function themselves, but they can
set the opertype attribute of an IrcUser object (in self.irc.users),
set the opertype attribute of an IrcUser object (in self.users),
and the change will be reflected here."""
userobj = self.irc.users[target]
userobj = self.users[target]
try:
otype = opertype or userobj.opertype or 'IRC Operator'
except AttributeError:
log.debug('(%s) opertype field for %s (%s) isn\'t filled yet!',
self.irc.name, target, userobj.nick)
self.name, target, userobj.nick)
# whatever, this is non-standard anyways.
otype = 'IRC Operator'
assert otype, "Tried to send an empty OPERTYPE!"
log.debug('(%s) Sending OPERTYPE from %s to oper them up.',
self.irc.name, target)
self.name, target)
userobj.opertype = otype
# InspIRCd 2.x uses _ in OPERTYPE to denote spaces, while InspIRCd 3.x does not. This is not
@ -194,64 +194,64 @@ class InspIRCdProtocol(TS6BaseProtocol):
# -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA
# -> :9PYAAAAAA MODE 9PYAAAAAA -i+w
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
log.debug('(%s) inspircd._send_with_prefixModes: received %r for mode list', self.irc.name, modes)
log.debug('(%s) inspircd._send_with_prefixModes: received %r for mode list', self.name, modes)
if ('+o', None) in modes and not utils.isChannel(target):
# https://github.com/inspircd/inspircd/blob/master/src/modules/m_spanningtree/opertype.cpp#L26-L28
# Servers need a special command to set umode +o on people.
self._operUp(target)
self.irc.applyModes(target, modes)
joinedmodes = self.irc.joinModes(modes)
self.applyModes(target, modes)
joinedmodes = self.joinModes(modes)
if utils.isChannel(target):
ts = ts or self.irc.channels[self.irc.toLower(target)].ts
ts = ts or self.channels[self.toLower(target)].ts
self._send_with_prefix(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))
else:
self._send_with_prefix(numeric, 'MODE %s %s' % (target, joinedmodes))
def kill(self, numeric, target, reason):
"""Sends a kill from a PyLink client/server."""
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
# InspIRCd will show the raw kill message sent from our server as the quit message.
# So, make the kill look actually like a kill instead of someone quitting with
# an arbitrary message.
if numeric in self.irc.servers:
sourcenick = self.irc.servers[numeric].name
if numeric in self.servers:
sourcenick = self.servers[numeric].name
else:
sourcenick = self.irc.users[numeric].nick
sourcenick = self.users[numeric].nick
self._send_with_prefix(numeric, 'KILL %s :Killed (%s (%s))' % (target, sourcenick, reason))
# We only need to call removeClient here if the target is one of our
# clients, since any remote servers will send a QUIT from
# their target if the command succeeds.
if self.irc.isInternalClient(target):
if self.isInternalClient(target):
self.removeClient(target)
def topicBurst(self, numeric, target, text):
"""Sends a topic change from a PyLink server. This is usually used on burst."""
if not self.irc.isInternalServer(numeric):
if not self.isInternalServer(numeric):
raise LookupError('No such PyLink server exists.')
ts = int(time.time())
servername = self.irc.servers[numeric].name
servername = self.servers[numeric].name
self._send_with_prefix(numeric, 'FTOPIC %s %s %s :%s' % (target, ts, servername, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
self.channels[target].topic = text
self.channels[target].topicset = True
def invite(self, numeric, target, channel):
"""Sends an INVITE from a PyLink client.."""
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(numeric, 'INVITE %s %s' % (target, channel))
def knock(self, numeric, target, text):
"""Sends a KNOCK from a PyLink client."""
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(numeric, 'ENCAP * KNOCK %s :%s' % (target, text))
@ -263,56 +263,56 @@ class InspIRCdProtocol(TS6BaseProtocol):
raise NotImplementedError("Changing field %r of a client is "
"unsupported by this protocol." % field)
if self.irc.isInternalClient(target):
if self.isInternalClient(target):
# It is one of our clients, use FIDENT/HOST/NAME.
if field == 'IDENT':
self.irc.users[target].ident = text
self.users[target].ident = text
self._send_with_prefix(target, 'FIDENT %s' % text)
elif field == 'HOST':
self.irc.users[target].host = text
self.users[target].host = text
self._send_with_prefix(target, 'FHOST %s' % text)
elif field in ('REALNAME', 'GECOS'):
self.irc.users[target].realname = text
self.users[target].realname = text
self._send_with_prefix(target, 'FNAME :%s' % text)
else:
# It is a client on another server, use CHGIDENT/HOST/NAME.
if field == 'IDENT':
if 'm_chgident.so' not in self.modsupport:
log.warning('(%s) Failed to change ident of %s to %r: load m_chgident.so!', self.irc.name, target, text)
log.warning('(%s) Failed to change ident of %s to %r: load m_chgident.so!', self.name, target, text)
return
self.irc.users[target].ident = text
self._send_with_prefix(self.irc.sid, 'CHGIDENT %s %s' % (target, text))
self.users[target].ident = text
self._send_with_prefix(self.sid, 'CHGIDENT %s %s' % (target, text))
# Send hook payloads for other plugins to listen to.
self.irc.callHooks([self.irc.sid, 'CHGIDENT',
self.callHooks([self.sid, 'CHGIDENT',
{'target': target, 'newident': text}])
elif field == 'HOST':
if 'm_chghost.so' not in self.modsupport:
log.warning('(%s) Failed to change host of %s to %r: load m_chghost.so!', self.irc.name, target, text)
log.warning('(%s) Failed to change host of %s to %r: load m_chghost.so!', self.name, target, text)
return
self.irc.users[target].host = text
self._send_with_prefix(self.irc.sid, 'CHGHOST %s %s' % (target, text))
self.users[target].host = text
self._send_with_prefix(self.sid, 'CHGHOST %s %s' % (target, text))
self.irc.callHooks([self.irc.sid, 'CHGHOST',
self.callHooks([self.sid, 'CHGHOST',
{'target': target, 'newhost': text}])
elif field in ('REALNAME', 'GECOS'):
if 'm_chgname.so' not in self.modsupport:
log.warning('(%s) Failed to change real name of %s to %r: load m_chgname.so!', self.irc.name, target, text)
log.warning('(%s) Failed to change real name of %s to %r: load m_chgname.so!', self.name, target, text)
return
self.irc.users[target].realname = text
self._send_with_prefix(self.irc.sid, 'CHGNAME %s :%s' % (target, text))
self.users[target].realname = text
self._send_with_prefix(self.sid, 'CHGNAME %s :%s' % (target, text))
self.irc.callHooks([self.irc.sid, 'CHGNAME',
self.callHooks([self.sid, 'CHGNAME',
{'target': target, 'newgecos': text}])
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
source = source or self.sid
target = target or self.uplink
if not (target is None or source is None):
self._send_with_prefix(source, 'PING %s %s' % (source, target))
@ -327,7 +327,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
# :<sid> NUM <numeric source sid> <target uuid> <3 digit number> <params>
# Take this into consideration if we ever target InspIRCd 2.2, even though m_spanningtree
# does provide backwards compatibility for commands like this. -GLolol
self._send_with_prefix(self.irc.sid, 'PUSH %s ::%s %s %s %s' % (target, source, numeric, target, text))
self._send_with_prefix(self.sid, 'PUSH %s ::%s %s %s %s' % (target, source, numeric, target, text))
def away(self, source, text):
"""Sends an AWAY message from a PyLink client. <text> can be an empty string
@ -336,7 +336,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
self._send_with_prefix(source, 'AWAY %s :%s' % (int(time.time()), text))
else:
self._send_with_prefix(source, 'AWAY')
self.irc.users[source].away = text
self.users[source].away = text
def spawnServer(self, name, sid=None, uplink=None, desc=None, endburst_delay=0):
"""
@ -351,30 +351,30 @@ class InspIRCdProtocol(TS6BaseProtocol):
and prevent connections from filling up the snomasks too much.
"""
# -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver
uplink = uplink or self.irc.sid
uplink = uplink or self.sid
name = name.lower()
# "desc" defaults to the configured server description.
desc = desc or self.irc.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
desc = desc or self.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
if sid is None: # No sid given; generate one!
sid = self.sidgen.next_sid()
assert len(sid) == 3, "Incorrect SID length"
if sid in self.irc.servers:
if sid in self.servers:
raise ValueError('A server with SID %r already exists!' % sid)
for server in self.irc.servers.values():
for server in self.servers.values():
if name == server.name:
raise ValueError('A server named %r already exists!' % name)
if not self.irc.isInternalServer(uplink):
if not self.isInternalServer(uplink):
raise ValueError('Server %r is not a PyLink server!' % uplink)
if not utils.isServerName(name):
raise ValueError('Invalid server name %r' % name)
self._send_with_prefix(uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc))
self.irc.servers[sid] = IrcServer(uplink, name, internal=True, desc=desc)
self.servers[sid] = IrcServer(uplink, name, internal=True, desc=desc)
def endburstf():
# Delay ENDBURST by X seconds if requested.
if self.irc.aborted.wait(endburst_delay):
if self.aborted.wait(endburst_delay):
# We managed to catch the abort flag before sending ENDBURST, so break
log.debug('(%s) stopping endburstf() for %s as aborted was set', self.irc.name, sid)
log.debug('(%s) stopping endburstf() for %s as aborted was set', self.name, sid)
return
self._send_with_prefix(sid, 'ENDBURST')
@ -394,25 +394,25 @@ class InspIRCdProtocol(TS6BaseProtocol):
def post_connect(self):
"""Initializes a connection to a server."""
ts = self.irc.start_ts
ts = self.start_ts
# Track the modules supported by the uplink.
self.modsupport = []
f = self.irc.send
f = self.send
f('CAPAB START %s' % self.proto_ver)
f('CAPAB CAPABILITIES :PROTOCOL=%s' % self.proto_ver)
f('CAPAB END')
host = self.irc.serverdata["hostname"]
host = self.serverdata["hostname"]
f('SERVER {host} {Pass} 0 {sid} :{sdesc}'.format(host=host,
Pass=self.irc.serverdata["sendpass"], sid=self.irc.sid,
sdesc=self.irc.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']))
Pass=self.serverdata["sendpass"], sid=self.sid,
sdesc=self.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']))
self._send_with_prefix(self.irc.sid, 'BURST %s' % ts)
self._send_with_prefix(self.sid, 'BURST %s' % ts)
# InspIRCd sends VERSION data on link, instead of whenever requested by a client.
self._send_with_prefix(self.irc.sid, 'VERSION :%s' % self.irc.version())
self._send_with_prefix(self.irc.sid, 'ENDBURST')
self._send_with_prefix(self.sid, 'VERSION :%s' % self.version())
self._send_with_prefix(self.sid, 'ENDBURST')
def handle_capab(self, source, command, args):
"""
@ -453,7 +453,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
name = 'owner'
# We don't care about mode prefixes; just the mode char.
self.irc.cmodes[name] = char[-1]
self.cmodes[name] = char[-1]
elif args[0] == 'USERMODES':
@ -467,7 +467,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
name, char = modepair.split('=')
# Strip u_ prefixes to be consistent with other protocols.
name = name.lstrip('u_')
self.irc.umodes[name] = char
self.umodes[name] = char
elif args[0] == 'CAPABILITIES':
# <- CAPAB CAPABILITIES :NICKMAX=21 CHANMAX=64 MAXMODES=20
@ -478,7 +478,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
# First, turn the arguments into a dict
caps = self.parseCapabilities(args[-1])
log.debug("(%s) capabilities list: %s", self.irc.name, caps)
log.debug("(%s) capabilities list: %s", self.name, caps)
# Check the protocol version
self.remote_proto_ver = protocol_version = int(caps['PROTOCOL'])
@ -491,30 +491,30 @@ class InspIRCdProtocol(TS6BaseProtocol):
elif protocol_version > self.max_proto_ver:
log.warning("(%s) PyLink support for InspIRCd 2.2+ is experimental, "
"and should not be relied upon for anything important.",
self.irc.name)
self.name)
# Store the max nick and channel lengths
self.irc.maxnicklen = int(caps['NICKMAX'])
self.irc.maxchanlen = int(caps['CHANMAX'])
self.maxnicklen = int(caps['NICKMAX'])
self.maxchanlen = int(caps['CHANMAX'])
# Modes are divided into A, B, C, and D classes
# See http://www.irc.org/tech_docs/005.html
# FIXME: Find a neater way to assign/store this.
self.irc.cmodes['*A'], self.irc.cmodes['*B'], self.irc.cmodes['*C'], self.irc.cmodes['*D'] \
self.cmodes['*A'], self.cmodes['*B'], self.cmodes['*C'], self.cmodes['*D'] \
= caps['CHANMODES'].split(',')
self.irc.umodes['*A'], self.irc.umodes['*B'], self.irc.umodes['*C'], self.irc.umodes['*D'] \
self.umodes['*A'], self.umodes['*B'], self.umodes['*C'], self.umodes['*D'] \
= caps['USERMODES'].split(',')
# Separate the prefixes field (e.g. "(Yqaohv)!~&@%+") into a
# dict mapping mode characters to mode prefixes.
self.irc.prefixmodes = self.parsePrefixes(caps['PREFIX'])
log.debug('(%s) self.irc.prefixmodes set to %r', self.irc.name,
self.irc.prefixmodes)
self.prefixmodes = self.parsePrefixes(caps['PREFIX'])
log.debug('(%s) self.prefixmodes set to %r', self.name,
self.prefixmodes)
# Finally, set the irc.connected (protocol negotiation complete)
# state to True.
self.irc.connected.set()
self.connected.set()
elif args[0] == 'MODSUPPORT':
# <- CAPAB MODSUPPORT :m_alltime.so m_check.so m_chghost.so m_chgident.so m_chgname.so m_fullversion.so m_gecosban.so m_knock.so m_muteban.so m_nicklock.so m_nopartmsg.so m_opmoderated.so m_sajoin.so m_sanick.so m_sapart.so m_serverban.so m_services_account.so m_showwhois.so m_silence.so m_swhois.so m_uninvite.so m_watch.so
self.modsupport = args[-1].split()
@ -523,19 +523,19 @@ class InspIRCdProtocol(TS6BaseProtocol):
"""Handles incoming PING commands, so we don't time out."""
# <- :70M PING 70M 0AL
# -> :0AL PONG 0AL 70M
if self.irc.isInternalServer(args[1]):
if self.isInternalServer(args[1]):
self._send_with_prefix(args[1], 'PONG %s %s' % (args[1], source), queue=False)
def handle_fjoin(self, servernumeric, command, args):
"""Handles incoming FJOIN commands (InspIRCd equivalent of JOIN/SJOIN)."""
# :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...>
channel = self.irc.toLower(args[0])
chandata = self.irc.channels[channel].deepcopy()
channel = self.toLower(args[0])
chandata = self.channels[channel].deepcopy()
# InspIRCd sends each channel's users in the form of 'modeprefix(es),UID'
userlist = args[-1].split()
modestring = args[2:-1] or args[2]
parsedmodes = self.irc.parseModes(channel, modestring)
parsedmodes = self.parseModes(channel, modestring)
namelist = []
# Keep track of other modes that are added due to prefix modes being joined too.
@ -545,18 +545,18 @@ class InspIRCdProtocol(TS6BaseProtocol):
modeprefix, user = user.split(',', 1)
# Don't crash when we get an invalid UID.
if user not in self.irc.users:
if user not in self.users:
log.debug('(%s) handle_fjoin: tried to introduce user %s not in our user list, ignoring...',
self.irc.name, user)
self.name, user)
continue
namelist.append(user)
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
# Only save mode changes if the remote has lower TS than us.
changedmodes |= {('+%s' % mode, user) for mode in modeprefix}
self.irc.channels[channel].users.add(user)
self.channels[channel].users.add(user)
# Statekeeping with timestamps. Note: some service packages (Anope 1.8) send a trailing
# 'd' after the timestamp, which we should strip out to prevent int() from erroring.
@ -565,7 +565,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
# <- :3AX FJOIN #monitor 1485462109d + :,3AXAAAAAK
their_ts = int(''.join(char for char in args[1] if char.isdigit()))
our_ts = self.irc.channels[channel].ts
our_ts = self.channels[channel].ts
self.updateTS(servernumeric, channel, their_ts, changedmodes)
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
@ -577,35 +577,35 @@ class InspIRCdProtocol(TS6BaseProtocol):
uid, ts, nick, realhost, host, ident, ip = args[0:7]
self.check_nick_collision(nick)
realname = args[-1]
self.irc.users[uid] = userobj = IrcUser(nick, ts, uid, numeric, ident, host, realname, realhost, ip)
self.users[uid] = userobj = IrcUser(nick, ts, uid, numeric, ident, host, realname, realhost, ip)
parsedmodes = self.irc.parseModes(uid, [args[8], args[9]])
self.irc.applyModes(uid, parsedmodes)
parsedmodes = self.parseModes(uid, [args[8], args[9]])
self.applyModes(uid, parsedmodes)
if (self.irc.umodes.get('servprotect'), None) in userobj.modes:
if (self.umodes.get('servprotect'), None) in userobj.modes:
# Services are usually given a "Network Service" WHOIS, so
# set that as the opertype.
self.irc.callHooks([uid, 'CLIENT_OPERED', {'text': 'Network Service'}])
self.callHooks([uid, 'CLIENT_OPERED', {'text': 'Network Service'}])
self.irc.servers[numeric].users.add(uid)
self.servers[numeric].users.add(uid)
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
def handle_server(self, numeric, command, args):
"""Handles incoming SERVER commands (introduction of servers)."""
# Initial SERVER command on connect.
if self.irc.uplink is None:
if self.uplink is None:
# <- SERVER whatever.net abcdefgh 0 10X :some server description
servername = args[0].lower()
numeric = args[3]
if args[1] != self.irc.serverdata['recvpass']:
if args[1] != self.serverdata['recvpass']:
# Check if recvpass is correct
raise ProtocolError('Error: recvpass from uplink server %s does not match configuration!' % servername)
sdesc = args[-1]
self.irc.servers[numeric] = IrcServer(None, servername, desc=sdesc)
self.irc.uplink = numeric
self.servers[numeric] = IrcServer(None, servername, desc=sdesc)
self.uplink = numeric
return
# Other server introductions.
@ -613,18 +613,18 @@ class InspIRCdProtocol(TS6BaseProtocol):
servername = args[0].lower()
sid = args[3]
sdesc = args[-1]
self.irc.servers[sid] = IrcServer(numeric, servername, desc=sdesc)
self.servers[sid] = IrcServer(numeric, servername, desc=sdesc)
return {'name': servername, 'sid': args[3], 'text': sdesc}
def handle_fmode(self, numeric, command, args):
"""Handles the FMODE command, used for channel mode changes."""
# <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD
channel = self.irc.toLower(args[0])
oldobj = self.irc.channels[channel].deepcopy()
channel = self.toLower(args[0])
oldobj = self.channels[channel].deepcopy()
modes = args[2:]
changedmodes = self.irc.parseModes(channel, modes)
self.irc.applyModes(channel, changedmodes)
changedmodes = self.parseModes(channel, modes)
self.applyModes(channel, changedmodes)
ts = int(args[1])
return {'target': channel, 'modes': changedmodes, 'ts': ts,
'channeldata': oldobj}
@ -636,8 +636,8 @@ class InspIRCdProtocol(TS6BaseProtocol):
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
target = args[0]
modestrings = args[1:]
changedmodes = self.irc.parseModes(target, modestrings)
self.irc.applyModes(target, changedmodes)
changedmodes = self.parseModes(target, modestrings)
self.applyModes(target, changedmodes)
return {'target': target, 'modes': changedmodes}
def handle_idle(self, numeric, command, args):
@ -662,12 +662,12 @@ class InspIRCdProtocol(TS6BaseProtocol):
def handle_ftopic(self, numeric, command, args):
"""Handles incoming FTOPIC (sets topic on burst)."""
# <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
ts = args[1]
setter = args[2]
topic = args[-1]
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
self.channels[channel].topic = topic
self.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
# SVSTOPIC is used by InspIRCd module m_topiclock - its arguments are the same as FTOPIC
@ -677,14 +677,14 @@ class InspIRCdProtocol(TS6BaseProtocol):
"""Handles incoming INVITEs."""
# <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0
target = args[0]
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
# We don't actually need to process this; just send the hook so plugins can use it
return {'target': target, 'channel': channel}
def handle_knock(self, numeric, command, args):
"""Handles channel KNOCKs."""
# <- :70MAAAAAA ENCAP * KNOCK #blah :abcdefg
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
text = args[1]
return {'channel': channel, 'text': text}
@ -701,29 +701,29 @@ class InspIRCdProtocol(TS6BaseProtocol):
# Set umode +o on the target.
omode = [('+o', None)]
self.irc.applyModes(target, omode)
self.applyModes(target, omode)
# Call the CLIENT_OPERED hook that protocols use. The MODE hook
# payload is returned below.
self.irc.callHooks([target, 'CLIENT_OPERED', {'text': opertype}])
self.callHooks([target, 'CLIENT_OPERED', {'text': opertype}])
return {'target': target, 'modes': omode}
def handle_fident(self, numeric, command, args):
"""Handles FIDENT, used for denoting ident changes."""
# <- :70MAAAAAB FIDENT test
self.irc.users[numeric].ident = newident = args[0]
self.users[numeric].ident = newident = args[0]
return {'target': numeric, 'newident': newident}
def handle_fhost(self, numeric, command, args):
"""Handles FHOST, used for denoting hostname changes."""
# <- :70MAAAAAB FHOST some.host
self.irc.users[numeric].host = newhost = args[0]
self.users[numeric].host = newhost = args[0]
return {'target': numeric, 'newhost': newhost}
def handle_fname(self, numeric, command, args):
"""Handles FNAME, used for denoting real name/gecos changes."""
# <- :70MAAAAAB FNAME :afdsafasf
self.irc.users[numeric].realname = newgecos = args[0]
self.users[numeric].realname = newgecos = args[0]
return {'target': numeric, 'newgecos': newgecos}
def handle_endburst(self, numeric, command, args):
@ -735,10 +735,10 @@ class InspIRCdProtocol(TS6BaseProtocol):
# <- :1MLAAAAIG AWAY 1439371390 :Auto-away
try:
ts = args[0]
self.irc.users[numeric].away = text = args[1]
self.users[numeric].away = text = args[1]
return {'text': text, 'ts': ts}
except IndexError: # User is unsetting away status
self.irc.users[numeric].away = ''
self.users[numeric].away = ''
return {'text': ''}
def handle_rsquit(self, numeric, command, args):
@ -760,15 +760,15 @@ class InspIRCdProtocol(TS6BaseProtocol):
# SQUIT, in order to be consistent with other IRCds which make SQUITs
# implicit.
target = self._get_SID(args[0])
if self.irc.isInternalServer(target):
if self.isInternalServer(target):
# The target has to be one of our servers in order to work...
uplink = self.irc.servers[target].uplink
reason = 'Requested by %s' % self.irc.getHostmask(numeric)
uplink = self.servers[target].uplink
reason = 'Requested by %s' % self.getHostmask(numeric)
self._send_with_prefix(uplink, 'SQUIT %s :%s' % (target, reason))
return self.handle_squit(numeric, 'SQUIT', [target, reason])
else:
log.debug("(%s) Got RSQUIT for '%s', which is either invalid or not "
"a server of ours!", self.irc.name, args[0])
"a server of ours!", self.name, args[0])
def handle_metadata(self, numeric, command, args):
"""
@ -777,12 +777,12 @@ class InspIRCdProtocol(TS6BaseProtocol):
"""
uid = args[0]
if args[1] == 'accountname' and uid in self.irc.users:
if args[1] == 'accountname' and uid in self.users:
# <- :00A METADATA 1MLAAAJET accountname :
# <- :00A METADATA 1MLAAAJET accountname :tester
# Sets the services login name of the client.
self.irc.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': args[-1]}])
self.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': args[-1]}])
def handle_version(self, numeric, command, args):
"""
@ -797,7 +797,7 @@ class InspIRCdProtocol(TS6BaseProtocol):
# removed from our user list.
# If not, we have to assume that KILL = QUIT and remove them
# ourselves.
data = self.irc.users.get(killed)
data = self.users.get(killed)
if data:
self.removeClient(killed)
return {'target': killed, 'text': args[1], 'userdata': data}
@ -808,20 +808,20 @@ class InspIRCdProtocol(TS6BaseProtocol):
# ENCAP -> SAKICK args: ['#test', '0ALAAAAAB', 'test']
target = args[1]
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
try:
reason = args[2]
except IndexError:
# Kick reason is optional, strange...
reason = self.irc.getFriendlyName(source)
reason = self.getFriendlyName(source)
if not self.irc.isInternalClient(target):
log.warning("(%s) Got SAKICK for client that not one of ours: %s", self.irc.name, target)
if not self.isInternalClient(target):
log.warning("(%s) Got SAKICK for client that not one of ours: %s", self.name, target)
return
else:
# Like RSQUIT, SAKICK requires that the receiving server acknowledge that a kick has
# happened. This comes from the server hosting the target client.
server = self.irc.getServer(target)
server = self.getServer(target)
self.kick(server, channel, target, reason)
return {'channel': channel, 'target': target, 'text': reason}
@ -833,6 +833,6 @@ class InspIRCdProtocol(TS6BaseProtocol):
# XXX: We override notice() here because that abstraction doesn't allow messages from servers.
timestring = '%s (%s)' % (time.strftime('%Y-%m-%d %H:%M:%S'), int(time.time()))
self._send_with_prefix(self.irc.sid, 'NOTICE %s :System time is %s on %s' % (source, timestring, self.irc.hostname()))
self._send_with_prefix(self.sid, 'NOTICE %s :System time is %s on %s' % (source, timestring, self.hostname()))
Class = InspIRCdProtocol

View File

@ -14,10 +14,10 @@ class IRCCommonProtocol(IRCNetwork):
def validate_server_conf(self):
"""Validates that the server block given contains the required keys."""
for k in self.conf_keys:
assert k in self.irc.serverdata, "Missing option %r in server block for network %s." % (k, self.irc.name)
assert k in self.serverdata, "Missing option %r in server block for network %s." % (k, self.name)
port = self.irc.serverdata['port']
assert type(port) == int and 0 < port < 65535, "Invalid port %r for network %s" % (port, self.irc.name)
port = self.serverdata['port']
assert type(port) == int and 0 < port < 65535, "Invalid port %r for network %s" % (port, self.name)
@staticmethod
def parseArgs(args):
@ -56,20 +56,20 @@ class IRCCommonProtocol(IRCNetwork):
# Normally we'd only need to check for our SID as the SQUIT target, but Nefarious
# actually uses the uplink server as the SQUIT target.
# <- ABAAE SQ nefarious.midnight.vpn 0 :test
if split_server in (self.irc.sid, self.irc.uplink):
if split_server in (self.sid, self.uplink):
raise ProtocolError('SQUIT received: (reason: %s)' % args[-1])
affected_users = []
affected_nicks = defaultdict(list)
log.debug('(%s) Splitting server %s (reason: %s)', self.irc.name, split_server, args[-1])
log.debug('(%s) Splitting server %s (reason: %s)', self.name, split_server, args[-1])
if split_server not in self.irc.servers:
log.warning("(%s) Tried to split a server (%s) that didn't exist!", self.irc.name, split_server)
if split_server not in self.servers:
log.warning("(%s) Tried to split a server (%s) that didn't exist!", self.name, split_server)
return
# Prevent RuntimeError: dictionary changed size during iteration
old_servers = self.irc.servers.copy()
old_channels = self.irc.channels.copy()
old_servers = self.servers.copy()
old_channels = self.channels.copy()
# Cycle through our list of servers. If any server's uplink is the one that is being SQUIT,
# remove them and all their users too.
@ -81,9 +81,9 @@ class IRCCommonProtocol(IRCNetwork):
"PyLink: Automatically splitting leaf servers of %s" % sid])
affected_users += args['users']
for user in self.irc.servers[split_server].users.copy():
for user in self.servers[split_server].users.copy():
affected_users.append(user)
nick = self.irc.users[user].nick
nick = self.users[user].nick
# Nicks affected is channel specific for SQUIT:. This makes Clientbot's SQUIT relaying
# much easier to implement.
@ -94,12 +94,12 @@ class IRCCommonProtocol(IRCNetwork):
log.debug('Removing client %s (%s)', user, nick)
self.removeClient(user)
serverdata = self.irc.servers[split_server]
serverdata = self.servers[split_server]
sname = serverdata.name
uplink = serverdata.uplink
del self.irc.servers[split_server]
log.debug('(%s) Netsplit affected users: %s', self.irc.name, affected_users)
del self.servers[split_server]
log.debug('(%s) Netsplit affected users: %s', self.name, affected_users)
return {'target': split_server, 'users': affected_users, 'name': sname,
'uplink': uplink, 'nicks': affected_nicks, 'serverdata': serverdata,
@ -168,37 +168,37 @@ class IRCS2SProtocol(IRCCommonProtocol):
sender_sid = self._get_SID(sender)
sender_uid = self._get_UID(sender)
if sender_sid in self.irc.servers:
if sender_sid in self.servers:
# Sender is a server (converting from name to SID gave a valid result).
sender = sender_sid
elif sender_uid in self.irc.users:
elif sender_uid in self.users:
# Sender is a user (converting from name to UID gave a valid result).
sender = sender_uid
else:
# No sender prefix; treat as coming from uplink IRCd.
sender = self.irc.uplink
sender = self.uplink
args.insert(0, sender)
if self.irc.isInternalClient(sender) or self.irc.isInternalServer(sender):
log.warning("(%s) Received command %s being routed the wrong way!", self.irc.name, command)
if self.isInternalClient(sender) or self.isInternalServer(sender):
log.warning("(%s) Received command %s being routed the wrong way!", self.name, command)
return
raw_command = args[1].upper()
args = args[2:]
log.debug('(%s) Found message sender as %s', self.irc.name, sender)
log.debug('(%s) Found message sender as %s', self.name, sender)
# For P10, convert the command token into a regular command, if present.
command = self.COMMAND_TOKENS.get(raw_command, raw_command)
if command != raw_command:
log.debug('(%s) Translating token %s to command %s', self.irc.name, raw_command, command)
log.debug('(%s) Translating token %s to command %s', self.name, raw_command, command)
if command == 'ENCAP':
# Special case for TS6 encapsulated commands (ENCAP), in forms like this:
# <- :00A ENCAP * SU 42XAAAAAC :GLolol
command = args[1]
args = args[2:]
log.debug("(%s) Rewriting incoming ENCAP to command %s (args: %s)", self.irc.name, command, args)
log.debug("(%s) Rewriting incoming ENCAP to command %s (args: %s)", self.name, command, args)
try:
func = getattr(self, 'handle_'+command.lower())
@ -232,8 +232,8 @@ class IRCS2SProtocol(IRCCommonProtocol):
# Note: don't mess with the case of the channel prefix, or ~#channel
# messages will break on RFC1459 casemapping networks (it becomes ^#channel
# instead).
target = '#'.join((split_channel[0], self.irc.toLower(split_channel[1])))
log.debug('(%s) Normalizing channel target %s to %s', self.irc.name, args[0], target)
target = '#'.join((split_channel[0], self.toLower(split_channel[1])))
log.debug('(%s) Normalizing channel target %s to %s', self.name, args[0], target)
return {'target': target, 'text': args[1]}
@ -243,13 +243,13 @@ class IRCS2SProtocol(IRCCommonProtocol):
"""
Nick collision checker.
"""
uid = self.irc.nickToUid(nick)
uid = self.nickToUid(nick)
# If there is a nick collision, we simply alert plugins. Relay will purposely try to
# lose fights and tag nicks instead, while other plugins can choose how to handle this.
if uid:
log.info('(%s) Nick collision on %s/%s, forwarding this to plugins', self.irc.name,
log.info('(%s) Nick collision on %s/%s, forwarding this to plugins', self.name,
uid, nick)
self.irc.callHooks([self.irc.sid, 'SAVE', {'target': uid}])
self.callHooks([self.sid, 'SAVE', {'target': uid}])
def handle_kill(self, source, command, args):
"""Handles incoming KILLs."""
@ -259,7 +259,7 @@ class IRCS2SProtocol(IRCCommonProtocol):
# removed from our user list.
# If not, we have to assume that KILL = QUIT and remove them
# ourselves.
data = self.irc.users.get(killed)
data = self.users.get(killed)
if data:
self.removeClient(killed)
@ -270,7 +270,7 @@ class IRCS2SProtocol(IRCCommonProtocol):
try:
# Get the nick or server name of the caller.
killer = self.irc.getFriendlyName(source)
killer = self.getFriendlyName(source)
except KeyError:
# Killer was... neither? We must have aliens or something. Fallback
# to the given "UID".
@ -295,9 +295,9 @@ class IRCS2SProtocol(IRCCommonProtocol):
# <- ABAAA A :blah
# <- ABAAA A
try:
self.irc.users[numeric].away = text = args[0]
self.users[numeric].away = text = args[0]
except IndexError: # User is unsetting away status
self.irc.users[numeric].away = text = ''
self.users[numeric].away = text = ''
return {'text': text}
def handle_version(self, numeric, command, args):
@ -333,5 +333,5 @@ class IRCS2SProtocol(IRCCommonProtocol):
def handle_pong(self, source, command, args):
"""Handles incoming PONG commands."""
if source == self.irc.uplink:
self.irc.lastping = time.time()
if source == self.uplink:
self.lastping = time.time()

View File

@ -11,6 +11,6 @@ class NefariousProtocol(P10Protocol):
log.warning("(%s) protocols/nefarious.py has been renamed to protocols/p10.py, which "
"now also supports other IRCu variants. Please update your configuration, "
"as this migration stub will be removed in a future version.",
self.irc.name)
self.name)
Class = NefariousProtocol

View File

@ -165,7 +165,7 @@ class P10Protocol(IRCS2SProtocol):
self.protocol_caps |= {'slash-in-hosts', 'underscore-in-hosts'}
def _send_with_prefix(self, source, text, **kwargs):
self.irc.send("%s %s" % (source, text), **kwargs)
self.send("%s %s" % (source, text), **kwargs)
@staticmethod
def access_sort(key):
@ -261,8 +261,8 @@ class P10Protocol(IRCS2SProtocol):
# -2 <numeric>
# -1 <fullname>
server = server or self.irc.sid
if not self.irc.isInternalServer(server):
server = server or self.sid
if not self.isInternalServer(server):
raise ValueError('Server %r is not a PyLink server!' % server)
# Create an UIDGenerator instance for every SID, so that each gets
@ -273,16 +273,16 @@ class P10Protocol(IRCS2SProtocol):
ts = ts or int(time.time())
realname = realname or conf.conf['bot']['realname']
realhost = realhost or host
raw_modes = self.irc.joinModes(modes)
raw_modes = self.joinModes(modes)
# Initialize an IrcUser instance
u = self.irc.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
u = self.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable,
opertype=opertype)
# Fill in modes and add it to our users index
self.irc.applyModes(uid, modes)
self.irc.servers[server].users.add(uid)
self.applyModes(uid, modes)
self.servers[server].users.add(uid)
# Encode IPs when sending
if ip_address(ip).version == 4:
@ -301,62 +301,62 @@ class P10Protocol(IRCS2SProtocol):
def away(self, source, text):
"""Sends an AWAY message from a PyLink client. <text> can be an empty string
to unset AWAY status."""
if not self.irc.isInternalClient(source):
if not self.isInternalClient(source):
raise LookupError('No such PyLink client exists.')
if text:
self._send_with_prefix(source, 'A :%s' % text)
else:
self._send_with_prefix(source, 'A')
self.irc.users[source].away = text
self.users[source].away = text
def invite(self, numeric, target, channel):
"""Sends INVITEs from a PyLink client."""
# Note: we have to send a nick as the target, not a UID.
# <- ABAAA I PyLink-devel #services 1460948992
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
nick = self.irc.users[target].nick
nick = self.users[target].nick
self._send_with_prefix(numeric, 'I %s %s %s' % (nick, channel, self.irc.channels[channel].ts))
self._send_with_prefix(numeric, 'I %s %s %s' % (nick, channel, self.channels[channel].ts))
def join(self, client, channel):
"""Joins a PyLink client to a channel."""
# <- ABAAB J #test3 1460744371
channel = self.irc.toLower(channel)
ts = self.irc.channels[channel].ts
channel = self.toLower(channel)
ts = self.channels[channel].ts
if not self.irc.isInternalClient(client):
if not self.isInternalClient(client):
raise LookupError('No such PyLink client exists.')
if not self.irc.channels[channel].users:
if not self.channels[channel].users:
# Empty channels should be created with the CREATE command.
self._send_with_prefix(client, "C {channel} {ts}".format(ts=ts, channel=channel))
else:
self._send_with_prefix(client, "J {channel} {ts}".format(ts=ts, channel=channel))
self.irc.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel)
self.channels[channel].users.add(client)
self.users[client].channels.add(channel)
def kick(self, numeric, channel, target, reason=None):
"""Sends kicks from a PyLink client/server."""
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
channel = self.irc.toLower(channel)
channel = self.toLower(channel)
if not reason:
reason = 'No reason given'
cobj = self.irc.channels[channel]
cobj = self.channels[channel]
# HACK: prevent kick bounces by sending our kick through the server if
# the sender isn't op.
if numeric not in self.irc.servers and (not cobj.isOp(numeric)) and (not cobj.isHalfop(numeric)):
reason = '(%s) %s' % (self.irc.getFriendlyName(numeric), reason)
numeric = self.irc.getServer(numeric)
if numeric not in self.servers and (not cobj.isOp(numeric)) and (not cobj.isHalfop(numeric)):
reason = '(%s) %s' % (self.getFriendlyName(numeric), reason)
numeric = self.getServer(numeric)
self._send_with_prefix(numeric, 'K %s %s :%s' % (channel, target, reason))
@ -369,8 +369,8 @@ class P10Protocol(IRCS2SProtocol):
"""Sends a kill from a PyLink client/server."""
# <- ABAAA D AyAAA :nefarious.midnight.vpn!GL (test)
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
self._send_with_prefix(numeric, 'D %s :Killed (%s)' % (target, reason))
@ -381,7 +381,7 @@ class P10Protocol(IRCS2SProtocol):
def message(self, numeric, target, text):
"""Sends a PRIVMSG from a PyLink client."""
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(numeric, 'P %s :%s' % (target, text))
@ -391,8 +391,8 @@ class P10Protocol(IRCS2SProtocol):
# <- ABAAA M GL -w
# <- ABAAA M #test +v ABAAB 1460747615
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
modes = list(modes)
@ -404,13 +404,13 @@ class P10Protocol(IRCS2SProtocol):
is_cmode = utils.isChannel(target)
if is_cmode:
# Channel mode changes have a trailing TS. User mode changes do not.
cobj = self.irc.channels[self.irc.toLower(target)]
cobj = self.channels[self.toLower(target)]
ts = ts or cobj.ts
# HACK: prevent mode bounces by sending our mode through the server if
# the sender isn't op.
if numeric not in self.irc.servers and (not cobj.isOp(numeric)) and (not cobj.isHalfop(numeric)):
numeric = self.irc.getServer(numeric)
if numeric not in self.servers and (not cobj.isOp(numeric)) and (not cobj.isHalfop(numeric)):
numeric = self.getServer(numeric)
# Wrap modes: start with max bufsize and subtract the lengths of the source, target,
# mode command, and whitespace.
@ -418,16 +418,16 @@ class P10Protocol(IRCS2SProtocol):
real_target = target
else:
assert target in self.irc.users, "Unknown mode target %s" % target
assert target in self.users, "Unknown mode target %s" % target
# P10 uses nicks in user MODE targets, NOT UIDs. ~GL
real_target = self.irc.users[target].nick
real_target = self.users[target].nick
self.irc.applyModes(target, modes)
self.applyModes(target, modes)
while modes[:12]:
joinedmodes = self.irc.joinModes([m for m in modes[:12]])
joinedmodes = self.joinModes([m for m in modes[:12]])
if is_cmode:
for wrapped_modes in self.irc.wrapModes(modes[:12], bufsize):
for wrapped_modes in self.wrapModes(modes[:12], bufsize):
self._send_with_prefix(numeric, 'M %s %s %s' % (real_target, wrapped_modes, ts))
else:
self._send_with_prefix(numeric, 'M %s %s' % (real_target, joinedmodes))
@ -436,14 +436,14 @@ class P10Protocol(IRCS2SProtocol):
def nick(self, numeric, newnick):
"""Changes the nick of a PyLink client."""
# <- ABAAA N GL_ 1460753763
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(numeric, 'N %s %s' % (newnick, int(time.time())))
self.irc.users[numeric].nick = newnick
self.users[numeric].nick = newnick
# Update the NICK TS.
self.irc.users[numeric].ts = int(time.time())
self.users[numeric].ts = int(time.time())
def numeric(self, source, numeric, target, text):
"""Sends raw numerics from a server to a remote client. This is used for WHOIS
@ -453,17 +453,17 @@ class P10Protocol(IRCS2SProtocol):
def notice(self, numeric, target, text):
"""Sends a NOTICE from a PyLink client or server."""
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
self._send_with_prefix(numeric, 'O %s :%s' % (target, text))
def part(self, client, channel, reason=None):
"""Sends a part from a PyLink client."""
channel = self.irc.toLower(channel)
channel = self.toLower(channel)
if not self.irc.isInternalClient(client):
if not self.isInternalClient(client):
raise LookupError('No such PyLink client exists.')
msg = "L %s" % channel
@ -475,7 +475,7 @@ class P10Protocol(IRCS2SProtocol):
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
source = source or self.sid
if source is None:
return
if target is not None:
@ -485,7 +485,7 @@ class P10Protocol(IRCS2SProtocol):
def quit(self, numeric, reason):
"""Quits a PyLink client."""
if self.irc.isInternalClient(numeric):
if self.isInternalClient(numeric):
self._send_with_prefix(numeric, "Q :%s" % reason)
self.removeClient(numeric)
else:
@ -500,14 +500,14 @@ class P10Protocol(IRCS2SProtocol):
Example uses:
sjoin('100', '#test', [('', '100AAABBC'), ('o', 100AAABBB'), ('v', '100AAADDD')])
sjoin(self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])
sjoin(self.sid, '#test', [('o', self.pseudoclient.uid)])
"""
# <- AB B #test 1460742014 +tnl 10 ABAAB,ABAAA:o :%*!*@other.bad.host ~ *!*@bad.host
channel = self.irc.toLower(channel)
server = server or self.irc.sid
channel = self.toLower(channel)
server = server or self.sid
assert users, "sjoin: No users sent?"
log.debug('(%s) sjoin: got %r for users', self.irc.name, users)
log.debug('(%s) sjoin: got %r for users', self.name, users)
if not server:
raise LookupError('No such PyLink client exists.')
@ -515,8 +515,8 @@ class P10Protocol(IRCS2SProtocol):
# <- AB B #test 1460742014 +tnl 10 ABAAB,ABAAA:o :%*!*@other.bad.host *!*@bad.host
# <- AB B #test2 1460743539 +l 10 ABAAA:vo :%*!*@bad.host
# <- AB B #test 1460747615 ABAAA:o :% ~ *!*@test.host
modes = modes or self.irc.channels[channel].modes
orig_ts = self.irc.channels[channel].ts
modes = modes or self.channels[channel].modes
orig_ts = self.channels[channel].ts
ts = ts or orig_ts
bans = []
@ -525,8 +525,8 @@ class P10Protocol(IRCS2SProtocol):
for mode in modes:
modechar = mode[0][-1]
# Store bans and exempts in separate lists for processing, but don't reset bans that have already been set.
if modechar in self.irc.cmodes['*A']:
if (modechar, mode[1]) not in self.irc.channels[channel].modes:
if modechar in self.cmodes['*A']:
if (modechar, mode[1]) not in self.channels[channel].modes:
if modechar == 'b':
bans.append(mode[1])
elif modechar == 'e':
@ -534,7 +534,7 @@ class P10Protocol(IRCS2SProtocol):
else:
regularmodes.append(mode)
log.debug('(%s) sjoin: bans: %s, exempts: %s, other modes: %s', self.irc.name, bans, exempts, regularmodes)
log.debug('(%s) sjoin: bans: %s, exempts: %s, other modes: %s', self.name, bans, exempts, regularmodes)
changedmodes = set(modes)
changedusers = []
@ -546,7 +546,7 @@ class P10Protocol(IRCS2SProtocol):
msgprefix = '{sid} B {channel} {ts} '.format(sid=server, channel=channel, ts=ts)
if regularmodes:
msgprefix += '%s ' % self.irc.joinModes(regularmodes)
msgprefix += '%s ' % self.joinModes(regularmodes)
last_prefixes = ''
for userpair in users:
@ -557,7 +557,7 @@ class P10Protocol(IRCS2SProtocol):
# Keep track of all the users and modes that are added. namelist is used
# to track what we actually send to the IRCd.
changedusers.append(user)
log.debug('(%s) sjoin: adding %s:%s to namelist', self.irc.name, user, prefixes)
log.debug('(%s) sjoin: adding %s:%s to namelist', self.name, user, prefixes)
if prefixes and prefixes != last_prefixes:
namelist.append('%s:%s' % (user, prefixes))
@ -569,10 +569,10 @@ class P10Protocol(IRCS2SProtocol):
for prefix in prefixes:
changedmodes.add(('+%s' % prefix, user))
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
else:
if namelist:
log.debug('(%s) sjoin: got %r for namelist', self.irc.name, namelist)
log.debug('(%s) sjoin: got %r for namelist', self.name, namelist)
# Flip the (prefixmodes, user) pairs in users, and save it as a dict for easy lookup
# later of what modes each target user should have.
@ -581,14 +581,14 @@ class P10Protocol(IRCS2SProtocol):
# Wrap all users and send them to prevent cutoff. Subtract 4 off the maximum
# buf size to account for user prefix data that may be re-added (e.g. ":ohv")
for linenum, wrapped_msg in \
enumerate(utils.wrapArguments(msgprefix, namelist, S2S_BUFSIZE-1-len(self.irc.prefixmodes),
enumerate(utils.wrapArguments(msgprefix, namelist, S2S_BUFSIZE-1-len(self.prefixmodes),
separator=',')):
if linenum: # Implies "if linenum > 0"
# XXX: Ugh, this postprocessing sucks, but we have to make sure that mode prefixes are accounted
# for in the burst.
wrapped_args = self.parseArgs(wrapped_msg.split(" "))
wrapped_namelist = wrapped_args[-1].split(',')
log.debug('(%s) sjoin: wrapped args: %s (post-wrap fixing)', self.irc.name,
log.debug('(%s) sjoin: wrapped args: %s (post-wrap fixing)', self.name,
wrapped_args)
# If the first UID was supposed to have a prefix mode attached, re-add it here
@ -596,20 +596,20 @@ class P10Protocol(IRCS2SProtocol):
# XXX: I'm not sure why the prefix list has to be reversed for it to match the
# original string...
first_prefix = names_dict.get(first_uid, '')[::-1]
log.debug('(%s) sjoin: prefixes for first user %s: %s (post-wrap fixing)', self.irc.name,
log.debug('(%s) sjoin: prefixes for first user %s: %s (post-wrap fixing)', self.name,
first_uid, first_prefix)
if (':' not in first_uid) and first_prefix:
log.debug('(%s) sjoin: re-adding prefix %s to user %s (post-wrap fixing)', self.irc.name,
log.debug('(%s) sjoin: re-adding prefix %s to user %s (post-wrap fixing)', self.name,
first_uid, first_prefix)
wrapped_namelist[0] += ':%s' % prefixes
wrapped_msg = ' '.join(wrapped_args[:-1])
wrapped_msg += ' '
wrapped_msg += ','.join(wrapped_namelist)
self.irc.send(wrapped_msg)
self.send(wrapped_msg)
self.irc.channels[channel].users.update(changedusers)
self.channels[channel].users.update(changedusers)
# Technically we can send bans together with the above user introductions, but
# it's easier to line wrap them separately.
@ -617,12 +617,12 @@ class P10Protocol(IRCS2SProtocol):
msgprefix += ':%' # Ban string starts with a % if there is anything
if bans:
for wrapped_msg in utils.wrapArguments(msgprefix, bans, S2S_BUFSIZE):
self.irc.send(wrapped_msg)
self.send(wrapped_msg)
if exempts:
# Now add exempts, which are separated from the ban list by a single argument "~".
msgprefix += ' ~ '
for wrapped_msg in utils.wrapArguments(msgprefix, exempts, S2S_BUFSIZE):
self.irc.send(wrapped_msg)
self.send(wrapped_msg)
self.updateTS(server, channel, ts, changedmodes)
@ -637,37 +637,37 @@ class P10Protocol(IRCS2SProtocol):
option will be ignored if given.
"""
# <- SERVER nefarious.midnight.vpn 1 1460673022 1460673239 J10 ABP]] +h6 :Nefarious2 test server
uplink = uplink or self.irc.sid
uplink = uplink or self.sid
name = name.lower()
desc = desc or self.irc.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
desc = desc or self.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
if sid is None: # No sid given; generate one!
sid = self.sidgen.next_sid()
assert len(sid) == 2, "Incorrect SID length"
if sid in self.irc.servers:
if sid in self.servers:
raise ValueError('A server with SID %r already exists!' % sid)
for server in self.irc.servers.values():
for server in self.servers.values():
if name == server.name:
raise ValueError('A server named %r already exists!' % name)
if not self.irc.isInternalServer(uplink):
if not self.isInternalServer(uplink):
raise ValueError('Server %r is not a PyLink server!' % uplink)
if not utils.isServerName(name):
raise ValueError('Invalid server name %r' % name)
self._send_with_prefix(uplink, 'SERVER %s 1 %s %s P10 %s]]] +h6 :%s' % \
(name, self.irc.start_ts, int(time.time()), sid, desc))
(name, self.start_ts, int(time.time()), sid, desc))
self.irc.servers[sid] = IrcServer(uplink, name, internal=True, desc=desc)
self.servers[sid] = IrcServer(uplink, name, internal=True, desc=desc)
return sid
def squit(self, source, target, text='No reason given'):
"""SQUITs a PyLink server."""
# <- ABAAE SQ nefarious.midnight.vpn 0 :test
targetname = self.irc.servers[target].name
targetname = self.servers[target].name
self._send_with_prefix(source, 'SQ %s 0 :%s' % (targetname, text))
self.handle_squit(source, 'SQUIT', [target, text])
@ -677,41 +677,41 @@ class P10Protocol(IRCS2SProtocol):
# <- ABAAA T #test GL!~gl@nefarious.midnight.vpn 1460852591 1460855795 :blah
# First timestamp is channel creation time, second is current time,
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
sendername = self.irc.getHostmask(numeric)
sendername = self.getHostmask(numeric)
creationts = self.irc.channels[target].ts
creationts = self.channels[target].ts
self._send_with_prefix(numeric, 'T %s %s %s %s :%s' % (target, sendername, creationts,
int(time.time()), text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
self.channels[target].topic = text
self.channels[target].topicset = True
def topicBurst(self, numeric, target, text):
"""Sends a TOPIC change from a PyLink server."""
# <- AB T #test GL!~gl@nefarious.midnight.vpn 1460852591 1460855795 :blah
if not self.irc.isInternalServer(numeric):
if not self.isInternalServer(numeric):
raise LookupError('No such PyLink server exists.')
sendername = self.irc.servers[numeric].name
sendername = self.servers[numeric].name
creationts = self.irc.channels[target].ts
creationts = self.channels[target].ts
self._send_with_prefix(numeric, 'T %s %s %s %s :%s' % (target, sendername, creationts,
int(time.time()), text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
self.channels[target].topic = text
self.channels[target].topicset = True
def updateClient(self, target, field, text):
"""Updates the ident or host of any connected client."""
uobj = self.irc.users[target]
uobj = self.users[target]
ircd = self.irc.serverdata.get('p10_ircd', 'nefarious').lower()
ircd = self.serverdata.get('p10_ircd', 'nefarious').lower()
if self.irc.isInternalClient(target):
if self.isInternalClient(target):
# Host changing via SETHOST is only supported on nefarious and snircd.
if ircd not in ('nefarious', 'snircd'):
raise NotImplementedError("Host changing for internal clients (via SETHOST) is only "
@ -737,12 +737,12 @@ class P10Protocol(IRCS2SProtocol):
"only available on nefarious, and we're using p10_ircd=%r" % ircd)
# Use FAKE (FA) for external clients.
self._send_with_prefix(self.irc.sid, 'FA %s %s' % (target, text))
self._send_with_prefix(self.sid, 'FA %s %s' % (target, text))
# Save the host change as a user mode (this is what P10 does on bursts),
# so further host checks work.
self.irc.applyModes(target, [('+f', text)])
self.mode(self.irc.sid, target, [('+x', None)])
self.applyModes(target, [('+f', text)])
self.mode(self.sid, target, [('+x', None)])
else:
raise NotImplementedError("Changing field %r of a client is "
"unsupported by this protocol." % field)
@ -756,9 +756,9 @@ class P10Protocol(IRCS2SProtocol):
def post_connect(self):
"""Initializes a connection to a server."""
ts = self.irc.start_ts
ts = self.start_ts
self.irc.send("PASS :%s" % self.irc.serverdata["sendpass"])
self.send("PASS :%s" % self.serverdata["sendpass"])
# {7S} *** SERVER
@ -771,15 +771,15 @@ class P10Protocol(IRCS2SProtocol):
# 7 <flags> <-- Mark ourselves as a service with IPv6 support (+s & +6) -GLolol
# -1 <description of new server>
name = self.irc.serverdata["hostname"]
name = self.serverdata["hostname"]
# Encode our SID using P10 Base64.
self.irc.sid = sid = p10b64encode(self.irc.serverdata["sid"])
self.sid = sid = p10b64encode(self.serverdata["sid"])
desc = self.irc.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
desc = self.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
# Enumerate modes, from https://github.com/evilnet/nefarious2/blob/master/doc/modes.txt
p10_ircd = self.irc.serverdata.get('p10_ircd', 'nefarious').lower()
p10_ircd = self.serverdata.get('p10_ircd', 'nefarious').lower()
if p10_ircd == 'nefarious':
cmodes = {'delayjoin': 'D', 'registered': 'R', 'key': 'k', 'banexception': 'e',
'redirect': 'L', 'oplevel_apass': 'A', 'oplevel_upass': 'U',
@ -787,7 +787,7 @@ class P10Protocol(IRCS2SProtocol):
'permanent': 'z', 'hidequits': 'Q', 'noctcp': 'C', 'noamsg': 'T', 'blockcolor': 'c',
'stripcolor': 'S', 'had_delayjoin': 'd', 'regonly': 'r',
'*A': 'be', '*B': 'AUk', '*C': 'Ll', '*D': 'psmtinrDRaOMNzQCTcSd'}
self.irc.umodes.update({'servprotect': 'k', 'sno_debug': 'g', 'cloak': 'x', 'privdeaf': 'D',
self.umodes.update({'servprotect': 'k', 'sno_debug': 'g', 'cloak': 'x', 'privdeaf': 'D',
'hidechans': 'n', 'deaf_commonchan': 'q', 'bot': 'B', 'deaf': 'd',
'hideoper': 'H', 'hideidle': 'I', 'regdeaf': 'R', 'showwhois': 'W',
'admin': 'a', 'override': 'X', 'noforward': 'L', 'ssl': 'z',
@ -802,7 +802,7 @@ class P10Protocol(IRCS2SProtocol):
'*A': 'b', '*B': 'AUk', '*C': 'l', '*D': 'imnpstrDducCMNT'}
# From https://www.quakenet.org/help/general/what-user-modes-are-available-on-quakenet
# plus my own testing.
self.irc.umodes.update({'servprotect': 'k', 'sno_debug': 'g', 'cloak': 'x',
self.umodes.update({'servprotect': 'k', 'sno_debug': 'g', 'cloak': 'x',
'hidechans': 'n', 'deaf': 'd', 'hideidle': 'I', 'regdeaf': 'R',
'override': 'X', 'registered': 'r', 'cloak_sethost': 'h', 'locop': 'O',
'*A': '', '*B': '', '*C': 'h', '*D': 'imnpstrkgxndIRXO'})
@ -811,18 +811,18 @@ class P10Protocol(IRCS2SProtocol):
cmodes = {'oplevel_apass': 'A', 'oplevel_upass': 'U', 'delayjoin': 'D', 'regonly': 'r',
'had_delayjoin': 'd', 'blockcolor': 'c', 'noctcp': 'C', 'registered': 'R',
'*A': 'b', '*B': 'AUk', '*C': 'l', '*D': 'imnpstrDdRcC'}
self.irc.umodes.update({'servprotect': 'k', 'sno_debug': 'g', 'cloak': 'x',
self.umodes.update({'servprotect': 'k', 'sno_debug': 'g', 'cloak': 'x',
'deaf': 'd', 'registered': 'r', 'locop': 'O',
'*A': '', '*B': '', '*C': '', '*D': 'imnpstrkgxdO'})
if self.irc.serverdata.get('use_halfop'):
if self.serverdata.get('use_halfop'):
cmodes['halfop'] = 'h'
self.irc.prefixmodes['h'] = '%'
self.irc.cmodes.update(cmodes)
self.prefixmodes['h'] = '%'
self.cmodes.update(cmodes)
self.irc.send('SERVER %s 1 %s %s J10 %s]]] +s6 :%s' % (name, ts, ts, sid, desc))
self.send('SERVER %s 1 %s %s J10 %s]]] +s6 :%s' % (name, ts, ts, sid, desc))
self._send_with_prefix(sid, "EB")
self.irc.connected.set()
self.connected.set()
def handle_server(self, source, command, args):
"""Handles incoming server introductions."""
@ -830,11 +830,11 @@ class P10Protocol(IRCS2SProtocol):
servername = args[0].lower()
sid = args[5][:2]
sdesc = args[-1]
self.irc.servers[sid] = IrcServer(source, servername, desc=sdesc)
self.servers[sid] = IrcServer(source, servername, desc=sdesc)
if self.irc.uplink is None:
if self.uplink is None:
# If we haven't already found our uplink, this is probably it.
self.irc.uplink = sid
self.uplink = sid
return {'name': servername, 'sid': sid, 'text': sdesc}
@ -853,11 +853,11 @@ class P10Protocol(IRCS2SProtocol):
realname = args[-1]
log.debug('(%s) handle_nick got args: nick=%s ts=%s uid=%s ident=%s '
'host=%s realname=%s realhost=%s ip=%s', self.irc.name, nick, ts, uid,
'host=%s realname=%s realhost=%s ip=%s', self.name, nick, ts, uid,
ident, host, realname, realhost, ip)
uobj = self.irc.users[uid] = IrcUser(nick, ts, uid, source, ident, host, realname, realhost, ip)
self.irc.servers[source].users.add(uid)
uobj = self.users[uid] = IrcUser(nick, ts, uid, source, ident, host, realname, realhost, ip)
self.servers[source].users.add(uid)
# https://github.com/evilnet/nefarious2/blob/master/doc/p10.txt#L708
# Mode list is optional, and can be detected if the 6th argument starts with a +.
@ -865,18 +865,18 @@ class P10Protocol(IRCS2SProtocol):
# parameters attached.
if args[5].startswith('+'):
modes = args[5:-3]
parsedmodes = self.irc.parseModes(uid, modes)
self.irc.applyModes(uid, parsedmodes)
parsedmodes = self.parseModes(uid, modes)
self.applyModes(uid, parsedmodes)
for modepair in parsedmodes:
if modepair[0][-1] == 'r':
# Parse account registrations, sent as usermode "+r accountname:TS"
accountname = modepair[1].split(':', 1)[0]
self.irc.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
self.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
# Call the OPERED UP hook if +o is being added to the mode list.
if ('+o', None) in parsedmodes:
self.irc.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
self.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
self.check_cloak_change(uid)
@ -884,21 +884,21 @@ class P10Protocol(IRCS2SProtocol):
else:
# <- ABAAA N GL_ 1460753763
oldnick = self.irc.users[source].nick
newnick = self.irc.users[source].nick = args[0]
oldnick = self.users[source].nick
newnick = self.users[source].nick = args[0]
self.irc.users[source].ts = ts = int(args[1])
self.users[source].ts = ts = int(args[1])
# Update the nick TS.
return {'newnick': newnick, 'oldnick': oldnick, 'ts': ts}
def check_cloak_change(self, uid):
"""Checks for cloak changes (ident and host) on the given UID."""
uobj = self.irc.users[uid]
uobj = self.users[uid]
ident = uobj.ident
modes = dict(uobj.modes)
log.debug('(%s) check_cloak_change: modes of %s are %s', self.irc.name, uid, modes)
log.debug('(%s) check_cloak_change: modes of %s are %s', self.name, uid, modes)
if 'x' not in modes: # +x isn't set, so cloaking is disabled.
newhost = uobj.realhost
@ -912,7 +912,7 @@ class P10Protocol(IRCS2SProtocol):
# +f represents another way of setting vHosts, via a command called FAKE.
# Atheme uses this for vHosts, afaik.
newhost = modes['f']
elif uobj.services_account and self.irc.serverdata.get('use_account_cloaks'):
elif uobj.services_account and self.serverdata.get('use_account_cloaks'):
# The user is registered. However, if account cloaks are enabled, we have to figure
# out their new cloaked host. There can be oper cloaks and user cloaks, each with
# a different suffix. Account cloaks take the format of <accountname>.<suffix>.
@ -920,24 +920,24 @@ class P10Protocol(IRCS2SProtocol):
# someone opered and logged in as "person2" might get cloak "person.opers.somenet.org"
# This is a lot of extra configuration on the services' side, but there's nothing else
# we can do about it.
if self.irc.serverdata.get('use_oper_account_cloaks') and 'o' in modes:
if self.serverdata.get('use_oper_account_cloaks') and 'o' in modes:
try:
# These errors should be fatal.
suffix = self.irc.serverdata['oper_cloak_suffix']
suffix = self.serverdata['oper_cloak_suffix']
except KeyError:
raise ProtocolError("(%s) use_oper_account_cloaks was enabled, but "
"oper_cloak_suffix was not defined!" % self.irc.name)
"oper_cloak_suffix was not defined!" % self.name)
else:
try:
suffix = self.irc.serverdata['cloak_suffix']
suffix = self.serverdata['cloak_suffix']
except KeyError:
raise ProtocolError("(%s) use_account_cloaks was enabled, but "
"cloak_suffix was not defined!" % self.irc.name)
"cloak_suffix was not defined!" % self.name)
accountname = uobj.services_account
newhost = "%s.%s" % (accountname, suffix)
elif 'C' in modes and self.irc.serverdata.get('use_account_cloaks'):
elif 'C' in modes and self.serverdata.get('use_account_cloaks'):
# +C propagates hashed IP cloaks, similar to UnrealIRCd. (thank god we don't
# need to generate these ourselves)
newhost = modes['C']
@ -947,9 +947,9 @@ class P10Protocol(IRCS2SProtocol):
# Propagate a hostname update to plugins, but only if the changed host is different.
if newhost != uobj.host:
self.irc.callHooks([uid, 'CHGHOST', {'target': uid, 'newhost': newhost}])
self.callHooks([uid, 'CHGHOST', {'target': uid, 'newhost': newhost}])
if ident != uobj.ident:
self.irc.callHooks([uid, 'CHGIDENT', {'target': uid, 'newident': ident}])
self.callHooks([uid, 'CHGIDENT', {'target': uid, 'newident': ident}])
uobj.host = newhost
uobj.ident = ident
@ -972,15 +972,15 @@ class P10Protocol(IRCS2SProtocol):
currtime = time.time()
timediff = int(time.time() - float(orig_pingtime))
if self.irc.isInternalServer(sid):
if self.isInternalServer(sid):
# Only respond if the target server is ours. No forwarding is needed because
# no IRCds can ever connect behind us...
self._send_with_prefix(self.irc.sid, 'Z %s %s %s %s' % (target, orig_pingtime, timediff, currtime), queue=False)
self._send_with_prefix(self.sid, 'Z %s %s %s %s' % (target, orig_pingtime, timediff, currtime), queue=False)
def handle_pass(self, source, command, args):
"""Handles authentication with our uplink."""
# <- PASS :testpass
if args[0] != self.irc.serverdata['recvpass']:
if args[0] != self.serverdata['recvpass']:
raise ProtocolError("Error: RECVPASS from uplink does not match configuration!")
def handle_burst(self, source, command, args):
@ -1002,8 +1002,8 @@ class P10Protocol(IRCS2SProtocol):
# No useful data was sent, ignore.
return
channel = self.irc.toLower(args[0])
chandata = self.irc.channels[channel].deepcopy()
channel = self.toLower(args[0])
chandata = self.channels[channel].deepcopy()
bans = []
if args[-1].startswith('%'):
@ -1030,7 +1030,7 @@ class P10Protocol(IRCS2SProtocol):
# If no modes are given, this will simply be empty.
modestring = args[2:-1]
if modestring:
parsedmodes = self.irc.parseModes(channel, modestring)
parsedmodes = self.parseModes(channel, modestring)
else:
parsedmodes = []
@ -1039,7 +1039,7 @@ class P10Protocol(IRCS2SProtocol):
namelist = []
prefixes = ''
userlist = args[-1].split(',')
log.debug('(%s) handle_burst: got userlist %r for %r', self.irc.name, userlist, channel)
log.debug('(%s) handle_burst: got userlist %r for %r', self.name, userlist, channel)
if args[-1] != args[1]: # Make sure the user list is the right argument (not the TS).
for userpair in userlist:
@ -1052,26 +1052,26 @@ class P10Protocol(IRCS2SProtocol):
user, prefixes = userpair.split(':')
except ValueError:
user = userpair
log.debug('(%s) handle_burst: got mode prefixes %r for user %r', self.irc.name, prefixes, user)
log.debug('(%s) handle_burst: got mode prefixes %r for user %r', self.name, prefixes, user)
# Don't crash when we get an invalid UID.
if user not in self.irc.users:
if user not in self.users:
log.warning('(%s) handle_burst: tried to introduce user %s not in our user list, ignoring...',
self.irc.name, user)
self.name, user)
continue
namelist.append(user)
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
# Only save mode changes if the remote has lower TS than us.
changedmodes |= {('+%s' % mode, user) for mode in prefixes}
self.irc.channels[channel].users.add(user)
self.channels[channel].users.add(user)
# Statekeeping with timestamps
their_ts = int(args[1])
our_ts = self.irc.channels[channel].ts
our_ts = self.channels[channel].ts
self.updateTS(source, channel, their_ts, changedmodes)
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
@ -1090,33 +1090,33 @@ class P10Protocol(IRCS2SProtocol):
if args[0] == '0' and command == 'JOIN':
# /join 0; part the user from all channels
oldchans = self.irc.users[source].channels.copy()
oldchans = self.users[source].channels.copy()
log.debug('(%s) Got /join 0 from %r, channel list is %r',
self.irc.name, source, oldchans)
self.name, source, oldchans)
for channel in oldchans:
self.irc.channels[channel].users.discard(source)
self.irc.users[source].channels.discard(channel)
self.channels[channel].users.discard(source)
self.users[source].channels.discard(channel)
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
else:
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
if ts: # Only update TS if one was sent.
self.updateTS(source, channel, ts)
self.irc.users[source].channels.add(channel)
self.irc.channels[channel].users.add(source)
self.users[source].channels.add(channel)
self.channels[channel].users.add(source)
return {'channel': channel, 'users': [source], 'modes':
self.irc.channels[channel].modes, 'ts': ts or int(time.time())}
self.channels[channel].modes, 'ts': ts or int(time.time())}
handle_create = handle_join
def handle_end_of_burst(self, source, command, args):
"""Handles end of burst from our uplink."""
# Send EOB acknowledgement; this is required by the P10 specification,
# and needed if we want to be able to receive channel messages, etc.
if source == self.irc.uplink:
self._send_with_prefix(self.irc.sid, 'EA')
if source == self.uplink:
self._send_with_prefix(self.sid, 'EA')
return {}
def handle_mode(self, source, command, args):
@ -1126,17 +1126,17 @@ class P10Protocol(IRCS2SProtocol):
# <- ABAAA OM #test +h ABAAA
target = self._get_UID(args[0])
if utils.isChannel(target):
target = self.irc.toLower(target)
target = self.toLower(target)
modestrings = args[1:]
changedmodes = self.irc.parseModes(target, modestrings)
self.irc.applyModes(target, changedmodes)
changedmodes = self.parseModes(target, modestrings)
self.applyModes(target, changedmodes)
# Call the CLIENT_OPERED hook if +o is being set.
if ('+o', None) in changedmodes and target in self.irc.users:
self.irc.callHooks([target, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
if ('+o', None) in changedmodes and target in self.users:
self.callHooks([target, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
if target in self.irc.users:
if target in self.users:
# Target was a user. Check for any cloak changes.
self.check_cloak_change(target)
@ -1149,31 +1149,31 @@ class P10Protocol(IRCS2SProtocol):
# <- ABAAA L #test,#test2
# <- ABAAA L #test :test
channels = self.irc.toLower(args[0]).split(',')
channels = self.toLower(args[0]).split(',')
for channel in channels:
# We should only get PART commands for channels that exist, right??
self.irc.channels[channel].removeuser(source)
self.channels[channel].removeuser(source)
try:
self.irc.users[source].channels.discard(channel)
self.users[source].channels.discard(channel)
except KeyError:
log.debug("(%s) handle_part: KeyError trying to remove %r from %r's channel list?",
self.irc.name, channel, source)
self.name, channel, source)
try:
reason = args[1]
except IndexError:
reason = ''
# Clear empty non-permanent channels.
if not self.irc.channels[channel].users:
del self.irc.channels[channel]
if not self.channels[channel].users:
del self.channels[channel]
return {'channels': channels, 'text': reason}
def handle_kick(self, source, command, args):
"""Handles incoming KICKs."""
# <- ABAAA K #TEST AyAAA :PyLink-devel
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
kicked = args[1]
self.handle_part(kicked, 'KICK', [channel, args[2]])
@ -1187,12 +1187,12 @@ class P10Protocol(IRCS2SProtocol):
def handle_topic(self, source, command, args):
"""Handles TOPIC changes."""
# <- ABAAA T #test GL!~gl@nefarious.midnight.vpn 1460852591 1460855795 :blah
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
topic = args[-1]
oldtopic = self.irc.channels[channel].topic
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
oldtopic = self.channels[channel].topic
self.channels[channel].topic = topic
self.channels[channel].topicset = True
return {'channel': channel, 'setter': args[1], 'text': topic,
'oldtopic': oldtopic}
@ -1205,25 +1205,25 @@ class P10Protocol(IRCS2SProtocol):
# - note that the target is a nickname, not a numeric.
# <- ABAAA I PyLink-devel #services 1460948992
target = self._get_UID(args[0])
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
return {'target': target, 'channel': channel}
def handle_clearmode(self, numeric, command, args):
"""Handles CLEARMODE, which is used to clear a channel's modes."""
# <- ABAAA CM #test ovpsmikbl
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
modes = args[1]
# Enumerate a list of our existing modes, including prefix modes.
existing = list(self.irc.channels[channel].modes)
for pmode, userlist in self.irc.channels[channel].prefixmodes.items():
existing = list(self.channels[channel].modes)
for pmode, userlist in self.channels[channel].prefixmodes.items():
# Expand the prefix modes lists to individual ('o', 'UID') mode pairs.
modechar = self.irc.cmodes.get(pmode)
modechar = self.cmodes.get(pmode)
existing += [(modechar, user) for user in userlist]
# Back up the channel state.
oldobj = self.irc.channels[channel].deepcopy()
oldobj = self.channels[channel].deepcopy()
changedmodes = []
@ -1233,14 +1233,14 @@ class P10Protocol(IRCS2SProtocol):
# Check if each mode matches any that we're unsetting.
if modechar in modes:
if modechar in (self.irc.cmodes['*A']+self.irc.cmodes['*B']+''.join(self.irc.prefixmodes.keys())):
if modechar in (self.cmodes['*A']+self.cmodes['*B']+''.join(self.prefixmodes.keys())):
# Mode is a list mode, prefix mode, or one that always takes a parameter when unsetting.
changedmodes.append(('-%s' % modechar, data))
else:
# Mode does not take an argument when unsetting.
changedmodes.append(('-%s' % modechar, None))
self.irc.applyModes(channel, changedmodes)
self.applyModes(channel, changedmodes)
return {'target': channel, 'modes': changedmodes, 'channeldata': oldobj}
def handle_account(self, numeric, command, args):
@ -1250,7 +1250,7 @@ class P10Protocol(IRCS2SProtocol):
target = args[0]
if self.irc.serverdata.get('use_extended_accounts'):
if self.serverdata.get('use_extended_accounts'):
# Registration: <- AA AC ABAAA R GL 1459019072
# Logout: <- AA AC ABAAA U
@ -1274,7 +1274,7 @@ class P10Protocol(IRCS2SProtocol):
accountname = args[1]
# Call this manually because we need the UID to be the sender.
self.irc.callHooks([target, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
self.callHooks([target, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
# Check for any cloak changes now.
self.check_cloak_change(target)
@ -1285,7 +1285,7 @@ class P10Protocol(IRCS2SProtocol):
text = args[1]
# Assume a usermode +f change, and then update the cloak checking.
self.irc.applyModes(target, [('+f', text)])
self.applyModes(target, [('+f', text)])
self.check_cloak_change(target)
# We don't need to send any hooks here, check_cloak_change does that for us.

View File

@ -19,7 +19,7 @@ class RatboxProtocol(TS6Protocol):
"""Initializes a connection to a server."""
# Note: +r, +e, and +I support will be negotiated on link
self.irc.cmodes = {'op': 'o', 'secret': 's', 'private': 'p', 'noextmsg': 'n', 'moderated': 'm',
self.cmodes = {'op': 'o', 'secret': 's', 'private': 'p', 'noextmsg': 'n', 'moderated': 'm',
'inviteonly': 'i', 'topiclock': 't', 'limit': 'l', 'ban': 'b', 'voice': 'v',
'key': 'k', 'sslonly': 'S',
'*A': 'beI',
@ -27,7 +27,7 @@ class RatboxProtocol(TS6Protocol):
'*C': 'l',
'*D': 'imnpstrS'}
self.irc.umodes = {
self.umodes = {
'invisible': 'i', 'callerid': 'g', 'oper': 'o', 'admin': 'a', 'sno_botfloods': 'b',
'sno_clientconnections': 'c', 'sno_extclientconnections': 'C', 'sno_debug': 'd',
'sno_fullauthblock': 'f', 'sno_skill': 'k', 'locops': 'l',
@ -51,23 +51,23 @@ class RatboxProtocol(TS6Protocol):
# parameters: nickname, hopcount, nickTS, umodes, username, visible hostname, IP address,
# UID, gecos
server = server or self.irc.sid
if not self.irc.isInternalServer(server):
server = server or self.sid
if not self.isInternalServer(server):
raise ValueError('Server %r is not a PyLink server!' % server)
uid = self.uidgen[server].next_uid()
ts = ts or int(time.time())
realname = realname or conf.conf['bot']['realname']
raw_modes = self.irc.joinModes(modes)
raw_modes = self.joinModes(modes)
orig_realhost = realhost
realhost = realhost or host
u = self.irc.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
u = self.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable)
self.irc.applyModes(uid, modes)
self.irc.servers[server].users.add(uid)
self.applyModes(uid, modes)
self.servers[server].users.add(uid)
self._send(server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
":{realname}".format(ts=ts, host=host,
nick=nick, ident=ident, uid=uid,
@ -86,11 +86,11 @@ class RatboxProtocol(TS6Protocol):
def handle_realhost(self, uid, command, args):
"""Handles real host propagation."""
log.debug('(%s) Got REALHOST %s for %s', args[0], uid)
self.irc.users[uid].realhost = args[0]
self.users[uid].realhost = args[0]
def handle_login(self, uid, command, args):
"""Handles login propagation on burst."""
self.irc.users[uid].services_account = args[0]
self.users[uid].services_account = args[0]
return {'text': args[0]}
Class = RatboxProtocol

View File

@ -36,9 +36,9 @@ class TS6Protocol(TS6BaseProtocol):
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
server = server or self.sid
if not self.irc.isInternalServer(server):
if not self.isInternalServer(server):
raise ValueError('Server %r is not a PyLink server!' % server)
uid = self.uidgen[server].next_uid()
@ -49,12 +49,12 @@ class TS6Protocol(TS6BaseProtocol):
ts = ts or int(time.time())
realname = realname or conf.conf['bot']['realname']
realhost = realhost or host
raw_modes = self.irc.joinModes(modes)
u = self.irc.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
raw_modes = self.joinModes(modes)
u = self.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
self.irc.applyModes(uid, modes)
self.irc.servers[server].users.add(uid)
self.applyModes(uid, modes)
self.servers[server].users.add(uid)
self._send_with_prefix(server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
"{realhost} * :{realname}".format(ts=ts, host=host,
@ -66,15 +66,15 @@ class TS6Protocol(TS6BaseProtocol):
def join(self, client, channel):
"""Joins a PyLink client to a channel."""
channel = self.irc.toLower(channel)
channel = self.toLower(channel)
# JOIN:
# parameters: channelTS, channel, '+' (a plus sign)
if not self.irc.isInternalClient(client):
log.error('(%s) Error trying to join %r to %r (no such client exists)', self.irc.name, client, channel)
if not self.isInternalClient(client):
log.error('(%s) Error trying to join %r to %r (no such client exists)', self.name, client, channel)
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(client, "JOIN {ts} {channel} +".format(ts=self.irc.channels[channel].ts, channel=channel))
self.irc.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel)
self._send_with_prefix(client, "JOIN {ts} {channel} +".format(ts=self.channels[channel].ts, channel=channel))
self.channels[channel].users.add(client)
self.users[client].channels.add(channel)
def sjoin(self, server, channel, users, ts=None, modes=set()):
"""Sends an SJOIN for a group of users to a channel.
@ -85,7 +85,7 @@ class TS6Protocol(TS6BaseProtocol):
Example uses:
sjoin('100', '#test', [('', '100AAABBC'), ('o', 100AAABBB'), ('v', '100AAADDD')])
sjoin(self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])
sjoin(self.sid, '#test', [('o', self.pseudoclient.uid)])
"""
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L821
# parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
@ -96,34 +96,34 @@ class TS6Protocol(TS6BaseProtocol):
# their status ('@+', '@', '+' or ''), for example:
# '@+1JJAAAAAB +2JJAAAA4C 1JJAAAADS'. All users must be behind the source server
# so it is not possible to use this message to force users to join a channel.
channel = self.irc.toLower(channel)
server = server or self.irc.sid
channel = self.toLower(channel)
server = server or self.sid
assert users, "sjoin: No users sent?"
log.debug('(%s) sjoin: got %r for users', self.irc.name, users)
log.debug('(%s) sjoin: got %r for users', self.name, users)
if not server:
raise LookupError('No such PyLink client exists.')
modes = set(modes or self.irc.channels[channel].modes)
orig_ts = self.irc.channels[channel].ts
modes = set(modes or self.channels[channel].modes)
orig_ts = self.channels[channel].ts
ts = ts or orig_ts
# Get all the ban modes in a separate list. These are bursted using a separate BMASK
# command.
banmodes = {k: [] for k in self.irc.cmodes['*A']}
banmodes = {k: [] for k in self.cmodes['*A']}
regularmodes = []
log.debug('(%s) Unfiltered SJOIN modes: %s', self.irc.name, modes)
log.debug('(%s) Unfiltered SJOIN modes: %s', self.name, modes)
for mode in modes:
modechar = mode[0][-1]
if modechar in self.irc.cmodes['*A']:
if modechar in self.cmodes['*A']:
# Mode character is one of 'beIq'
if (modechar, mode[1]) in self.irc.channels[channel].modes:
if (modechar, mode[1]) in self.channels[channel].modes:
# Don't reset modes that are already set.
continue
banmodes[modechar].append(mode[1])
else:
regularmodes.append(mode)
log.debug('(%s) Filtered SJOIN modes to be regular modes: %s, banmodes: %s', self.irc.name, regularmodes, banmodes)
log.debug('(%s) Filtered SJOIN modes to be regular modes: %s, banmodes: %s', self.name, regularmodes, banmodes)
changedmodes = modes
while users[:12]:
@ -135,22 +135,22 @@ class TS6Protocol(TS6BaseProtocol):
prefixes, user = userpair
prefixchars = ''
for prefix in prefixes:
pr = self.irc.prefixmodes.get(prefix)
pr = self.prefixmodes.get(prefix)
if pr:
prefixchars += pr
changedmodes.add(('+%s' % prefix, user))
namelist.append(prefixchars+user)
uids.append(user)
try:
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
except KeyError: # Not initialized yet?
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.name, channel, user)
users = users[12:]
namelist = ' '.join(namelist)
self._send_with_prefix(server, "SJOIN {ts} {channel} {modes} :{users}".format(
ts=ts, users=namelist, channel=channel,
modes=self.irc.joinModes(regularmodes)))
self.irc.channels[channel].users.update(uids)
modes=self.joinModes(regularmodes)))
self.channels[channel].users.update(uids)
# Now, burst bans.
# <- :42X BMASK 1424222769 #dev b :*!test@*.isp.net *!badident@*
@ -158,12 +158,12 @@ class TS6Protocol(TS6BaseProtocol):
# Max 15-3 = 12 bans per line to prevent cut off. (TS6 allows a max of 15 parameters per
# line)
if bans:
log.debug('(%s) sjoin: bursting mode %s with bans %s, ts:%s', self.irc.name, bmode, bans, ts)
log.debug('(%s) sjoin: bursting mode %s with bans %s, ts:%s', self.name, bmode, bans, ts)
msgprefix = ':{sid} BMASK {ts} {channel} {bmode} :'.format(sid=server, ts=ts,
channel=channel, bmode=bmode)
# Actually, we cut off at 17 arguments/line, since the prefix and command name don't count.
for msg in utils.wrapArguments(msgprefix, bans, S2S_BUFSIZE, max_args_per_line=17):
self.irc.send(msg)
self.send(msg)
self.updateTS(server, channel, ts, changedmodes)
@ -172,15 +172,15 @@ class TS6Protocol(TS6BaseProtocol):
# c <- :0UYAAAAAA TMODE 0 #a +o 0T4AAAAAC
# u <- :0UYAAAAAA MODE 0UYAAAAAA :-Facdefklnou
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
self.irc.applyModes(target, modes)
self.applyModes(target, modes)
modes = list(modes)
if utils.isChannel(target):
ts = ts or self.irc.channels[self.irc.toLower(target)].ts
ts = ts or self.channels[self.toLower(target)].ts
# TMODE:
# parameters: channelTS, channel, cmode changes, opt. cmode parameters...
@ -189,40 +189,40 @@ class TS6Protocol(TS6BaseProtocol):
msgprefix = ':%s TMODE %s %s ' % (numeric, ts, target)
bufsize = S2S_BUFSIZE - len(msgprefix)
for modestr in self.irc.wrapModes(modes, bufsize, max_modes_per_msg=10):
self.irc.send(msgprefix + modestr)
for modestr in self.wrapModes(modes, bufsize, max_modes_per_msg=10):
self.send(msgprefix + modestr)
else:
joinedmodes = self.irc.joinModes(modes)
joinedmodes = self.joinModes(modes)
self._send_with_prefix(numeric, 'MODE %s %s' % (target, joinedmodes))
def topicBurst(self, numeric, target, text):
"""Sends a topic change from a PyLink server. This is usually used on burst."""
if not self.irc.isInternalServer(numeric):
if not self.isInternalServer(numeric):
raise LookupError('No such PyLink server exists.')
# TB
# capab: TB
# source: server
# propagation: broadcast
# parameters: channel, topicTS, opt. topic setter, topic
ts = self.irc.channels[target].ts
servername = self.irc.servers[numeric].name
ts = self.channels[target].ts
servername = self.servers[numeric].name
self._send_with_prefix(numeric, 'TB %s %s %s :%s' % (target, ts, servername, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
self.channels[target].topic = text
self.channels[target].topicset = True
def invite(self, numeric, target, channel):
"""Sends an INVITE from a PyLink client.."""
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(numeric, 'INVITE %s %s %s' % (target, channel, self.irc.channels[channel].ts))
self._send_with_prefix(numeric, 'INVITE %s %s %s' % (target, channel, self.channels[channel].ts))
def knock(self, numeric, target, text):
"""Sends a KNOCK from a PyLink client."""
if 'KNOCK' not in self.irc.caps:
if 'KNOCK' not in self.caps:
log.debug('(%s) knock: Dropping KNOCK to %r since the IRCd '
'doesn\'t support it.', self.irc.name, target)
'doesn\'t support it.', self.name, target)
return
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
# No text value is supported here; drop it.
self._send_with_prefix(numeric, 'KNOCK %s' % target)
@ -231,12 +231,12 @@ class TS6Protocol(TS6BaseProtocol):
"""Updates the hostname of any connected client."""
field = field.upper()
if field == 'HOST':
self.irc.users[target].host = text
self._send_with_prefix(self.irc.sid, 'CHGHOST %s :%s' % (target, text))
if not self.irc.isInternalClient(target):
self.users[target].host = text
self._send_with_prefix(self.sid, 'CHGHOST %s :%s' % (target, text))
if not self.isInternalClient(target):
# If the target isn't one of our clients, send hook payload
# for other plugins to listen to.
self.irc.callHooks([self.irc.sid, 'CHGHOST',
self.callHooks([self.sid, 'CHGHOST',
{'target': target, 'newhost': text}])
else:
raise NotImplementedError("Changing field %r of a client is "
@ -245,7 +245,7 @@ class TS6Protocol(TS6BaseProtocol):
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
source = source or self.sid
if source is None:
return
if target is not None:
@ -257,10 +257,10 @@ class TS6Protocol(TS6BaseProtocol):
def post_connect(self):
"""Initializes a connection to a server."""
ts = self.irc.start_ts
ts = self.start_ts
self.has_eob = False
f = self.irc.send
f = self.send
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L80
chary_cmodes = { # TS6 generic modes (note that +p is noknock instead of private):
@ -278,17 +278,17 @@ class TS6Protocol(TS6BaseProtocol):
# Now, map all the ABCD type modes:
'*A': 'beIq', '*B': 'k', '*C': 'lfj', '*D': 'mnprstFLPQcgzCOAST'}
if self.irc.serverdata.get('use_owner'):
if self.serverdata.get('use_owner'):
chary_cmodes['owner'] = 'y'
self.irc.prefixmodes['y'] = '~'
if self.irc.serverdata.get('use_admin'):
self.prefixmodes['y'] = '~'
if self.serverdata.get('use_admin'):
chary_cmodes['admin'] = 'a'
self.irc.prefixmodes['a'] = '!'
if self.irc.serverdata.get('use_halfop'):
self.prefixmodes['a'] = '!'
if self.serverdata.get('use_halfop'):
chary_cmodes['halfop'] = 'h'
self.irc.prefixmodes['h'] = '%'
self.prefixmodes['h'] = '%'
self.irc.cmodes = chary_cmodes
self.cmodes = chary_cmodes
# Define supported user modes
chary_umodes = {'deaf': 'D', 'servprotect': 'S', 'admin': 'a',
@ -298,26 +298,26 @@ class TS6Protocol(TS6BaseProtocol):
'cloak': 'x', 'override': 'p',
# Now, map all the ABCD type modes:
'*A': '', '*B': '', '*C': '', '*D': 'DSaiowsQRgzlxp'}
self.irc.umodes = chary_umodes
self.umodes = chary_umodes
# Toggles support of shadowircd/elemental-ircd specific channel modes:
# +T (no notice), +u (hidden ban list), +E (no kicks), +J (blocks kickrejoin),
# +K (no repeat messages), +d (no nick changes), and user modes:
# +B (bot), +C (blocks CTCP), +D (deaf), +V (no invites), +I (hides channel list)
if self.irc.serverdata.get('use_elemental_modes'):
if self.serverdata.get('use_elemental_modes'):
elemental_cmodes = {'hiddenbans': 'u', 'nokick': 'E',
'kicknorejoin': 'J', 'repeat': 'K', 'nonick': 'd',
'blockcaps': 'G'}
self.irc.cmodes.update(elemental_cmodes)
self.irc.cmodes['*D'] += ''.join(elemental_cmodes.values())
self.cmodes.update(elemental_cmodes)
self.cmodes['*D'] += ''.join(elemental_cmodes.values())
elemental_umodes = {'noctcp': 'C', 'deaf': 'D', 'bot': 'B', 'noinvite': 'V',
'hidechans': 'I'}
self.irc.umodes.update(elemental_umodes)
self.irc.umodes['*D'] += ''.join(elemental_umodes.values())
self.umodes.update(elemental_umodes)
self.umodes['*D'] += ''.join(elemental_umodes.values())
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L55
f('PASS %s TS 6 %s' % (self.irc.serverdata["sendpass"], self.irc.sid))
f('PASS %s TS 6 %s' % (self.serverdata["sendpass"], self.sid))
# We request the following capabilities (for charybdis):
@ -338,8 +338,8 @@ class TS6Protocol(TS6BaseProtocol):
# EOPMOD: supports ETB (extended TOPIC burst) and =#channel messages for opmoderated +z
f('CAPAB :QS ENCAP EX CHW IE KNOCK SAVE SERVICES TB EUID RSFNC EOPMOD SAVETS_100')
f('SERVER %s 0 :%s' % (self.irc.serverdata["hostname"],
self.irc.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']))
f('SERVER %s 0 :%s' % (self.serverdata["hostname"],
self.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']))
# Finally, end all the initialization with a PING - that's Charybdis'
# way of saying end-of-burst :)
@ -352,7 +352,7 @@ class TS6Protocol(TS6BaseProtocol):
"""
# <- PASS $somepassword TS 6 :42X
if args[0] != self.irc.serverdata['recvpass']:
if args[0] != self.serverdata['recvpass']:
# Check if recvpass is correct
raise ProtocolError('Recvpass from uplink server %s does not match configuration!' % servername)
@ -360,12 +360,12 @@ class TS6Protocol(TS6BaseProtocol):
raise ProtocolError("Remote protocol version is too old! Is this even TS6?")
numeric = args[-1]
log.debug('(%s) Found uplink SID as %r', self.irc.name, numeric)
log.debug('(%s) Found uplink SID as %r', self.name, numeric)
# Server name and SID are sent in different messages, so we fill this
# with dummy information until we get the actual sid.
self.irc.servers[numeric] = IrcServer(None, '')
self.irc.uplink = numeric
self.servers[numeric] = IrcServer(None, '')
self.uplink = numeric
def handle_capab(self, numeric, command, args):
"""
@ -374,21 +374,21 @@ class TS6Protocol(TS6BaseProtocol):
# We only get a list of keywords here. Charybdis obviously assumes that
# we know what modes it supports (indeed, this is a standard list).
# <- CAPAB :BAN CHW CLUSTER ENCAP EOPMOD EUID EX IE KLN KNOCK MLOCK QS RSFNC SAVE SERVICES TB UNKLN
self.irc.caps = caps = args[0].split()
self.caps = caps = args[0].split()
for required_cap in self.required_caps:
if required_cap not in caps:
raise ProtocolError('%s not found in TS6 capabilities list; this is required! (got %r)' % (required_cap, caps))
if 'EX' in caps:
self.irc.cmodes['banexception'] = 'e'
self.cmodes['banexception'] = 'e'
if 'IE' in caps:
self.irc.cmodes['invex'] = 'I'
self.cmodes['invex'] = 'I'
if 'SERVICES' in caps:
self.irc.cmodes['regonly'] = 'r'
self.cmodes['regonly'] = 'r'
log.debug('(%s) self.irc.connected set!', self.irc.name)
self.irc.connected.set()
log.debug('(%s) self.connected set!', self.name)
self.connected.set()
def handle_ping(self, source, command, args):
"""Handles incoming PING commands."""
@ -405,11 +405,11 @@ class TS6Protocol(TS6BaseProtocol):
try:
destination = args[1]
except IndexError:
destination = self.irc.sid
if self.irc.isInternalServer(destination):
destination = self.sid
if self.isInternalServer(destination):
self._send_with_prefix(destination, 'PONG %s %s' % (destination, source), queue=False)
if destination == self.irc.sid and not self.has_eob:
if destination == self.sid and not self.has_eob:
# Charybdis' idea of endburst is just sending a PING. No, really!
# https://github.com/charybdis-ircd/charybdis/blob/dc336d1/modules/core/m_server.c#L484-L485
self.has_eob = True
@ -421,18 +421,18 @@ class TS6Protocol(TS6BaseProtocol):
"""Handles incoming SJOIN commands."""
# parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
# <- :0UY SJOIN 1451041566 #channel +nt :@0UYAAAAAB
channel = self.irc.toLower(args[1])
chandata = self.irc.channels[channel].deepcopy()
channel = self.toLower(args[1])
chandata = self.channels[channel].deepcopy()
userlist = args[-1].split()
modestring = args[2:-1] or args[2]
parsedmodes = self.irc.parseModes(channel, modestring)
parsedmodes = self.parseModes(channel, modestring)
namelist = []
# Keep track of other modes that are added due to prefix modes being joined too.
changedmodes = set(parsedmodes)
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name, userlist, channel)
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.name, userlist, channel)
for userpair in userlist:
# charybdis sends this in the form "@+UID1, +UID2, UID3, @UID4"
r = re.search(r'([^\d]*)(.*)', userpair)
@ -440,30 +440,30 @@ class TS6Protocol(TS6BaseProtocol):
modeprefix = r.group(1) or ''
finalprefix = ''
assert user, 'Failed to get the UID from %r; our regex needs updating?' % userpair
log.debug('(%s) handle_sjoin: got modeprefix %r for user %r', self.irc.name, modeprefix, user)
log.debug('(%s) handle_sjoin: got modeprefix %r for user %r', self.name, modeprefix, user)
# Don't crash when we get an invalid UID.
if user not in self.irc.users:
if user not in self.users:
log.debug('(%s) handle_sjoin: tried to introduce user %s not in our user list, ignoring...',
self.irc.name, user)
self.name, user)
continue
for m in modeprefix:
# Iterate over the mapping of prefix chars to prefixes, and
# find the characters that match.
for char, prefix in self.irc.prefixmodes.items():
for char, prefix in self.prefixmodes.items():
if m == prefix:
finalprefix += char
namelist.append(user)
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
# Only save mode changes if the remote has lower TS than us.
changedmodes |= {('+%s' % mode, user) for mode in finalprefix}
self.irc.channels[channel].users.add(user)
self.channels[channel].users.add(user)
# Statekeeping with timestamps
their_ts = int(args[0])
our_ts = self.irc.channels[channel].ts
our_ts = self.channels[channel].ts
self.updateTS(servernumeric, channel, their_ts, changedmodes)
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts,
@ -476,24 +476,24 @@ class TS6Protocol(TS6BaseProtocol):
ts = int(args[0])
if args[0] == '0':
# /join 0; part the user from all channels
oldchans = self.irc.users[numeric].channels.copy()
oldchans = self.users[numeric].channels.copy()
log.debug('(%s) Got /join 0 from %r, channel list is %r',
self.irc.name, numeric, oldchans)
self.name, numeric, oldchans)
for channel in oldchans:
self.irc.channels[channel].users.discard(numeric)
self.irc.users[numeric].channels.discard(channel)
self.channels[channel].users.discard(numeric)
self.users[numeric].channels.discard(channel)
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
else:
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
self.updateTS(numeric, channel, ts)
self.irc.users[numeric].channels.add(channel)
self.irc.channels[channel].users.add(numeric)
self.users[numeric].channels.add(channel)
self.channels[channel].users.add(numeric)
# We send users and modes here because SJOIN and JOIN both use one hook,
# for simplicity's sake (with plugins).
return {'channel': channel, 'users': [numeric], 'modes':
self.irc.channels[channel].modes, 'ts': ts}
self.channels[channel].modes, 'ts': ts}
def handle_euid(self, numeric, command, args):
"""Handles incoming EUID commands (user introduction)."""
@ -505,28 +505,28 @@ class TS6Protocol(TS6BaseProtocol):
realhost = None
log.debug('(%s) handle_euid got args: nick=%s ts=%s uid=%s ident=%s '
'host=%s realname=%s realhost=%s ip=%s', self.irc.name, nick, ts, uid,
'host=%s realname=%s realhost=%s ip=%s', self.name, nick, ts, uid,
ident, host, realname, realhost, ip)
assert ts != 0, "Bad TS 0 for user %s" % uid
if ip == '0': # IP was invalid; something used for services.
ip = '0.0.0.0'
self.irc.users[uid] = IrcUser(nick, ts, uid, numeric, ident, host, realname, realhost, ip)
self.users[uid] = IrcUser(nick, ts, uid, numeric, ident, host, realname, realhost, ip)
parsedmodes = self.irc.parseModes(uid, [modes])
parsedmodes = self.parseModes(uid, [modes])
log.debug('Applying modes %s for %s', parsedmodes, uid)
self.irc.applyModes(uid, parsedmodes)
self.irc.servers[numeric].users.add(uid)
self.applyModes(uid, parsedmodes)
self.servers[numeric].users.add(uid)
# Call the OPERED UP hook if +o is being added to the mode list.
if ('+o', None) in parsedmodes:
otype = 'Server Administrator' if ('+a', None) in parsedmodes else 'IRC Operator'
self.irc.callHooks([uid, 'CLIENT_OPERED', {'text': otype}])
self.callHooks([uid, 'CLIENT_OPERED', {'text': otype}])
# Set the accountname if present
if accountname != "*":
self.irc.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
self.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
@ -555,7 +555,7 @@ class TS6Protocol(TS6BaseProtocol):
servername = args[0].lower()
sid = args[2]
sdesc = args[-1]
self.irc.servers[sid] = IrcServer(numeric, servername, desc=sdesc)
self.servers[sid] = IrcServer(numeric, servername, desc=sdesc)
return {'name': servername, 'sid': sid, 'text': sdesc}
def handle_server(self, numeric, command, args):
@ -563,35 +563,35 @@ class TS6Protocol(TS6BaseProtocol):
Handles 1) incoming legacy (no SID) server introductions,
2) Sending server data in initial connection.
"""
if numeric == self.irc.uplink and not self.irc.servers[numeric].name:
if numeric == self.uplink and not self.servers[numeric].name:
# <- 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[numeric].name = sname
self.irc.servers[numeric].desc = args[-1]
log.debug('(%s) Found uplink server name as %r', self.name, sname)
self.servers[numeric].name = sname
self.servers[numeric].desc = args[-1]
# 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()))
self.send('SVINFO 6 6 0 :%s' % int(time.time()))
return
# <- :services.int SERVER a.bc 2 :(H) [GL] a
servername = args[0].lower()
sdesc = args[-1]
self.irc.servers[servername] = IrcServer(numeric, servername, desc=sdesc)
self.servers[servername] = IrcServer(numeric, servername, desc=sdesc)
return {'name': servername, 'sid': None, 'text': sdesc}
def handle_tmode(self, numeric, command, args):
"""Handles incoming TMODE commands (channel mode change)."""
# <- :42XAAAAAB TMODE 1437450768 #test -c+lkC 3 agte4
# <- :0UYAAAAAD TMODE 0 #a +h 0UYAAAAAD
channel = self.irc.toLower(args[1])
oldobj = self.irc.channels[channel].deepcopy()
channel = self.toLower(args[1])
oldobj = self.channels[channel].deepcopy()
modes = args[2:]
changedmodes = self.irc.parseModes(channel, modes)
self.irc.applyModes(channel, changedmodes)
changedmodes = self.parseModes(channel, modes)
self.applyModes(channel, changedmodes)
ts = int(args[0])
return {'target': channel, 'modes': changedmodes, 'ts': ts,
'channeldata': oldobj}
@ -601,42 +601,42 @@ class TS6Protocol(TS6BaseProtocol):
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
target = args[0]
modestrings = args[1:]
changedmodes = self.irc.parseModes(target, modestrings)
self.irc.applyModes(target, changedmodes)
changedmodes = self.parseModes(target, modestrings)
self.applyModes(target, changedmodes)
# Call the OPERED UP hook if +o is being set.
if ('+o', None) in changedmodes:
otype = 'Server Administrator' if ('a', None) in self.irc.users[target].modes else 'IRC Operator'
self.irc.callHooks([target, 'CLIENT_OPERED', {'text': otype}])
otype = 'Server Administrator' if ('a', None) in self.users[target].modes else 'IRC Operator'
self.callHooks([target, 'CLIENT_OPERED', {'text': otype}])
return {'target': target, 'modes': changedmodes}
def handle_tb(self, numeric, command, args):
"""Handles incoming topic burst (TB) commands."""
# <- :42X TB #chat 1467427448 GL!~gl@127.0.0.1 :test
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
ts = args[1]
setter = args[2]
topic = args[-1]
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
self.channels[channel].topic = topic
self.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
def handle_etb(self, numeric, command, args):
"""Handles extended topic burst (ETB)."""
# <- :00AAAAAAC ETB 0 #test 1470021157 GL :test | abcd
# Same as TB, with extra TS and extensions arguments.
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
ts = args[2]
setter = args[3]
topic = args[-1]
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
self.channels[channel].topic = topic
self.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic}
def handle_invite(self, numeric, command, args):
"""Handles incoming INVITEs."""
# <- :70MAAAAAC INVITE 0ALAAAAAA #blah 12345
target = args[0]
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
try:
ts = args[3]
except IndexError:
@ -647,20 +647,20 @@ class TS6Protocol(TS6BaseProtocol):
def handle_chghost(self, numeric, command, args):
"""Handles incoming CHGHOST commands."""
target = self._get_UID(args[0])
self.irc.users[target].host = newhost = args[1]
self.users[target].host = newhost = args[1]
return {'target': target, 'newhost': newhost}
def handle_bmask(self, numeric, command, args):
"""Handles incoming BMASK commands (ban propagation on burst)."""
# <- :42X BMASK 1424222769 #dev b :*!test@*.isp.net *!badident@*
# This is used for propagating bans, not TMODE!
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
mode = args[2]
ts = int(args[0])
modes = []
for ban in args[-1].split():
modes.append(('+%s' % mode, ban))
self.irc.applyModes(channel, modes)
self.applyModes(channel, modes)
return {'target': channel, 'modes': modes, 'ts': ts}
def handle_472(self, numeric, command, args):
@ -681,7 +681,7 @@ class TS6Protocol(TS6BaseProtocol):
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.', self.irc.name, setter, badmode,
'your IRCd configuration.', self.name, setter, badmode,
charlist[badmode])
def handle_su(self, numeric, command, args):
@ -696,7 +696,7 @@ class TS6Protocol(TS6BaseProtocol):
account = '' # No account name means a logout
uid = args[0]
self.irc.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': account}])
self.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': account}])
def handle_rsfnc(self, numeric, command, args):
"""

View File

@ -79,7 +79,7 @@ class TS6SIDGenerator():
"""
Returns the next unused TS6 SID for the server.
"""
while ''.join(self.output) in self.irc.servers:
while ''.join(self.output) in self.servers:
# Increment until the SID we have doesn't already exist.
self.increment()
sid = ''.join(self.output)
@ -110,7 +110,7 @@ class TS6BaseProtocol(IRCS2SProtocol):
def _send_with_prefix(self, source, msg, **kwargs):
"""Sends a TS6-style raw command from a source numeric to the self.irc connection given."""
self.irc.send(':%s %s' % (source, msg), **kwargs)
self.send(':%s %s' % (source, msg), **kwargs)
def _expandPUID(self, uid):
"""
@ -134,11 +134,11 @@ class TS6BaseProtocol(IRCS2SProtocol):
def kick(self, numeric, channel, target, reason=None):
"""Sends kicks from a PyLink client/server."""
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
channel = self.irc.toLower(channel)
channel = self.toLower(channel)
if not reason:
reason = 'No reason given'
@ -155,8 +155,8 @@ class TS6BaseProtocol(IRCS2SProtocol):
def kill(self, numeric, target, reason):
"""Sends a kill from a PyLink client/server."""
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
# From TS6 docs:
@ -167,41 +167,41 @@ class TS6BaseProtocol(IRCS2SProtocol):
# the kill followed by a space and a parenthesized reason. To avoid overflow,
# it is recommended not to add anything to the path.
assert target in self.irc.users, "Unknown target %r for kill()!" % target
assert target in self.users, "Unknown target %r for kill()!" % target
if numeric in self.irc.users:
if numeric in self.users:
# Killer was an user. Follow examples of setting the path to be "killer.host!killer.nick".
userobj = self.irc.users[numeric]
userobj = self.users[numeric]
killpath = '%s!%s' % (userobj.host, userobj.nick)
elif numeric in self.irc.servers:
elif numeric in self.servers:
# Sender was a server; killpath is just its name.
killpath = self.irc.servers[numeric].name
killpath = self.servers[numeric].name
else:
# Invalid sender?! This shouldn't happen, but make the killpath our server name anyways.
log.warning('(%s) Invalid sender %s for kill(); using our server name instead.',
self.irc.name, numeric)
killpath = self.irc.servers[self.irc.sid].name
self.name, numeric)
killpath = self.servers[self.sid].name
self._send_with_prefix(numeric, 'KILL %s :%s (%s)' % (target, killpath, reason))
self.removeClient(target)
def nick(self, numeric, newnick):
"""Changes the nick of a PyLink client."""
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(numeric, 'NICK %s %s' % (newnick, int(time.time())))
self.irc.users[numeric].nick = newnick
self.users[numeric].nick = newnick
# Update the NICK TS.
self.irc.users[numeric].ts = int(time.time())
self.users[numeric].ts = int(time.time())
def part(self, client, channel, reason=None):
"""Sends a part from a PyLink client."""
channel = self.irc.toLower(channel)
if not self.irc.isInternalClient(client):
log.error('(%s) Error trying to part %r from %r (no such client exists)', self.irc.name, client, channel)
channel = self.toLower(channel)
if not self.isInternalClient(client):
log.error('(%s) Error trying to part %r from %r (no such client exists)', self.name, client, channel)
raise LookupError('No such PyLink client exists.')
msg = "PART %s" % channel
if reason:
@ -211,7 +211,7 @@ class TS6BaseProtocol(IRCS2SProtocol):
def quit(self, numeric, reason):
"""Quits a PyLink client."""
if self.irc.isInternalClient(numeric):
if self.isInternalClient(numeric):
self._send_with_prefix(numeric, "QUIT :%s" % reason)
self.removeClient(numeric)
else:
@ -219,7 +219,7 @@ class TS6BaseProtocol(IRCS2SProtocol):
def message(self, numeric, target, text):
"""Sends a PRIVMSG from a PyLink client."""
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
# Mangle message targets for IRCds that require it.
@ -229,8 +229,8 @@ class TS6BaseProtocol(IRCS2SProtocol):
def notice(self, numeric, target, text):
"""Sends a NOTICE from a PyLink client or server."""
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
# Mangle message targets for IRCds that require it.
@ -240,11 +240,11 @@ class TS6BaseProtocol(IRCS2SProtocol):
def topic(self, numeric, target, text):
"""Sends a TOPIC change from a PyLink client."""
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(numeric, 'TOPIC %s :%s' % (target, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
self.channels[target].topic = text
self.channels[target].topicset = True
def spawnServer(self, name, sid=None, uplink=None, desc=None, endburst_delay=0):
"""
@ -257,23 +257,23 @@ class TS6BaseProtocol(IRCS2SProtocol):
option will be ignored if given.
"""
# -> :0AL SID test.server 1 0XY :some silly pseudoserver
uplink = uplink or self.irc.sid
uplink = uplink or self.sid
name = name.lower()
desc = desc or self.irc.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
desc = desc or self.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
if sid is None: # No sid given; generate one!
sid = self.sidgen.next_sid()
assert len(sid) == 3, "Incorrect SID length"
if sid in self.irc.servers:
if sid in self.servers:
raise ValueError('A server with SID %r already exists!' % sid)
for server in self.irc.servers.values():
for server in self.servers.values():
if name == server.name:
raise ValueError('A server named %r already exists!' % name)
if not self.irc.isInternalServer(uplink):
if not self.isInternalServer(uplink):
raise ValueError('Server %r is not a PyLink server!' % uplink)
if not utils.isServerName(name):
raise ValueError('Invalid server name %r' % name)
self._send_with_prefix(uplink, 'SID %s 1 %s :%s' % (name, sid, desc))
self.irc.servers[sid] = IrcServer(uplink, name, internal=True, desc=desc)
self.servers[sid] = IrcServer(uplink, name, internal=True, desc=desc)
return sid
def squit(self, source, target, text='No reason given'):
@ -290,13 +290,13 @@ class TS6BaseProtocol(IRCS2SProtocol):
self._send_with_prefix(source, 'AWAY :%s' % text)
else:
self._send_with_prefix(source, 'AWAY')
self.irc.users[source].away = text
self.users[source].away = text
### HANDLERS
def handle_kick(self, source, command, args):
"""Handles incoming KICKs."""
# :70MAAAAAA KICK #test 70MAAAAAA :some reason
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
kicked = self._get_UID(args[1])
try:
@ -304,18 +304,18 @@ class TS6BaseProtocol(IRCS2SProtocol):
except IndexError:
reason = ''
log.debug('(%s) Removing kick target %s from %s', self.irc.name, kicked, channel)
log.debug('(%s) Removing kick target %s from %s', self.name, kicked, channel)
self.handle_part(kicked, 'KICK', [channel, reason])
return {'channel': channel, 'target': kicked, 'text': reason}
def handle_nick(self, numeric, command, args):
"""Handles incoming NICK changes."""
# <- :70MAAAAAA NICK GL-devel 1434744242
oldnick = self.irc.users[numeric].nick
newnick = self.irc.users[numeric].nick = args[0]
oldnick = self.users[numeric].nick
newnick = self.users[numeric].nick = args[0]
# Update the nick TS.
self.irc.users[numeric].ts = ts = int(args[1])
self.users[numeric].ts = ts = int(args[1])
return {'newnick': newnick, 'oldnick': oldnick, 'ts': ts}
@ -330,12 +330,12 @@ class TS6BaseProtocol(IRCS2SProtocol):
# -> :0AL000001 NICK Derp_ 1433728673
# <- :70M SAVE 0AL000001 1433728673
user = args[0]
oldnick = self.irc.users[user].nick
self.irc.users[user].nick = user
oldnick = self.users[user].nick
self.users[user].nick = user
# TS6 SAVE sets nick TS to 100. This is hardcoded in InspIRCd and
# charybdis.
self.irc.users[user].ts = 100
self.users[user].ts = 100
return {'target': user, 'ts': 100, 'oldnick': oldnick}
@ -343,33 +343,33 @@ class TS6BaseProtocol(IRCS2SProtocol):
"""Handles incoming TOPIC changes from clients. For topic bursts,
TB (TS6/charybdis) and FTOPIC (InspIRCd) are used instead."""
# <- :70MAAAAAA TOPIC #test :test
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
topic = args[1]
oldtopic = self.irc.channels[channel].topic
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
oldtopic = self.channels[channel].topic
self.channels[channel].topic = topic
self.channels[channel].topicset = True
return {'channel': channel, 'setter': numeric, 'text': topic,
'oldtopic': oldtopic}
def handle_part(self, source, command, args):
"""Handles incoming PART commands."""
channels = self.irc.toLower(args[0]).split(',')
channels = self.toLower(args[0]).split(',')
for channel in channels:
# We should only get PART commands for channels that exist, right??
self.irc.channels[channel].removeuser(source)
self.channels[channel].removeuser(source)
try:
self.irc.users[source].channels.discard(channel)
self.users[source].channels.discard(channel)
except KeyError:
log.debug("(%s) handle_part: KeyError trying to remove %r from %r's channel list?", self.irc.name, channel, source)
log.debug("(%s) handle_part: KeyError trying to remove %r from %r's channel list?", self.name, channel, source)
try:
reason = args[1]
except IndexError:
reason = ''
# Clear empty non-permanent channels.
if not (self.irc.channels[channel].users or ((self.irc.cmodes.get('permanent'), None) in self.irc.channels[channel].modes)):
del self.irc.channels[channel]
if not (self.channels[channel].users or ((self.cmodes.get('permanent'), None) in self.channels[channel].modes)):
del self.channels[channel]
return {'channels': channels, 'text': reason}
def handle_svsnick(self, source, command, args):

View File

@ -34,7 +34,7 @@ class UnrealProtocol(TS6BaseProtocol):
'EOS': 'ENDBURST'}
self.caps = []
self.irc.prefixmodes = {'q': '~', 'a': '&', 'o': '@', 'h': '%', 'v': '+'}
self.prefixmodes = {'q': '~', 'a': '&', 'o': '@', 'h': '%', 'v': '+'}
self.needed_caps = ["VL", "SID", "CHANMODES", "NOQUIT", "SJ3", "NICKIP", "UMODE2", "SJOIN"]
@ -47,11 +47,11 @@ class UnrealProtocol(TS6BaseProtocol):
3.2 users), this will change the PUID given to the actual user's nick,
so that that the older IRCds can understand it.
"""
if uid in self.irc.users and '@' in uid:
if uid in self.users and '@' in uid:
# UID exists and has a @ in it, meaning it's a PUID (orignick@counter style).
# Return this user's nick accordingly.
nick = self.irc.users[uid].nick
log.debug('(%s) Mangling target PUID %s to nick %s', self.irc.name, uid, nick)
nick = self.users[uid].nick
log.debug('(%s) Mangling target PUID %s to nick %s', self.name, uid, nick)
return nick
return uid
@ -65,8 +65,8 @@ class UnrealProtocol(TS6BaseProtocol):
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
if not self.irc.isInternalServer(server):
server = server or self.sid
if not self.isInternalServer(server):
raise ValueError('Server %r is not a PyLink server!' % server)
# Unreal 4.0 uses TS6-style UIDs. They don't start from AAAAAA like other IRCd's
@ -81,11 +81,11 @@ class UnrealProtocol(TS6BaseProtocol):
modes = set(modes) # Ensure type safety
modes |= {('+x', None), ('+t', None)}
raw_modes = self.irc.joinModes(modes)
u = self.irc.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
raw_modes = self.joinModes(modes)
u = self.users[uid] = IrcUser(nick, ts, uid, server, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip, manipulatable=manipulatable, opertype=opertype)
self.irc.applyModes(uid, modes)
self.irc.servers[server].users.add(uid)
self.applyModes(uid, modes)
self.servers[server].users.add(uid)
# UnrealIRCd requires encoding the IP by first packing it into a binary format,
# and then encoding the binary with Base64.
@ -116,12 +116,12 @@ class UnrealProtocol(TS6BaseProtocol):
def join(self, client, channel):
"""Joins a PyLink client to a channel."""
channel = self.irc.toLower(channel)
if not self.irc.isInternalClient(client):
channel = self.toLower(channel)
if not self.isInternalClient(client):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(client, "JOIN %s" % channel)
self.irc.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel)
self.channels[channel].users.add(client)
self.users[client].channels.add(channel)
def sjoin(self, server, channel, users, ts=None, modes=set()):
"""Sends an SJOIN for a group of users to a channel.
@ -132,17 +132,17 @@ class UnrealProtocol(TS6BaseProtocol):
Example uses:
sjoin('100', '#test', [('', '100AAABBC'), ('o', 100AAABBB'), ('v', '100AAADDD')])
sjoin(self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])
sjoin(self.sid, '#test', [('o', self.pseudoclient.uid)])
"""
# <- :001 SJOIN 1444361345 #test :*@+1JJAAAAAB %2JJAAAA4C 1JJAAAADS
channel = self.irc.toLower(channel)
server = server or self.irc.sid
channel = self.toLower(channel)
server = server or self.sid
assert users, "sjoin: No users sent?"
if not server:
raise LookupError('No such PyLink server exists.')
changedmodes = set(modes or self.irc.channels[channel].modes)
orig_ts = self.irc.channels[channel].ts
changedmodes = set(modes or self.channels[channel].modes)
orig_ts = self.channels[channel].ts
ts = ts or orig_ts
uids = []
itemlist = []
@ -163,17 +163,17 @@ class UnrealProtocol(TS6BaseProtocol):
uids.append(user)
try:
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
except KeyError: # Not initialized yet?
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
log.debug("(%s) sjoin: KeyError trying to add %r to %r's channel list?", self.name, channel, user)
# Track simple modes separately.
simplemodes = set()
for modepair in modes:
if modepair[0][-1] in self.irc.cmodes['*A']:
if modepair[0][-1] in self.cmodes['*A']:
# Bans, exempts, invex get expanded to forms like "&*!*@some.host" in SJOIN.
if (modepair[0][-1], modepair[1]) in self.irc.channels[channel].modes:
if (modepair[0][-1], modepair[1]) in self.channels[channel].modes:
# Mode is already set; skip it.
continue
@ -189,25 +189,25 @@ class UnrealProtocol(TS6BaseProtocol):
# Modes are optional; add them if they exist
if modes:
sjoin_prefix += " %s" % self.irc.joinModes(simplemodes)
sjoin_prefix += " %s" % self.joinModes(simplemodes)
sjoin_prefix += " :"
# Wrap arguments to the max supported S2S line length to prevent cutoff
# (https://github.com/GLolol/PyLink/issues/378)
for line in utils.wrapArguments(sjoin_prefix, itemlist, S2S_BUFSIZE):
self.irc.send(line)
self.send(line)
self.irc.channels[channel].users.update(uids)
self.channels[channel].users.update(uids)
self.updateTS(server, channel, ts, changedmodes)
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
source = source or self.sid
target = target or self.uplink
if not (target is None or source is None):
self._send_with_prefix(source, 'PING %s %s' % (self.irc.servers[source].name, self.irc.servers[target].name))
self._send_with_prefix(source, 'PING %s %s' % (self.servers[source].name, self.servers[target].name))
def mode(self, numeric, target, modes, ts=None):
"""
@ -216,22 +216,22 @@ class UnrealProtocol(TS6BaseProtocol):
"""
# <- :unreal.midnight.vpn MODE #test +ntCo GL 1444361345
if (not self.irc.isInternalClient(numeric)) and \
(not self.irc.isInternalServer(numeric)):
if (not self.isInternalClient(numeric)) and \
(not self.isInternalServer(numeric)):
raise LookupError('No such PyLink client/server exists.')
self.irc.applyModes(target, modes)
self.applyModes(target, modes)
if utils.isChannel(target):
# Make sure we expand any PUIDs when sending outgoing modes...
for idx, mode in enumerate(modes):
if mode[0][-1] in self.irc.prefixmodes:
log.debug('(%s) mode: expanding PUID of mode %s', self.irc.name, str(mode))
if mode[0][-1] in self.prefixmodes:
log.debug('(%s) mode: expanding PUID of mode %s', self.name, str(mode))
modes[idx] = (mode[0], self._expandPUID(mode[1]))
# The MODE command is used for channel mode changes only
ts = ts or self.irc.channels[self.irc.toLower(target)].ts
ts = ts or self.channels[self.toLower(target)].ts
# 7 characters for "MODE", the space between MODE and the target, the space between the
# target and mode list, and the space between the mode list and TS.
@ -242,7 +242,7 @@ class UnrealProtocol(TS6BaseProtocol):
bufsize -= len(target)
# Subtract the prefix (":SID " for servers or ":SIDAAAAAA " for servers)
bufsize -= (5 if self.irc.isInternalServer(numeric) else 11)
bufsize -= (5 if self.isInternalServer(numeric) else 11)
# There is also an (undocumented) 15 args per line limit for MODE. The target, mode
# characters, and TS take up three args, so we're left with 12 spaces for parameters.
@ -251,28 +251,28 @@ class UnrealProtocol(TS6BaseProtocol):
# * *** Warning! Possible desynch: MODE for channel #test ('+bbbbbbbbbbbb *!*@0.1 *!*@1.1 *!*@2.1 *!*@3.1 *!*@4.1 *!*@5.1 *!*@6.1 *!*@7.1 *!*@8.1 *!*@9.1 *!*@10.1 *!*@11.1') has fishy timestamp (12) (from pylink.local/pylink.local)
# Thanks to kevin and Jobe for helping me debug this!
for modestring in self.irc.wrapModes(modes, bufsize, max_modes_per_msg=12):
for modestring in self.wrapModes(modes, bufsize, max_modes_per_msg=12):
self._send_with_prefix(numeric, 'MODE %s %s %s' % (target, modestring, ts))
else:
# For user modes, the only way to set modes (for non-U:Lined servers)
# is through UMODE2, which sets the modes on the caller.
# U:Lines can use SVSMODE/SVS2MODE, but I won't expect people to
# U:Line a PyLink daemon...
if not self.irc.isInternalClient(target):
if not self.isInternalClient(target):
raise ProtocolError('Cannot force mode change on external clients!')
# XXX: I don't expect usermode changes to ever get cut off, but length
# checks could be added just to be safe...
joinedmodes = self.irc.joinModes(modes)
joinedmodes = self.joinModes(modes)
self._send_with_prefix(target, 'UMODE2 %s' % joinedmodes)
def topicBurst(self, numeric, target, text):
"""Sends a TOPIC change from a PyLink server."""
if not self.irc.isInternalServer(numeric):
if not self.isInternalServer(numeric):
raise LookupError('No such PyLink server exists.')
self._send_with_prefix(numeric, 'TOPIC %s :%s' % (target, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
self.channels[target].topic = text
self.channels[target].topicset = True
def updateClient(self, target, field, text):
"""Updates the ident, host, or realname of any connected client."""
@ -282,44 +282,44 @@ class UnrealProtocol(TS6BaseProtocol):
raise NotImplementedError("Changing field %r of a client is "
"unsupported by this protocol." % field)
if self.irc.isInternalClient(target):
if self.isInternalClient(target):
# It is one of our clients, use SETIDENT/HOST/NAME.
if field == 'IDENT':
self.irc.users[target].ident = text
self.users[target].ident = text
self._send_with_prefix(target, 'SETIDENT %s' % text)
elif field == 'HOST':
self.irc.users[target].host = text
self.users[target].host = text
self._send_with_prefix(target, 'SETHOST %s' % text)
elif field in ('REALNAME', 'GECOS'):
self.irc.users[target].realname = text
self.users[target].realname = text
self._send_with_prefix(target, 'SETNAME :%s' % text)
else:
# It is a client on another server, use CHGIDENT/HOST/NAME.
if field == 'IDENT':
self.irc.users[target].ident = text
self._send_with_prefix(self.irc.sid, 'CHGIDENT %s %s' % (target, text))
self.users[target].ident = text
self._send_with_prefix(self.sid, 'CHGIDENT %s %s' % (target, text))
# Send hook payloads for other plugins to listen to.
self.irc.callHooks([self.irc.sid, 'CHGIDENT',
self.callHooks([self.sid, 'CHGIDENT',
{'target': target, 'newident': text}])
elif field == 'HOST':
self.irc.users[target].host = text
self._send_with_prefix(self.irc.sid, 'CHGHOST %s %s' % (target, text))
self.users[target].host = text
self._send_with_prefix(self.sid, 'CHGHOST %s %s' % (target, text))
self.irc.callHooks([self.irc.sid, 'CHGHOST',
self.callHooks([self.sid, 'CHGHOST',
{'target': target, 'newhost': text}])
elif field in ('REALNAME', 'GECOS'):
self.irc.users[target].realname = text
self._send_with_prefix(self.irc.sid, 'CHGNAME %s :%s' % (target, text))
self.users[target].realname = text
self._send_with_prefix(self.sid, 'CHGNAME %s :%s' % (target, text))
self.irc.callHooks([self.irc.sid, 'CHGNAME',
self.callHooks([self.sid, 'CHGNAME',
{'target': target, 'newgecos': text}])
def invite(self, numeric, target, channel):
"""Sends an INVITE from a PyLink client.."""
if not self.irc.isInternalClient(numeric):
if not self.isInternalClient(numeric):
raise LookupError('No such PyLink client exists.')
self._send_with_prefix(numeric, 'INVITE %s %s' % (target, channel))
@ -329,21 +329,21 @@ class UnrealProtocol(TS6BaseProtocol):
# sent to all ops in a channel.
# <- :unreal.midnight.vpn NOTICE @#test :[Knock] by GL|!gl@hidden-1C620195 (test)
assert utils.isChannel(target), "Can only knock on channels!"
sender = self.irc.getServer(numeric)
s = '[Knock] by %s (%s)' % (self.irc.getHostmask(numeric), text)
sender = self.getServer(numeric)
s = '[Knock] by %s (%s)' % (self.getHostmask(numeric), text)
self._send_with_prefix(sender, 'NOTICE @%s :%s' % (target, s))
### HANDLERS
def post_connect(self):
"""Initializes a connection to a server."""
ts = self.irc.start_ts
self.irc.prefixmodes = {'q': '~', 'a': '&', 'o': '@', 'h': '%', 'v': '+'}
ts = self.start_ts
self.prefixmodes = {'q': '~', 'a': '&', 'o': '@', 'h': '%', 'v': '+'}
# Track usages of legacy (Unreal 3.2) nicks.
self.legacy_uidgen = utils.PUIDGenerator('U32user')
self.irc.umodes.update({'deaf': 'd', 'invisible': 'i', 'hidechans': 'p',
self.umodes.update({'deaf': 'd', 'invisible': 'i', 'hidechans': 'p',
'protected': 'q', 'registered': 'r',
'snomask': 's', 'vhost': 't', 'wallops': 'w',
'bot': 'B', 'cloak': 'x', 'ssl': 'z',
@ -352,10 +352,10 @@ class UnrealProtocol(TS6BaseProtocol):
'noctcp': 'T', 'showwhois': 'W',
'*A': '', '*B': '', '*C': '', '*D': 'dipqrstwBxzGHIRSTW'})
f = self.irc.send
host = self.irc.serverdata["hostname"]
f = self.send
host = self.serverdata["hostname"]
f('PASS :%s' % self.irc.serverdata["sendpass"])
f('PASS :%s' % self.serverdata["sendpass"])
# https://github.com/unrealircd/unrealircd/blob/2f8cb55e/doc/technical/protoctl.txt
# We support the following protocol features:
# SJOIN - supports SJOIN for user introduction
@ -374,11 +374,11 @@ class UnrealProtocol(TS6BaseProtocol):
# not work for any UnrealIRCd 3.2 users.
# ESVID - Supports account names in services stamps instead of just the signon time.
# AFAIK this doesn't actually affect services' behaviour?
f('PROTOCTL SJOIN SJ3 NOQUIT NICKv2 VL UMODE2 PROTOCTL NICKIP EAUTH=%s SID=%s VHP ESVID' % (self.irc.serverdata["hostname"], self.irc.sid))
sdesc = self.irc.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
f('SERVER %s 1 U%s-h6e-%s :%s' % (host, self.proto_ver, self.irc.sid, sdesc))
f('NETINFO 1 %s %s * 0 0 0 :%s' % (self.irc.start_ts, self.proto_ver, self.irc.serverdata.get("netname", self.irc.name)))
self._send_with_prefix(self.irc.sid, 'EOS')
f('PROTOCTL SJOIN SJ3 NOQUIT NICKv2 VL UMODE2 PROTOCTL NICKIP EAUTH=%s SID=%s VHP ESVID' % (self.serverdata["hostname"], self.sid))
sdesc = self.serverdata.get('serverdesc') or conf.conf['bot']['serverdesc']
f('SERVER %s 1 U%s-h6e-%s :%s' % (host, self.proto_ver, self.sid, sdesc))
f('NETINFO 1 %s %s * 0 0 0 :%s' % (self.start_ts, self.proto_ver, self.serverdata.get("netname", self.name)))
self._send_with_prefix(self.sid, 'EOS')
def handle_eos(self, numeric, command, args):
"""EOS is used to denote end of burst."""
@ -418,50 +418,50 @@ class UnrealProtocol(TS6BaseProtocol):
realname = args[-1]
self.irc.users[uid] = IrcUser(nick, ts, uid, numeric, ident, host, realname, realhost, ip)
self.irc.servers[numeric].users.add(uid)
self.users[uid] = IrcUser(nick, ts, uid, numeric, ident, host, realname, realhost, ip)
self.servers[numeric].users.add(uid)
# Handle user modes
parsedmodes = self.irc.parseModes(uid, [modestring])
self.irc.applyModes(uid, parsedmodes)
parsedmodes = self.parseModes(uid, [modestring])
self.applyModes(uid, parsedmodes)
# The cloaked (+x) host is completely separate from the displayed host
# and real host in that it is ONLY shown if the user is +x (cloak mode
# enabled) but NOT +t (vHost set).
self.irc.users[uid].cloaked_host = args[9]
self.users[uid].cloaked_host = args[9]
if ('+o', None) in parsedmodes:
# If +o being set, call the CLIENT_OPERED internal hook.
self.irc.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
self.callHooks([uid, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
if ('+x', None) not in parsedmodes:
# If +x is not set, update to use the person's real host.
self.irc.users[uid].host = realhost
self.users[uid].host = realhost
# Set the account name if present: if this is a number, set it to the user nick.
if ('+r', None) in parsedmodes and accountname.isdigit():
accountname = nick
if not accountname.isdigit():
self.irc.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
self.callHooks([uid, 'CLIENT_SERVICES_LOGIN', {'text': accountname}])
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
def handle_pass(self, numeric, command, args):
# <- PASS :abcdefg
if args[0] != self.irc.serverdata['recvpass']:
if args[0] != self.serverdata['recvpass']:
raise ProtocolError("Error: RECVPASS from uplink does not match configuration!")
def handle_ping(self, numeric, command, args):
if numeric == self.irc.uplink:
self.irc.send('PONG %s :%s' % (self.irc.serverdata['hostname'], args[-1]), queue=False)
if numeric == self.uplink:
self.send('PONG %s :%s' % (self.serverdata['hostname'], args[-1]), queue=False)
def handle_server(self, numeric, command, args):
"""Handles the SERVER command, which is used for both authentication and
introducing legacy (non-SID) servers."""
# <- SERVER unreal.midnight.vpn 1 :U3999-Fhin6OoEM UnrealIRCd test server
sname = args[0]
if numeric == self.irc.uplink and not self.irc.connected.is_set(): # We're doing authentication
if numeric == self.uplink and not self.connected.is_set(): # We're doing authentication
for cap in self.needed_caps:
if cap not in self.caps:
raise ProtocolError("Not all required capabilities were met "
@ -485,17 +485,17 @@ class UnrealProtocol(TS6BaseProtocol):
if protover < self.min_proto_ver:
raise ProtocolError("Protocol version too old! (needs at least %s "
"(Unreal 4.x), got %s)" % (self.min_proto_ver, protover))
self.irc.servers[numeric] = IrcServer(None, sname, desc=sdesc)
self.servers[numeric] = IrcServer(None, sname, desc=sdesc)
# Set irc.connected to True, meaning that protocol negotiation passed.
log.debug('(%s) self.irc.connected set!', self.irc.name)
self.irc.connected.set()
log.debug('(%s) self.connected set!', self.name)
self.connected.set()
else:
# Legacy (non-SID) servers can still be introduced using the SERVER command.
# <- :services.int SERVER a.bc 2 :(H) [GL] a
servername = args[0].lower()
sdesc = args[-1]
self.irc.servers[servername] = IrcServer(numeric, servername, desc=sdesc)
self.servers[servername] = IrcServer(numeric, servername, desc=sdesc)
return {'name': servername, 'sid': None, 'text': sdesc}
def handle_sid(self, numeric, command, args):
@ -504,7 +504,7 @@ class UnrealProtocol(TS6BaseProtocol):
sname = args[0].lower()
sid = args[2]
sdesc = args[-1]
self.irc.servers[sid] = IrcServer(numeric, sname, desc=sdesc)
self.servers[sid] = IrcServer(numeric, sname, desc=sdesc)
return {'name': sname, 'sid': sid, 'text': sdesc}
def handle_squit(self, numeric, command, args):
@ -534,18 +534,18 @@ class UnrealProtocol(TS6BaseProtocol):
# <- PROTOCTL CHANMODES=beI,k,l,psmntirzMQNRTOVKDdGPZSCc NICKCHARS= SID=001 MLOCK TS=1441314501 EXTSWHOIS
for cap in args:
if cap.startswith('SID'):
self.irc.uplink = cap.split('=', 1)[1]
self.uplink = cap.split('=', 1)[1]
elif cap.startswith('CHANMODES'):
# Parse all the supported channel modes.
supported_cmodes = cap.split('=', 1)[1]
self.irc.cmodes['*A'], self.irc.cmodes['*B'], self.irc.cmodes['*C'], self.irc.cmodes['*D'] = supported_cmodes.split(',')
self.cmodes['*A'], self.cmodes['*B'], self.cmodes['*C'], self.cmodes['*D'] = supported_cmodes.split(',')
for namedmode, modechar in cmodes.items():
if modechar in supported_cmodes:
self.irc.cmodes[namedmode] = modechar
self.irc.cmodes['*B'] += 'f' # Add +f to the list too, dunno why it isn't there.
self.cmodes[namedmode] = modechar
self.cmodes['*B'] += 'f' # Add +f to the list too, dunno why it isn't there.
# Add in the supported prefix modes.
self.irc.cmodes.update({'halfop': 'h', 'admin': 'a', 'owner': 'q',
self.cmodes.update({'halfop': 'h', 'admin': 'a', 'owner': 'q',
'op': 'o', 'voice': 'v'})
def handle_join(self, numeric, command, args):
@ -553,38 +553,38 @@ class UnrealProtocol(TS6BaseProtocol):
# <- :GL JOIN #pylink,#test
if args[0] == '0':
# /join 0; part the user from all channels
oldchans = self.irc.users[numeric].channels.copy()
oldchans = self.users[numeric].channels.copy()
log.debug('(%s) Got /join 0 from %r, channel list is %r',
self.irc.name, numeric, oldchans)
self.name, numeric, oldchans)
for ch in oldchans:
self.irc.channels[ch].users.discard(numeric)
self.irc.users[numeric].channels.discard(ch)
self.channels[ch].users.discard(numeric)
self.users[numeric].channels.discard(ch)
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
else:
for channel in args[0].split(','):
# Normalize channel case.
channel = self.irc.toLower(channel)
channel = self.toLower(channel)
c = self.irc.channels[channel]
c = self.channels[channel]
self.irc.users[numeric].channels.add(channel)
self.irc.channels[channel].users.add(numeric)
self.users[numeric].channels.add(channel)
self.channels[channel].users.add(numeric)
# Call hooks manually, because one JOIN command in UnrealIRCd can
# have multiple channels...
self.irc.callHooks([numeric, command, {'channel': channel, 'users': [numeric], 'modes':
self.callHooks([numeric, command, {'channel': channel, 'users': [numeric], 'modes':
c.modes, 'ts': c.ts}])
def handle_sjoin(self, numeric, command, args):
"""Handles the UnrealIRCd SJOIN command."""
# <- :001 SJOIN 1444361345 #test :001AAAAAA @001AAAAAB +001AAAAAC
# <- :001 SJOIN 1483250129 #services +nt :+001OR9V02 @*~001DH6901 &*!*@test "*!*@blah.blah '*!*@yes.no
channel = self.irc.toLower(args[1])
chandata = self.irc.channels[channel].deepcopy()
channel = self.toLower(args[1])
chandata = self.channels[channel].deepcopy()
userlist = args[-1].split()
namelist = []
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name, userlist, channel)
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.name, userlist, channel)
modestring = ''
@ -599,7 +599,7 @@ class UnrealProtocol(TS6BaseProtocol):
# Strip extra spaces between the mode argument and the user list, if
# there are any. XXX: report this as a bug in unreal's s2s protocol?
modestring = [m for m in modestring if m]
parsedmodes = self.irc.parseModes(channel, modestring)
parsedmodes = self.parseModes(channel, modestring)
changedmodes = set(parsedmodes)
except IndexError:
pass
@ -630,22 +630,22 @@ class UnrealProtocol(TS6BaseProtocol):
modeprefix = (r.group(1) or '').replace("~", "&").replace("*", "~")
finalprefix = ''
log.debug('(%s) handle_sjoin: got modeprefix %r for user %r', self.irc.name, modeprefix, user)
log.debug('(%s) handle_sjoin: got modeprefix %r for user %r', self.name, modeprefix, user)
for m in modeprefix:
# Iterate over the mapping of prefix chars to prefixes, and
# find the characters that match.
for char, prefix in self.irc.prefixmodes.items():
for char, prefix in self.prefixmodes.items():
if m == prefix:
finalprefix += char
namelist.append(user)
self.irc.users[user].channels.add(channel)
self.users[user].channels.add(channel)
# Only merge the remote's prefix modes if their TS is smaller or equal to ours.
changedmodes |= {('+%s' % mode, user) for mode in finalprefix}
self.irc.channels[channel].users.add(user)
self.channels[channel].users.add(user)
our_ts = self.irc.channels[channel].ts
our_ts = self.channels[channel].ts
their_ts = int(args[0])
self.updateTS(numeric, channel, their_ts, changedmodes)
@ -666,7 +666,7 @@ class UnrealProtocol(TS6BaseProtocol):
# <- NICK GL32 2 1470699865 gl localhost unreal32.midnight.vpn GL +iowx hidden-1C620195 AAAAAAAAAAAAAAAAAAAAAQ== :realname
# to this:
# <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * hidden-1C620195 fwAAAQ== :realname
log.debug('(%s) got legacy NICK args: %s', self.irc.name, ' '.join(args))
log.debug('(%s) got legacy NICK args: %s', self.name, ' '.join(args))
new_args = args[:] # Clone the old args list
servername = new_args[5].lower() # Get the name of the users' server.
@ -682,7 +682,7 @@ class UnrealProtocol(TS6BaseProtocol):
# hosts from UnrealIRCd 3.2 users. Otherwise, +x host cloaking won't work!
new_args.insert(-2, args[4])
log.debug('(%s) translating legacy NICK args to: %s', self.irc.name, ' '.join(new_args))
log.debug('(%s) translating legacy NICK args to: %s', self.name, ' '.join(new_args))
return self.handle_uid(servername, 'UID_LEGACY', new_args)
else:
@ -705,11 +705,11 @@ class UnrealProtocol(TS6BaseProtocol):
# Also, we need to get rid of that extra space following the +f argument. :|
if utils.isChannel(args[0]):
channel = self.irc.toLower(args[0])
oldobj = self.irc.channels[channel].deepcopy()
channel = self.toLower(args[0])
oldobj = self.channels[channel].deepcopy()
modes = [arg for arg in args[1:] if arg] # normalize whitespace
parsedmodes = self.irc.parseModes(channel, modes)
parsedmodes = self.parseModes(channel, modes)
if parsedmodes:
if parsedmodes[0][0] == '+&':
@ -717,12 +717,12 @@ class UnrealProtocol(TS6BaseProtocol):
# attempt to set modes by us was rejected for some reason (usually due to
# timestamps). Drop the mode change to prevent mode floods.
log.debug("(%s) Received mode bounce %s in channel %s! Our TS: %s",
self.irc.name, modes, channel, self.irc.channels[channel].ts)
self.name, modes, channel, self.channels[channel].ts)
return
self.irc.applyModes(channel, parsedmodes)
self.applyModes(channel, parsedmodes)
if numeric in self.irc.servers and args[-1].isdigit():
if numeric in self.servers and args[-1].isdigit():
# Sender is a server AND last arg is number. Perform TS updates.
their_ts = int(args[-1])
if their_ts > 0:
@ -738,7 +738,7 @@ class UnrealProtocol(TS6BaseProtocol):
hostname of the user given to or from their cloaked host if True.
"""
userobj = self.irc.users[uid]
userobj = self.users[uid]
final_modes = userobj.modes
oldhost = userobj.host
@ -763,7 +763,7 @@ class UnrealProtocol(TS6BaseProtocol):
if newhost != oldhost:
# Only send a payload if the old and new hosts are different.
self.irc.callHooks([uid, 'SETHOST',
self.callHooks([uid, 'SETHOST',
{'target': uid, 'newhost': newhost}])
def handle_svsmode(self, numeric, command, args):
@ -772,8 +772,8 @@ class UnrealProtocol(TS6BaseProtocol):
target = self._get_UID(args[0])
modes = args[1:]
parsedmodes = self.irc.parseModes(target, modes)
self.irc.applyModes(target, parsedmodes)
parsedmodes = self.parseModes(target, modes)
self.applyModes(target, parsedmodes)
# If +x/-x is being set, update cloaked host info.
self.checkCloakChange(target, parsedmodes)
@ -818,7 +818,7 @@ class UnrealProtocol(TS6BaseProtocol):
# <- :NickServ SVS2MODE 001SALZ01 +r
target = self._get_UID(args[0])
parsedmodes = self.irc.parseModes(target, args[1:])
parsedmodes = self.parseModes(target, args[1:])
if ('+r', None) in parsedmodes:
# Umode +r is being set (log in)
@ -828,19 +828,19 @@ class UnrealProtocol(TS6BaseProtocol):
except IndexError:
# If one doesn't exist, make it the same as the nick, but only if the account name
# wasn't set already.
if not self.irc.users[target].services_account:
account = self.irc.getFriendlyName(target)
if not self.users[target].services_account:
account = self.getFriendlyName(target)
else:
return
else:
if account.isdigit():
# If the +d argument is a number, ignore it and set the account name to the nick.
account = self.irc.getFriendlyName(target)
account = self.getFriendlyName(target)
elif ('-r', None) in parsedmodes:
# Umode -r being set.
if not self.irc.users[target].services_account:
if not self.users[target].services_account:
# User already has no account; ignore.
return
@ -853,17 +853,17 @@ class UnrealProtocol(TS6BaseProtocol):
else:
return
self.irc.callHooks([target, 'CLIENT_SERVICES_LOGIN', {'text': account}])
self.callHooks([target, 'CLIENT_SERVICES_LOGIN', {'text': account}])
def handle_umode2(self, numeric, command, args):
"""Handles UMODE2, used to set user modes on oneself."""
# <- :GL UMODE2 +W
parsedmodes = self.irc.parseModes(numeric, args)
self.irc.applyModes(numeric, parsedmodes)
parsedmodes = self.parseModes(numeric, args)
self.applyModes(numeric, parsedmodes)
if ('+o', None) in parsedmodes:
# If +o being set, call the CLIENT_OPERED internal hook.
self.irc.callHooks([numeric, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
self.callHooks([numeric, 'CLIENT_OPERED', {'text': 'IRC Operator'}])
self.checkCloakChange(numeric, parsedmodes)
@ -873,14 +873,14 @@ class UnrealProtocol(TS6BaseProtocol):
"""Handles the TOPIC command."""
# <- GL TOPIC #services GL 1444699395 :weeee
# <- TOPIC #services devel.relay 1452399682 :test
channel = self.irc.toLower(args[0])
channel = self.toLower(args[0])
topic = args[-1]
setter = args[1]
ts = args[2]
oldtopic = self.irc.channels[channel].topic
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
oldtopic = self.channels[channel].topic
self.channels[channel].topic = topic
self.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'text': topic,
'oldtopic': oldtopic}
@ -888,42 +888,42 @@ class UnrealProtocol(TS6BaseProtocol):
def handle_setident(self, numeric, command, args):
"""Handles SETIDENT, used for self ident changes."""
# <- :70MAAAAAB SETIDENT test
self.irc.users[numeric].ident = newident = args[0]
self.users[numeric].ident = newident = args[0]
return {'target': numeric, 'newident': newident}
def handle_sethost(self, numeric, command, args):
"""Handles CHGHOST, used for self hostname changes."""
# <- :70MAAAAAB SETIDENT some.host
self.irc.users[numeric].host = newhost = args[0]
self.users[numeric].host = newhost = args[0]
# When SETHOST or CHGHOST is used, modes +xt are implicitly set on the
# target.
self.irc.applyModes(numeric, [('+x', None), ('+t', None)])
self.applyModes(numeric, [('+x', None), ('+t', None)])
return {'target': numeric, 'newhost': newhost}
def handle_setname(self, numeric, command, args):
"""Handles SETNAME, used for self real name/gecos changes."""
# <- :70MAAAAAB SETNAME :afdsafasf
self.irc.users[numeric].realname = newgecos = args[0]
self.users[numeric].realname = newgecos = args[0]
return {'target': numeric, 'newgecos': newgecos}
def handle_chgident(self, numeric, command, args):
"""Handles CHGIDENT, used for denoting ident changes."""
# <- :GL CHGIDENT GL test
target = self._get_UID(args[0])
self.irc.users[target].ident = newident = args[1]
self.users[target].ident = newident = args[1]
return {'target': target, 'newident': newident}
def handle_chghost(self, numeric, command, args):
"""Handles CHGHOST, used for denoting hostname changes."""
# <- :GL CHGHOST GL some.host
target = self._get_UID(args[0])
self.irc.users[target].host = newhost = args[1]
self.users[target].host = newhost = args[1]
# When SETHOST or CHGHOST is used, modes +xt are implicitly set on the
# target.
self.irc.applyModes(target, [('+x', None), ('+t', None)])
self.applyModes(target, [('+x', None), ('+t', None)])
return {'target': target, 'newhost': newhost}
@ -931,14 +931,14 @@ class UnrealProtocol(TS6BaseProtocol):
"""Handles CHGNAME, used for denoting real name/gecos changes."""
# <- :GL CHGNAME GL :afdsafasf
target = self._get_UID(args[0])
self.irc.users[target].realname = newgecos = args[1]
self.users[target].realname = newgecos = args[1]
return {'target': target, 'newgecos': newgecos}
def handle_invite(self, numeric, command, args):
"""Handles incoming INVITEs."""
# <- :GL INVITE PyLink-devel :#a
target = self._get_UID(args[0])
channel = self.irc.toLower(args[1])
channel = self.toLower(args[1])
# We don't actually need to process this; it's just something plugins/hooks can use
return {'target': target, 'channel': channel}
@ -958,6 +958,6 @@ class UnrealProtocol(TS6BaseProtocol):
if args[0] == 'alltime':
# XXX: We override notice() here because that abstraction doesn't allow messages from servers.
self._send_with_prefix(self.irc.sid, 'NOTICE %s :*** Server=%s time()=%d' % (source, self.irc.hostname(), time.time()))
self._send_with_prefix(self.sid, 'NOTICE %s :*** Server=%s time()=%d' % (source, self.hostname(), time.time()))
Class = UnrealProtocol