3
0
mirror of https://github.com/jlu5/PyLink.git synced 2025-01-26 04:04:22 +01:00

Document the sources of protocols/inspircd & ts6_common

This commit is contained in:
James Lu 2015-09-05 14:25:11 -07:00
parent 72cfe04904
commit 9f2d8a1b01
3 changed files with 220 additions and 166 deletions

View File

@ -4,32 +4,36 @@ import os
import re import re
from copy import copy from copy import copy
# Import hacks to access utils and classes...
curdir = os.path.dirname(__file__) curdir = os.path.dirname(__file__)
sys.path += [curdir, os.path.dirname(curdir)] sys.path += [curdir, os.path.dirname(curdir)]
import utils import utils
from log import log from log import log
from classes import * from classes import *
# Some functions are shared with the charybdis module... (ts6_common)
from ts6_common import nickClient, kickServer, kickClient, _sendKick, quitClient, \ from ts6_common import nickClient, kickServer, kickClient, _sendKick, quitClient, \
removeClient, partClient, messageClient, noticeClient, topicClient, parseTS6Args removeClient, partClient, messageClient, noticeClient, topicClient, parseTS6Args
from ts6_common import handle_privmsg, handle_kill, handle_kick, handle_error, \ 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_quit, handle_nick, handle_save, handle_squit, handle_mode, handle_topic, \
handle_notice, _send, handle_part handle_notice, _send, handle_part
# Set our case mapping (rfc1459 maps "\" and "|" together, for example".
casemapping = 'rfc1459' casemapping = 'rfc1459'
# Raw commands sent from servers vary from protocol to protocol. Here, we map # Raw commands sent from servers vary from protocol to protocol. Here, we map
# non-standard names to our hook handlers, so plugins get the information they need. # 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', hook_map = {'FJOIN': 'JOIN', 'RSQUIT': 'SQUIT', 'FMODE': 'MODE',
'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST', 'FTOPIC': 'TOPIC', 'OPERTYPE': 'MODE', 'FHOST': 'CHGHOST',
'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'} 'FIDENT': 'CHGIDENT', 'FNAME': 'CHGNAME'}
def _send(irc, sid, msg):
irc.send(':%s %s' % (sid, msg))
def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(), def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(),
server=None, ip='0.0.0.0', realname=None, ts=None, opertype=None): 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 server = server or irc.sid
if not utils.isInternalServer(irc, server): if not utils.isInternalServer(irc, server):
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server) raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
@ -56,9 +60,10 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set()
return u return u
def joinClient(irc, client, channel): def joinClient(irc, client, channel):
"""Joins an internal spawned client <client> to a channel."""
# InspIRCd doesn't distinguish between burst joins and regular joins, # InspIRCd doesn't distinguish between burst joins and regular joins,
# so what we're actually doing here is sending FJOIN from the server, # so what we're actually doing here is sending FJOIN from the server,
# on behalf of the clients that call it. # on behalf of the clients that are joining.
channel = utils.toLower(irc, channel) channel = utils.toLower(irc, channel)
server = utils.isInternalClient(irc, client) server = utils.isInternalClient(irc, client)
if not server: if not server:
@ -73,6 +78,16 @@ def joinClient(irc, client, channel):
irc.users[client].channels.add(channel) irc.users[client].channels.add(channel)
def sjoinServer(irc, server, channel, users, ts=None): def sjoinServer(irc, 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
to the one we've stored in the channel state if not given.
<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)])
"""
channel = utils.toLower(irc, channel) channel = utils.toLower(irc, channel)
server = server or irc.sid server = server or irc.sid
assert users, "sjoinServer: No users sent?" assert users, "sjoinServer: No users sent?"
@ -92,7 +107,7 @@ def sjoinServer(irc, server, channel, users, ts=None):
p.clear() 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, irc.name, ts,
time.strftime("%c", time.localtime(ts))) time.strftime("%c", time.localtime(ts)))
# Strip out list-modes, they shouldn't be ever sent in FJOIN. # 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 irc.channels[channel].modes if m[0] not in irc.cmodes['*A']]
uids = [] uids = []
changedmodes = [] changedmodes = []
@ -119,6 +134,15 @@ def sjoinServer(irc, server, channel, users, ts=None):
irc.channels[channel].users.update(uids) irc.channels[channel].users.update(uids)
def _operUp(irc, target, opertype=None): def _operUp(irc, 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
InspIRCd requires a special command (OPERTYPE) to be sent in order to
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),
and the change will be reflected here."""
userobj = irc.users[target] userobj = irc.users[target]
try: try:
otype = opertype or userobj.opertype otype = opertype or userobj.opertype
@ -133,6 +157,7 @@ def _operUp(irc, target, opertype=None):
_send(irc, target, 'OPERTYPE %s' % otype) _send(irc, target, 'OPERTYPE %s' % otype)
def _sendModes(irc, numeric, target, modes, ts=None): def _sendModes(irc, numeric, target, modes, ts=None):
"""Internal function to send modes from a PyLink client/server."""
# -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA # -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA
# -> :9PYAAAAAA MODE 9PYAAAAAA -i+w # -> :9PYAAAAAA MODE 9PYAAAAAA -i+w
log.debug('(%s) inspircd._sendModes: received %r for mode list', irc.name, modes) log.debug('(%s) inspircd._sendModes: received %r for mode list', irc.name, modes)
@ -150,20 +175,18 @@ def _sendModes(irc, numeric, target, modes, ts=None):
_send(irc, numeric, 'MODE %s %s' % (target, joinedmodes)) _send(irc, numeric, 'MODE %s %s' % (target, joinedmodes))
def modeClient(irc, numeric, target, modes, ts=None): def modeClient(irc, numeric, target, modes, ts=None):
"""<irc object> <client numeric> <list of modes> """
Sends modes from a PyLink client. <modes> should be
Sends modes from a PyLink PseudoClient. <list of modes> should be a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output.
a list of (mode, arg) tuples, in the format of utils.parseModes() output.
""" """
if not utils.isInternalClient(irc, numeric): if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
_sendModes(irc, numeric, target, modes, ts=ts) _sendModes(irc, numeric, target, modes, ts=ts)
def modeServer(irc, numeric, target, modes, ts=None): def modeServer(irc, numeric, target, modes, ts=None):
"""<irc object> <server SID> <list of modes> """
Sends modes from a PyLink server. <list of modes> should be
Sends modes from a PyLink PseudoServer. <list of modes> should be a list of (mode, arg) tuples, i.e. the format of utils.parseModes() output.
a list of (mode, arg) tuples, in the format of utils.parseModes() output.
""" """
if not utils.isInternalServer(irc, numeric): if not utils.isInternalServer(irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.') raise LookupError('No such PyLink PseudoServer exists.')
@ -192,6 +215,7 @@ def killClient(irc, numeric, target, reason):
# will send a QUIT from the target if the command succeeds. # will send a QUIT from the target if the command succeeds.
def topicServer(irc, numeric, target, text): def topicServer(irc, 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(irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.') raise LookupError('No such PyLink PseudoServer exists.')
ts = int(time.time()) ts = int(time.time())
@ -201,25 +225,19 @@ def topicServer(irc, numeric, target, text):
irc.channels[target].topicset = True irc.channels[target].topicset = True
def inviteClient(irc, numeric, target, channel): def inviteClient(irc, numeric, target, channel):
"""<irc object> <client numeric> <text> """Sends an INVITE from a PyLink client.."""
Invites <target> to <channel> to <text> from PyLink client <client numeric>."""
if not utils.isInternalClient(irc, numeric): if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'INVITE %s %s' % (target, channel)) _send(irc, numeric, 'INVITE %s %s' % (target, channel))
def knockClient(irc, numeric, target, text): def knockClient(irc, numeric, target, text):
"""<irc object> <client numeric> <text> """Sends a KNOCK from a PyLink client."""
Knocks on <channel> with <text> from PyLink client <client numeric>."""
if not utils.isInternalClient(irc, numeric): if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'ENCAP * KNOCK %s :%s' % (target, text)) _send(irc, numeric, 'ENCAP * KNOCK %s :%s' % (target, text))
def updateClient(irc, numeric, field, text): def updateClient(irc, numeric, field, text):
"""<irc object> <client numeric> <field> <text> """Updates the ident, host, or realname of a PyLink client."""
Changes the <field> field of <target> PyLink PseudoClient <client numeric>."""
field = field.upper() field = field.upper()
if field == 'IDENT': if field == 'IDENT':
irc.users[numeric].ident = text irc.users[numeric].ident = text
@ -234,6 +252,8 @@ def updateClient(irc, numeric, field, text):
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field) raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
def pingServer(irc, source=None, target=None): def pingServer(irc, 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 source = source or irc.sid
target = target or irc.uplink target = target or irc.uplink
if not (target is None or source is None): if not (target is None or source is None):
@ -246,16 +266,46 @@ def numericServer(irc, source, numeric, text):
"need for PyLink to send numerics directly yet.") "need for PyLink to send numerics directly yet.")
def awayClient(irc, source, text): def awayClient(irc, source, text):
"""<irc object> <numeric> <text> """Sends an AWAY message from a PyLink client. <text> can be an empty string
to unset AWAY status."""
Sends an AWAY message with text <text> from PyLink client <numeric>.
<text> can be an empty string to unset AWAY status."""
if text: if text:
_send(irc, source, 'AWAY %s :%s' % (int(time.time()), text)) _send(irc, source, 'AWAY %s :%s' % (int(time.time()), text))
else: else:
_send(irc, source, 'AWAY') _send(irc, source, 'AWAY')
def spawnServer(irc, 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
name = name.lower()
# "desc" defaults to the configured server description.
desc = desc or irc.serverdata.get('serverdesc') or irc.botdata['serverdesc']
if sid is None: # No sid given; generate one!
irc.sidgen = utils.TS6SIDGenerator(irc.serverdata["sidrange"])
sid = irc.sidgen.next_sid()
assert len(sid) == 3, "Incorrect SID length"
if sid in irc.servers:
raise ValueError('A server with SID %r already exists!' % sid)
for server in irc.servers.values():
if name == server.name:
raise ValueError('A server named %r already exists!' % name)
if not utils.isInternalServer(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')
return sid
def squitServer(irc, 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])
def connect(irc): def connect(irc):
"""Initializes a connection to a server."""
ts = irc.start_ts ts = irc.start_ts
f = irc.send f = irc.send
@ -268,84 +318,10 @@ def connect(irc):
f(':%s BURST %s' % (irc.sid, ts)) f(':%s BURST %s' % (irc.sid, ts))
f(':%s ENDBURST' % (irc.sid)) f(':%s ENDBURST' % (irc.sid))
def handle_ping(irc, source, command, args):
# <- :70M PING 70M 0AL
# -> :0AL PONG 0AL 70M
if utils.isInternalServer(irc, args[1]):
_send(irc, args[1], 'PONG %s %s' % (args[1], source))
def handle_pong(irc, source, command, args):
if source == irc.uplink and args[1] == irc.sid:
irc.lastping = time.time()
def handle_fjoin(irc, servernumeric, command, args):
# :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...>
channel = utils.toLower(irc, args[0])
# InspIRCd sends each user's channel data in the form of 'modeprefix(es),UID'
userlist = args[-1].split()
our_ts = 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():
p.clear()
modestring = args[2:-1] or args[2]
parsedmodes = utils.parseModes(irc, channel, modestring)
utils.applyModes(irc, channel, parsedmodes)
namelist = []
for user in userlist:
modeprefix, user = user.split(',', 1)
namelist.append(user)
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)
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts}
def handle_uid(irc, numeric, command, args):
# :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]])
log.debug('Applying modes %s for %s', parsedmodes, uid)
utils.applyModes(irc, uid, parsedmodes)
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):
# SERVER is sent by our uplink or any other server to introduce others.
# <- :00A SERVER test.server * 1 00C :testing raw message syntax
# <- :70M SERVER millennium.overdrive.pw * 1 1ML :a relatively long period of time... (Fremont, California)
servername = args[0].lower()
sid = args[3]
sdesc = args[-1]
irc.servers[sid] = IrcServer(numeric, servername)
return {'name': servername, 'sid': args[3], 'text': sdesc}
def handle_fmode(irc, numeric, command, args):
# <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD
channel = utils.toLower(irc, args[0])
modes = args[2:]
changedmodes = utils.parseModes(irc, channel, modes)
utils.applyModes(irc, channel, changedmodes)
ts = int(args[1])
return {'target': channel, 'modes': changedmodes, 'ts': ts}
def handle_idle(irc, numeric, command, args):
"""Handle 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))
def handle_events(irc, data): def handle_events(irc, 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."""
# Each server message looks something like this: # Each server message looks something like this:
# :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :v,1SRAAESWE # :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :v,1SRAAESWE
# :<sid> <command> <argument1> <argument2> ... :final multi word argument # :<sid> <command> <argument1> <argument2> ... :final multi word argument
@ -425,35 +401,92 @@ def handle_events(irc, data):
if parsed_args is not None: if parsed_args is not None:
return [numeric, command, parsed_args] return [numeric, command, parsed_args]
def spawnServer(irc, name, sid=None, uplink=None, desc=None): def handle_ping(irc, source, command, args):
# -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver """Handles incoming PING commands, so we don't time out."""
uplink = uplink or irc.sid # <- :70M PING 70M 0AL
name = name.lower() # -> :0AL PONG 0AL 70M
desc = desc or irc.serverdata.get('serverdesc') or irc.botdata['serverdesc'] if utils.isInternalServer(irc, args[1]):
if sid is None: # No sid given; generate one! _send(irc, args[1], 'PONG %s %s' % (args[1], source))
irc.sidgen = utils.TS6SIDGenerator(irc.serverdata["sidrange"])
sid = irc.sidgen.next_sid()
assert len(sid) == 3, "Incorrect SID length"
if sid in irc.servers:
raise ValueError('A server with SID %r already exists!' % sid)
for server in irc.servers.values():
if name == server.name:
raise ValueError('A server named %r already exists!' % name)
if not utils.isInternalServer(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')
return sid
def squitServer(irc, source, target, text='No reason given'): def handle_pong(irc, source, command, args):
# -> :9PY SQUIT 9PZ :blah, blah """Handles incoming PONG commands. This is used to keep track of whether
_send(irc, source, 'SQUIT %s :%s' % (target, text)) the uplink is alive by the Irc() internals - a server that fails to reply
handle_squit(irc, source, 'SQUIT', [target, text]) to our PINGs eventually times out and is disconnected."""
if source == irc.uplink and args[1] == irc.sid:
irc.lastping = time.time()
def handle_fjoin(irc, 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])
# InspIRCd sends each user's channel data in the form of 'modeprefix(es),UID'
userlist = args[-1].split()
our_ts = 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():
p.clear()
modestring = args[2:-1] or args[2]
parsedmodes = utils.parseModes(irc, channel, modestring)
utils.applyModes(irc, channel, parsedmodes)
namelist = []
for user in userlist:
modeprefix, user = user.split(',', 1)
namelist.append(user)
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)
return {'channel': channel, 'users': namelist, 'modes': parsedmodes, 'ts': their_ts}
def handle_uid(irc, 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]])
log.debug('Applying modes %s for %s', parsedmodes, uid)
utils.applyModes(irc, uid, parsedmodes)
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):
"""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
# <- :70M SERVER millennium.overdrive.pw * 1 1ML :a relatively long period of time... (Fremont, California)
servername = args[0].lower()
sid = args[3]
sdesc = args[-1]
irc.servers[sid] = IrcServer(numeric, servername)
return {'name': servername, 'sid': args[3], 'text': sdesc}
def handle_fmode(irc, 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])
modes = args[2:]
changedmodes = utils.parseModes(irc, channel, modes)
utils.applyModes(irc, channel, changedmodes)
ts = int(args[1])
return {'target': channel, 'modes': changedmodes, 'ts': ts}
def handle_idle(irc, 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))
def handle_ftopic(irc, numeric, command, args): def handle_ftopic(irc, numeric, command, args):
"""Handles incoming FTOPIC (sets topic on burst)."""
# <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic # <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic
channel = utils.toLower(irc, args[0]) channel = utils.toLower(irc, args[0])
ts = args[1] ts = args[1]
@ -464,13 +497,19 @@ def handle_ftopic(irc, numeric, command, args):
return {'channel': channel, 'setter': setter, 'ts': ts, 'topic': topic} return {'channel': channel, 'setter': setter, 'ts': ts, 'topic': topic}
def handle_invite(irc, numeric, command, args): def handle_invite(irc, numeric, command, args):
"""Handles incoming INVITEs."""
# <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0 # <- :70MAAAAAC INVITE 0ALAAAAAA #blah 0
target = args[0] target = args[0]
channel = utils.toLower(irc, args[1]) channel = utils.toLower(irc, args[1])
# We don't actually need to process this; it's just something plugins/hooks can use # We don't actually need to process this; just send the hook so plugins can use it
return {'target': target, 'channel': channel} return {'target': target, 'channel': channel}
def handle_encap(irc, numeric, command, args): def handle_encap(irc, 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 # <- :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/irc-docs/blob/03ba884a54f1cef2193cd62b6a86803d89c1ac41/server/ts6.txt
@ -490,6 +529,10 @@ def handle_encap(irc, numeric, command, args):
'text': text} 'text': text}
def handle_opertype(irc, numeric, command, args): def handle_opertype(irc, 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
opertype of the client, and assumes setting user mode +o on the caller."""
# This is used by InspIRCd to denote an oper up; there is no MODE # This is used by InspIRCd to denote an oper up; there is no MODE
# command sent for it. # command sent for it.
# <- :70MAAAAAB OPERTYPE Network_Owner # <- :70MAAAAAB OPERTYPE Network_Owner
@ -502,24 +545,29 @@ def handle_opertype(irc, numeric, command, args):
return {'target': numeric, 'modes': omode} return {'target': numeric, 'modes': omode}
def handle_fident(irc, numeric, command, args): def handle_fident(irc, numeric, command, args):
# :70MAAAAAB FHOST test """Handles FIDENT, used for denoting ident changes."""
# :70MAAAAAB FNAME :afdsafasf # <- :70MAAAAAB FIDENT test
# :70MAAAAAB FIDENT test
irc.users[numeric].ident = newident = args[0] irc.users[numeric].ident = newident = args[0]
return {'target': numeric, 'newident': newident} return {'target': numeric, 'newident': newident}
def handle_fhost(irc, numeric, command, args): def handle_fhost(irc, numeric, command, args):
"""Handles FHOST, used for denoting hostname changes."""
# <- :70MAAAAAB FIDENT some.host
irc.users[numeric].host = newhost = args[0] irc.users[numeric].host = newhost = args[0]
return {'target': numeric, 'newhost': newhost} return {'target': numeric, 'newhost': newhost}
def handle_fname(irc, numeric, command, args): def handle_fname(irc, numeric, command, args):
"""Handles FNAME, used for denoting real name/gecos changes."""
# <- :70MAAAAAB FNAME :afdsafasf
irc.users[numeric].realname = newgecos = args[0] irc.users[numeric].realname = newgecos = args[0]
return {'target': numeric, 'newgecos': newgecos} return {'target': numeric, 'newgecos': newgecos}
def handle_endburst(irc, numeric, command, args): def handle_endburst(irc, numeric, command, args):
"""ENDBURST handler; sends a hook with empty contents."""
return {} return {}
def handle_away(irc, numeric, command, args): def handle_away(irc, numeric, command, args):
"""Handles incoming AWAY messages."""
# <- :1MLAAAAIG AWAY 1439371390 :Auto-away # <- :1MLAAAAIG AWAY 1439371390 :Auto-away
try: try:
ts = args[0] ts = args[0]

View File

@ -9,7 +9,7 @@ import utils
from log import log from log import log
from classes import * from classes import *
# Shared with inspircd module because the output is the same. # Some functions are shared with the InspIRCd module (ts6_common)
from ts6_common import nickClient, kickServer, kickClient, _sendKick, quitClient, \ from ts6_common import nickClient, kickServer, kickClient, _sendKick, quitClient, \
removeClient, partClient, messageClient, noticeClient, topicClient, parseTS6Args removeClient, partClient, messageClient, noticeClient, topicClient, parseTS6Args
from ts6_common import handle_privmsg, handle_kill, handle_kick, handle_error, \ from ts6_common import handle_privmsg, handle_kill, handle_kick, handle_error, \

View File

@ -7,12 +7,12 @@ import utils
from log import log from log import log
from classes import * from classes import *
def _send(irc, sid, msg): def _send(irc, source, msg):
irc.send(':%s %s' % (sid, msg)) """Sends a TS6-style raw command from a source numeric to the IRC connection given."""
irc.send(':%s %s' % (source, msg))
def parseArgs(args): def parseArgs(args):
"""<arg list> """Parses a string of RFC1459-style arguments split into a list, where ":" may
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. be used for multi-word arguments that last until the end of a line.
""" """
real_args = [] real_args = []
@ -35,9 +35,7 @@ def parseArgs(args):
return real_args return real_args
def parseTS6Args(args): def parseTS6Args(args):
"""<arg list> """Similar to parseArgs(), but stripping leading colons from the first argument
Similar to parseArgs(), but stripping leading colons from the first argument
of a line (usually the sender field).""" of a line (usually the sender field)."""
args = parseArgs(args) args = parseArgs(args)
args[0] = args[0].split(':', 1)[1] args[0] = args[0].split(':', 1)[1]
@ -46,9 +44,7 @@ def parseTS6Args(args):
### OUTGOING COMMANDS ### OUTGOING COMMANDS
def _sendKick(irc, numeric, channel, target, reason=None): def _sendKick(irc, numeric, channel, target, reason=None):
"""<irc object> <kicker client numeric> """Internal function to send kicks from a PyLink client/server."""
Sends a kick from a PyLink PseudoClient."""
channel = utils.toLower(irc, channel) channel = utils.toLower(irc, channel)
if not reason: if not reason:
reason = 'No reason given' reason = 'No reason given'
@ -59,29 +55,26 @@ def _sendKick(irc, numeric, channel, target, reason=None):
handle_part(irc, target, 'KICK', [channel]) handle_part(irc, target, 'KICK', [channel])
def kickClient(irc, numeric, channel, target, reason=None): def kickClient(irc, numeric, channel, target, reason=None):
"""Sends a kick from a PyLink client."""
if not utils.isInternalClient(irc, numeric): if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
_sendKick(irc, numeric, channel, target, reason=reason) _sendKick(irc, numeric, channel, target, reason=reason)
def kickServer(irc, numeric, channel, target, reason=None): def kickServer(irc, numeric, channel, target, reason=None):
"""Sends a kick from a PyLink server."""
if not utils.isInternalServer(irc, numeric): if not utils.isInternalServer(irc, numeric):
raise LookupError('No such PyLink PseudoServer exists.') raise LookupError('No such PyLink PseudoServer exists.')
_sendKick(irc, numeric, channel, target, reason=reason) _sendKick(irc, numeric, channel, target, reason=reason)
def nickClient(irc, numeric, newnick): def nickClient(irc, numeric, newnick):
"""<irc object> <client numeric> <new nickname> """Changes the nick of a PyLink client."""
Changes the nick of a PyLink PseudoClient."""
if not utils.isInternalClient(irc, numeric): if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'NICK %s %s' % (newnick, int(time.time()))) _send(irc, numeric, 'NICK %s %s' % (newnick, int(time.time())))
irc.users[numeric].nick = newnick irc.users[numeric].nick = newnick
def removeClient(irc, numeric): def removeClient(irc, numeric):
"""<irc object> <client numeric> """Internal function to remove a client from our internal state."""
Removes a client from our internal databases, regardless
of whether it's one of our pseudoclients or not."""
for c, v in irc.channels.copy().items(): for c, v in irc.channels.copy().items():
v.removeuser(numeric) v.removeuser(numeric)
# Clear empty non-permanent channels. # Clear empty non-permanent channels.
@ -95,6 +88,7 @@ def removeClient(irc, numeric):
irc.servers[sid].users.discard(numeric) irc.servers[sid].users.discard(numeric)
def partClient(irc, client, channel, reason=None): def partClient(irc, client, channel, reason=None):
"""Sends a part from a PyLink client."""
channel = utils.toLower(irc, channel) channel = utils.toLower(irc, channel)
if not utils.isInternalClient(irc, client): 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) log.error('(%s) Error trying to part client %r to %r (no such pseudoclient exists)', irc.name, client, channel)
@ -106,9 +100,7 @@ def partClient(irc, client, channel, reason=None):
handle_part(irc, client, 'PART', [channel]) handle_part(irc, client, 'PART', [channel])
def quitClient(irc, numeric, reason): def quitClient(irc, numeric, reason):
"""<irc object> <client numeric> """Quits a PyLink client."""
Quits a PyLink PseudoClient."""
if utils.isInternalClient(irc, numeric): if utils.isInternalClient(irc, numeric):
_send(irc, numeric, "QUIT :%s" % reason) _send(irc, numeric, "QUIT :%s" % reason)
removeClient(irc, numeric) removeClient(irc, numeric)
@ -116,22 +108,19 @@ def quitClient(irc, numeric, reason):
raise LookupError("No such PyLink PseudoClient exists.") raise LookupError("No such PyLink PseudoClient exists.")
def messageClient(irc, numeric, target, text): def messageClient(irc, numeric, target, text):
"""<irc object> <client numeric> <text> """Sends a PRIVMSG from a PyLink client."""
Sends PRIVMSG <text> from PyLink client <client numeric>."""
if not utils.isInternalClient(irc, numeric): if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'PRIVMSG %s :%s' % (target, text)) _send(irc, numeric, 'PRIVMSG %s :%s' % (target, text))
def noticeClient(irc, numeric, target, text): def noticeClient(irc, numeric, target, text):
"""<irc object> <client numeric> <text> """Sends a NOTICE from a PyLink client."""
Sends NOTICE <text> from PyLink client <client numeric>."""
if not utils.isInternalClient(irc, numeric): if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'NOTICE %s :%s' % (target, text)) _send(irc, numeric, 'NOTICE %s :%s' % (target, text))
def topicClient(irc, numeric, target, text): def topicClient(irc, numeric, target, text):
"""Sends a ROPIC from a PyLink client."""
if not utils.isInternalClient(irc, numeric): if not utils.isInternalClient(irc, numeric):
raise LookupError('No such PyLink PseudoClient exists.') raise LookupError('No such PyLink PseudoClient exists.')
_send(irc, numeric, 'TOPIC %s :%s' % (target, text)) _send(irc, numeric, 'TOPIC %s :%s' % (target, text))
@ -141,6 +130,7 @@ def topicClient(irc, numeric, target, text):
### HANDLERS ### HANDLERS
def handle_privmsg(irc, source, command, args): def handle_privmsg(irc, source, command, args):
"""Handles incoming PRIVMSG/NOTICE."""
# <- :70MAAAAAA PRIVMSG #dev :afasfsa # <- :70MAAAAAA PRIVMSG #dev :afasfsa
# <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa # <- :70MAAAAAA NOTICE 0ALAAAAAA :afasfsa
target = args[0] target = args[0]
@ -152,13 +142,18 @@ def handle_privmsg(irc, source, command, args):
handle_notice = handle_privmsg handle_notice = handle_privmsg
def handle_kill(irc, source, command, args): def handle_kill(irc, source, command, args):
"""Handles incoming KILLs."""
killed = args[0] killed = args[0]
# Depending on whether the 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 = irc.users.get(killed)
if data: if data:
removeClient(irc, killed) removeClient(irc, killed)
return {'target': killed, 'text': args[1], 'userdata': data} return {'target': killed, 'text': args[1], 'userdata': data}
def handle_kick(irc, source, command, args): def handle_kick(irc, source, command, args):
"""Handles incoming KICKs."""
# :70MAAAAAA KICK #endlessvoid 70MAAAAAA :some reason # :70MAAAAAA KICK #endlessvoid 70MAAAAAA :some reason
channel = utils.toLower(irc, args[0]) channel = utils.toLower(irc, args[0])
kicked = args[1] kicked = args[1]
@ -166,24 +161,29 @@ def handle_kick(irc, source, command, args):
return {'channel': channel, 'target': kicked, 'text': args[2]} return {'channel': channel, 'target': kicked, 'text': args[2]}
def handle_error(irc, numeric, command, args): def handle_error(irc, numeric, command, args):
"""Handles ERROR messages - these mean that our uplink has disconnected us!"""
irc.connected.clear() irc.connected.clear()
raise ProtocolError('Received an ERROR, killing!') raise ProtocolError('Received an ERROR, disconnecting!')
def handle_nick(irc, numeric, command, args): def handle_nick(irc, numeric, command, args):
"""Handles incoming NICK changes."""
# <- :70MAAAAAA NICK GL-devel 1434744242 # <- :70MAAAAAA NICK GL-devel 1434744242
oldnick = irc.users[numeric].nick oldnick = irc.users[numeric].nick
newnick = irc.users[numeric].nick = args[0] newnick = irc.users[numeric].nick = args[0]
return {'newnick': newnick, 'oldnick': oldnick, 'ts': int(args[1])} return {'newnick': newnick, 'oldnick': oldnick, 'ts': int(args[1])}
def handle_quit(irc, numeric, command, args): def handle_quit(irc, numeric, command, args):
"""Handles incoming QUITs."""
# <- :1SRAAGB4T QUIT :Quit: quit message goes here # <- :1SRAAGB4T QUIT :Quit: quit message goes here
removeClient(irc, numeric) removeClient(irc, numeric)
return {'text': args[0]} return {'text': args[0]}
def handle_save(irc, numeric, command, args): def handle_save(irc, numeric, command, args):
# This is used to handle nick collisions. Here, the client Derp_ already exists, """Handles incoming SAVE messages, used to handle nick collisions."""
# so trying to change nick to it will cause a nick collision. On InspIRCd, # In this below example, the client Derp_ already exists,
# this will simply set the collided user's nick to its UID. # 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
# nick to its UID.
# <- :70MAAAAAA PRIVMSG 0AL000001 :nickclient PyLink Derp_ # <- :70MAAAAAA PRIVMSG 0AL000001 :nickclient PyLink Derp_
# -> :0AL000001 NICK Derp_ 1433728673 # -> :0AL000001 NICK Derp_ 1433728673
@ -194,6 +194,7 @@ def handle_save(irc, numeric, command, args):
return {'target': user, 'ts': int(args[1]), 'oldnick': oldnick} return {'target': user, 'ts': int(args[1]), 'oldnick': oldnick}
def handle_squit(irc, numeric, command, args): def handle_squit(irc, numeric, command, args):
"""Handles incoming SQUITs (netsplits)."""
# :70M SQUIT 1ML :Server quit by GL!gl@0::1 # :70M SQUIT 1ML :Server quit by GL!gl@0::1
split_server = args[0] split_server = args[0]
affected_users = [] affected_users = []
@ -214,6 +215,8 @@ def handle_squit(irc, numeric, command, args):
return {'target': split_server, 'users': affected_users} return {'target': split_server, 'users': affected_users}
def handle_mode(irc, numeric, command, args): def handle_mode(irc, 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 # In InspIRCd, MODE is used for setting user modes and
# FMODE is used for channel modes: # FMODE is used for channel modes:
# <- :70MAAAAAA MODE 70MAAAAAA -i+xc # <- :70MAAAAAA MODE 70MAAAAAA -i+xc
@ -224,6 +227,8 @@ def handle_mode(irc, numeric, command, args):
return {'target': target, 'modes': changedmodes} return {'target': target, 'modes': changedmodes}
def handle_topic(irc, numeric, command, args): def handle_topic(irc, numeric, command, args):
"""Handles incoming TOPIC changes from clients. For topic bursts,
TB (TS6/charybdis) and FTOPIC (InspIRCd) are used instead."""
# <- :70MAAAAAA TOPIC #test :test # <- :70MAAAAAA TOPIC #test :test
channel = utils.toLower(irc, args[0]) channel = utils.toLower(irc, args[0])
topic = args[1] topic = args[1]
@ -233,6 +238,7 @@ def handle_topic(irc, numeric, command, args):
return {'channel': channel, 'setter': numeric, 'ts': ts, 'topic': topic} return {'channel': channel, 'setter': numeric, 'ts': ts, 'topic': topic}
def handle_part(irc, source, command, args): def handle_part(irc, source, command, args):
"""Handles incoming PART commands."""
channels = utils.toLower(irc, args[0]).split(',') channels = utils.toLower(irc, args[0]).split(',')
for channel in channels: for channel in channels:
# We should only get PART commands for channels that exist, right?? # We should only get PART commands for channels that exist, right??