3
0
mirror of https://github.com/jlu5/PyLink.git synced 2025-10-15 16:17:25 +02:00

protocols/unreal: class()-ify everything

This commit is contained in:
James Lu 2015-09-10 18:41:01 -07:00
parent da1b101bc4
commit ca3372b0af

View File

@ -8,64 +8,77 @@ 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 ts6_common import parseArgs, removeClient, _send, messageClient, noticeClient
from ts6_common import handle_quit, handle_part, handle_nick, handle_kill
from classes import * from classes import *
from ts6_common import TS6BaseProtocol
casemapping = 'ascii' class UnrealProtocol(TS6BaseProtocol):
proto_ver = 2351 def __init__(self, irc):
super(UnrealProtocol, self).__init__(irc)
# Set our case mapping (rfc1459 maps "\" and "|" together, for example".
self.casemapping = 'ascii'
self.proto_ver = 2351
self.hook_map = {}
hook_map = {}
### OUTGOING COMMAND FUNCTIONS self.caps = {}
self._unrealCmodes = {'l': 'limit', 'c': 'blockcolor', 'G': 'censor',
'D': 'delayjoin', 'n': 'noextmsg', 's': 'secret',
'T': 'nonotice', 'z': 'sslonly', 'b': 'ban', 'V': 'noinvite',
'Z': 'issecure', 'r': 'registered', 'N': 'nonick',
'e': 'banexception', 'R': 'regonly', 'M': 'regmoderated',
'p': 'private', 'Q': 'nokick', 'P': 'permanent', 'k': 'key',
'C': 'noctcp', 'O': 'operonly', 'S': 'stripcolor',
'm': 'moderated', 'K': 'noknock', 'o': 'op', 'v': 'voice',
'I': 'invex', 't': 'topiclock'}
self._neededCaps = ["VL", "SID", "CHANMODES", "NOQUIT", "SJ3"]
def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(), ### OUTGOING COMMAND FUNCTIONS
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=None, ip='0.0.0.0', realname=None, ts=None, opertype=None):
server = server or irc.sid server = server or self.irc.sid
if not utils.isInternalServer(irc, server): if not utils.isInternalServer(self.irc, server):
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server) raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
# Unreal 3.4 uses TS6-style UIDs. They don't start from AAAAAA like other IRCd's # Unreal 3.4 uses TS6-style UIDs. They don't start from AAAAAA like other IRCd's
# do, but we can do that fine... # do, but we can do that fine...
if server not in irc.uidgen: if server not in self.irc.uidgen:
irc.uidgen[server] = utils.TS6UIDGenerator(server) self.irc.uidgen[server] = utils.TS6UIDGenerator(server)
uid = irc.uidgen[server].next_uid() uid = self.irc.uidgen[server].next_uid()
ts = ts or int(time.time()) ts = ts or int(time.time())
realname = realname or irc.botdata['realname'] realname = realname or self.irc.botdata['realname']
realhost = realhost or host realhost = realhost or host
raw_modes = utils.joinModes(modes) 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) realhost=realhost, ip=ip)
utils.applyModes(irc, uid, modes) utils.applyModes(self.irc, uid, modes)
irc.servers[server].users.add(uid) self.irc.servers[server].users.add(uid)
# <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname # <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname
_send(irc, server, "UID {nick} 0 {ts} {ident} {realhost} {uid} 0 {modes} " self._send(server, "UID {nick} 0 {ts} {ident} {realhost} {uid} 0 {modes} "
"* {host} * :{realname}".format(ts=ts, host=host, "* {host} * :{realname}".format(ts=ts, host=host,
nick=nick, ident=ident, uid=uid, nick=nick, ident=ident, uid=uid,
modes=raw_modes, realname=realname, modes=raw_modes, realname=realname,
realhost=realhost)) realhost=realhost))
return u return u
def joinClient(irc, client, channel): def joinClient(self, client, channel):
pass pass
def pingServer(irc, source=None, target=None): def pingServer(self, source=None, target=None):
source = source or irc.sid source = source or self.irc.sid
target = target or irc.uplink target = target or self.irc.uplink
if not (target is None or source is None): if not (target is None or source is None):
_send(irc, source, 'PING %s %s' % (irc.servers[source].name, irc.servers[target].name)) self._send(source, 'PING %s %s' % (self.irc.servers[source].name, self.irc.servers[target].name))
### HANDLERS ### HANDLERS
def connect(irc): def connect(self):
ts = irc.start_ts ts = self.irc.start_ts
irc.caps = [] self.irc.prefixmodes = {'q': '~', 'a': '&', 'o': '@', 'h': '%', 'v': '+'}
irc.prefixmodes = {'q': '~', 'a': '&', 'o': '@', 'h', '%', 'v': '+'} ### XXX: fill out self.irc.umodes
### XXX: fill out irc.umodes
f = irc.send f = self.irc.send
host = irc.serverdata["hostname"] host = self.irc.serverdata["hostname"]
f('PASS :%s' % irc.serverdata["sendpass"]) f('PASS :%s' % self.irc.serverdata["sendpass"])
# https://github.com/unrealircd/unrealircd/blob/2f8cb55e/doc/technical/protoctl.txt # https://github.com/unrealself.ircd/unrealself.ircd/blob/2f8cb55e/doc/technical/protoctl.txt
# We support the following protocol features: # We support the following protocol features:
# SJ3 - extended SJOIN # SJ3 - extended SJOIN
# NOQUIT - QUIT messages aren't sent for all users in a netsplit # NOQUIT - QUIT messages aren't sent for all users in a netsplit
@ -75,22 +88,22 @@ def connect(irc):
# UMODE2 - used for users setting modes on themselves (one less argument needed) # UMODE2 - used for users setting modes on themselves (one less argument needed)
# EAUTH - Early auth? (Unreal 3.4 linking protocol) # EAUTH - Early auth? (Unreal 3.4 linking protocol)
# ~~NICKIP - sends the IP in the NICK/UID command~~ Doesn't work with SID/UID support # ~~NICKIP - sends the IP in the NICK/UID command~~ Doesn't work with SID/UID support
f('PROTOCTL SJ3 NOQUIT NICKv2 VL UMODE2 PROTOCTL EAUTH=%s SID=%s' % (irc.serverdata["hostname"], irc.sid)) f('PROTOCTL SJ3 NOQUIT NICKv2 VL UMODE2 PROTOCTL EAUTH=%s SID=%s' % (self.irc.serverdata["hostname"], self.irc.sid))
sdesc = irc.serverdata.get('serverdesc') or irc.botdata['serverdesc'] sdesc = self.irc.serverdata.get('serverdesc') or self.irc.botdata['serverdesc']
f('SERVER %s 1 U%s-h6e-%s :%s' % (host, proto_ver, irc.sid, sdesc)) f('SERVER %s 1 U%s-h6e-%s :%s' % (host, self.proto_ver, self.irc.sid, sdesc))
# Now, we wait until remote sends its NETINFO command (handle_netinfo), # Now, we wait until remote sends its NETINFO command (handle_netinfo),
# so we can find and use a matching netname, preventing netname mismatch # so we can find and use a matching netname, preventing netname mismatch
# errors. # errors.
def handle_netinfo(irc, numeric, command, args): def handle_netinfo(self, numeric, command, args):
# <- NETINFO maxglobal currenttime protocolversion cloakhash 0 0 0 :networkname # <- NETINFO maxglobal currenttime protocolversion cloakhash 0 0 0 :networkname
# "maxglobal" is the amount of maximum global users we've seen so far. # "maxglobal" is the amount of maximum global users we've seen so far.
# We'll just set it to 1 (the PyLink client), since this is completely # We'll just set it to 1 (the PyLink client), since this is completely
# arbitrary. # arbitrary.
irc.send('NETINFO 1 %s %s * 0 0 0 :%s' % (irc.start_ts, proto_ver, args[-1])) self.irc.send('NETINFO 1 %s %s * 0 0 0 :%s' % (self.irc.start_ts, self.proto_ver, args[-1]))
_send(irc, irc.sid, 'EOS') self._send(self.irc.sid, 'EOS')
def handle_uid(irc, numeric, command, args): def handle_uid(self, numeric, command, args):
# <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname # <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname
# <- :001 UID GL| 0 1441389007 gl 10.120.0.6 001ZO8F03 0 +iwx * 391A9CB9.26A16454.D9847B69.IP CngABg== :realname # <- :001 UID GL| 0 1441389007 gl 10.120.0.6 001ZO8F03 0 +iwx * 391A9CB9.26A16454.D9847B69.IP CngABg== :realname
# arguments: nick, number???, ts, ident, real-host, UID, number???, modes, # arguments: nick, number???, ts, ident, real-host, UID, number???, modes,
@ -108,38 +121,38 @@ def handle_uid(irc, numeric, command, args):
# NICKIP doesn't seem to work for the UID command... # NICKIP doesn't seem to work for the UID command...
ip = "0.0.0.0" ip = "0.0.0.0"
realname = args[-1] realname = args[-1]
irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip) self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname, realhost, ip)
parsedmodes = utils.parseModes(irc, uid, [modestring]) parsedmodes = utils.parseModes(self.irc, uid, [modestring])
utils.applyModes(irc, uid, parsedmodes) utils.applyModes(self.irc, uid, parsedmodes)
irc.servers[numeric].users.add(uid) self.irc.servers[numeric].users.add(uid)
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip} return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
def handle_pass(irc, numeric, command, args): def handle_pass(self, numeric, command, args):
# <- PASS :abcdefg # <- PASS :abcdefg
if args[0] != irc.serverdata['recvpass']: if args[0] != self.irc.serverdata['recvpass']:
raise ProtocolError("Error: RECVPASS from uplink does not match configuration!") raise ProtocolError("Error: RECVPASS from uplink does not match configuration!")
def handle_ping(irc, numeric, command, args): def handle_ping(self, numeric, command, args):
if numeric == irc.uplink: if numeric == self.irc.uplink:
irc.send('PONG %s :%s' % (irc.serverdata['hostname'], args[-1])) self.irc.send('PONG %s :%s' % (self.irc.serverdata['hostname'], args[-1]))
def handle_pong(irc, source, command, args): def handle_pong(self, source, command, args):
log.debug('(%s) Ping received from %s for %s.', irc.name, source, args[-1]) log.debug('(%s) Ping received from %s for %s.', self.irc.name, source, args[-1])
if source in (irc.uplink, irc.servers[irc.uplink].name) and args[-1] == irc.serverdata['hostname']: if source in (self.irc.uplink, self.irc.servers[self.irc.uplink].name) and args[-1] == self.irc.serverdata['hostname']:
log.debug('(%s) Set irc.lastping.', irc.name) log.debug('(%s) Set self.irc.lastping.', self.irc.name)
irc.lastping = time.time() self.irc.lastping = time.time()
def handle_server(irc, numeric, command, args): def handle_server(self, numeric, command, args):
# <- SERVER unreal.midnight.vpn 1 :U2351-Fhin6OoEM UnrealIRCd test server # <- SERVER unreal.midnight.vpn 1 :U2351-Fhin6OoEM UnrealIRCd test server
sname = args[0] sname = args[0]
# TODO: handle introductions for other servers # TODO: handle introductions for other servers
if numeric == irc.uplink: if numeric == self.irc.uplink:
for cap in _neededCaps: for cap in self._neededCaps:
if cap not in irc.protodata: if cap not in self.caps:
raise ProtocolError("Not all required capabilities were met " raise ProtocolError("Not all required capabilities were met "
"by the remote server. Your version of UnrealIRCd " "by the remote server. Your version of UnrealIRCd "
"is probably too old! (Got: %s, needed: %s)" % "is probably too old! (Got: %s, needed: %s)" %
(sorted(irc.protodata.keys()), (sorted(self.caps.keys()),
sorted(_neededCaps))) sorted(_neededCaps)))
sdesc = args[-1].split(" ") sdesc = args[-1].split(" ")
# Get our protocol version :) # Get our protocol version :)
@ -154,62 +167,51 @@ def handle_server(irc, numeric, command, args):
if protover < 2351: if protover < 2351:
raise ProtocolError("Protocol version too old! (needs at least 2351 " raise ProtocolError("Protocol version too old! (needs at least 2351 "
"(Unreal 3.4-beta1/2), got %s)" % protover) "(Unreal 3.4-beta1/2), got %s)" % protover)
irc.servers[numeric] = IrcServer(None, sname) self.irc.servers[numeric] = IrcServer(None, sname)
else: else:
raise NotImplementedError raise NotImplementedError
_unrealCmodes = {'l': 'limit', 'c': 'blockcolor', 'G': 'censor', def handle_protoctl(self, numeric, command, args):
'D': 'delayjoin', 'n': 'noextmsg', 's': 'secret',
'T': 'nonotice', 'z': 'sslonly', 'b': 'ban', 'V': 'noinvite',
'Z': 'issecure', 'r': 'registered', 'N': 'nonick',
'e': 'banexception', 'R': 'regonly', 'M': 'regmoderated',
'p': 'private', 'Q': 'nokick', 'P': 'permanent', 'k': 'key',
'C': 'noctcp', 'O': 'operonly', 'S': 'stripcolor',
'm': 'moderated', 'K': 'noknock', 'o': 'op', 'v': 'voice',
'I': 'invex', 't': 'topiclock'}
_neededCaps = ["VL", "SID", "CHANMODES", "NOQUIT", "SJ3"]
def handle_protoctl(irc, numeric, command, args):
# <- PROTOCTL NOQUIT NICKv2 SJOIN SJOIN2 UMODE2 VL SJ3 TKLEXT TKLEXT2 NICKIP ESVID # <- PROTOCTL NOQUIT NICKv2 SJOIN SJOIN2 UMODE2 VL SJ3 TKLEXT TKLEXT2 NICKIP ESVID
# <- PROTOCTL CHANMODES=beI,k,l,psmntirzMQNRTOVKDdGPZSCc NICKCHARS= SID=001 MLOCK TS=1441314501 EXTSWHOIS # <- PROTOCTL CHANMODES=beI,k,l,psmntirzMQNRTOVKDdGPZSCc NICKCHARS= SID=001 MLOCK TS=1441314501 EXTSWHOIS
irc.caps += args
for cap in args: for cap in args:
if cap.startswith('SID'): if cap.startswith('SID'):
irc.uplink = cap.split('=', 1)[1] self.irc.uplink = cap.split('=', 1)[1]
irc.protodata['SID'] = True self.caps['SID'] = True
elif cap.startswith('CHANMODES'): elif cap.startswith('CHANMODES'):
cmodes = cap.split('=', 1)[1] cmodes = cap.split('=', 1)[1]
irc.cmodes['*A'], irc.cmodes['*B'], irc.cmodes['*C'], irc.cmodes['*D'] = cmodes.split(',') self.irc.cmodes['*A'], self.irc.cmodes['*B'], self.irc.cmodes['*C'], self.irc.cmodes['*D'] = cmodes.split(',')
for m in cmodes: for m in cmodes:
if m in _unrealCmodes: if m in self._unrealCmodes:
irc.cmodes[_unrealCmodes[m]] = m self.irc.cmodes[self._unrealCmodes[m]] = m
irc.protodata['CHANMODES'] = True self.caps['CHANMODES'] = True
# Because more than one PROTOCTL line is sent, we have to delay the # Because more than one PROTOCTL line is sent, we have to delay the
# check to see whether our needed capabilities are all there... # check to see whether our needed capabilities are all there...
# That's done by handle_server(), which comes right after PROTOCTL. # That's done by handle_server(), which comes right after PROTOCTL.
elif cap == 'VL': elif cap == 'VL':
irc.protodata['VL'] = True self.caps['VL'] = True
elif cap == 'NOQUIT': elif cap == 'NOQUIT':
irc.protodata['NOQUIT'] = True self.caps['NOQUIT'] = True
elif cap == 'SJ3': elif cap == 'SJ3':
irc.protodata['SJ3'] = True self.caps['SJ3'] = True
def _sidToServer(irc, sname): def _sidToServer(self, sname):
"""<irc object> <server name> """<self.irc object> <server name>
Returns the SID of a server named <server name>, if present.""" Returns the SID of a server named <server name>, if present."""
nick = sname.lower() nick = sname.lower()
for k, v in irc.servers.items(): for k, v in self.irc.servers.items():
if v.name.lower() == nick: if v.name.lower() == nick:
return k return k
def _convertNick(irc, target): def _convertNick(self, target):
target = utils.nickToUid(irc, target) or target target = utils.nickToUid(self.irc, target) or target
if target not in irc.users: if target not in self.irc.users:
log.warning("(%s) Possible desync? Got command target %s, who " log.warning("(%s) Possible desync? Got command target %s, who "
"isn't in our user list!") "isn't in our user list!")
return target return target
def handle_events(irc, data): def handle_events(self, data):
# Unreal's protocol has three styles of commands, @servernumeric, :user, and plain commands. # Unreal's protocol has three styles of commands, @servernumeric, :user, and plain commands.
# e.g. NICK introduction looks like: # e.g. NICK introduction looks like:
# <- NICK nick hopcount timestamp username hostname server service-identifier-token +usermodes virtualhost :realname # <- NICK nick hopcount timestamp username hostname server service-identifier-token +usermodes virtualhost :realname
@ -219,7 +221,7 @@ def handle_events(irc, data):
# <- @servernumeric SJOIN <ts> <chname> [<modes>] [<mode para> ...] :<[[*~@%+]member] [&"ban/except] ...> # <- @servernumeric SJOIN <ts> <chname> [<modes>] [<mode para> ...] :<[[*~@%+]member] [&"ban/except] ...>
# Same deal as TS6 with :'s indicating a long argument lasting to the # Same deal as TS6 with :'s indicating a long argument lasting to the
# end of the line. # end of the line.
args = parseArgs(data.split(" ")) args = self.parseArgs(data.split(" "))
# Message starts with a SID/UID prefix. # Message starts with a SID/UID prefix.
if args[0][0] in ':@': if args[0][0] in ':@':
sender = args[0].lstrip(':@') sender = args[0].lstrip(':@')
@ -227,44 +229,46 @@ def handle_events(irc, data):
args = args[2:] args = args[2:]
# If the sender isn't in UID format, try to convert it automatically. # If the sender isn't in UID format, try to convert it automatically.
# Unreal's protocol isn't quite consistent with this yet! # Unreal's protocol isn't quite consistent with this yet!
numeric = _sidToServer(irc, sender) or utils.nickToUid(irc, sender) or \ numeric = self._sidToServer(sender) or utils.nickToUid(self.irc, sender) or \
sender sender
else: else:
# Raw command without an explicit sender; assume it's being sent by our uplink. # Raw command without an explicit sender; assume it's being sent by our uplink.
numeric = irc.uplink numeric = self.irc.uplink
command = args[0] command = args[0]
args = args[1:] args = args[1:]
try: try:
func = globals()['handle_'+command.lower()] func = getattr(self, 'handle_'+command.lower())
except KeyError: # unhandled command except AttributeError: # unhandled command
pass pass
else: else:
parsed_args = func(irc, numeric, command, args) parsed_args = func(numeric, command, args)
if parsed_args is not None: if parsed_args is not None:
return [numeric, command, parsed_args] return [numeric, command, parsed_args]
def handle_privmsg(irc, source, command, args): def handle_privmsg(self, source, command, args):
# Convert nicks to UIDs, where they exist. # Convert nicks to UIDs, where they exist.
target = _convertNick(irc, args[0]) target = self._convertNick(args[0])
# We use lowercase channels internally, but uppercase UIDs. # We use lowercase channels internally, but uppercase UIDs.
if utils.isChannel(target): if utils.isChannel(target):
target = utils.toLower(irc, target) target = utils.toLower(self.irc, target)
return {'target': target, 'text': args[1]} return {'target': target, 'text': args[1]}
handle_notice = handle_privmsg handle_notice = handle_privmsg
def handle_join(irc, numeric, command, args): def handle_join(self, numeric, command, args):
# <- GL JOIN #pylink,#test # <- GL JOIN #pylink,#test
for channel in args[0].split(','): for channel in args[0].split(','):
c = irc.channels[channel] c = self.irc.channels[channel]
if args[0] == '0': if args[0] == '0':
# /join 0; part the user from all channels # /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', log.debug('(%s) Got /join 0 from %r, channel list is %r',
irc.name, numeric, oldchans) self.irc.name, numeric, oldchans)
for ch in oldchans: for ch in oldchans:
irc.channels[ch].users.discard(numeric) self.irc.channels[ch].users.discard(numeric)
irc.users[numeric].channels.discard(ch) self.irc.users[numeric].channels.discard(ch)
return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'} return {'channels': oldchans, 'text': 'Left all channels.', 'parse_as': 'PART'}
# Call hooks manually, because one JOIN command can have multiple channels... # Call hooks manually, because one JOIN command can have multiple channels...
irc.callHooks([numeric, command, {'channel': channel, 'users': [numeric], 'modes': self.irc.callHooks([numeric, command, {'channel': channel, 'users': [numeric], 'modes':
c.modes, 'ts': c.ts}]) c.modes, 'ts': c.ts}])
Class = UnrealProtocol