3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-12-24 19:52:53 +01:00

Classify all our protocol modules - why didn't I do this earlier

This commit is contained in:
James Lu 2015-09-05 18:00:57 -07:00
parent 3b7d81d62a
commit 57da0aa3b3
8 changed files with 1393 additions and 1377 deletions

View File

@ -65,7 +65,8 @@ class Irc():
self.serverdata = conf['servers'][netname]
self.sid = self.serverdata["sid"]
self.botdata = conf['bot']
self.proto = proto
self.protoname = proto.__name__
self.proto = proto.Class(self)
self.pingfreq = self.serverdata.get('pingfreq') or 30
self.pingtimeout = self.pingfreq * 2
@ -142,7 +143,7 @@ class Irc():
sha1fp)
if checks_ok:
self.proto.connect(self)
self.proto.connect()
self.spawnMain()
log.info('(%s) Starting ping schedulers....', self.name)
self.schedulePing()
@ -200,7 +201,7 @@ class Irc():
log.debug("(%s) <- %s", self.name, line)
hook_args = None
try:
hook_args = self.proto.handle_events(self, line)
hook_args = self.proto.handle_events(line)
except Exception:
log.exception('(%s) Caught error in handle_events, disconnecting!', self.name)
return
@ -249,7 +250,7 @@ class Irc():
log.debug("(%s) Dropping message %r; network isn't connected!", self.name, stripped_data)
def schedulePing(self):
self.proto.pingServer(self)
self.proto.pingServer()
self.pingTimer = threading.Timer(self.pingfreq, self.schedulePing)
self.pingTimer.daemon = True
self.pingTimer.start()
@ -261,9 +262,9 @@ class Irc():
host = self.serverdata["hostname"]
log.info('(%s) Connected! Spawning main client %s.', self.name, nick)
olduserobj = self.pseudoclient
self.pseudoclient = self.proto.spawnClient(self, nick, ident, host, modes={("+o", None)})
self.pseudoclient = self.proto.spawnClient(nick, ident, host, modes={("+o", None)})
for chan in self.serverdata['channels']:
self.proto.joinClient(self, self.pseudoclient.uid, chan)
self.proto.joinClient(self.pseudoclient.uid, chan)
# PyLink internal hook called when spawnMain is called and the
# contents of Irc().pseudoclient change.
self.callHooks([self.sid, 'PYLINK_SPAWNMAIN', {'olduser': olduserobj}])
@ -339,7 +340,7 @@ class FakeIRC(Irc):
def run(self, data):
"""Queues a message to the fake IRC server."""
log.debug('<- ' + data)
hook_args = self.proto.handle_events(self, data)
hook_args = self.proto.handle_events(data)
if hook_args is not None:
self.hookmsgs.append(hook_args)
self.callHooks(hook_args)
@ -374,6 +375,13 @@ class FakeIRC(Irc):
self.hookmsgs = []
return hookmsgs
class Protocol():
# TODO: Future state-keeping things will go here
def __init__(self, irc):
self.irc = irc
self.casemapping = 'rfc1459'
self.hook_map = {}
class FakeProto():
"""Dummy protocol module for testing purposes."""
def __init__(self):

View File

