3
0
mirror of https://github.com/jlu5/PyLink.git synced 2025-05-03 13:17:32 +02:00

Merge branch 'wip/protocol-caps' into devel

This commit is contained in:
James Lu 2017-03-23 23:54:46 -07:00
commit c894beed2e
11 changed files with 40 additions and 23 deletions

View File

@ -1409,6 +1409,12 @@ class Protocol():
return real_args return real_args
def hasCap(self, capab):
"""
Returns whether this protocol module instance has the requested capability.
"""
return capab.lower() in self.protocol_caps
def removeClient(self, numeric): def removeClient(self, numeric):
"""Internal function to remove a client from our internal state.""" """Internal function to remove a client from our internal state."""
for c, v in self.irc.channels.copy().items(): for c, v in self.irc.channels.copy().items():

View File

@ -14,6 +14,11 @@ def spawn_service(irc, source, command, args):
# Service name # Service name
name = args['name'] name = args['name']
if name != 'pylink' and not irc.proto.hasCap('can-spawn-clients'):
log.debug("(%s) Not spawning service %s because the server doesn't support spawning clients",
irc.name, name)
return
# Get the ServiceBot object. # Get the ServiceBot object.
sbot = world.services[name] sbot = world.services[name]
@ -26,10 +31,6 @@ def spawn_service(irc, source, command, args):
nick = irc.serverdata.get("%s_nick" % name) or conf.conf.get(name, {}).get('nick') or sbot.nick or name nick = irc.serverdata.get("%s_nick" % name) or conf.conf.get(name, {}).get('nick') or sbot.nick or name
ident = irc.serverdata.get("%s_ident" % name) or conf.conf.get(name, {}).get('ident') or sbot.ident or name ident = irc.serverdata.get("%s_ident" % name) or conf.conf.get(name, {}).get('ident') or sbot.ident or name
if name != 'pylink' and irc.protoname == 'clientbot':
# Prefix service bots spawned on Clientbot to prevent possible nick collisions.
nick = 'PyLinkService@' + nick
# TODO: make this configurable? # TODO: make this configurable?
host = irc.hostname() host = irc.hostname()

View File

@ -108,7 +108,7 @@ def showchan(irc, source, args):
if c.topic: if c.topic:
f('\x02Channel topic\x02: %s' % c.topic) f('\x02Channel topic\x02: %s' % c.topic)
if irc.protoname != 'clientbot': if irc.proto.hasCap('has-ts'):
# Clientbot-specific hack: don't show channel TS because it's not properly tracked. # Clientbot-specific hack: don't show channel TS because it's not properly tracked.
f('\x02Channel creation time\x02: %s (%s)' % (ctime(c.ts), c.ts)) f('\x02Channel creation time\x02: %s (%s)' % (ctime(c.ts), c.ts))

View File

@ -112,13 +112,12 @@ def normalize_nick(irc, netname, nick, times_tagged=0, uid=''):
log.debug('(%s) relay.normalize_nick: using %r as separator.', irc.name, separator) log.debug('(%s) relay.normalize_nick: using %r as separator.', irc.name, separator)
orig_nick = nick orig_nick = nick
protoname = irc.protoname
maxnicklen = irc.maxnicklen maxnicklen = irc.maxnicklen
# Charybdis, IRCu, etc. don't allow / in nicks, and will SQUIT with a protocol # Charybdis, IRCu, etc. don't allow / in nicks, and will SQUIT with a protocol
# violation if it sees one. Or it might just ignore the client introduction and # violation if it sees one. Or it might just ignore the client introduction and
# cause bad desyncs. # cause bad desyncs.
protocol_allows_slashes = protoname.startswith(('insp', 'unreal', 'clientbot')) or \ protocol_allows_slashes = irc.proto.hasCap('slash-in-nicks') or \
irc.serverdata.get('relay_force_slashes') irc.serverdata.get('relay_force_slashes')
if '/' not in separator or not protocol_allows_slashes: if '/' not in separator or not protocol_allows_slashes:
@ -177,12 +176,12 @@ def normalize_host(irc, host):
log.debug('(%s) relay.normalize_host: IRCd=%s, host=%s', irc.name, irc.protoname, host) log.debug('(%s) relay.normalize_host: IRCd=%s, host=%s', irc.name, irc.protoname, host)
allowed_chars = string.ascii_letters + string.digits + '-.:' allowed_chars = string.ascii_letters + string.digits + '-.:'
if irc.protoname in ('inspircd', 'ts6', 'clientbot', 'nefarious'): if irc.proto.hasCap('slash-in-hosts'):
# UnrealIRCd and IRCd-Hybrid don't allow slashes in hostnames # UnrealIRCd and IRCd-Hybrid don't allow slashes in hostnames
allowed_chars += '/' allowed_chars += '/'
if irc.protoname in ('inspircd', 'clientbot', 'nefarious', 'unreal'): if irc.proto.hasCap('underscore-in-hosts'):
# The above IRCds allow _ in hostnames, while TS6-like IRCds do not. # Most IRCds allow _ in hostnames, but hybrid/charybdis/ratbox IRCds do not.
allowed_chars += '_' allowed_chars += '_'
for char in host: for char in host:
@ -648,7 +647,7 @@ def relay_joins(irc, channel, users, ts, burst=True):
# Fetch the known channel TS and all the prefix modes for each user. This ensures # Fetch the known channel TS and all the prefix modes for each user. This ensures
# the different sides of the relay are merged properly. # the different sides of the relay are merged properly.
if irc.protoname == 'clientbot': if not irc.proto.hasCap('has-ts'):
# Special hack for clientbot: just use the remote's modes so mode changes # Special hack for clientbot: just use the remote's modes so mode changes
# take precendence. protocols/clientbot does not track channel TS. # take precendence. protocols/clientbot does not track channel TS.
ts = remoteirc.channels[remotechan].ts ts = remoteirc.channels[remotechan].ts
@ -783,7 +782,7 @@ def get_supported_cmodes(irc, remoteirc, channel, modes):
"for network %r.", "for network %r.",
irc.name, modechar, arg, remoteirc.name) irc.name, modechar, arg, remoteirc.name)
if irc.protoname == 'clientbot' and irc.pseudoclient and arg == irc.pseudoclient.uid: if (not irc.proto.hasCap('can-spawn-clients')) and irc.pseudoclient and arg == irc.pseudoclient.uid:
# Skip modesync on the main PyLink client. # Skip modesync on the main PyLink client.
log.debug("(%s) relay.get_supported_cmodes: filtering prefix change (%r, %r) on Clientbot relayer", log.debug("(%s) relay.get_supported_cmodes: filtering prefix change (%r, %r) on Clientbot relayer",
irc.name, name, arg) irc.name, name, arg)
@ -876,7 +875,7 @@ def handle_relay_whois(irc, source, command, args):
# Send account information if told to and the target is logged in. # Send account information if told to and the target is logged in.
wreply(330, "%s :is logged in (on %s) as" % (realuser.services_account, netname)) wreply(330, "%s :is logged in (on %s) as" % (realuser.services_account, netname))
if checkSendKey('whois_show_server') and realirc.protoname != 'clientbot': if checkSendKey('whois_show_server') and realirc.proto.hasCap('can-track-servers'):
wreply(320, ":is actually connected via the following server:") wreply(320, ":is actually connected via the following server:")
realserver = realirc.getServer(uid) realserver = realirc.getServer(uid)
realserver = realirc.servers[realserver] realserver = realirc.servers[realserver]
@ -1017,7 +1016,7 @@ def handle_part(irc, numeric, command, args):
if numeric == irc.pseudoclient.uid: if numeric == irc.pseudoclient.uid:
# For clientbot: treat forced parts to the bot as clearchan, and attempt to rejoin only # For clientbot: treat forced parts to the bot as clearchan, and attempt to rejoin only
# if it affected a relay. # if it affected a relay.
if irc.protoname == 'clientbot': if not irc.proto.hasCap('can-spawn-clients'):
for channel in [c for c in channels if get_relay((irc.name, c))]: for channel in [c for c in channels if get_relay((irc.name, c))]:
for user in irc.channels[channel].users: for user in irc.channels[channel].users:
if (not irc.isInternalClient(user)) and (not isRelayClient(irc, user)): if (not irc.isInternalClient(user)) and (not isRelayClient(irc, user)):
@ -1087,8 +1086,9 @@ def handle_messages(irc, numeric, command, args):
# No relay clone exists for the sender; route the message through our # No relay clone exists for the sender; route the message through our
# main client (or SID for notices). # main client (or SID for notices).
# Skip "from:" formatting for servers; it's messy with longer hostnames # Skip "from:" formatting for servers; it's messy with longer hostnames.
if numeric not in irc.servers: # Also skip this formatting for servicebot relaying.
if numeric not in irc.servers and not irc.getServiceBot(numeric):
displayedname = irc.getFriendlyName(numeric) displayedname = irc.getFriendlyName(numeric)
real_text = '<%s/%s> %s' % (displayedname, irc.name, text) real_text = '<%s/%s> %s' % (displayedname, irc.name, text)
else: else:
@ -1148,7 +1148,7 @@ def handle_messages(irc, numeric, command, args):
return return
remoteirc = world.networkobjects[homenet] remoteirc = world.networkobjects[homenet]
if remoteirc.protoname == 'clientbot' and not conf.conf.get('relay', {}).get('allow_clientbot_pms'): if (not remoteirc.proto.hasCap('can-spawn-clients')) and not conf.conf.get('relay', {}).get('allow_clientbot_pms'):
irc.msg(numeric, 'Private messages to users connected via Clientbot have ' irc.msg(numeric, 'Private messages to users connected via Clientbot have '
'been administratively disabled.', notice=True) 'been administratively disabled.', notice=True)
return return
@ -1179,7 +1179,7 @@ def handle_kick(irc, source, command, args):
relay = get_relay((irc.name, channel)) relay = get_relay((irc.name, channel))
# Special case for clientbot: treat kicks to the PyLink service bot as channel clear. # Special case for clientbot: treat kicks to the PyLink service bot as channel clear.
if irc.protoname == 'clientbot' and irc.pseudoclient and target == irc.pseudoclient.uid: if (not irc.proto.hasCap('can-spawn-clients')) and irc.pseudoclient and target == irc.pseudoclient.uid:
for user in irc.channels[channel].users: for user in irc.channels[channel].users:
if (not irc.isInternalClient(user)) and (not isRelayClient(irc, user)): if (not irc.isInternalClient(user)) and (not isRelayClient(irc, user)):
reason = "Clientbot kicked by %s (Reason: %s)" % (irc.getFriendlyName(source), text) reason = "Clientbot kicked by %s (Reason: %s)" % (irc.getFriendlyName(source), text)
@ -1246,7 +1246,7 @@ def handle_kick(irc, source, command, args):
# common channels with the target relay network. # common channels with the target relay network.
rsid = get_remote_sid(remoteirc, irc) rsid = get_remote_sid(remoteirc, irc)
log.debug('(%s) relay.handle_kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan, rsid, kicker, irc.name) log.debug('(%s) relay.handle_kick: Kicking %s from channel %s via %s on behalf of %s/%s', irc.name, real_target, remotechan, rsid, kicker, irc.name)
if irc.protoname == 'clientbot': if not irc.proto.hasCap('can-spawn-clients'):
# Special case for clientbot: no kick prefixes are needed. # Special case for clientbot: no kick prefixes are needed.
text = args['text'] text = args['text']
else: else:
@ -1582,7 +1582,7 @@ def create(irc, source, args):
if not utils.isChannel(channel): if not utils.isChannel(channel):
irc.error('Invalid channel %r.' % channel) irc.error('Invalid channel %r.' % channel)
return return
if irc.protoname == 'clientbot': if not irc.proto.hasCap('can-host-relay'):
irc.error('Clientbot networks cannot be used to host a relay.') irc.error('Clientbot networks cannot be used to host a relay.')
return return
if source not in irc.channels[channel].users: if source not in irc.channels[channel].users:
@ -1780,7 +1780,7 @@ def link(irc, source, args):
our_ts = irc.channels[localchan].ts our_ts = irc.channels[localchan].ts
their_ts = world.networkobjects[remotenet].channels[args.channel].ts their_ts = world.networkobjects[remotenet].channels[args.channel].ts
if (our_ts < their_ts) and irc.protoname != 'clientbot': if (our_ts < their_ts) and irc.proto.hasCap('has-ts'):
log.debug('(%s) relay: Blocking link request %s%s -> %s%s due to bad TS (%s < %s)', irc.name, log.debug('(%s) relay: Blocking link request %s%s -> %s%s due to bad TS (%s < %s)', irc.name,
irc.name, localchan, remotenet, args.channel, our_ts, their_ts) irc.name, localchan, remotenet, args.channel, our_ts, their_ts)
irc.error("The channel creation date (TS) on %s (%s) is lower than the target " irc.error("The channel creation date (TS) on %s (%s) is lower than the target "

View File

@ -204,7 +204,7 @@ def rpm(irc, source, args):
return return
relay = world.plugins.get('relay') relay = world.plugins.get('relay')
if irc.protoname != 'clientbot': if irc.proto.hasCap('can-spawn-clients'):
irc.error('This command is only supported on Clientbot networks. Try /msg %s <text>' % target) irc.error('This command is only supported on Clientbot networks. Try /msg %s <text>' % target)
return return
elif relay is None: elif relay is None:

View File

@ -78,7 +78,7 @@ def _map(irc, source, args, show_relay=True):
# This is a relay server - display the remote map of the network it represents # This is a relay server - display the remote map of the network it represents
relay_server = serverlist[leaf].remote relay_server = serverlist[leaf].remote
remoteirc = world.networkobjects[relay_server] remoteirc = world.networkobjects[relay_server]
if remoteirc.protoname != 'clientbot': if remoteirc.proto.hasCap('can-track-servers'):
# Only ever show relay subservers once - this prevents infinite loops. # Only ever show relay subservers once - this prevents infinite loops.
showall(remoteirc, remoteirc.sid, hops=hops, is_relay_server=True) showall(remoteirc, remoteirc.sid, hops=hops, is_relay_server=True)

View File

@ -14,6 +14,8 @@ class ClientbotWrapperProtocol(Protocol):
def __init__(self, irc): def __init__(self, irc):
super().__init__(irc) super().__init__(irc)
self.protocol_caps = {'clear-channels-on-leave', 'slash-in-nicks', 'slash-in-hosts', 'underscore-in-hosts'}
self.has_eob = False self.has_eob = False
# Remove conf key checks for those not needed for Clientbot. # Remove conf key checks for those not needed for Clientbot.

View File

@ -13,6 +13,9 @@ from pylinkirc.protocols.ts6_common import *
class InspIRCdProtocol(TS6BaseProtocol): class InspIRCdProtocol(TS6BaseProtocol):
def __init__(self, irc): def __init__(self, irc):
super().__init__(irc) super().__init__(irc)
self.protocol_caps |= {'slash-in-nicks', 'slash-in-hosts', 'underscore-in-hosts'}
# Set our case mapping (rfc1459 maps "\" and "|" together, for example). # Set our case mapping (rfc1459 maps "\" and "|" together, for example).
self.casemapping = 'rfc1459' self.casemapping = 'rfc1459'

View File

@ -35,6 +35,7 @@ def p10b64encode(num, length=2):
class P10SIDGenerator(): class P10SIDGenerator():
def __init__(self, irc): def __init__(self, irc):
self.irc = irc self.irc = irc
try: try:
query = irc.serverdata["sidrange"] query = irc.serverdata["sidrange"]
except (KeyError, ValueError): except (KeyError, ValueError):
@ -74,6 +75,8 @@ class P10Protocol(IRCS2SProtocol):
self.hook_map = {'END_OF_BURST': 'ENDBURST', 'OPMODE': 'MODE', 'CLEARMODE': 'MODE', 'BURST': 'JOIN'} self.hook_map = {'END_OF_BURST': 'ENDBURST', 'OPMODE': 'MODE', 'CLEARMODE': 'MODE', 'BURST': 'JOIN'}
self.protocol_caps |= {'slash-in-hosts', 'underscore-in-hosts'}
def _send(self, source, text): def _send(self, source, text):
self.irc.send("%s %s" % (source, text)) self.irc.send("%s %s" % (source, text))

View File

@ -15,6 +15,7 @@ S2S_BUFSIZE = 510
class TS6Protocol(TS6BaseProtocol): class TS6Protocol(TS6BaseProtocol):
def __init__(self, irc): def __init__(self, irc):
super().__init__(irc) super().__init__(irc)
self.protocol_caps |= {'slash-in-hosts'}
self.casemapping = 'rfc1459' self.casemapping = 'rfc1459'
self.hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE', self.hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE',
'EUID': 'UID', 'RSFNC': 'SVSNICK', 'ETB': 'TOPIC'} 'EUID': 'UID', 'RSFNC': 'SVSNICK', 'ETB': 'TOPIC'}

View File

@ -23,6 +23,7 @@ S2S_BUFSIZE = 427
class UnrealProtocol(TS6BaseProtocol): class UnrealProtocol(TS6BaseProtocol):
def __init__(self, irc): def __init__(self, irc):
super().__init__(irc) super().__init__(irc)
self.protocol_caps |= {'slash-in-nicks', 'underscore-in-hosts'}
# Set our case mapping (rfc1459 maps "\" and "|" together, for example) # Set our case mapping (rfc1459 maps "\" and "|" together, for example)
self.casemapping = 'ascii' self.casemapping = 'ascii'
self.proto_ver = 4000 self.proto_ver = 4000