@ -15,7 +15,7 @@ def handle_kick(irc, source, command, args):
kicked = args['target']
channel = args['channel']
if kicked == irc.pseudoclient.uid:
irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
irc.proto.joinClient(irc.pseudoclient.uid, channel)
utils.add_hook(handle_kick, 'KICK')
# Handle commands sent to the PyLink client (PRIVMSG)
@ -51,7 +51,7 @@ def handle_whois(irc, source, command, args):
sourceisOper = ('o', None) in irc.users[source].modes
# https://www.alien.net.au/irc/irc2numerics.html
# 311: sends nick!user@host information
f(irc, server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname))
f(server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname))
# 319: RPL_WHOISCHANNELS, shows channel list
public_chans = []
for chan in user.channels:
@ -69,9 +69,9 @@ def handle_whois(irc, source, command, args):
chan = prefixchar + chan
public_chans.append(chan)
if public_chans:
f(irc, server, 319, source, '%s :%s' % (nick, ' '.join(public_chans)))
f(server, 319, source, '%s :%s' % (nick, ' '.join(public_chans)))
# 312: sends the server the target is on, and its server description.
f(irc, server, 312, source, "%s %s :%s" % (nick, irc.serverdata['hostname'],
f(server, 312, source, "%s %s :%s" % (nick, irc.serverdata['hostname'],
irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']))
# 313: sends a string denoting the target's operator privilege,
# only if they have umode +o.
@ -82,15 +82,15 @@ def handle_whois(irc, source, command, args):
opertype = "IRC Operator"
# Let's be gramatically correct.
n = 'n' if opertype[0].lower() in 'aeiou' else ''
f(irc, server, 313, source, "%s :is a%s %s" % (nick, n, opertype))
f(server, 313, source, "%s :is a%s %s" % (nick, n, opertype))
# 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
# Only show this to opers!
if sourceisOper:
f(irc, server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes)))
f(server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes)))
# 317: shows idle and signon time. However, we don't track the user's real
# idle time, so we simply return 0.
# <- 317 GL GL 15 1437632859 :seconds idle, signon time
f(irc, server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
f(server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
for func in world.whois_handlers:
# Iterate over custom plugin WHOIS handlers. They return a tuple
# or list with two arguments: the numeric, and the text to send.
@ -98,11 +98,11 @@ def handle_whois(irc, source, command, args):
res = func(irc, target)
if res:
num, text = res
f(irc, server, num, source, text)
f(server, num, source, text)
except Exception as e:
# Again, we wouldn't want this to crash our service, in case
# something goes wrong!
log.exception('(%s) Error caught in WHOIS handler: %s', irc.name, e)
# 318: End of WHOIS.
f(irc, server, 318, source, "%s :End of /WHOIS list" % nick)
f(server, 318, source, "%s :End of /WHOIS list" % nick)
utils.add_hook(handle_whois, 'WHOIS')

View File

@ -22,7 +22,7 @@ def spawnclient(irc, source, args):
except ValueError:
utils.msg(irc, source, "Error: Not enough arguments. Needs 3: nick, user, host.")
return
irc.proto.spawnClient(irc, nick, ident, host)
irc.proto.spawnClient(nick, ident, host)
@utils.add_cmd
def quit(irc, source, args):
@ -40,7 +40,7 @@ def quit(irc, source, args):
return
u = utils.nickToUid(irc, nick)
quitmsg = ' '.join(args[1:]) or 'Client Quit'
irc.proto.quitClient(irc, u, quitmsg)
irc.proto.quitClient(u, quitmsg)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_QUIT', {'text': quitmsg, 'parse_as': 'QUIT'}])
def joinclient(irc, source, args):
@ -61,7 +61,7 @@ def joinclient(irc, source, args):
if not utils.isChannel(channel):
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
return
irc.proto.joinClient(irc, u, channel)
irc.proto.joinClient(u, channel)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_JOIN', {'channel': channel, 'users': [u],
'modes': irc.channels[channel].modes,
'parse_as': 'JOIN'}])
@ -85,7 +85,7 @@ def nick(irc, source, args):
elif not utils.isNick(newnick):
utils.msg(irc, source, 'Error: Invalid nickname %r.' % newnick)
return
irc.proto.nickClient(irc, u, newnick)
irc.proto.nickClient(u, newnick)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_NICK', {'newnick': newnick, 'oldnick': nick, 'parse_as': 'NICK'}])
@utils.add_cmd
@ -106,7 +106,7 @@ def part(irc, source, args):
if not utils.isChannel(channel):
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
return
irc.proto.partClient(irc, u, channel, reason)
irc.proto.partClient(u, channel, reason)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_PART', {'channels': clist, 'text': reason, 'parse_as': 'PART'}])
@utils.add_cmd
@ -129,9 +129,9 @@ def kick(irc, source, args):
utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
return
if utils.isInternalServer(irc, u):
irc.proto.kickServer(irc, u, channel, targetu, reason)
irc.proto.kickServer(u, channel, targetu, reason)
else:
irc.proto.kickClient(irc, u, channel, targetu, reason)
irc.proto.kickClient(u, channel, targetu, reason)
irc.callHooks([u, 'PYLINK_BOTSPLUGIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}])
@utils.add_cmd
@ -156,11 +156,11 @@ def mode(irc, source, args):
utils.msg(irc, source, "Error: Invalid channel or nick %r." % target)
return
if utils.isInternalServer(irc, modesource):
irc.proto.modeServer(irc, modesource, target, parsedmodes)
irc.proto.modeServer(modesource, target, parsedmodes)
irc.callHooks([modesource, 'PYLINK_BOTSPLUGIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
else:
sourceuid = utils.nickToUid(irc, modesource)
irc.proto.modeClient(irc, sourceuid, target, parsedmodes)
irc.proto.modeClient(sourceuid, target, parsedmodes)
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MODE', {'target': target, 'modes': parsedmodes, 'parse_as': 'MODE'}])
@utils.add_cmd
@ -188,5 +188,5 @@ def msg(irc, source, args):
if not text:
utils.msg(irc, source, 'Error: No text given.')
return
irc.proto.messageClient(irc, sourceuid, real_target, text)
irc.proto.messageClient(sourceuid, real_target, text)
irc.callHooks([sourceuid, 'PYLINK_BOTSPLUGIN_MSG', {'target': real_target, 'text': text, 'parse_as': 'PRIVMSG'}])

View File

@ -39,7 +39,7 @@ def normalizeNick(irc, netname, nick, separator=None, uid=''):
separator = separator or irc.serverdata.get('separator') or "/"
log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator)
orig_nick = nick
protoname = irc.proto.__name__
protoname = irc.protoname
maxnicklen = irc.maxnicklen
if '/' not in separator or not protoname.startswith(('insp', 'unreal')):
# Charybdis doesn't allow / in usernames, and will SQUIT with
@ -172,7 +172,7 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
hideoper_mode = remoteirc.umodes.get('hideoper')
if hideoper_mode:
modes.append((hideoper_mode, None))
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
u = remoteirc.proto.spawnClient(nick, ident=ident,
host=host, realname=realname,
modes=modes, ts=userobj.ts,
opertype=opertype).uid
@ -180,7 +180,7 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
remoteirc.users[u].opertype = opertype
away = userobj.away
if away:
remoteirc.proto.awayClient(remoteirc, u, away)
remoteirc.proto.awayClient(u, away)
relayusers[(irc.name, user)][remoteirc.name] = u
return u
@ -275,11 +275,11 @@ def initializeChannel(irc, channel):
# Only update the topic if it's different from what we already have,
# and topic bursting is complete.
if remoteirc.channels[remotechan].topicset and topic != irc.channels[channel].topic:
irc.proto.topicServer(irc, irc.sid, channel, topic)
irc.proto.topicServer(irc.sid, channel, topic)
# Send our users and channel modes to the other nets
log.debug('(%s) initializeChannel: joining our users: %s', irc.name, c.users)
relayJoins(irc, channel, c.users, c.ts)
irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
irc.proto.joinClient(irc.pseudoclient.uid, channel)
def handle_join(irc, numeric, command, args):
channel = args['channel']
@ -294,7 +294,7 @@ utils.add_hook(handle_join, 'JOIN')
def handle_quit(irc, numeric, command, args):
for netname, user in relayusers[(irc.name, numeric)].copy().items():
remoteirc = world.networkobjects[netname]
remoteirc.proto.quitClient(remoteirc, user, args['text'])
remoteirc.proto.quitClient(user, args['text'])
del relayusers[(irc.name, numeric)]
utils.add_hook(handle_quit, 'QUIT')
@ -310,7 +310,7 @@ def handle_nick(irc, numeric, command, args):
remoteirc = world.networkobjects[netname]
newnick = normalizeNick(remoteirc, irc.name, args['newnick'], uid=user)
if remoteirc.users[user].nick != newnick:
remoteirc.proto.nickClient(remoteirc, user, newnick)
remoteirc.proto.nickClient(user, newnick)
utils.add_hook(handle_nick, 'NICK')
def handle_part(irc, numeric, command, args):
@ -325,9 +325,9 @@ def handle_part(irc, numeric, command, args):
remotechan = findRemoteChan(irc, remoteirc, channel)
if remotechan is None:
continue
remoteirc.proto.partClient(remoteirc, user, remotechan, text)
remoteirc.proto.partClient(user, remotechan, text)
if not remoteirc.users[user].channels:
remoteirc.proto.quitClient(remoteirc, user, 'Left all shared channels.')
remoteirc.proto.quitClient(user, 'Left all shared channels.')
del relayusers[(irc.name, numeric)][remoteirc.name]
utils.add_hook(handle_part, 'PART')
@ -363,9 +363,9 @@ def handle_privmsg(irc, numeric, command, args):
continue
real_target = prefix + real_target
if notice:
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
remoteirc.proto.noticeClient(user, real_target, text)
else:
remoteirc.proto.messageClient(remoteirc, user, real_target, text)
remoteirc.proto.messageClient(user, real_target, text)
else:
remoteuser = getLocalUser(irc, target)
if remoteuser is None:
@ -383,9 +383,9 @@ def handle_privmsg(irc, numeric, command, args):
remoteirc = world.networkobjects[homenet]
user = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
if notice:
remoteirc.proto.noticeClient(remoteirc, user, real_target, text)
remoteirc.proto.noticeClient(user, real_target, text)
else:
remoteirc.proto.messageClient(remoteirc, user, real_target, text)
remoteirc.proto.messageClient(user, real_target, text)
utils.add_hook(handle_privmsg, 'PRIVMSG')
utils.add_hook(handle_privmsg, 'NOTICE')
@ -431,7 +431,7 @@ def handle_kick(irc, source, command, args):
# kick ops, admins can't kick owners, etc.
modes = getPrefixModes(remoteirc, irc, remotechan, real_target)
# Join the kicked client back with its respective modes.
irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)])
irc.proto.sjoinServer(irc.sid, channel, [(modes, target)])
if kicker in irc.users:
log.info('(%s) Relay claim: Blocked KICK (reason %r) from %s to relay client %s/%s on %s.',
irc.name, args['text'], irc.users[source].nick,
@ -450,7 +450,7 @@ def handle_kick(irc, source, command, args):
# Propogate the kick!
if real_kicker:
log.debug('(%s) Relay kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan,real_kicker, kicker, irc.name)
remoteirc.proto.kickClient(remoteirc, real_kicker,
remoteirc.proto.kickClient(real_kicker,
remotechan, real_target, text)
else:
# Kick originated from a server, or the kicker isn't in any
@ -464,17 +464,17 @@ def handle_kick(irc, source, command, args):
text = "(%s/%s) %s" % (kname, irc.name, text)
except AttributeError:
text = "(<unknown kicker>@%s) %s" % (irc.name, text)
remoteirc.proto.kickServer(remoteirc, remoteirc.sid,
remoteirc.proto.kickServer(remoteirc.sid,
remotechan, real_target, text)
# If the target isn't on any channels, quit them.
if origuser and origuser[0] != remoteirc.name and not remoteirc.users[real_target].channels:
del relayusers[origuser][remoteirc.name]
remoteirc.proto.quitClient(remoteirc, real_target, 'Left all shared channels.')
remoteirc.proto.quitClient(real_target, 'Left all shared channels.')
if origuser and not irc.users[target].channels:
del relayusers[origuser][irc.name]
irc.proto.quitClient(irc, target, 'Left all shared channels.')
irc.proto.quitClient(target, 'Left all shared channels.')
utils.add_hook(handle_kick, 'KICK')
@ -493,7 +493,7 @@ def handle_chgclient(irc, source, command, args):
for netname, user in relayusers[(irc.name, target)].items():
remoteirc = world.networkobjects[netname]
try:
remoteirc.proto.updateClient(remoteirc, user, field, text)
remoteirc.proto.updateClient(user, field, text)
except NotImplementedError: # IRCd doesn't support changing the field we want
log.debug('(%s) Ignoring changing field %r of %s on %s (for %s/%s);'
' remote IRCd doesn\'t support it', irc.name, field,
@ -584,9 +584,9 @@ def relayModes(irc, remoteirc, sender, channel, modes=None):
# Check if the sender is a user; remember servers are allowed to set modes too.
u = getRemoteUser(irc, remoteirc, sender, spawnIfMissing=False)
if u:
remoteirc.proto.modeClient(remoteirc, u, remotechan, supported_modes)
remoteirc.proto.modeClient(u, remotechan, supported_modes)
else:
remoteirc.proto.modeServer(remoteirc, remoteirc.sid, remotechan, supported_modes)
remoteirc.proto.modeServer(remoteirc.sid, remotechan, supported_modes)
def getSupportedUmodes(irc, remoteirc, modes):
supported_modes = []
@ -613,7 +613,7 @@ def getSupportedUmodes(irc, remoteirc, modes):
log.debug("(%s) getSupportedUmodes: skipping mode (%r, %r) because "
"the remote network (%s)'s IRCd (%s) doesn't support it.",
irc.name, modechar, arg, remoteirc.name,
remoteirc.proto.__name__)
remoteirc.protoname)
return supported_modes
def handle_mode(irc, numeric, command, args):
@ -636,7 +636,7 @@ def handle_mode(irc, numeric, command, args):
modes.append(('-%s' % hideoper_mode, None))
remoteuser = getRemoteUser(irc, remoteirc, target, spawnIfMissing=False)
if remoteuser and modes:
remoteirc.proto.modeClient(remoteirc, remoteuser, remoteuser, modes)
remoteirc.proto.modeClient(remoteuser, remoteuser, modes)
utils.add_hook(handle_mode, 'MODE')
@ -654,9 +654,9 @@ def handle_topic(irc, numeric, command, args):
# This might originate from a server too.
remoteuser = getRemoteUser(irc, remoteirc, numeric, spawnIfMissing=False)
if remoteuser:
remoteirc.proto.topicClient(remoteirc, remoteuser, remotechan, topic)
remoteirc.proto.topicClient(remoteuser, remotechan, topic)
else:
remoteirc.proto.topicServer(remoteirc, remoteirc.sid, remotechan, topic)
remoteirc.proto.topicServer(remoteirc.sid, remotechan, topic)
utils.add_hook(handle_topic, 'TOPIC')
def handle_kill(irc, numeric, command, args):
@ -676,7 +676,7 @@ def handle_kill(irc, numeric, command, args):
modes = getPrefixModes(remoteirc, irc, localchan, realuser[1])
log.debug('(%s) relay handle_kill: userpair: %s, %s', irc.name, modes, realuser)
client = getRemoteUser(remoteirc, irc, realuser[1])
irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)])
irc.proto.sjoinServer(irc.sid, localchan, [(modes, client)])
if userdata and numeric in irc.users:
log.info('(%s) Relay claim: Blocked KILL (reason %r) from %s to relay client %s/%s.',
irc.name, args['text'], irc.users[numeric].nick,
@ -745,10 +745,10 @@ def relayJoins(irc, channel, users, ts, burst=True):
# Burst was explicitly given, or we're trying to join multiple
# users/someone with a prefix.
if burst or len(queued_users) > 1 or queued_users[0][0]:
remoteirc.proto.sjoinServer(remoteirc, remoteirc.sid, remotechan, queued_users, ts=ts)
remoteirc.proto.sjoinServer(remoteirc.sid, remotechan, queued_users, ts=ts)
relayModes(irc, remoteirc, irc.sid, channel, irc.channels[channel].modes)
else:
remoteirc.proto.joinClient(remoteirc, queued_users[0][1], remotechan)
remoteirc.proto.joinClient(queued_users[0][1], remotechan)
def relayPart(irc, channel, user):
for name, remoteirc in world.networkobjects.items():
@ -762,16 +762,16 @@ def relayPart(irc, channel, user):
log.debug('(%s) relayPart: remoteuser for %s/%s found as %s', irc.name, user, irc.name, remoteuser)
if remotechan is None or remoteuser is None:
continue
remoteirc.proto.partClient(remoteirc, remoteuser, remotechan, 'Channel delinked.')
remoteirc.proto.partClient(remoteuser, remotechan, 'Channel delinked.')
if isRelayClient(remoteirc, remoteuser) and not remoteirc.users[remoteuser].channels:
remoteirc.proto.quitClient(remoteirc, remoteuser, 'Left all shared channels.')
remoteirc.proto.quitClient(remoteuser, 'Left all shared channels.')
del relayusers[(irc.name, user)][remoteirc.name]
def removeChannel(irc, channel):
if irc is None:
return
if channel not in map(str.lower, irc.serverdata['channels']):
irc.proto.partClient(irc, irc.pseudoclient.uid, channel, 'Channel delinked.')
irc.proto.partClient(irc.pseudoclient.uid, channel, 'Channel delinked.')
relay = findRelay((irc.name, channel))
if relay:
for user in irc.channels[channel].users.copy():
@ -782,12 +782,12 @@ def removeChannel(irc, channel):
if user == irc.pseudoclient.uid and channel in \
irc.serverdata['channels']:
continue
irc.proto.partClient(irc, user, channel, 'Channel delinked.')
irc.proto.partClient(user, channel, 'Channel delinked.')
# Don't ever quit it either...
if user != irc.pseudoclient.uid and not irc.users[user].channels:
remoteuser = getLocalUser(irc, user)
del relayusers[remoteuser][irc.name]
irc.proto.quitClient(irc, user, 'Left all shared channels.')
irc.proto.quitClient(user, 'Left all shared channels.')
@utils.add_cmd
def create(irc, source, args):
@ -986,7 +986,7 @@ def handle_save(irc, numeric, command, args):
newnick = normalizeNick(irc, remotenet, nick)
log.info('(%s) SAVE received for relay client %r (%s), fixing nick to %s',
irc.name, target, nick, newnick)
irc.proto.nickClient(irc, target, newnick)
irc.proto.nickClient(target, newnick)
else:
log.warning('(%s) SAVE received for relay client %r (%s), not '
'fixing nick again due to 5 failed attempts in '
@ -1020,7 +1020,7 @@ def linked(irc, source, args):
def handle_away(irc, numeric, command, args):
for netname, user in relayusers[(irc.name, numeric)].items():
remoteirc = world.networkobjects[netname]
remoteirc.proto.awayClient(remoteirc, user, args['text'])
remoteirc.proto.awayClient(user, args['text'])
utils.add_hook(handle_away, 'AWAY')
def handle_spawnmain(irc, numeric, command, args):
@ -1048,7 +1048,7 @@ def handle_invite(irc, source, command, args):
'channel not on their network!',
notice=True)
else:
remoteirc.proto.inviteClient(remoteirc, remotesource, remoteuser,
remoteirc.proto.inviteClient(remotesource, remoteuser,
remotechan)
utils.add_hook(handle_invite, 'INVITE')

View File

@ -11,73 +11,71 @@ import utils
from log import log
from classes import *
# Some functions are shared with the charybdis module (ts6_common)
from ts6_common import nickClient, kickServer, kickClient, _sendKick, quitClient, \
removeClient, partClient, messageClient, noticeClient, topicClient, parseTS6Args
from ts6_common import handle_privmsg, handle_kill, handle_kick, handle_error, \
handle_quit, handle_nick, handle_save, handle_squit, handle_mode, handle_topic, \
handle_notice, _send, handle_part
from ts6_common import TS6BaseProtocol
# Set our case mapping (rfc1459 maps "\" and "|" together, for example".
casemapping = 'rfc1459'
class InspIRCdProtocol(TS6BaseProtocol):
def __init__(self, irc):
super(InspIRCdProtocol, self).__init__(irc)
# Set our case mapping (rfc1459 maps "\" and "|" together, for example".
self.casemapping = 'rfc1459'
# Raw commands sent from servers vary from protocol to protocol. Here, we map
# non-standard names to our hook handlers, so command handlers' outputs
# are called with the right hooks.
hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE',
# Raw commands sent from servers vary from protocol to protocol. Here, we map
# non-standard names to our hook handlers, so command handlers' outputs
# are called with the right hooks.
self.hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE',
'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST',
'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'}
def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(),
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):
"""Spawns a client with nick <nick> on the given IRC connection.
Note: No nick collision / valid nickname checks are done here; it is
up to plugins to make sure they don't introduce anything invalid."""
server = server or irc.sid
if not utils.isInternalServer(irc, server):
server = server or self.irc.sid
if not utils.isInternalServer(self.irc, server):
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
# We need a separate UID generator instance for every PseudoServer
# we spawn. Otherwise, things won't wrap around properly.
if server not in irc.uidgen:
irc.uidgen[server] = utils.TS6UIDGenerator(server)
uid = irc.uidgen[server].next_uid()
if server not in self.irc.uidgen:
self.irc.uidgen[server] = utils.TS6UIDGenerator(server)
uid = self.irc.uidgen[server].next_uid()
ts = ts or int(time.time())
realname = realname or irc.botdata['realname']
realname = realname or self.irc.botdata['realname']
realhost = realhost or host
raw_modes = utils.joinModes(modes)
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip)
utils.applyModes(irc, uid, modes)
irc.servers[server].users.add(uid)
_send(irc, server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
utils.applyModes(self.irc, uid, modes)
self.irc.servers[server].users.add(uid)
self._send(server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
" {ts} {modes} + :{realname}".format(ts=ts, host=host,
nick=nick, ident=ident, uid=uid,
modes=raw_modes, ip=ip, realname=realname,
realhost=realhost))
if ('o', None) in modes or ('+o', None) in modes:
_operUp(irc, uid, opertype=opertype or 'IRC_Operator')
self._operUp(uid, opertype=opertype or 'IRC_Operator')
return u
def joinClient(irc, client, channel):
def joinClient(self, client, channel):
"""Joins an internal spawned client <client> to a channel."""
# 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 = utils.toLower(irc, channel)
server = utils.isInternalClient(irc, client)
channel = utils.toLower(self.irc, channel)
server = utils.isInternalClient(self.irc, client)
if not server:
log.error('(%s) Error trying to join client %r to %r (no such pseudoclient exists)', irc.name, client, channel)
log.error('(%s) Error trying to join client %r to %r (no such pseudoclient exists)', self.irc.name, client, channel)
raise LookupError('No such PyLink PseudoClient exists.')
# Strip out list-modes, they shouldn't be ever sent in FJOIN.
modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
_send(irc, server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
ts=irc.channels[channel].ts, uid=client, channel=channel,
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']]
self._send(server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
ts=self.irc.channels[channel].ts, uid=client, channel=channel,
modes=utils.joinModes(modes)))
irc.channels[channel].users.add(client)
irc.users[client].channels.add(channel)
self.irc.channels[channel].users.add(client)
self.irc.users[client].channels.add(channel)
def sjoinServer(irc, server, channel, users, ts=None):
def sjoinServer(self, server, channel, users, ts=None):
"""Sends an SJOIN for a group of users to a channel.
The sender should always be a Server ID (SID). TS is optional, and defaults
@ -85,30 +83,30 @@ def sjoinServer(irc, server, channel, users, ts=None):
<users> is a list of (prefix mode, UID) pairs:
Example uses:
sjoinServer(irc, '100', '#test', [('', '100AAABBC'), ('qo', 100AAABBB'), ('h', '100AAADDD')])
sjoinServer(irc, irc.sid, '#test', [('o', irc.pseudoclient.uid)])
sjoinServer(self.irc, '100', '#test', [('', '100AAABBC'), ('qo', 100AAABBB'), ('h', '100AAADDD')])
sjoinServer(self.irc, self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])
"""
channel = utils.toLower(irc, channel)
server = server or irc.sid
channel = utils.toLower(self.irc, channel)
server = server or self.irc.sid
assert users, "sjoinServer: No users sent?"
log.debug('(%s) sjoinServer: got %r for users', irc.name, users)
log.debug('(%s) sjoinServer: got %r for users', self.irc.name, users)
if not server:
raise LookupError('No such PyLink PseudoClient exists.')
orig_ts = irc.channels[channel].ts
orig_ts = self.irc.channels[channel].ts
ts = ts or orig_ts
if ts < orig_ts:
# If the TS we're sending is lower than the one that existing, clear the
# mode lists from our channel state and reset the timestamp.
log.debug('(%s) sjoinServer: resetting TS of %r from %s to %s (clearing modes)',
irc.name, channel, orig_ts, ts)
irc.channels[channel].ts = ts
irc.channels[channel].modes.clear()
for p in irc.channels[channel].prefixmodes.values():
self.irc.name, channel, orig_ts, ts)
self.irc.channels[channel].ts = ts
self.irc.channels[channel].modes.clear()
for p in self.irc.channels[channel].prefixmodes.values():
p.clear()
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, irc.name, ts,
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, self.irc.name, ts,
time.strftime("%c", time.localtime(ts)))
# Strip out list-modes, they shouldn't ever be sent in FJOIN (protocol rules).
modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']]
uids = []
changedmodes = []
namelist = []
@ -121,19 +119,19 @@ def sjoinServer(irc, server, channel, users, ts=None):
for m in prefixes:
changedmodes.append(('+%s' % m, user))
try:
irc.users[user].channels.add(channel)
self.irc.users[user].channels.add(channel)
except KeyError: # Not initialized yet?
log.debug("(%s) sjoinServer: KeyError trying to add %r to %r's channel list?", irc.name, channel, user)
log.debug("(%s) sjoinServer: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
if ts <= orig_ts:
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
utils.applyModes(irc, channel, changedmodes)
utils.applyModes(self.irc, channel, changedmodes)
namelist = ' '.join(namelist)
_send(irc, server, "FJOIN {channel} {ts} {modes} :{users}".format(
self._send(server, "FJOIN {channel} {ts} {modes} :{users}".format(
ts=ts, users=namelist, channel=channel,
modes=utils.joinModes(modes)))
irc.channels[channel].users.update(uids)
self.irc.channels[channel].users.update(uids)
def _operUp(irc, target, opertype=None):
def _operUp(self, target, opertype=None):
"""Opers a client up (internal function specific to InspIRCd).
This should be called whenever user mode +o is set on anyone, because
@ -141,184 +139,184 @@ def _operUp(irc, target, opertype=None):
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 irc.users),
set the opertype attribute of an IrcUser object (in self.irc.users),
and the change will be reflected here."""
userobj = irc.users[target]
userobj = self.irc.users[target]
try:
otype = opertype or userobj.opertype
except AttributeError:
log.debug('(%s) opertype field for %s (%s) isn\'t filled yet!',
irc.name, target, userobj.nick)
self.irc.name, target, userobj.nick)
# whatever, this is non-standard anyways.
otype = 'IRC_Operator'
log.debug('(%s) Sending OPERTYPE from %s to oper them up.',
irc.name, target)
self.irc.name, target)
userobj.opertype = otype
_send(irc, target, 'OPERTYPE %s' % otype)
self._send(target, 'OPERTYPE %s' % otype)
def _sendModes(irc, numeric, target, modes, ts=None):
def _sendModes(self, numeric, target, modes, ts=None):
"""Internal function to send modes from a PyLink client/server."""
# -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA
# -> :9PYAAAAAA MODE 9PYAAAAAA -i+w
log.debug('(%s) inspircd._sendModes: received %r for mode list', irc.name, modes)
log.debug('(%s) inspself.ircd._sendModes: received %r for mode list', self.irc.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
# https://github.com/inspself.ircd/inspself.ircd/blob/master/src/modules/m_spanningtree/opertype.cpp#L26-L28
# Servers need a special command to set umode +o on people.
# Why isn't this documented anywhere, InspIRCd?
_operUp(irc, target)
utils.applyModes(irc, target, modes)
self._operUp(target)
utils.applyModes(self.irc, target, modes)
joinedmodes = utils.joinModes(modes)
if utils.isChannel(target):
ts = ts or irc.channels[utils.toLower(irc, target)].ts
_send(irc, numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
self._send(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))
else:
_send(irc, numeric, 'MODE %s %s' % (target, joinedmodes))
self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
def modeClient(irc, numeric, target, modes, ts=None):
def modeClient(self, numeric, target, modes, ts=None):
"""
Sends modes from a PyLink client. <modes> should be
a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output.
"""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_sendModes(irc, numeric, target, modes, ts=ts)
self._sendModes(numeric, target, modes, ts=ts)
def modeServer(irc, numeric, target, modes, ts=None):
def modeServer(self, numeric, target, modes, ts=None):
"""
Sends modes from a PyLink server. <list of modes> should be
a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output.
"""
if not utils.isInternalServer(irc, numeric):
if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
_sendModes(irc, numeric, target, modes, ts=ts)
self._sendModes(numeric, target, modes, ts=ts)
def killServer(irc, numeric, target, reason):
"""<irc object> <server SID> <target> <reason>
def killServer(self, numeric, target, reason):
"""<self.irc object> <server SID> <target> <reason>
Sends a kill to <target> from a PyLink PseudoServer.
"""
if not utils.isInternalServer(irc, numeric):
if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
_send(irc, numeric, 'KILL %s :%s' % (target, reason))
self._send(numeric, 'KILL %s :%s' % (target, reason))
# We don't need to call removeClient here, since the remote server
# will send a QUIT from the target if the command succeeds.
def killClient(irc, numeric, target, reason):
"""<irc object> <client numeric> <target> <reason>
def killClient(self, numeric, target, reason):
"""<self.irc object> <client numeric> <target> <reason>
Sends a kill to <target> from a PyLink PseudoClient.
"""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'KILL %s :%s' % (target, reason))
self._send(numeric, 'KILL %s :%s' % (target, reason))
# We don't need to call removeClient here, since the remote server
# will send a QUIT from the target if the command succeeds.
def topicServer(irc, numeric, target, text):
def topicServer(self, numeric, target, text):
"""Sends a burst topic from a PyLink server. This is usally used on burst."""
if not utils.isInternalServer(irc, numeric):
if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
ts = int(time.time())
servername = irc.servers[numeric].name
_send(irc, numeric, 'FTOPIC %s %s %s :%s' % (target, ts, servername, text))
irc.channels[target].topic = text
irc.channels[target].topicset = True
servername = self.irc.servers[numeric].name
self._send(numeric, 'FTOPIC %s %s %s :%s' % (target, ts, servername, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
def inviteClient(irc, numeric, target, channel):
def inviteClient(self, numeric, target, channel):
"""Sends an INVITE from a PyLink client.."""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'INVITE %s %s' % (target, channel))
self._send(numeric, 'INVITE %s %s' % (target, channel))
def knockClient(irc, numeric, target, text):
def knockClient(self, numeric, target, text):
"""Sends a KNOCK from a PyLink client."""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'ENCAP * KNOCK %s :%s' % (target, text))
self._send(numeric, 'ENCAP * KNOCK %s :%s' % (target, text))
def updateClient(irc, numeric, field, text):
def updateClient(self, numeric, field, text):
"""Updates the ident, host, or realname of a PyLink client."""
field = field.upper()
if field == 'IDENT':
irc.users[numeric].ident = text
_send(irc, numeric, 'FIDENT %s' % text)
self.irc.users[numeric].ident = text
self._send(numeric, 'FIDENT %s' % text)
elif field == 'HOST':
irc.users[numeric].host = text
_send(irc, numeric, 'FHOST %s' % text)
self.irc.users[numeric].host = text
self._send(numeric, 'FHOST %s' % text)
elif field in ('REALNAME', 'GECOS'):
irc.users[numeric].realname = text
_send(irc, numeric, 'FNAME :%s' % text)
self.irc.users[numeric].realname = text
self._send(numeric, 'FNAME :%s' % text)
else:
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
def pingServer(irc, source=None, target=None):
def pingServer(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 irc.sid
target = target or irc.uplink
source = source or self.irc.sid
target = target or self.irc.uplink
if not (target is None or source is None):
_send(irc, source, 'PING %s %s' % (source, target))
self._send(source, 'PING %s %s' % (source, target))
def numericServer(irc, source, numeric, text):
def numericServer(self, source, numeric, text):
raise NotImplementedError("Numeric sending is not yet implemented by this "
"protocol module. WHOIS requests are handled "
"locally by InspIRCd servers, so there is no "
"need for PyLink to send numerics directly yet.")
def awayClient(irc, source, text):
def awayClient(self, source, text):
"""Sends an AWAY message from a PyLink client. <text> can be an empty string
to unset AWAY status."""
if text:
_send(irc, source, 'AWAY %s :%s' % (int(time.time()), text))
self._send(source, 'AWAY %s :%s' % (int(time.time()), text))
else:
_send(irc, source, 'AWAY')
self._send(source, 'AWAY')
def spawnServer(irc, name, sid=None, uplink=None, desc=None):
def spawnServer(self, name, sid=None, uplink=None, desc=None):
"""Spawns a server off a PyLink server."""
# -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver
uplink = uplink or irc.sid
uplink = uplink or self.irc.sid
name = name.lower()
# "desc" defaults to the configured server description.
desc = desc or irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']
desc = desc or self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']
if sid is None: # No sid given; generate one!
irc.sidgen = utils.TS6SIDGenerator(irc.serverdata["sidrange"])
sid = irc.sidgen.next_sid()
self.irc.sidgen = utils.TS6SIDGenerator(self.irc.serverdata["sidrange"])
sid = self.irc.sidgen.next_sid()
assert len(sid) == 3, "Incorrect SID length"
if sid in irc.servers:
if sid in self.irc.servers:
raise ValueError('A server with SID %r already exists!' % sid)
for server in irc.servers.values():
for server in self.irc.servers.values():
if name == server.name:
raise ValueError('A server named %r already exists!' % name)
if not utils.isInternalServer(irc, uplink):
if not utils.isInternalServer(self.irc, uplink):
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % uplink)
if not utils.isServerName(name):
raise ValueError('Invalid server name %r' % name)
_send(irc, uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc))
irc.servers[sid] = IrcServer(uplink, name, internal=True)
_send(irc, sid, 'ENDBURST')
self._send(uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc))
self.irc.servers[sid] = IrcServer(uplink, name, internal=True)
self._send(sid, 'ENDBURST')
return sid
def squitServer(irc, source, target, text='No reason given'):
def squitServer(self, source, target, text='No reason given'):
"""SQUITs a PyLink server."""
# -> :9PY SQUIT 9PZ :blah, blah
_send(irc, source, 'SQUIT %s :%s' % (target, text))
handle_squit(irc, source, 'SQUIT', [target, text])
self._send(source, 'SQUIT %s :%s' % (target, text))
self.handle_squit(source, 'SQUIT', [target, text])
def connect(irc):
def connect(self):
"""Initializes a connection to a server."""
ts = irc.start_ts
ts = self.irc.start_ts
f = irc.send
f = self.irc.send
f('CAPAB START 1202')
f('CAPAB CAPABILITIES :PROTOCOL=1202')
f('CAPAB END')
f('SERVER {host} {Pass} 0 {sid} :{sdesc}'.format(host=irc.serverdata["hostname"],
Pass=irc.serverdata["sendpass"], sid=irc.sid,
sdesc=irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']))
f(':%s BURST %s' % (irc.sid, ts))
f(':%s ENDBURST' % (irc.sid))
f('SERVER {host} {Pass} 0 {sid} :{sdesc}'.format(host=self.irc.serverdata["hostname"],
Pass=self.irc.serverdata["sendpass"], sid=self.irc.sid,
sdesc=self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']))
f(':%s BURST %s' % (self.irc.sid, ts))
f(':%s ENDBURST' % (self.irc.sid))
def handle_events(irc, data):
def handle_events(self, data):
"""Event handler for the InspIRCd protocol. This passes most commands to
the various handle_ABCD() functions elsewhere in this module, but also
handles commands sent in the initial server linking phase."""
@ -333,11 +331,11 @@ def handle_events(irc, data):
# <- SERVER whatever.net abcdefgh 0 10X :something
servername = args[1].lower()
numeric = args[4]
if args[2] != irc.serverdata['recvpass']:
if args[2] != self.irc.serverdata['recvpass']:
# Check if recvpass is correct
raise ProtocolError('Error: recvpass from uplink server %s does not match configuration!' % servername)
irc.servers[numeric] = IrcServer(None, servername)
irc.uplink = numeric
self.irc.servers[numeric] = IrcServer(None, servername)
self.irc.uplink = numeric
return
elif args[0] == 'CAPAB':
# Capability negotiation with our uplink
@ -355,36 +353,36 @@ def handle_events(irc, data):
# (the former is config default, but I personally prefer the latter.)
name = 'owner'
# We don't really care about mode prefixes; just the mode char
irc.cmodes[name.lstrip(':')] = char[-1]
self.irc.cmodes[name.lstrip(':')] = char[-1]
elif args[1] == 'USERMODES':
# <- CAPAB USERMODES :bot=B callerid=g cloak=x deaf_commonchan=c helpop=h hidechans=I hideoper=H invisible=i oper=o regdeaf=R servprotect=k showwhois=W snomask=s u_registered=r u_stripcolor=S wallops=w
# Ditto above.
for modepair in args[2:]:
name, char = modepair.split('=')
irc.umodes[name.lstrip(':')] = char
self.irc.umodes[name.lstrip(':')] = char
elif args[1] == 'CAPABILITIES':
# <- CAPAB CAPABILITIES :NICKMAX=21 CHANMAX=64 MAXMODES=20 IDENTMAX=11 MAXQUIT=255 MAXTOPIC=307 MAXKICK=255 MAXGECOS=128 MAXAWAY=200 IP6SUPPORT=1 PROTOCOL=1202 PREFIX=(Yqaohv)!~&@%+ CHANMODES=IXbegw,k,FHJLfjl,ACKMNOPQRSTUcimnprstz USERMODES=,,s,BHIRSWcghikorwx GLOBOPS=1 SVSPART=1
caps = dict([x.lstrip(':').split('=') for x in args[2:]])
protocol_version = int(caps['PROTOCOL'])
if protocol_version < 1202:
raise ProtocolError("Remote protocol version is too old! At least 1202 (InspIRCd 2.0.x) is needed. (got %s)" % protocol_version)
irc.maxnicklen = int(caps['NICKMAX'])
irc.maxchanlen = int(caps['CHANMAX'])
self.irc.maxnicklen = int(caps['NICKMAX'])
self.irc.maxchanlen = int(caps['CHANMAX'])
# Modes are divided into A, B, C, and D classes
# See http://www.irc.org/tech_docs/005.html
# See http://www.self.irc.org/tech_docs/005.html
# FIXME: Find a better way to assign/store this.
irc.cmodes['*A'], irc.cmodes['*B'], irc.cmodes['*C'], irc.cmodes['*D'] \
self.irc.cmodes['*A'], self.irc.cmodes['*B'], self.irc.cmodes['*C'], self.irc.cmodes['*D'] \
= caps['CHANMODES'].split(',')
irc.umodes['*A'], irc.umodes['*B'], irc.umodes['*C'], irc.umodes['*D'] \
self.irc.umodes['*A'], self.irc.umodes['*B'], self.irc.umodes['*C'], self.irc.umodes['*D'] \
= caps['USERMODES'].split(',')
prefixsearch = re.search(r'\(([A-Za-z]+)\)(.*)', caps['PREFIX'])
irc.prefixmodes = dict(zip(prefixsearch.group(1), prefixsearch.group(2)))
log.debug('(%s) irc.prefixmodes set to %r', irc.name, irc.prefixmodes)
self.irc.prefixmodes = dict(zip(prefixsearch.group(1), prefixsearch.group(2)))
log.debug('(%s) self.irc.prefixmodes set to %r', self.irc.name, self.irc.prefixmodes)
# Sanity check: set this AFTER we fetch the capabilities for the network!
irc.connected.set()
self.irc.connected.set()
try:
args = parseTS6Args(args)
args = self.parseTS6Args(args)
numeric = args[0]
command = args[1]
args = args[2:]
@ -393,70 +391,70 @@ def handle_events(irc, data):
# We will do wildcard event handling here. Unhandled events are just ignored.
try:
func = globals()['handle_'+command.lower()]
except KeyError: # unhandled event
func = getattr(self, 'handle_'+command.lower())
except AttributeError: # unhandled event
pass
else:
parsed_args = func(irc, numeric, command, args)
parsed_args = func(numeric, command, args)
if parsed_args is not None:
return [numeric, command, parsed_args]
def handle_ping(irc, source, command, args):
def handle_ping(self, source, command, args):
"""Handles incoming PING commands, so we don't time out."""
# <- :70M PING 70M 0AL
# -> :0AL PONG 0AL 70M
if utils.isInternalServer(irc, args[1]):
_send(irc, args[1], 'PONG %s %s' % (args[1], source))
if utils.isInternalServer(self.irc, args[1]):
self._send(args[1], 'PONG %s %s' % (args[1], source))
def handle_pong(irc, source, command, args):
def handle_pong(self, source, command, args):
"""Handles incoming PONG commands. This is used to keep track of whether
the uplink is alive by the Irc() internals - a server that fails to reply
to our PINGs eventually times out and is disconnected."""
if source == irc.uplink and args[1] == irc.sid:
irc.lastping = time.time()
if source == self.irc.uplink and args[1] == self.irc.sid:
self.irc.lastping = time.time()
def handle_fjoin(irc, servernumeric, command, args):
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 = utils.toLower(irc, args[0])
channel = utils.toLower(self.irc, args[0])
# InspIRCd sends each channel's users in the form of 'modeprefix(es),UID'
userlist = args[-1].split()
our_ts = irc.channels[channel].ts
our_ts = self.irc.channels[channel].ts
their_ts = int(args[1])
if their_ts < our_ts:
# Channel timestamp was reset on burst
log.debug('(%s) Setting channel TS of %s to %s from %s',
irc.name, channel, their_ts, our_ts)
irc.channels[channel].ts = their_ts
irc.channels[channel].modes.clear()
for p in irc.channels[channel].prefixmodes.values():
self.irc.name, channel, their_ts, our_ts)
self.irc.channels[channel].ts = their_ts
self.irc.channels[channel].modes.clear()
for p in self.irc.channels[channel].prefixmodes.values():
p.clear()
modestring = args[2:-1] or args[2]
parsedmodes = utils.parseModes(irc, channel, modestring)
utils.applyModes(irc, channel, parsedmodes)
parsedmodes = utils.parseModes(self.irc, channel, modestring)
utils.applyModes(self.irc, channel, parsedmodes)
namelist = []
for user in userlist:
modeprefix, user = user.split(',', 1)
namelist.append(user)
irc.users[user].channels.add(channel)
self.irc.users[user].channels.add(channel)
if their_ts <= our_ts:
utils.applyModes(irc, channel, [('+%s' % mode, user) for mode in modeprefix])
irc.channels[channel].users.add(user)
utils.applyModes(self.irc, channel, [('+%s' % mode, user) for mode in modeprefix])
self.irc.channels[channel].users.add(user)
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts}
def handle_uid(irc, numeric, command, args):
def handle_uid(self, numeric, command, args):
"""Handles incoming UID commands (user introduction)."""
# :70M UID 70MAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname
uid, ts, nick, realhost, host, ident, ip = args[0:7]
realname = args[-1]
irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
parsedmodes = utils.parseModes(irc, uid, [args[8], args[9]])
self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
parsedmodes = utils.parseModes(self.irc, uid, [args[8], args[9]])
log.debug('Applying modes %s for %s', parsedmodes, uid)
utils.applyModes(irc, uid, parsedmodes)
irc.servers[numeric].users.add(uid)
utils.applyModes(self.irc, uid, parsedmodes)
self.irc.servers[numeric].users.add(uid)
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
def handle_server(irc, numeric, command, args):
def handle_server(self, numeric, command, args):
"""Handles incoming SERVER commands (introduction of servers)."""
# SERVER is sent by our uplink or any other server to introduce others.
# <- :00A SERVER test.server * 1 00C :testing raw message syntax
@ -464,54 +462,54 @@ def handle_server(irc, numeric, command, args):
servername = args[0].lower()
sid = args[3]
sdesc = args[-1]
irc.servers[sid] = IrcServer(numeric, servername)
self.irc.servers[sid] = IrcServer(numeric, servername)
return {'name': servername, 'sid': args[3], 'text': sdesc}
def handle_fmode(irc, numeric, command, args):
def handle_fmode(self, numeric, command, args):
"""Handles the FMODE command, used for channel mode changes."""
# <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD
channel = utils.toLower(irc, args[0])
channel = utils.toLower(self.irc, args[0])
modes = args[2:]
changedmodes = utils.parseModes(irc, channel, modes)
utils.applyModes(irc, channel, changedmodes)
changedmodes = utils.parseModes(self.irc, channel, modes)
utils.applyModes(self.irc, channel, changedmodes)
ts = int(args[1])
return {'target': channel, 'modes': changedmodes, 'ts': ts}
def handle_idle(irc, numeric, command, args):
def handle_idle(self, numeric, command, args):
"""Handles the IDLE command, sent between servers in remote WHOIS queries."""
# <- :70MAAAAAA IDLE 1MLAAAAIG
# -> :1MLAAAAIG IDLE 70MAAAAAA 1433036797 319
sourceuser = numeric
targetuser = args[0]
_send(irc, targetuser, 'IDLE %s %s 0' % (sourceuser, irc.users[targetuser].ts))
self._send(targetuser, 'IDLE %s %s 0' % (sourceuser, self.irc.users[targetuser].ts))
def handle_ftopic(irc, numeric, command, args):
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 = utils.toLower(irc, args[0])
channel = utils.toLower(self.irc, args[0])
ts = args[1]
setter = args[2]
topic = args[-1]
irc.channels[channel].topic = topic
irc.channels[channel].topicset = True
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'topic': topic}
def handle_invite(irc, numeric, command, args):
def handle_invite(self, numeric, command, args):
"""Handles incoming INVITEs."""
# <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0
target = args[0]
channel = utils.toLower(irc, args[1])
channel = utils.toLower(self.irc, 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_encap(irc, numeric, command, args):
def handle_encap(self, numeric, command, args):
"""Handles incoming encapsulated commands (ENCAP). Hook arguments
returned by this should have a parse_as field, that sets the correct
hook name for the message.
For InspIRCd, the only ENCAP command we handle right now is KNOCK."""
# <- :70MAAAAAA ENCAP * KNOCK #blah :agsdfas
# From charybdis TS6 docs: https://github.com/grawity/irc-docs/blob/03ba884a54f1cef2193cd62b6a86803d89c1ac41/server/ts6.txt
# From charybdis TS6 docs: https://github.com/grawity/self.irc-docs/blob/03ba884a54f1cef2193cd62b6a86803d89c1ac41/server/ts6.txt
# ENCAP
# source: any
@ -523,12 +521,12 @@ def handle_encap(irc, numeric, command, args):
targetmask = args[0]
real_command = args[1]
if targetmask == '*' and real_command == 'KNOCK':
channel = utils.toLower(irc, args[2])
channel = utils.toLower(self.irc, args[2])
text = args[3]
return {'parse_as': real_command, 'channel': channel,
'text': text}
def handle_opertype(irc, numeric, command, args):
def handle_opertype(self, numeric, command, args):
"""Handles incoming OPERTYPE, which is used to denote an oper up.
This calls the internal hook PYLINK_CLIENT_OPERED, sets the internal
@ -537,42 +535,44 @@ def handle_opertype(irc, numeric, command, args):
# command sent for it.
# <- :70MAAAAAB OPERTYPE Network_Owner
omode = [('+o', None)]
irc.users[numeric].opertype = opertype = args[0]
utils.applyModes(irc, numeric, omode)
self.irc.users[numeric].opertype = opertype = args[0]
utils.applyModes(self.irc, numeric, omode)
# OPERTYPE is essentially umode +o and metadata in one command;
# we'll call that too.
irc.callHooks([numeric, 'PYLINK_CLIENT_OPERED', {'text': opertype}])
self.irc.callHooks([numeric, 'PYLINK_CLIENT_OPERED', {'text': opertype}])
return {'target': numeric, 'modes': omode}
def handle_fident(irc, numeric, command, args):
def handle_fident(self, numeric, command, args):
"""Handles FIDENT, used for denoting ident changes."""
# <- :70MAAAAAB FIDENT test
irc.users[numeric].ident = newident = args[0]
self.irc.users[numeric].ident = newident = args[0]
return {'target': numeric, 'newident': newident}
def handle_fhost(irc, numeric, command, args):
def handle_fhost(self, numeric, command, args):
"""Handles FHOST, used for denoting hostname changes."""
# <- :70MAAAAAB FIDENT some.host
irc.users[numeric].host = newhost = args[0]
self.irc.users[numeric].host = newhost = args[0]
return {'target': numeric, 'newhost': newhost}
def handle_fname(irc, numeric, command, args):
def handle_fname(self, numeric, command, args):
"""Handles FNAME, used for denoting real name/gecos changes."""
# <- :70MAAAAAB FNAME :afdsafasf
irc.users[numeric].realname = newgecos = args[0]
self.irc.users[numeric].realname = newgecos = args[0]
return {'target': numeric, 'newgecos': newgecos}
def handle_endburst(irc, numeric, command, args):
def handle_endburst(self, numeric, command, args):
"""ENDBURST handler; sends a hook with empty contents."""
return {}
def handle_away(irc, numeric, command, args):
def handle_away(self, numeric, command, args):
"""Handles incoming AWAY messages."""
# <- :1MLAAAAIG AWAY 1439371390 :Auto-away
try:
ts = args[0]
irc.users[numeric].away = text = args[1]
self.irc.users[numeric].away = text = args[1]
return {'text': text, 'ts': ts}
except IndexError: # User is unsetting away status
irc.users[numeric].away = ''
self.irc.users[numeric].away = ''
return {'text': ''}
Class = InspIRCdProtocol

View File

@ -9,56 +9,62 @@ import utils
from log import log
from classes import *
''''
# Some functions are shared with the InspIRCd module (ts6_common)
from ts6_common import nickClient, kickServer, kickClient, _sendKick, quitClient, \
removeClient, partClient, messageClient, noticeClient, topicClient, parseTS6Args
from ts6_common import handle_privmsg, handle_kill, handle_kick, handle_error, \
handle_quit, handle_nick, handle_save, handle_squit, handle_mode, handle_topic, \
handle_notice, _send, handle_part
'''
from ts6_common import TS6BaseProtocol
casemapping = 'rfc1459'
hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE'}
class TS6Protocol(TS6BaseProtocol):
def __init__(self, irc):
super(TS6Protocol, self).__init__(irc)
self.casemapping = 'rfc1459'
self.hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE'}
def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(),
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):
server = server or irc.sid
if not utils.isInternalServer(irc, server):
server = server or self.irc.sid
if not utils.isInternalServer(self.irc, server):
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
# We need a separate UID generator instance for every PseudoServer
# we spawn. Otherwise, things won't wrap around properly.
if server not in irc.uidgen:
irc.uidgen[server] = utils.TS6UIDGenerator(server)
uid = irc.uidgen[server].next_uid()
if server not in self.irc.uidgen:
self.irc.uidgen[server] = utils.TS6UIDGenerator(server)
uid = self.irc.uidgen[server].next_uid()
# EUID:
# parameters: nickname, hopcount, nickTS, umodes, username,
# visible hostname, IP address, UID, real hostname, account name, gecos
ts = ts or int(time.time())
realname = realname or irc.botdata['realname']
realname = realname or self.irc.botdata['realname']
realhost = realhost or host
raw_modes = utils.joinModes(modes)
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
u = self.irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
realhost=realhost, ip=ip)
utils.applyModes(irc, uid, modes)
irc.servers[server].users.add(uid)
_send(irc, server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
utils.applyModes(self.irc, uid, modes)
self.irc.servers[server].users.add(uid)
self._send(server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
"{realhost} * :{realname}".format(ts=ts, host=host,
nick=nick, ident=ident, uid=uid,
modes=raw_modes, ip=ip, realname=realname,
realhost=realhost))
return u
def joinClient(irc, client, channel):
channel = utils.toLower(irc, channel)
def joinClient(self, client, channel):
channel = utils.toLower(self.irc, channel)
# JOIN:
# parameters: channelTS, channel, '+' (a plus sign)
if not utils.isInternalClient(irc, client):
log.error('(%s) Error trying to join client %r to %r (no such pseudoclient exists)', irc.name, client, channel)
if not utils.isInternalClient(self.irc, client):
log.error('(%s) Error trying to join client %r to %r (no such pseudoclient exists)', self.irc.name, client, channel)
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, client, "JOIN {ts} {channel} +".format(ts=irc.channels[channel].ts, channel=channel))
irc.channels[channel].users.add(client)
irc.users[client].channels.add(channel)
self._send(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)
def sjoinServer(irc, server, channel, users, ts=None):
def sjoinServer(self, server, channel, users, ts=None):
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L821
# parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
@ -68,26 +74,26 @@ def sjoinServer(irc, server, channel, users, ts=None):
# 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 = utils.toLower(irc, channel)
server = server or irc.sid
channel = utils.toLower(self.irc, channel)
server = server or self.irc.sid
assert users, "sjoinServer: No users sent?"
log.debug('(%s) sjoinServer: got %r for users', irc.name, users)
log.debug('(%s) sjoinServer: got %r for users', self.irc.name, users)
if not server:
raise LookupError('No such PyLink PseudoClient exists.')
orig_ts = irc.channels[channel].ts
orig_ts = self.irc.channels[channel].ts
ts = ts or orig_ts
if ts < orig_ts:
# If the TS we're sending is lower than the one that existing, clear the
# mode lists from our channel state and reset the timestamp.
log.debug('(%s) sjoinServer: resetting TS of %r from %s to %s (clearing modes)',
irc.name, channel, orig_ts, ts)
irc.channels[channel].ts = ts
irc.channels[channel].modes.clear()
for p in irc.channels[channel].prefixmodes.values():
self.irc.name, channel, orig_ts, ts)
self.irc.channels[channel].ts = ts
self.irc.channels[channel].modes.clear()
for p in self.irc.channels[channel].prefixmodes.values():
p.clear()
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, irc.name, ts,
log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel, self.irc.name, ts,
time.strftime("%c", time.localtime(ts)))
modes = [m for m in irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
modes = [m for m in self.irc.channels[channel].modes if m[0] not in self.irc.cmodes['*A']]
changedmodes = []
while users[:10]:
uids = []
@ -98,69 +104,69 @@ def sjoinServer(irc, server, channel, users, ts=None):
prefixes, user = userpair
prefixchars = ''
for prefix in prefixes:
pr = irc.prefixmodes.get(prefix)
pr = self.irc.prefixmodes.get(prefix)
if pr:
prefixchars += pr
changedmodes.append(('+%s' % prefix, user))
namelist.append(prefixchars+user)
uids.append(user)
try:
irc.users[user].channels.add(channel)
self.irc.users[user].channels.add(channel)
except KeyError: # Not initialized yet?
log.debug("(%s) sjoinServer: KeyError trying to add %r to %r's channel list?", irc.name, channel, user)
log.debug("(%s) sjoinServer: KeyError trying to add %r to %r's channel list?", self.irc.name, channel, user)
users = users[10:]
namelist = ' '.join(namelist)
_send(irc, server, "SJOIN {ts} {channel} {modes} :{users}".format(
self._send(server, "SJOIN {ts} {channel} {modes} :{users}".format(
ts=ts, users=namelist, channel=channel,
modes=utils.joinModes(modes)))
irc.channels[channel].users.update(uids)
self.irc.channels[channel].users.update(uids)
if ts <= orig_ts:
# Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
utils.applyModes(irc, channel, changedmodes)
utils.applyModes(self.irc, channel, changedmodes)
def _sendModes(irc, numeric, target, modes, ts=None):
utils.applyModes(irc, target, modes)
def _sendModes(self, numeric, target, modes, ts=None):
utils.applyModes(self.irc, target, modes)
if utils.isChannel(target):
ts = ts or irc.channels[utils.toLower(irc, target)].ts
ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
# TMODE:
# parameters: channelTS, channel, cmode changes, opt. cmode parameters...
# On output, at most ten cmode parameters should be sent; if there are more,
# multiple TMODE messages should be sent.
while modes[:9]:
joinedmodes = utils.joinModes(modes = [m for m in modes[:9] if m[0] not in irc.cmodes['*A']])
joinedmodes = utils.joinModes(modes = [m for m in modes[:9] if m[0] not in self.irc.cmodes['*A']])
modes = modes[9:]
_send(irc, numeric, 'TMODE %s %s %s' % (ts, target, joinedmodes))
self._send(numeric, 'TMODE %s %s %s' % (ts, target, joinedmodes))
else:
joinedmodes = utils.joinModes(modes)
_send(irc, numeric, 'MODE %s %s' % (target, joinedmodes))
self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
def modeClient(irc, numeric, target, modes, ts=None):
def modeClient(self, numeric, target, modes, ts=None):
"""<irc object> <client numeric> <list of modes>
Sends modes from a PyLink PseudoClient. <list of modes> should be
a list of (mode, arg) tuples, in the format of utils.parseModes() output.
"""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_sendModes(irc, numeric, target, modes, ts=ts)
self._sendModes(numeric, target, modes, ts=ts)
def modeServer(irc, numeric, target, modes, ts=None):
def modeServer(self, numeric, target, modes, ts=None):
"""<irc object> <server SID> <list of modes>
Sends modes from a PyLink PseudoServer. <list of modes> should be
a list of (mode, arg) tuples, in the format of utils.parseModes() output.
"""
if not utils.isInternalServer(irc, numeric):
if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
_sendModes(irc, numeric, target, modes, ts=ts)
self._sendModes(numeric, target, modes, ts=ts)
def killServer(irc, numeric, target, reason):
def killServer(self, numeric, target, reason):
"""<irc object> <server SID> <target> <reason>
Sends a kill to <target> from a PyLink PseudoServer.
"""
if not utils.isInternalServer(irc, numeric):
if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
# KILL:
# parameters: target user, path
@ -169,93 +175,93 @@ def killServer(irc, numeric, target, reason):
# 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 irc.users, "Unknown target %r for killServer!" % target
_send(irc, numeric, 'KILL %s :Killed (%s)' % (target, reason))
removeClient(irc, target)
assert target in self.irc.users, "Unknown target %r for killServer!" % target
self._send(numeric, 'KILL %s :Killed (%s)' % (target, reason))
removeClient(self.irc, target)
def killClient(irc, numeric, target, reason):
def killClient(self, numeric, target, reason):
"""<irc object> <client numeric> <target> <reason>
Sends a kill to <target> from a PyLink PseudoClient.
"""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
assert target in irc.users, "Unknown target %r for killClient!" % target
_send(irc, numeric, 'KILL %s :Killed (%s)' % (target, reason))
removeClient(irc, target)
assert target in self.irc.users, "Unknown target %r for killClient!" % target
self._send(numeric, 'KILL %s :Killed (%s)' % (target, reason))
removeClient(self.irc, target)
def topicServer(irc, numeric, target, text):
if not utils.isInternalServer(irc, numeric):
def topicServer(self, numeric, target, text):
if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
# TB
# capab: TB
# source: server
# propagation: broadcast
# parameters: channel, topicTS, opt. topic setter, topic
ts = irc.channels[target].ts
servername = irc.servers[numeric].name
_send(irc, numeric, 'TB %s %s %s :%s' % (target, ts, servername, text))
irc.channels[target].topic = text
irc.channels[target].topicset = True
ts = self.irc.channels[target].ts
servername = self.irc.servers[numeric].name
self._send(numeric, 'TB %s %s %s :%s' % (target, ts, servername, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
def inviteClient(irc, numeric, target, channel):
def inviteClient(self, numeric, target, channel):
"""<irc object> <client numeric> <text>
Invites <target> to <channel> to <text> from PyLink client <client numeric>."""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'INVITE %s %s %s' % (target, channel, irc.channels[channel].ts))
self._send(numeric, 'INVITE %s %s %s' % (target, channel, self.irc.channels[channel].ts))
def knockClient(irc, numeric, target, text):
def knockClient(self, numeric, target, text):
"""<irc object> <client numeric> <text>
Knocks on <channel> with <text> from PyLink client <client numeric>."""
if 'KNOCK' not in irc.caps:
if 'KNOCK' not in self.irc.caps:
log.debug('(%s) knockClient: Dropping KNOCK to %r since the IRCd '
'doesn\'t support it.', irc.name, target)
'doesn\'t support it.', self.irc.name, target)
return
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
# No text value is supported here; drop it.
_send(irc, numeric, 'KNOCK %s' % target)
self._send(numeric, 'KNOCK %s' % target)
def updateClient(irc, numeric, field, text):
def updateClient(self, numeric, field, text):
"""<irc object> <client numeric> <field> <text>
Changes the <field> field of <target> PyLink PseudoClient <client numeric>."""
field = field.upper()
if field == 'HOST':
irc.users[numeric].host = text
_send(irc, irc.sid, 'CHGHOST %s :%s' % (numeric, text))
self.irc.users[numeric].host = text
self._send(self.irc.sid, 'CHGHOST %s :%s' % (numeric, text))
else:
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
def pingServer(irc, source=None, target=None):
source = source or irc.sid
def pingServer(self, source=None, target=None):
source = source or self.irc.sid
if source is None:
return
if target is not None:
_send(irc, source, 'PING %s %s' % (source, target))
self._send(source, 'PING %s %s' % (source, target))
else:
_send(irc, source, 'PING %s' % source)
self._send(source, 'PING %s' % source)
def numericServer(irc, source, numeric, target, text):
_send(irc, source, '%s %s %s' % (numeric, target, text))
def numericServer(self, source, numeric, target, text):
self._send(source, '%s %s %s' % (numeric, target, text))
def awayClient(irc, source, text):
def awayClient(self, source, text):
"""<irc object> <numeric> <text>
Sends an AWAY message with text <text> from PyLink client <numeric>.
<text> can be an empty string to unset AWAY status."""
if text:
_send(irc, source, 'AWAY :%s' % text)
self._send(source, 'AWAY :%s' % text)
else:
_send(irc, source, 'AWAY')
self._send(source, 'AWAY')
def connect(irc):
ts = irc.start_ts
def connect(self):
ts = self.irc.start_ts
f = irc.send
f = self.irc.send
# Valid keywords (from mostly InspIRCd's named modes):
# admin allowinvite autoop ban banexception blockcolor
# c_registered exemptchanops filter forward flood halfop history invex
@ -283,17 +289,17 @@ def connect(irc):
# Now, map all the ABCD type modes:
'*A': 'beIq', '*B': 'k', '*C': 'l', '*D': 'mnprst'}
if irc.serverdata.get('use_owner'):
if self.irc.serverdata.get('use_owner'):
chary_cmodes['owner'] = 'y'
irc.prefixmodes['y'] = '~'
if irc.serverdata.get('use_admin'):
self.irc.prefixmodes['y'] = '~'
if self.irc.serverdata.get('use_admin'):
chary_cmodes['admin'] = 'a'
irc.prefixmodes['a'] = '!'
if irc.serverdata.get('use_halfop'):
self.irc.prefixmodes['a'] = '!'
if self.irc.serverdata.get('use_halfop'):
chary_cmodes['halfop'] = 'h'
irc.prefixmodes['h'] = '%'
self.irc.prefixmodes['h'] = '%'
irc.cmodes.update(chary_cmodes)
self.irc.cmodes.update(chary_cmodes)
# Same thing with umodes:
# bot callerid cloak deaf_commonchan helpop hidechans hideoper invisible oper regdeaf servprotect showwhois snomask u_registered u_stripcolor wallops
@ -304,24 +310,24 @@ def connect(irc):
'l',
# Now, map all the ABCD type modes:
'*A': '', '*B': '', '*C': '', '*D': 'DSaiowsQRgzl'}
irc.umodes.update(chary_umodes)
self.irc.umodes.update(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 irc.serverdata.get('use_elemental_modes'):
if self.irc.serverdata.get('use_elemental_modes'):
elemental_cmodes = {'nonotice': 'T', 'hiddenbans': 'u', 'nokick': 'E',
'kicknorejoin': 'J', 'repeat': 'K', 'nonick': 'd'}
irc.cmodes.update(elemental_cmodes)
irc.cmodes['*D'] += ''.join(elemental_cmodes.values())
self.irc.cmodes.update(elemental_cmodes)
self.irc.cmodes['*D'] += ''.join(elemental_cmodes.values())
elemental_umodes = {'u_noctcp': 'C', 'deaf': 'D', 'bot': 'B', 'u_noinvite': 'V',
'hidechans': 'I'}
irc.umodes.update(elemental_umodes)
irc.umodes['*D'] += ''.join(elemental_umodes.values())
self.irc.umodes.update(elemental_umodes)
self.irc.umodes['*D'] += ''.join(elemental_umodes.values())
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L55
f('PASS %s TS 6 %s' % (irc.serverdata["sendpass"], irc.sid))
f('PASS %s TS 6 %s' % (self.irc.serverdata["sendpass"], self.irc.sid))
# We request the following capabilities (for charybdis):
@ -342,10 +348,10 @@ def connect(irc):
# and allows sending CHGHOST without ENCAP.
f('CAPAB :QS ENCAP EX CHW IE KNOCK SAVE SERVICES TB EUID')
f('SERVER %s 0 :%s' % (irc.serverdata["hostname"],
irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']))
f('SERVER %s 0 :%s' % (self.irc.serverdata["hostname"],
self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']))
def handle_ping(irc, source, command, args):
def handle_ping(self, source, command, args):
# PING:
# source: any
# parameters: origin, opt. destination server
@ -359,33 +365,33 @@ def handle_ping(irc, source, command, args):
try:
destination = args[1]
except IndexError:
destination = irc.sid
if utils.isInternalServer(irc, destination):
_send(irc, destination, 'PONG %s %s' % (destination, source))
destination = self.irc.sid
if utils.isInternalServer(self.irc, destination):
self._send(destination, 'PONG %s %s' % (destination, source))
def handle_pong(irc, source, command, args):
if source == irc.uplink:
irc.lastping = time.time()
def handle_pong(self, source, command, args):
if source == self.irc.uplink:
self.irc.lastping = time.time()
def handle_sjoin(irc, servernumeric, command, args):
def handle_sjoin(self, servernumeric, command, args):
# parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
channel = utils.toLower(irc, args[1])
channel = utils.toLower(self.irc, args[1])
userlist = args[-1].split()
our_ts = irc.channels[channel].ts
our_ts = self.irc.channels[channel].ts
their_ts = int(args[0])
if their_ts < our_ts:
# Channel timestamp was reset on burst
log.debug('(%s) Setting channel TS of %s to %s from %s',
irc.name, channel, their_ts, our_ts)
irc.channels[channel].ts = their_ts
irc.channels[channel].modes.clear()
for p in irc.channels[channel].prefixmodes.values():
self.irc.name, channel, their_ts, our_ts)
self.irc.channels[channel].ts = their_ts
self.irc.channels[channel].modes.clear()
for p in self.irc.channels[channel].prefixmodes.values():
p.clear()
modestring = args[2:-1] or args[2]
parsedmodes = utils.parseModes(irc, channel, modestring)
utils.applyModes(irc, channel, parsedmodes)
parsedmodes = utils.parseModes(self.irc, channel, modestring)
utils.applyModes(self.irc, channel, parsedmodes)
namelist = []
log.debug('(%s) handle_sjoin: got userlist %r for %r', irc.name, userlist, channel)
log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name, userlist, channel)
for userpair in userlist:
# charybdis sends this in the form "@+UID1, +UID2, UID3, @UID4"
r = re.search(r'([^\d]*)(.*)', userpair)
@ -393,48 +399,48 @@ def handle_sjoin(irc, servernumeric, command, args):
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', irc.name, modeprefix, user)
log.debug('(%s) handle_sjoin: got modeprefix %r for user %r', self.irc.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 irc.prefixmodes.items():
for char, prefix in self.irc.prefixmodes.items():
if m == prefix:
finalprefix += char
namelist.append(user)
irc.users[user].channels.add(channel)
self.irc.users[user].channels.add(channel)
if their_ts <= our_ts:
utils.applyModes(irc, channel, [('+%s' % mode, user) for mode in finalprefix])
irc.channels[channel].users.add(user)
utils.applyModes(self.irc, channel, [('+%s' % mode, user) for mode in finalprefix])
self.irc.channels[channel].users.add(user)
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts}
def handle_join(irc, numeric, command, args):
def handle_join(self, numeric, command, args):
# parameters: channelTS, channel, '+' (a plus sign)
ts = int(args[0])
if args[0] == '0':
# /join 0; part the user from all channels
oldchans = irc.users[numeric].channels.copy()
oldchans = self.irc.users[numeric].channels.copy()
log.debug('(%s) Got /join 0 from %r, channel list is %r',
irc.name, numeric, oldchans)
self.irc.name, numeric, oldchans)
for channel in oldchans:
irc.channels[channel].users.discard(numeric)
irc.users[numeric].channels.discard(channel)
self.irc.channels[channel].users.discard(numeric)
self.irc.users[numeric].channels.discard(channel)
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
else:
channel = utils.toLower(irc, args[1])
our_ts = irc.channels[channel].ts
channel = utils.toLower(self.irc, args[1])
our_ts = self.irc.channels[channel].ts
if ts < our_ts:
# Channel timestamp was reset on burst
log.debug('(%s) Setting channel TS of %s to %s from %s',
irc.name, channel, ts, our_ts)
irc.channels[channel].ts = ts
irc.channels[channel].users.add(numeric)
irc.users[numeric].channels.add(channel)
self.irc.name, channel, ts, our_ts)
self.irc.channels[channel].ts = ts
self.irc.channels[channel].users.add(numeric)
self.irc.users[numeric].channels.add(channel)
# 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':
irc.channels[channel].modes, 'ts': ts}
self.irc.channels[channel].modes, 'ts': ts}
def handle_euid(irc, numeric, command, args):
def handle_euid(self, numeric, command, args):
# <- :42X EUID GL 1 1437505322 +ailoswz ~gl 127.0.0.1 127.0.0.1 42XAAAAAB * * :realname
nick = args[0]
ts, modes, ident, host, ip, uid, realhost = args[2:9]
@ -442,24 +448,24 @@ def handle_euid(irc, numeric, command, args):
realhost = None
realname = args[-1]
log.debug('(%s) handle_euid got args: nick=%s ts=%s uid=%s ident=%s '
'host=%s realname=%s realhost=%s ip=%s', irc.name, nick, ts, uid,
'host=%s realname=%s realhost=%s ip=%s', self.irc.name, nick, ts, uid,
ident, host, realname, realhost, ip)
irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
parsedmodes = utils.parseModes(irc, uid, [modes])
self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
parsedmodes = utils.parseModes(self.irc, uid, [modes])
log.debug('Applying modes %s for %s', parsedmodes, uid)
utils.applyModes(irc, uid, parsedmodes)
irc.servers[numeric].users.add(uid)
utils.applyModes(self.irc, uid, parsedmodes)
self.irc.servers[numeric].users.add(uid)
if ('o', None) in parsedmodes:
otype = 'Server_Administrator' if ('a', None) in parsedmodes else 'IRC_Operator'
irc.callHooks([uid, 'PYLINK_CLIENT_OPERED', {'text': otype}])
self.irc.callHooks([uid, 'PYLINK_CLIENT_OPERED', {'text': otype}])
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
def handle_uid(irc, numeric, command, args):
def handle_uid(self, numeric, command, args):
raise ProtocolError("Servers should use EUID instead of UID to send users! "
"This IS a required capability after all...")
def handle_server(irc, numeric, command, args):
def handle_server(self, numeric, command, args):
# parameters: server name, hopcount, sid, server description
servername = args[0].lower()
try:
@ -470,32 +476,32 @@ def handle_server(irc, numeric, command, args):
# XXX: don't just save these by their server names; that's ugly!
sid = servername
sdesc = args[-1]
irc.servers[sid] = IrcServer(numeric, servername)
self.irc.servers[sid] = IrcServer(numeric, servername)
return {'name': servername, 'sid': sid, 'text': sdesc}
handle_sid = handle_server
handle_sid = handle_server
def handle_tmode(irc, numeric, command, args):
def handle_tmode(self, numeric, command, args):
# <- :42XAAAAAB TMODE 1437450768 #endlessvoid -c+lkC 3 agte4
channel = utils.toLower(irc, args[1])
channel = utils.toLower(self.irc, args[1])
modes = args[2:]
changedmodes = utils.parseModes(irc, channel, modes)
utils.applyModes(irc, channel, changedmodes)
changedmodes = utils.parseModes(self.irc, channel, modes)
utils.applyModes(self.irc, channel, changedmodes)
ts = int(args[0])
return {'target': channel, 'modes': changedmodes, 'ts': ts}
def handle_mode(irc, numeric, command, args):
def handle_mode(self, numeric, command, args):
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
target = args[0]
modestrings = args[1:]
changedmodes = utils.parseModes(irc, numeric, modestrings)
utils.applyModes(irc, target, changedmodes)
changedmodes = utils.parseModes(self.irc, numeric, modestrings)
utils.applyModes(self.irc, target, changedmodes)
if ('+o', None) in changedmodes:
otype = 'Server_Administrator' if ('a', None) in irc.users[target].modes else 'IRC_Operator'
irc.callHooks([target, 'PYLINK_CLIENT_OPERED', {'text': otype}])
otype = 'Server_Administrator' if ('a', None) in self.irc.users[target].modes else 'IRC_Operator'
self.irc.callHooks([target, 'PYLINK_CLIENT_OPERED', {'text': otype}])
return {'target': target, 'modes': changedmodes}
def handle_events(irc, data):
def handle_events(self, data):
# TS6 messages:
# :42X COMMAND arg1 arg2 :final long arg
# :42XAAAAAA PRIVMSG #somewhere :hello!
@ -505,59 +511,59 @@ def handle_events(irc, data):
return
if args[0] == 'PASS':
# <- PASS $somepassword TS 6 :42X
if args[1] != irc.serverdata['recvpass']:
if args[1] != self.irc.serverdata['recvpass']:
# Check if recvpass is correct
raise ProtocolError('Error: recvpass from uplink server %s does not match configuration!' % servername)
if 'TS 6' not in data:
raise ProtocolError("Remote protocol version is too old! Is this even TS6?")
# Server name and SID are sent in different messages, grr
numeric = data.rsplit(':', 1)[1]
log.debug('(%s) Found uplink SID as %r', irc.name, numeric)
irc.servers[numeric] = IrcServer(None, 'unknown')
irc.uplink = numeric
log.debug('(%s) Found uplink SID as %r', self.irc.name, numeric)
self.irc.servers[numeric] = IrcServer(None, 'unknown')
self.irc.uplink = numeric
return
elif args[0] == 'SERVER':
# <- SERVER charybdis.midnight.vpn 1 :charybdis test server
sname = args[1].lower()
log.debug('(%s) Found uplink server name as %r', irc.name, sname)
irc.servers[irc.uplink].name = sname
log.debug('(%s) Found uplink server name as %r', self.irc.name, sname)
self.irc.servers[self.irc.uplink].name = sname
# According to the TS6 protocol documentation, we should send SVINFO
# when we get our uplink's SERVER command.
irc.send('SVINFO 6 6 0 :%s' % int(time.time()))
self.irc.send('SVINFO 6 6 0 :%s' % int(time.time()))
elif args[0] == 'SQUIT':
# What? Charybdis send this in a different format!
# <- SQUIT 00A :Remote host closed the connection
split_server = args[1]
res = handle_squit(irc, split_server, 'SQUIT', [split_server])
irc.callHooks([split_server, 'SQUIT', res])
res = self.handle_squit(split_server, 'SQUIT', [split_server])
self.irc.callHooks([split_server, 'SQUIT', res])
elif args[0] == 'CAPAB':
# We only get a list of keywords here. Charybdis obviously assumes that
# we know what modes it supports (indeed, this is a standard list).
# <- CAPAB :BAN CHW CLUSTER ENCAP EOPMOD EUID EX IE KLN KNOCK MLOCK QS RSFNC SAVE SERVICES TB UNKLN
irc.caps = caps = data.split(':', 1)[1].split()
self.irc.caps = caps = data.split(':', 1)[1].split()
for required_cap in ('EUID', 'SAVE', 'TB', 'ENCAP', 'QS'):
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:
irc.cmodes['banexception'] = 'e'
self.irc.cmodes['banexception'] = 'e'
if 'IE' in caps:
irc.cmodes['invex'] = 'I'
self.irc.cmodes['invex'] = 'I'
if 'SERVICES' in caps:
irc.cmodes['regonly'] = 'r'
self.irc.cmodes['regonly'] = 'r'
log.debug('(%s) irc.connected set!', irc.name)
irc.connected.set()
log.debug('(%s) self.irc.connected set!', self.irc.name)
self.irc.connected.set()
# Charybdis doesn't have the idea of an explicit endburst; but some plugins
# like relay require it to know that the network's connected.
# We'll set a timer to manually call endburst. It's not beautiful,
# but it's the best we can do.
endburst_timer = threading.Timer(1, irc.callHooks, args=([irc.uplink, 'ENDBURST', {}],))
log.debug('(%s) Starting delay to send ENDBURST', irc.name)
endburst_timer = threading.Timer(1, self.irc.callHooks, args=([self.irc.uplink, 'ENDBURST', {}],))
log.debug('(%s) Starting delay to send ENDBURST', self.irc.name)
endburst_timer.start()
try:
args = parseTS6Args(args)
args = self.parseTS6Args(args)
numeric = args[0]
command = args[1]
@ -565,54 +571,54 @@ def handle_events(irc, data):
except IndexError:
return
# We will do wildcard event handling here. Unhandled events are just ignored.
# We will do wildcard command handling here. Unhandled commands are just ignored.
try:
func = globals()['handle_'+command.lower()]
except KeyError: # unhandled event
func = getattr(self, 'handle_'+command.lower())
except AttributeError: # Unhandled command
pass
else:
parsed_args = func(irc, numeric, command, args)
parsed_args = func(numeric, command, args)
if parsed_args is not None:
return [numeric, command, parsed_args]
def spawnServer(irc, name, sid=None, uplink=None, desc=None):
def spawnServer(self, name, sid=None, uplink=None, desc=None):
# -> :0AL SID test.server 1 0XY :some silly pseudoserver
uplink = uplink or irc.sid
uplink = uplink or self.irc.sid
name = name.lower()
desc = desc or irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']
desc = desc or self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']
if sid is None: # No sid given; generate one!
irc.sidgen = utils.TS6SIDGenerator(irc.serverdata["sidrange"])
sid = irc.sidgen.next_sid()
self.irc.sidgen = utils.TS6SIDGenerator(self.irc.serverdata["sidrange"])
sid = self.irc.sidgen.next_sid()
assert len(sid) == 3, "Incorrect SID length"
if sid in irc.servers:
if sid in self.irc.servers:
raise ValueError('A server with SID %r already exists!' % sid)
for server in irc.servers.values():
for server in self.irc.servers.values():
if name == server.name:
raise ValueError('A server named %r already exists!' % name)
if not utils.isInternalServer(irc, uplink):
if not utils.isInternalServer(self.irc, uplink):
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % uplink)
if not utils.isServerName(name):
raise ValueError('Invalid server name %r' % name)
_send(irc, uplink, 'SID %s 1 %s :%s' % (name, sid, desc))
irc.servers[sid] = IrcServer(uplink, name, internal=True)
self._send(uplink, 'SID %s 1 %s :%s' % (name, sid, desc))
self.irc.servers[sid] = IrcServer(uplink, name, internal=True)
return sid
def squitServer(irc, source, target, text='No reason given'):
def squitServer(self, source, target, text='No reason given'):
# -> SQUIT 9PZ :blah, blah
irc.send('SQUIT %s :%s' % (target, text))
handle_squit(irc, source, 'SQUIT', [target, text])
self.irc.send('SQUIT %s :%s' % (target, text))
self.handle_squit(source, 'SQUIT', [target, text])
def handle_tb(irc, numeric, command, args):
def handle_tb(self, numeric, command, args):
# <- :42X TB 1434510754 #channel GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic
channel = args[1].lower()
ts = args[0]
setter = args[2]
topic = args[-1]
irc.channels[channel].topic = topic
irc.channels[channel].topicset = True
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
return {'channel': channel, 'setter': setter, 'ts': ts, 'topic': topic}
def handle_invite(irc, numeric, command, args):
def handle_invite(self, numeric, command, args):
# <- :70MAAAAAC INVITE 0ALAAAAAA #blah 12345
target = args[0]
channel = args[1].lower()
@ -623,12 +629,12 @@ def handle_invite(irc, numeric, command, args):
# We don't actually need to process this; it's just something plugins/hooks can use
return {'target': target, 'channel': channel}
def handle_chghost(irc, numeric, command, args):
def handle_chghost(self, numeric, command, args):
target = args[0]
irc.users[target].host = newhost = args[1]
self.irc.users[target].host = newhost = args[1]
return {'target': numeric, 'newhost': newhost}
def handle_bmask(irc, numeric, command, args):
def handle_bmask(self, numeric, command, args):
# <- :42X BMASK 1424222769 #dev b :*!test@*.isp.net *!badident@*
# This is used for propagating bans, not TMODE!
channel = args[1].lower()
@ -637,14 +643,14 @@ def handle_bmask(irc, numeric, command, args):
modes = []
for ban in args[-1].split():
modes.append(('+%s' % mode, ban))
utils.applyModes(irc, channel, modes)
utils.applyModes(self.irc, channel, modes)
return {'target': channel, 'modes': modes, 'ts': ts}
def handle_whois(irc, numeric, command, args):
def handle_whois(self, numeric, command, args):
# <- :42XAAAAAB WHOIS 5PYAAAAAA :pylink-devel
return {'target': args[0]}
def handle_472(irc, numeric, command, args):
def handle_472(self, numeric, command, args):
# <- :charybdis.midnight.vpn 472 GL|devel O :is an unknown mode char to me
# 472 is sent to us when one of our clients tries to set a mode the server
# doesn't support. In this case, we'll raise a warning to alert the user
@ -657,15 +663,16 @@ def handle_472(irc, numeric, command, args):
log.warning('(%s) User %r attempted to set channel mode %r, but the '
'extension providing it isn\'t loaded! To prevent possible'
' desyncs, try adding the line "loadmodule "extensions/%s.so";" to '
'your IRCd configuration.', irc.name, setter, badmode,
'your IRCd configuration.', self.irc.name, setter, badmode,
charlist[badmode])
def handle_away(irc, numeric, command, args):
def handle_away(self, numeric, command, args):
# <- :6ELAAAAAB AWAY :Auto-away
try:
irc.users[numeric].away = text = args[0]
self.irc.users[numeric].away = text = args[0]
except IndexError: # User is unsetting away status
irc.users[numeric].away = text = ''
self.irc.users[numeric].away = text = ''
return {'text': text}
Class = TS6Protocol

View File

@ -7,11 +7,12 @@ import utils
from log import log
from classes import *
def _send(irc, source, msg):
"""Sends a TS6-style raw command from a source numeric to the IRC connection given."""
irc.send(':%s %s' % (source, msg))
class TS6BaseProtocol(Protocol):
def _send(self, source, msg):
"""Sends a TS6-style raw command from a source numeric to the self.irc connection given."""
self.irc.send(':%s %s' % (source, msg))
def parseArgs(args):
def parseArgs(self, args):
"""Parses a string of RFC1459-style arguments split into a list, where ":" may
be used for multi-word arguments that last until the end of a line.
"""
@ -34,224 +35,224 @@ def parseArgs(args):
break
return real_args
def parseTS6Args(args):
def parseTS6Args(self, args):
"""Similar to parseArgs(), but stripping leading colons from the first argument
of a line (usually the sender field)."""
args = parseArgs(args)
args = self.parseArgs(args)
args[0] = args[0].split(':', 1)[1]
return args
### OUTGOING COMMANDS
### OUTGOING COMMANDS
def _sendKick(irc, numeric, channel, target, reason=None):
def _sendKick(self, numeric, channel, target, reason=None):
"""Internal function to send kicks from a PyLink client/server."""
channel = utils.toLower(irc, channel)
channel = utils.toLower(self.irc, channel)
if not reason:
reason = 'No reason given'
_send(irc, numeric, 'KICK %s %s :%s' % (channel, target, reason))
self._send(numeric, 'KICK %s %s :%s' % (channel, target, reason))
# We can pretend the target left by its own will; all we really care about
# is that the target gets removed from the channel userlist, and calling
# handle_part() does that just fine.
handle_part(irc, target, 'KICK', [channel])
self.handle_part(target, 'KICK', [channel])
def kickClient(irc, numeric, channel, target, reason=None):
def kickClient(self, numeric, channel, target, reason=None):
"""Sends a kick from a PyLink client."""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_sendKick(irc, numeric, channel, target, reason=reason)
self._sendKick(numeric, channel, target, reason=reason)
def kickServer(irc, numeric, channel, target, reason=None):
def kickServer(self, numeric, channel, target, reason=None):
"""Sends a kick from a PyLink server."""
if not utils.isInternalServer(irc, numeric):
if not utils.isInternalServer(self.irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.')
_sendKick(irc, numeric, channel, target, reason=reason)
self._sendKick(numeric, channel, target, reason=reason)
def nickClient(irc, numeric, newnick):
def nickClient(self, numeric, newnick):
"""Changes the nick of a PyLink client."""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'NICK %s %s' % (newnick, int(time.time())))
irc.users[numeric].nick = newnick
self._send(numeric, 'NICK %s %s' % (newnick, int(time.time())))
self.irc.users[numeric].nick = newnick
def removeClient(irc, numeric):
def removeClient(self, numeric):
"""Internal function to remove a client from our internal state."""
for c, v in irc.channels.copy().items():
for c, v in self.irc.channels.copy().items():
v.removeuser(numeric)
# Clear empty non-permanent channels.
if not (irc.channels[c].users or ((irc.cmodes.get('permanent'), None) in irc.channels[c].modes)):
del irc.channels[c]
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]
sid = numeric[:3]
log.debug('Removing client %s from irc.users', numeric)
del irc.users[numeric]
log.debug('Removing client %s from irc.servers[%s]', numeric, sid)
irc.servers[sid].users.discard(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]', numeric, sid)
self.irc.servers[sid].users.discard(numeric)
def partClient(irc, client, channel, reason=None):
def partClient(self, client, channel, reason=None):
"""Sends a part from a PyLink client."""
channel = utils.toLower(irc, channel)
if not utils.isInternalClient(irc, client):
log.error('(%s) Error trying to part client %r to %r (no such pseudoclient exists)', irc.name, client, channel)
channel = utils.toLower(self.irc, channel)
if not utils.isInternalClient(self.irc, client):
log.error('(%s) Error trying to part client %r to %r (no such pseudoclient exists)', self.irc.name, client, channel)
raise LookupError('No such PyLink PseudoClient exists.')
msg = "PART %s" % channel
if reason:
msg += " :%s" % reason
_send(irc, client, msg)
handle_part(irc, client, 'PART', [channel])
self._send(client, msg)
self.handle_part(client, 'PART', [channel])
def quitClient(irc, numeric, reason):
def quitClient(self, numeric, reason):
"""Quits a PyLink client."""
if utils.isInternalClient(irc, numeric):
_send(irc, numeric, "QUIT :%s" % reason)
removeClient(irc, numeric)
if utils.isInternalClient(self.irc, numeric):
self._send(numeric, "QUIT :%s" % reason)
self.removeClient(numeric)
else:
raise LookupError("No such PyLink PseudoClient exists.")
def messageClient(irc, numeric, target, text):
def messageClient(self, numeric, target, text):
"""Sends a PRIVMSG from a PyLink client."""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'PRIVMSG %s :%s' % (target, text))
self._send(numeric, 'PRIVMSG %s :%s' % (target, text))
def noticeClient(irc, numeric, target, text):
def noticeClient(self, numeric, target, text):
"""Sends a NOTICE from a PyLink client."""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'NOTICE %s :%s' % (target, text))
self._send(numeric, 'NOTICE %s :%s' % (target, text))
def topicClient(irc, numeric, target, text):
def topicClient(self, numeric, target, text):
"""Sends a ROPIC from a PyLink client."""
if not utils.isInternalClient(irc, numeric):
if not utils.isInternalClient(self.irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'TOPIC %s :%s' % (target, text))
irc.channels[target].topic = text
irc.channels[target].topicset = True
self._send(numeric, 'TOPIC %s :%s' % (target, text))
self.irc.channels[target].topic = text
self.irc.channels[target].topicset = True
### HANDLERS
### HANDLERS
def handle_privmsg(irc, source, command, args):
def handle_privmsg(self, source, command, args):
"""Handles incoming PRIVMSG/NOTICE."""
# <- :70MAAAAAA PRIVMSG #dev :afasfsa
# <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa
target = args[0]
# We use lowercase channels internally, but uppercase UIDs.
if utils.isChannel(target):
target = utils.toLower(irc, target)
target = utils.toLower(self.irc, target)
return {'target': target, 'text': args[1]}
handle_notice = handle_privmsg
handle_notice = handle_privmsg
def handle_kill(irc, source, command, args):
def handle_kill(self, source, command, args):
"""Handles incoming KILLs."""
killed = args[0]
# Depending on whether the IRCd sends explicit QUIT messages for
# Depending on whether the self.ircd sends explicit QUIT messages for
# KILLed clients, the user may or may not have automatically been removed.
# If not, we have to assume that KILL = QUIT and remove them ourselves.
data = irc.users.get(killed)
data = self.irc.users.get(killed)
if data:
removeClient(irc, killed)
self.removeClient(killed)
return {'target': killed, 'text': args[1], 'userdata': data}
def handle_kick(irc, source, command, args):
def handle_kick(self, source, command, args):
"""Handles incoming KICKs."""
# :70MAAAAAA KICK #endlessvoid 70MAAAAAA :some reason
channel = utils.toLower(irc, args[0])
channel = utils.toLower(self.irc, args[0])
kicked = args[1]
handle_part(irc, kicked, 'KICK', [channel, args[2]])
self.handle_part(kicked, 'KICK', [channel, args[2]])
return {'channel': channel, 'target': kicked, 'text': args[2]}
def handle_error(irc, numeric, command, args):
def handle_error(self, numeric, command, args):
"""Handles ERROR messages - these mean that our uplink has disconnected us!"""
irc.connected.clear()
self.irc.connected.clear()
raise ProtocolError('Received an ERROR, disconnecting!')
def handle_nick(irc, numeric, command, args):
def handle_nick(self, numeric, command, args):
"""Handles incoming NICK changes."""
# <- :70MAAAAAA NICK GL-devel 1434744242
oldnick = irc.users[numeric].nick
newnick = irc.users[numeric].nick = args[0]
oldnick = self.irc.users[numeric].nick
newnick = self.irc.users[numeric].nick = args[0]
return {'newnick': newnick, 'oldnick': oldnick, 'ts': int(args[1])}
def handle_quit(irc, numeric, command, args):
def handle_quit(self, numeric, command, args):
"""Handles incoming QUITs."""
# <- :1SRAAGB4T QUIT :Quit: quit message goes here
removeClient(irc, numeric)
self.removeClient(numeric)
return {'text': args[0]}
def handle_save(irc, numeric, command, args):
def handle_save(self, numeric, command, args):
"""Handles incoming SAVE messages, used to handle nick collisions."""
# In this below example, the client Derp_ already exists,
# and trying to change someone's nick to it will cause a nick
# collision. On TS6 IRCds, this will simply set the collided user's
# collision. On TS6 self.ircds, this will simply set the collided user's
# nick to its UID.
# <- :70MAAAAAA PRIVMSG 0AL000001 :nickclient PyLink Derp_
# -> :0AL000001 NICK Derp_ 1433728673
# <- :70M SAVE 0AL000001 1433728673
user = args[0]
oldnick = irc.users[user].nick
irc.users[user].nick = user
oldnick = self.irc.users[user].nick
self.irc.users[user].nick = user
return {'target': user, 'ts': int(args[1]), 'oldnick': oldnick}
def handle_squit(irc, numeric, command, args):
def handle_squit(self, numeric, command, args):
"""Handles incoming SQUITs (netsplits)."""
# :70M SQUIT 1ML :Server quit by GL!gl@0::1
split_server = args[0]
affected_users = []
log.info('(%s) Netsplit on server %s', irc.name, split_server)
log.info('(%s) Netsplit on server %s', self.irc.name, split_server)
# Prevent RuntimeError: dictionary changed size during iteration
old_servers = irc.servers.copy()
old_servers = self.irc.servers.copy()
for sid, data in old_servers.items():
if data.uplink == split_server:
log.debug('Server %s also hosts server %s, removing those users too...', split_server, sid)
args = handle_squit(irc, sid, 'SQUIT', [sid, "PyLink: Automatically splitting leaf servers of %s" % sid])
args = self.handle_squit(sid, 'SQUIT', [sid, "PyLink: Automatically splitting leaf servers of %s" % sid])
affected_users += args['users']
for user in irc.servers[split_server].users.copy():
for user in self.irc.servers[split_server].users.copy():
affected_users.append(user)
log.debug('Removing client %s (%s)', user, irc.users[user].nick)
removeClient(irc, user)
del irc.servers[split_server]
log.debug('(%s) Netsplit affected users: %s', irc.name, affected_users)
log.debug('Removing client %s (%s)', user, self.irc.users[user].nick)
self.removeClient(user)
del self.irc.servers[split_server]
log.debug('(%s) Netsplit affected users: %s', self.irc.name, affected_users)
return {'target': split_server, 'users': affected_users}
def handle_mode(irc, numeric, command, args):
def handle_mode(self, numeric, command, args):
"""Handles incoming user mode changes. For channel mode changes,
TMODE (TS6/charybdis) and FMODE (InspIRCd) are used instead."""
# In InspIRCd, MODE is used for setting user modes and
TMODE (TS6/charybdis) and FMODE (Inspself.ircd) are used instead."""
# In Inspself.ircd, MODE is used for setting user modes and
# FMODE is used for channel modes:
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc
target = args[0]
modestrings = args[1:]
changedmodes = utils.parseModes(irc, numeric, modestrings)
utils.applyModes(irc, target, changedmodes)
changedmodes = utils.parseModes(self.irc, numeric, modestrings)
utils.applyModes(self.irc, target, changedmodes)
return {'target': target, 'modes': changedmodes}
def handle_topic(irc, numeric, command, args):
def handle_topic(self, numeric, command, args):
"""Handles incoming TOPIC changes from clients. For topic bursts,
TB (TS6/charybdis) and FTOPIC (InspIRCd) are used instead."""
TB (TS6/charybdis) and FTOPIC (Inspself.ircd) are used instead."""
# <- :70MAAAAAA TOPIC #test :test
channel = utils.toLower(irc, args[0])
channel = utils.toLower(self.irc, args[0])
topic = args[1]
ts = int(time.time())
irc.channels[channel].topic = topic
irc.channels[channel].topicset = True
self.irc.channels[channel].topic = topic
self.irc.channels[channel].topicset = True
return {'channel': channel, 'setter': numeric, 'ts': ts, 'topic': topic}
def handle_part(irc, source, command, args):
def handle_part(self, source, command, args):
"""Handles incoming PART commands."""
channels = utils.toLower(irc, args[0]).split(',')
channels = utils.toLower(self.irc, args[0]).split(',')
for channel in channels:
# We should only get PART commands for channels that exist, right??
irc.channels[channel].removeuser(source)
self.irc.channels[channel].removeuser(source)
try:
irc.users[source].channels.discard(channel)
self.irc.users[source].channels.discard(channel)
except KeyError:
log.debug("(%s) handle_part: KeyError trying to remove %r from %r's channel list?", irc.name, channel, source)
log.debug("(%s) handle_part: KeyError trying to remove %r from %r's channel list?", self.irc.name, channel, source)
try:
reason = args[1]
except IndexError:
reason = ''
# Clear empty non-permanent channels.
if not (irc.channels[channel].users or ((irc.cmodes.get('permanent'), None) in irc.channels[channel].modes)):
del irc.channels[channel]
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]
return {'channels': channels, 'text': reason}

View File

@ -100,9 +100,9 @@ class TS6SIDGenerator():
def msg(irc, target, text, notice=False):
if notice:
irc.proto.noticeClient(irc, irc.pseudoclient.uid, target, text)
irc.proto.noticeClient(irc.pseudoclient.uid, target, text)
else:
irc.proto.messageClient(irc, irc.pseudoclient.uid, target, text)
irc.proto.messageClient(irc.pseudoclient.uid, target, text)
def add_cmd(func, name=None):
if name is None: