mirror of
https://github.com/jlu5/PyLink.git
synced 2024-12-18 08:02:51 +01:00
relay: sort code and document most internal functions
This commit is contained in:
parent
6476aefb5f
commit
75de9c6be6
775
plugins/relay.py
775
plugins/relay.py
@ -26,18 +26,33 @@ spawnlocks = defaultdict(threading.RLock)
|
|||||||
savecache = ExpiringDict(max_len=5, max_age_seconds=10)
|
savecache = ExpiringDict(max_len=5, max_age_seconds=10)
|
||||||
killcache = ExpiringDict(max_len=5, max_age_seconds=10)
|
killcache = ExpiringDict(max_len=5, max_age_seconds=10)
|
||||||
|
|
||||||
def relayWhoisHandler(irc, target):
|
### INTERNAL FUNCTIONS
|
||||||
user = irc.users[target]
|
|
||||||
orig = getLocalUser(irc, target)
|
def initializeAll(irc):
|
||||||
if orig:
|
"""Initializes all relay channels for the given IRC object."""
|
||||||
network, remoteuid = orig
|
log.debug('(%s) initializeAll: waiting for world.started', irc.name)
|
||||||
remotenick = world.networkobjects[network].users[remoteuid].nick
|
world.started.wait()
|
||||||
return [320, "%s :is a remote user connected via PyLink Relay. Home "
|
for chanpair, entrydata in db.items():
|
||||||
"network: %s; Home nick: %s" % (user.nick, network,
|
network, channel = chanpair
|
||||||
remotenick)]
|
initializeChannel(irc, channel)
|
||||||
world.whois_handlers.append(relayWhoisHandler)
|
for link in entrydata['links']:
|
||||||
|
network, channel = link
|
||||||
|
initializeChannel(irc, channel)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function, called during plugin loading at start."""
|
||||||
|
loadDB()
|
||||||
|
world.schedulers['relaydb'] = scheduler = sched.scheduler()
|
||||||
|
scheduler.enter(30, 1, exportDB, argument=(True,))
|
||||||
|
# Thread this because exportDB() queues itself as part of its
|
||||||
|
# execution, in order to get a repeating loop.
|
||||||
|
thread = threading.Thread(target=scheduler.run)
|
||||||
|
thread.daemon = True
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def normalizeNick(irc, netname, nick, separator=None, uid=''):
|
def normalizeNick(irc, netname, nick, separator=None, uid=''):
|
||||||
|
"""Creates a normalized nickname for the given nick suitable for
|
||||||
|
introduction to a remote network (as a relay client)."""
|
||||||
separator = separator or irc.serverdata.get('separator') or "/"
|
separator = separator or irc.serverdata.get('separator') or "/"
|
||||||
log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator)
|
log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator)
|
||||||
orig_nick = nick
|
orig_nick = nick
|
||||||
@ -87,6 +102,7 @@ def normalizeNick(irc, netname, nick, separator=None, uid=''):
|
|||||||
return nick
|
return nick
|
||||||
|
|
||||||
def loadDB():
|
def loadDB():
|
||||||
|
"""Loads the relay database, creating a new one if this fails."""
|
||||||
global db
|
global db
|
||||||
try:
|
try:
|
||||||
with open(dbname, "rb") as f:
|
with open(dbname, "rb") as f:
|
||||||
@ -97,6 +113,8 @@ def loadDB():
|
|||||||
db = {}
|
db = {}
|
||||||
|
|
||||||
def exportDB(reschedule=False):
|
def exportDB(reschedule=False):
|
||||||
|
"""Exports the relay database, optionally creating a loop to do this
|
||||||
|
automatically."""
|
||||||
scheduler = world.schedulers.get('relaydb')
|
scheduler = world.schedulers.get('relaydb')
|
||||||
if reschedule and scheduler:
|
if reschedule and scheduler:
|
||||||
scheduler.enter(30, 1, exportDB, argument=(True,))
|
scheduler.enter(30, 1, exportDB, argument=(True,))
|
||||||
@ -104,18 +122,6 @@ def exportDB(reschedule=False):
|
|||||||
with open(dbname, 'wb') as f:
|
with open(dbname, 'wb') as f:
|
||||||
pickle.dump(db, f, protocol=4)
|
pickle.dump(db, f, protocol=4)
|
||||||
|
|
||||||
@utils.add_cmd
|
|
||||||
def save(irc, source, args):
|
|
||||||
"""takes no arguments.
|
|
||||||
|
|
||||||
Saves the relay database to disk."""
|
|
||||||
if utils.isOper(irc, source):
|
|
||||||
exportDB()
|
|
||||||
irc.msg(source, 'Done.')
|
|
||||||
else:
|
|
||||||
irc.msg(source, 'Error: You are not authenticated!')
|
|
||||||
return
|
|
||||||
|
|
||||||
def getPrefixModes(irc, remoteirc, channel, user, mlist=None):
|
def getPrefixModes(irc, remoteirc, channel, user, mlist=None):
|
||||||
"""
|
"""
|
||||||
Fetches all prefix modes for a user in a channel that are supported by the
|
Fetches all prefix modes for a user in a channel that are supported by the
|
||||||
@ -137,7 +143,7 @@ def getPrefixModes(irc, remoteirc, channel, user, mlist=None):
|
|||||||
return modes
|
return modes
|
||||||
|
|
||||||
def getRemoteSid(irc, remoteirc):
|
def getRemoteSid(irc, remoteirc):
|
||||||
"""Get the remote server SID representing remoteirc on irc, spawning
|
"""Gets the remote server SID representing remoteirc on irc, spawning
|
||||||
it if it doesn't exist."""
|
it if it doesn't exist."""
|
||||||
with spawnlocks[irc.name]:
|
with spawnlocks[irc.name]:
|
||||||
try:
|
try:
|
||||||
@ -148,6 +154,8 @@ def getRemoteSid(irc, remoteirc):
|
|||||||
return sid
|
return sid
|
||||||
|
|
||||||
def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
||||||
|
"""Gets the UID of the relay client for the given IRC network/user pair,
|
||||||
|
spawning one if it doesn't exist and spawnIfMissing is True."""
|
||||||
# If the user (stored here as {('netname', 'UID'):
|
# If the user (stored here as {('netname', 'UID'):
|
||||||
# {'network1': 'UID1', 'network2': 'UID2'}}) exists, don't spawn it
|
# {'network1': 'UID1', 'network2': 'UID2'}}) exists, don't spawn it
|
||||||
# again!
|
# again!
|
||||||
@ -207,30 +215,20 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
|||||||
relayusers[(irc.name, user)][remoteirc.name] = u
|
relayusers[(irc.name, user)][remoteirc.name] = u
|
||||||
return u
|
return u
|
||||||
|
|
||||||
def handle_operup(irc, numeric, command, args):
|
|
||||||
newtype = args['text'] + '_(remote)'
|
|
||||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
|
||||||
log.debug('(%s) relay.handle_opertype: setting OPERTYPE of %s/%s to %s', irc.name, user, netname, newtype)
|
|
||||||
remoteirc = world.networkobjects[netname]
|
|
||||||
remoteirc.users[user].opertype = newtype
|
|
||||||
utils.add_hook(handle_operup, 'PYLINK_CLIENT_OPERED')
|
|
||||||
|
|
||||||
def getLocalUser(irc, user, targetirc=None):
|
def getLocalUser(irc, user, targetirc=None):
|
||||||
"""<irc object> <pseudoclient uid> [<target irc object>]
|
"""
|
||||||
|
Given the UID of a relay client, returns a tuple of the home network name
|
||||||
|
and original UID of the user it was spawned for.
|
||||||
|
|
||||||
Returns a tuple with the home network name and the UID of the original
|
If targetirc is given, getRemoteUser() is called to get the relay client
|
||||||
user that <pseudoclient uid> was spawned for, where <pseudoclient uid>
|
representing the original user on that target network."""
|
||||||
is the UID of a PyLink relay dummy client.
|
|
||||||
|
|
||||||
If <target irc object> is specified, returns the UID of the pseudoclient
|
|
||||||
representing the original user on the target network, similar to what
|
|
||||||
getRemoteUser() does."""
|
|
||||||
# First, iterate over everyone!
|
# First, iterate over everyone!
|
||||||
try:
|
try:
|
||||||
remoteuser = irc.users[user].remote
|
remoteuser = irc.users[user].remote
|
||||||
except (AttributeError, KeyError):
|
except (AttributeError, KeyError):
|
||||||
remoteuser = None
|
remoteuser = None
|
||||||
log.debug('(%s) getLocalUser: remoteuser set to %r (looking up %s/%s).', irc.name, remoteuser, user, irc.name)
|
log.debug('(%s) getLocalUser: remoteuser set to %r (looking up %s/%s).',
|
||||||
|
irc.name, remoteuser, user, irc.name)
|
||||||
if remoteuser:
|
if remoteuser:
|
||||||
# If targetirc is given, we'll return simply the UID of the user on the
|
# If targetirc is given, we'll return simply the UID of the user on the
|
||||||
# target network, if it exists. Otherwise, we'll return a tuple
|
# target network, if it exists. Otherwise, we'll return a tuple
|
||||||
@ -242,13 +240,17 @@ def getLocalUser(irc, user, targetirc=None):
|
|||||||
# requested; just return the UID then.
|
# requested; just return the UID then.
|
||||||
return remoteuser[1]
|
return remoteuser[1]
|
||||||
# Otherwise, use getRemoteUser to find our UID.
|
# Otherwise, use getRemoteUser to find our UID.
|
||||||
res = getRemoteUser(sourceobj, targetirc, remoteuser[1], spawnIfMissing=False)
|
res = getRemoteUser(sourceobj, targetirc, remoteuser[1],
|
||||||
log.debug('(%s) getLocalUser: targetirc found, getting %r as remoteuser for %r (looking up %s/%s).', irc.name, res, remoteuser[1], user, irc.name)
|
spawnIfMissing=False)
|
||||||
|
log.debug('(%s) getLocalUser: targetirc found, getting %r as '
|
||||||
|
'remoteuser for %r (looking up %s/%s).', irc.name, res,
|
||||||
|
remoteuser[1], user, irc.name)
|
||||||
return res
|
return res
|
||||||
else:
|
else:
|
||||||
return remoteuser
|
return remoteuser
|
||||||
|
|
||||||
def findRelay(chanpair):
|
def findRelay(chanpair):
|
||||||
|
"""Finds a matching relay for the given (network name, channel) pair."""
|
||||||
if chanpair in db: # This chanpair is a shared channel; others link to it
|
if chanpair in db: # This chanpair is a shared channel; others link to it
|
||||||
return chanpair
|
return chanpair
|
||||||
# This chanpair is linked *to* a remote channel
|
# This chanpair is linked *to* a remote channel
|
||||||
@ -257,6 +259,8 @@ def findRelay(chanpair):
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
def findRemoteChan(irc, remoteirc, channel):
|
def findRemoteChan(irc, remoteirc, channel):
|
||||||
|
"""Returns the linked channel name for the given channel on remoteirc,
|
||||||
|
if one exists."""
|
||||||
query = (irc.name, channel)
|
query = (irc.name, channel)
|
||||||
remotenetname = remoteirc.name
|
remotenetname = remoteirc.name
|
||||||
chanpair = findRelay(query)
|
chanpair = findRelay(query)
|
||||||
@ -270,6 +274,7 @@ def findRemoteChan(irc, remoteirc, channel):
|
|||||||
return link[1]
|
return link[1]
|
||||||
|
|
||||||
def initializeChannel(irc, channel):
|
def initializeChannel(irc, channel):
|
||||||
|
"""Initializes a relay channel (merge local/remote users, set modes, etc.)."""
|
||||||
# We're initializing a relay that already exists. This can be done at
|
# We're initializing a relay that already exists. This can be done at
|
||||||
# ENDBURST, or on the LINK command.
|
# ENDBURST, or on the LINK command.
|
||||||
relay = findRelay((irc.name, channel))
|
relay = findRelay((irc.name, channel))
|
||||||
@ -303,6 +308,264 @@ def initializeChannel(irc, channel):
|
|||||||
relayJoins(irc, channel, irc.channels[channel].users, irc.channels[channel].ts)
|
relayJoins(irc, channel, irc.channels[channel].users, irc.channels[channel].ts)
|
||||||
irc.proto.joinClient(irc.pseudoclient.uid, channel)
|
irc.proto.joinClient(irc.pseudoclient.uid, channel)
|
||||||
|
|
||||||
|
def removeChannel(irc, channel):
|
||||||
|
"""Destroys a relay channel by parting all of its users."""
|
||||||
|
if irc is None:
|
||||||
|
return
|
||||||
|
if channel not in map(str.lower, irc.serverdata['channels']):
|
||||||
|
irc.proto.partClient(irc.pseudoclient.uid, channel, 'Channel delinked.')
|
||||||
|
relay = findRelay((irc.name, channel))
|
||||||
|
if relay:
|
||||||
|
for user in irc.channels[channel].users.copy():
|
||||||
|
if not isRelayClient(irc, user):
|
||||||
|
relayPart(irc, channel, user)
|
||||||
|
# Don't ever part the main client from any of its autojoin channels.
|
||||||
|
else:
|
||||||
|
if user == irc.pseudoclient.uid and channel in \
|
||||||
|
irc.serverdata['channels']:
|
||||||
|
continue
|
||||||
|
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(user, 'Left all shared channels.')
|
||||||
|
|
||||||
|
def checkClaim(irc, channel, sender, chanobj=None):
|
||||||
|
"""
|
||||||
|
Checks whether the sender of a kick/mode change passes CLAIM checks for
|
||||||
|
a given channel. This returns True if any of the following criteria are met:
|
||||||
|
|
||||||
|
1) The originating network is in the CLAIM list for the relay in question.
|
||||||
|
2) The sender is halfop or above in the channel.
|
||||||
|
3) The sender is a PyLink client/server (checks are suppressed in this case).
|
||||||
|
4) No relay exists for the channel in question.
|
||||||
|
5) The originating network is the one that created the relay.
|
||||||
|
"""
|
||||||
|
relay = findRelay((irc.name, channel))
|
||||||
|
try:
|
||||||
|
mlist = chanobj.prefixmodes
|
||||||
|
except AttributeError:
|
||||||
|
mlist = None
|
||||||
|
sender_modes = getPrefixModes(irc, irc, channel, sender, mlist=mlist)
|
||||||
|
log.debug('(%s) relay.checkClaim: sender modes (%s/%s) are %s (mlist=%s)', irc.name,
|
||||||
|
sender, channel, sender_modes, mlist)
|
||||||
|
return (not relay) or irc.name == relay[0] or irc.name in db[relay]['claim'] or \
|
||||||
|
any([mode in sender_modes for mode in ('y', 'q', 'a', 'o', 'h')]) \
|
||||||
|
or utils.isInternalClient(irc, sender) or \
|
||||||
|
utils.isInternalServer(irc, sender)
|
||||||
|
|
||||||
|
def getSupportedUmodes(irc, remoteirc, modes):
|
||||||
|
"""Given a list of user modes, filters out all of those not supported by the
|
||||||
|
remote network."""
|
||||||
|
supported_modes = []
|
||||||
|
for modepair in modes:
|
||||||
|
try:
|
||||||
|
prefix, modechar = modepair[0]
|
||||||
|
except ValueError:
|
||||||
|
modechar = modepair[0]
|
||||||
|
prefix = '+'
|
||||||
|
arg = modepair[1]
|
||||||
|
for name, m in irc.umodes.items():
|
||||||
|
supported_char = None
|
||||||
|
if modechar == m:
|
||||||
|
if name not in whitelisted_umodes:
|
||||||
|
log.debug("(%s) getSupportedUmodes: skipping mode (%r, %r) because "
|
||||||
|
"it isn't a whitelisted (safe) mode for relay.",
|
||||||
|
irc.name, modechar, arg)
|
||||||
|
break
|
||||||
|
supported_char = remoteirc.umodes.get(name)
|
||||||
|
if supported_char:
|
||||||
|
supported_modes.append((prefix+supported_char, arg))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
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.protoname)
|
||||||
|
return supported_modes
|
||||||
|
|
||||||
|
def isRelayClient(irc, user):
|
||||||
|
"""Returns whether the given user is a relay client."""
|
||||||
|
try:
|
||||||
|
if irc.users[user].remote:
|
||||||
|
# Is the .remote attribute set? If so, don't relay already
|
||||||
|
# relayed clients; that'll trigger an endless loop!
|
||||||
|
return True
|
||||||
|
except AttributeError: # Nope, it isn't.
|
||||||
|
pass
|
||||||
|
except KeyError: # The user doesn't exist?!?
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
### EVENT HANDLER INTERNALS
|
||||||
|
|
||||||
|
def relayJoins(irc, channel, users, ts, burst=True):
|
||||||
|
for name, remoteirc in world.networkobjects.items():
|
||||||
|
queued_users = []
|
||||||
|
if name == irc.name or not remoteirc.connected.is_set():
|
||||||
|
# Don't relay things to their source network...
|
||||||
|
continue
|
||||||
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
|
if remotechan is None:
|
||||||
|
# If there is no link on our network for the user, don't
|
||||||
|
# bother spawning it.
|
||||||
|
continue
|
||||||
|
log.debug('(%s) relayJoins: got %r for users', irc.name, users)
|
||||||
|
for user in users.copy():
|
||||||
|
if isRelayClient(irc, user):
|
||||||
|
# Don't clone relay clients; that'll cause some bad, bad
|
||||||
|
# things to happen.
|
||||||
|
continue
|
||||||
|
log.debug('Okay, spawning %s/%s everywhere', user, irc.name)
|
||||||
|
assert user in irc.users, "(%s) How is this possible? %r isn't in our user database." % (irc.name, user)
|
||||||
|
u = getRemoteUser(irc, remoteirc, user)
|
||||||
|
# Only join users if they aren't already joined. This prevents op floods
|
||||||
|
# on charybdis from all the SJOINing.
|
||||||
|
if u not in remoteirc.channels[remotechan].users:
|
||||||
|
ts = irc.channels[channel].ts
|
||||||
|
prefixes = getPrefixModes(irc, remoteirc, channel, user)
|
||||||
|
userpair = (prefixes, u)
|
||||||
|
queued_users.append(userpair)
|
||||||
|
log.debug('(%s) relayJoins: joining %s to %s%s', irc.name, userpair, remoteirc.name, remotechan)
|
||||||
|
else:
|
||||||
|
log.debug('(%s) relayJoins: not joining %s to %s%s; they\'re already there!', irc.name,
|
||||||
|
u, remoteirc.name, remotechan)
|
||||||
|
if queued_users:
|
||||||
|
# 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]:
|
||||||
|
rsid = getRemoteSid(remoteirc, irc)
|
||||||
|
remoteirc.proto.sjoinServer(rsid, remotechan, queued_users, ts=ts)
|
||||||
|
relayModes(irc, remoteirc, getRemoteSid(irc, remoteirc), channel, irc.channels[channel].modes)
|
||||||
|
else:
|
||||||
|
remoteirc.proto.joinClient(queued_users[0][1], remotechan)
|
||||||
|
|
||||||
|
def relayPart(irc, channel, user):
|
||||||
|
for name, remoteirc in world.networkobjects.items():
|
||||||
|
if name == irc.name or not remoteirc.connected.is_set():
|
||||||
|
# Don't relay things to their source network...
|
||||||
|
continue
|
||||||
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
|
log.debug('(%s) relayPart: looking for %s/%s on %s', irc.name, user, irc.name, remoteirc.name)
|
||||||
|
log.debug('(%s) relayPart: remotechan found as %s', irc.name, remotechan)
|
||||||
|
remoteuser = getRemoteUser(irc, remoteirc, user, spawnIfMissing=False)
|
||||||
|
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(remoteuser, remotechan, 'Channel delinked.')
|
||||||
|
if isRelayClient(remoteirc, remoteuser) and not remoteirc.users[remoteuser].channels:
|
||||||
|
remoteirc.proto.quitClient(remoteuser, 'Left all shared channels.')
|
||||||
|
del relayusers[(irc.name, user)][remoteirc.name]
|
||||||
|
|
||||||
|
|
||||||
|
whitelisted_cmodes = {'admin', 'allowinvite', 'autoop', 'ban', 'banexception',
|
||||||
|
'blockcolor', 'halfop', 'invex', 'inviteonly', 'key',
|
||||||
|
'limit', 'moderated', 'noctcp', 'noextmsg', 'nokick',
|
||||||
|
'noknock', 'nonick', 'nonotice', 'op', 'operonly',
|
||||||
|
'opmoderated', 'owner', 'private', 'regonly',
|
||||||
|
'regmoderated', 'secret', 'sslonly', 'adminonly',
|
||||||
|
'stripcolor', 'topiclock', 'voice'}
|
||||||
|
whitelisted_umodes = {'bot', 'hidechans', 'hideoper', 'invisible', 'oper',
|
||||||
|
'regdeaf', 'u_stripcolor', 'u_noctcp', 'wallops'}
|
||||||
|
def relayModes(irc, remoteirc, sender, channel, modes=None):
|
||||||
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
|
log.debug('(%s) Relay mode: remotechan for %s on %s is %s', irc.name, channel, irc.name, remotechan)
|
||||||
|
if remotechan is None:
|
||||||
|
return
|
||||||
|
if modes is None:
|
||||||
|
modes = irc.channels[channel].modes
|
||||||
|
log.debug('(%s) Relay mode: channel data for %s%s: %s', irc.name, remoteirc.name, remotechan, remoteirc.channels[remotechan])
|
||||||
|
supported_modes = []
|
||||||
|
log.debug('(%s) Relay mode: initial modelist for %s is %s', irc.name, channel, modes)
|
||||||
|
for modepair in modes:
|
||||||
|
try:
|
||||||
|
prefix, modechar = modepair[0]
|
||||||
|
except ValueError:
|
||||||
|
modechar = modepair[0]
|
||||||
|
prefix = '+'
|
||||||
|
arg = modepair[1]
|
||||||
|
# Iterate over every mode see whether the remote IRCd supports
|
||||||
|
# this mode, and what its mode char for it is (if it is different).
|
||||||
|
for name, m in irc.cmodes.items():
|
||||||
|
supported_char = None
|
||||||
|
if modechar == m:
|
||||||
|
supported_char = remoteirc.cmodes.get(name)
|
||||||
|
if supported_char is None:
|
||||||
|
break
|
||||||
|
if name not in whitelisted_cmodes:
|
||||||
|
log.debug("(%s) Relay mode: skipping mode (%r, %r) because "
|
||||||
|
"it isn't a whitelisted (safe) mode for relay.",
|
||||||
|
irc.name, modechar, arg)
|
||||||
|
break
|
||||||
|
if modechar in irc.prefixmodes:
|
||||||
|
# This is a prefix mode (e.g. +o). We must coerse the argument
|
||||||
|
# so that the target exists on the remote relay network.
|
||||||
|
log.debug("(%s) Relay mode: coersing argument of (%r, %r) "
|
||||||
|
"for network %r.",
|
||||||
|
irc.name, modechar, arg, remoteirc.name)
|
||||||
|
# If the target is a remote user, get the real target
|
||||||
|
# (original user).
|
||||||
|
arg = getLocalUser(irc, arg, targetirc=remoteirc) or \
|
||||||
|
getRemoteUser(irc, remoteirc, arg, spawnIfMissing=False)
|
||||||
|
log.debug("(%s) Relay mode: argument found as (%r, %r) "
|
||||||
|
"for network %r.",
|
||||||
|
irc.name, modechar, arg, remoteirc.name)
|
||||||
|
oplist = remoteirc.channels[remotechan].prefixmodes[name+'s']
|
||||||
|
log.debug("(%s) Relay mode: list of %ss on %r is: %s",
|
||||||
|
irc.name, name, remotechan, oplist)
|
||||||
|
if prefix == '+' and arg in oplist:
|
||||||
|
# Don't set prefix modes that are already set.
|
||||||
|
log.debug("(%s) Relay mode: skipping setting %s on %s/%s because it appears to be already set.",
|
||||||
|
irc.name, name, arg, remoteirc.name)
|
||||||
|
break
|
||||||
|
supported_char = remoteirc.cmodes.get(name)
|
||||||
|
if supported_char:
|
||||||
|
final_modepair = (prefix+supported_char, arg)
|
||||||
|
if name in ('ban', 'banexception', 'invex') and not utils.isHostmask(arg):
|
||||||
|
# Don't add bans that don't match n!u@h syntax!
|
||||||
|
log.debug("(%s) Relay mode: skipping mode (%r, %r) because it doesn't match nick!user@host syntax.",
|
||||||
|
irc.name, modechar, arg)
|
||||||
|
break
|
||||||
|
# Don't set modes that are already set, to prevent floods on TS6
|
||||||
|
# where the same mode can be set infinite times.
|
||||||
|
if prefix == '+' and final_modepair in remoteirc.channels[remotechan].modes:
|
||||||
|
log.debug("(%s) Relay mode: skipping setting mode (%r, %r) on %s%s because it appears to be already set.",
|
||||||
|
irc.name, supported_char, arg, remoteirc.name, remotechan)
|
||||||
|
break
|
||||||
|
supported_modes.append(final_modepair)
|
||||||
|
log.debug('(%s) Relay mode: final modelist (sending to %s%s) is %s', irc.name, remoteirc.name, remotechan, supported_modes)
|
||||||
|
# Don't send anything if there are no supported modes left after filtering.
|
||||||
|
if supported_modes:
|
||||||
|
# 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(u, remotechan, supported_modes)
|
||||||
|
else:
|
||||||
|
rsid = getRemoteSid(remoteirc, irc)
|
||||||
|
remoteirc.proto.modeServer(rsid, remotechan, supported_modes)
|
||||||
|
|
||||||
|
def relayWhoisHandler(irc, target):
|
||||||
|
user = irc.users[target]
|
||||||
|
orig = getLocalUser(irc, target)
|
||||||
|
if orig:
|
||||||
|
network, remoteuid = orig
|
||||||
|
remotenick = world.networkobjects[network].users[remoteuid].nick
|
||||||
|
return [320, "%s :is a remote user connected via PyLink Relay. Home "
|
||||||
|
"network: %s; Home nick: %s" % (user.nick, network,
|
||||||
|
remotenick)]
|
||||||
|
world.whois_handlers.append(relayWhoisHandler)
|
||||||
|
|
||||||
|
### GENERIC EVENT HOOK HANDLERS
|
||||||
|
|
||||||
|
def handle_operup(irc, numeric, command, args):
|
||||||
|
newtype = args['text'] + '_(remote)'
|
||||||
|
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||||
|
log.debug('(%s) relay.handle_opertype: setting OPERTYPE of %s/%s to %s', irc.name, user, netname, newtype)
|
||||||
|
remoteirc = world.networkobjects[netname]
|
||||||
|
remoteirc.users[user].opertype = newtype
|
||||||
|
utils.add_hook(handle_operup, 'PYLINK_CLIENT_OPERED')
|
||||||
|
|
||||||
def handle_join(irc, numeric, command, args):
|
def handle_join(irc, numeric, command, args):
|
||||||
channel = args['channel']
|
channel = args['channel']
|
||||||
if not findRelay((irc.name, channel)):
|
if not findRelay((irc.name, channel)):
|
||||||
@ -425,30 +688,6 @@ def handle_privmsg(irc, numeric, command, args):
|
|||||||
utils.add_hook(handle_privmsg, 'PRIVMSG')
|
utils.add_hook(handle_privmsg, 'PRIVMSG')
|
||||||
utils.add_hook(handle_privmsg, 'NOTICE')
|
utils.add_hook(handle_privmsg, 'NOTICE')
|
||||||
|
|
||||||
def checkClaim(irc, channel, sender, chanobj=None):
|
|
||||||
"""
|
|
||||||
Checks whether the sender of a kick/mode change passes CLAIM checks for
|
|
||||||
a given channel. This returns True if any of the following criteria are met:
|
|
||||||
|
|
||||||
1) The originating network is in the CLAIM list for the relay in question.
|
|
||||||
2) The sender is halfop or above in the channel.
|
|
||||||
3) The sender is a PyLink client/server (checks are suppressed in this case).
|
|
||||||
4) No relay exists for the channel in question.
|
|
||||||
5) The originating network is the one that created the relay.
|
|
||||||
"""
|
|
||||||
relay = findRelay((irc.name, channel))
|
|
||||||
try:
|
|
||||||
mlist = chanobj.prefixmodes
|
|
||||||
except AttributeError:
|
|
||||||
mlist = None
|
|
||||||
sender_modes = getPrefixModes(irc, irc, channel, sender, mlist=mlist)
|
|
||||||
log.debug('(%s) relay.checkClaim: sender modes (%s/%s) are %s (mlist=%s)', irc.name,
|
|
||||||
sender, channel, sender_modes, mlist)
|
|
||||||
return (not relay) or irc.name == relay[0] or irc.name in db[relay]['claim'] or \
|
|
||||||
any([mode in sender_modes for mode in ('y', 'q', 'a', 'o', 'h')]) \
|
|
||||||
or utils.isInternalClient(irc, sender) or \
|
|
||||||
utils.isInternalServer(irc, sender)
|
|
||||||
|
|
||||||
def handle_kick(irc, source, command, args):
|
def handle_kick(irc, source, command, args):
|
||||||
channel = args['channel']
|
channel = args['channel']
|
||||||
target = args['target']
|
target = args['target']
|
||||||
@ -559,120 +798,6 @@ def handle_chgclient(irc, source, command, args):
|
|||||||
for c in ('CHGHOST', 'CHGNAME', 'CHGIDENT'):
|
for c in ('CHGHOST', 'CHGNAME', 'CHGIDENT'):
|
||||||
utils.add_hook(handle_chgclient, c)
|
utils.add_hook(handle_chgclient, c)
|
||||||
|
|
||||||
whitelisted_cmodes = {'admin', 'allowinvite', 'autoop', 'ban', 'banexception',
|
|
||||||
'blockcolor', 'halfop', 'invex', 'inviteonly', 'key',
|
|
||||||
'limit', 'moderated', 'noctcp', 'noextmsg', 'nokick',
|
|
||||||
'noknock', 'nonick', 'nonotice', 'op', 'operonly',
|
|
||||||
'opmoderated', 'owner', 'private', 'regonly',
|
|
||||||
'regmoderated', 'secret', 'sslonly', 'adminonly',
|
|
||||||
'stripcolor', 'topiclock', 'voice'}
|
|
||||||
whitelisted_umodes = {'bot', 'hidechans', 'hideoper', 'invisible', 'oper',
|
|
||||||
'regdeaf', 'u_stripcolor', 'u_noctcp', 'wallops'}
|
|
||||||
def relayModes(irc, remoteirc, sender, channel, modes=None):
|
|
||||||
remotechan = findRemoteChan(irc, remoteirc, channel)
|
|
||||||
log.debug('(%s) Relay mode: remotechan for %s on %s is %s', irc.name, channel, irc.name, remotechan)
|
|
||||||
if remotechan is None:
|
|
||||||
return
|
|
||||||
if modes is None:
|
|
||||||
modes = irc.channels[channel].modes
|
|
||||||
log.debug('(%s) Relay mode: channel data for %s%s: %s', irc.name, remoteirc.name, remotechan, remoteirc.channels[remotechan])
|
|
||||||
supported_modes = []
|
|
||||||
log.debug('(%s) Relay mode: initial modelist for %s is %s', irc.name, channel, modes)
|
|
||||||
for modepair in modes:
|
|
||||||
try:
|
|
||||||
prefix, modechar = modepair[0]
|
|
||||||
except ValueError:
|
|
||||||
modechar = modepair[0]
|
|
||||||
prefix = '+'
|
|
||||||
arg = modepair[1]
|
|
||||||
# Iterate over every mode see whether the remote IRCd supports
|
|
||||||
# this mode, and what its mode char for it is (if it is different).
|
|
||||||
for name, m in irc.cmodes.items():
|
|
||||||
supported_char = None
|
|
||||||
if modechar == m:
|
|
||||||
supported_char = remoteirc.cmodes.get(name)
|
|
||||||
if supported_char is None:
|
|
||||||
break
|
|
||||||
if name not in whitelisted_cmodes:
|
|
||||||
log.debug("(%s) Relay mode: skipping mode (%r, %r) because "
|
|
||||||
"it isn't a whitelisted (safe) mode for relay.",
|
|
||||||
irc.name, modechar, arg)
|
|
||||||
break
|
|
||||||
if modechar in irc.prefixmodes:
|
|
||||||
# This is a prefix mode (e.g. +o). We must coerse the argument
|
|
||||||
# so that the target exists on the remote relay network.
|
|
||||||
log.debug("(%s) Relay mode: coersing argument of (%r, %r) "
|
|
||||||
"for network %r.",
|
|
||||||
irc.name, modechar, arg, remoteirc.name)
|
|
||||||
# If the target is a remote user, get the real target
|
|
||||||
# (original user).
|
|
||||||
arg = getLocalUser(irc, arg, targetirc=remoteirc) or \
|
|
||||||
getRemoteUser(irc, remoteirc, arg, spawnIfMissing=False)
|
|
||||||
log.debug("(%s) Relay mode: argument found as (%r, %r) "
|
|
||||||
"for network %r.",
|
|
||||||
irc.name, modechar, arg, remoteirc.name)
|
|
||||||
oplist = remoteirc.channels[remotechan].prefixmodes[name+'s']
|
|
||||||
log.debug("(%s) Relay mode: list of %ss on %r is: %s",
|
|
||||||
irc.name, name, remotechan, oplist)
|
|
||||||
if prefix == '+' and arg in oplist:
|
|
||||||
# Don't set prefix modes that are already set.
|
|
||||||
log.debug("(%s) Relay mode: skipping setting %s on %s/%s because it appears to be already set.",
|
|
||||||
irc.name, name, arg, remoteirc.name)
|
|
||||||
break
|
|
||||||
supported_char = remoteirc.cmodes.get(name)
|
|
||||||
if supported_char:
|
|
||||||
final_modepair = (prefix+supported_char, arg)
|
|
||||||
if name in ('ban', 'banexception', 'invex') and not utils.isHostmask(arg):
|
|
||||||
# Don't add bans that don't match n!u@h syntax!
|
|
||||||
log.debug("(%s) Relay mode: skipping mode (%r, %r) because it doesn't match nick!user@host syntax.",
|
|
||||||
irc.name, modechar, arg)
|
|
||||||
break
|
|
||||||
# Don't set modes that are already set, to prevent floods on TS6
|
|
||||||
# where the same mode can be set infinite times.
|
|
||||||
if prefix == '+' and final_modepair in remoteirc.channels[remotechan].modes:
|
|
||||||
log.debug("(%s) Relay mode: skipping setting mode (%r, %r) on %s%s because it appears to be already set.",
|
|
||||||
irc.name, supported_char, arg, remoteirc.name, remotechan)
|
|
||||||
break
|
|
||||||
supported_modes.append(final_modepair)
|
|
||||||
log.debug('(%s) Relay mode: final modelist (sending to %s%s) is %s', irc.name, remoteirc.name, remotechan, supported_modes)
|
|
||||||
# Don't send anything if there are no supported modes left after filtering.
|
|
||||||
if supported_modes:
|
|
||||||
# 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(u, remotechan, supported_modes)
|
|
||||||
else:
|
|
||||||
rsid = getRemoteSid(remoteirc, irc)
|
|
||||||
remoteirc.proto.modeServer(rsid, remotechan, supported_modes)
|
|
||||||
|
|
||||||
def getSupportedUmodes(irc, remoteirc, modes):
|
|
||||||
supported_modes = []
|
|
||||||
for modepair in modes:
|
|
||||||
try:
|
|
||||||
prefix, modechar = modepair[0]
|
|
||||||
except ValueError:
|
|
||||||
modechar = modepair[0]
|
|
||||||
prefix = '+'
|
|
||||||
arg = modepair[1]
|
|
||||||
for name, m in irc.umodes.items():
|
|
||||||
supported_char = None
|
|
||||||
if modechar == m:
|
|
||||||
if name not in whitelisted_umodes:
|
|
||||||
log.debug("(%s) getSupportedUmodes: skipping mode (%r, %r) because "
|
|
||||||
"it isn't a whitelisted (safe) mode for relay.",
|
|
||||||
irc.name, modechar, arg)
|
|
||||||
break
|
|
||||||
supported_char = remoteirc.umodes.get(name)
|
|
||||||
if supported_char:
|
|
||||||
supported_modes.append((prefix+supported_char, arg))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
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.protoname)
|
|
||||||
return supported_modes
|
|
||||||
|
|
||||||
def handle_mode(irc, numeric, command, args):
|
def handle_mode(irc, numeric, command, args):
|
||||||
target = args['target']
|
target = args['target']
|
||||||
modes = args['modes']
|
modes = args['modes']
|
||||||
@ -778,97 +903,94 @@ def handle_kill(irc, numeric, command, args):
|
|||||||
|
|
||||||
utils.add_hook(handle_kill, 'KILL')
|
utils.add_hook(handle_kill, 'KILL')
|
||||||
|
|
||||||
def isRelayClient(irc, user):
|
def handle_away(irc, numeric, command, args):
|
||||||
try:
|
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||||
if irc.users[user].remote:
|
remoteirc = world.networkobjects[netname]
|
||||||
# Is the .remote attribute set? If so, don't relay already
|
remoteirc.proto.awayClient(user, args['text'])
|
||||||
# relayed clients; that'll trigger an endless loop!
|
utils.add_hook(handle_away, 'AWAY')
|
||||||
return True
|
|
||||||
except AttributeError: # Nope, it isn't.
|
|
||||||
pass
|
|
||||||
except KeyError: # The user doesn't exist?!?
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def relayJoins(irc, channel, users, ts, burst=True):
|
def handle_spawnmain(irc, numeric, command, args):
|
||||||
for name, remoteirc in world.networkobjects.items():
|
if args['olduser']:
|
||||||
queued_users = []
|
# Kills to the main PyLink client force reinitialization; this makes sure
|
||||||
if name == irc.name or not remoteirc.connected.is_set():
|
# it joins all the relay channels like it's supposed to.
|
||||||
# Don't relay things to their source network...
|
initializeAll(irc)
|
||||||
continue
|
utils.add_hook(handle_spawnmain, 'PYLINK_SPAWNMAIN')
|
||||||
|
|
||||||
|
def handle_invite(irc, source, command, args):
|
||||||
|
target = args['target']
|
||||||
|
channel = args['channel']
|
||||||
|
if isRelayClient(irc, target):
|
||||||
|
remotenet, remoteuser = getLocalUser(irc, target)
|
||||||
|
remoteirc = world.networkobjects[remotenet]
|
||||||
remotechan = findRemoteChan(irc, remoteirc, channel)
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
if remotechan is None:
|
remotesource = getRemoteUser(irc, remoteirc, source, spawnIfMissing=False)
|
||||||
# If there is no link on our network for the user, don't
|
if remotesource is None:
|
||||||
# bother spawning it.
|
irc.msg(source, 'Error: You must be in a common channel '
|
||||||
continue
|
'with %s to invite them to channels.' % \
|
||||||
log.debug('(%s) relayJoins: got %r for users', irc.name, users)
|
irc.users[target].nick,
|
||||||
for user in users.copy():
|
notice=True)
|
||||||
if isRelayClient(irc, user):
|
elif remotechan is None:
|
||||||
# Don't clone relay clients; that'll cause some bad, bad
|
irc.msg(source, 'Error: You cannot invite someone to a '
|
||||||
# things to happen.
|
'channel not on their network!',
|
||||||
continue
|
notice=True)
|
||||||
log.debug('Okay, spawning %s/%s everywhere', user, irc.name)
|
else:
|
||||||
assert user in irc.users, "(%s) How is this possible? %r isn't in our user database." % (irc.name, user)
|
remoteirc.proto.inviteClient(remotesource, remoteuser,
|
||||||
u = getRemoteUser(irc, remoteirc, user)
|
remotechan)
|
||||||
# Only join users if they aren't already joined. This prevents op floods
|
utils.add_hook(handle_invite, 'INVITE')
|
||||||
# on charybdis from all the SJOINing.
|
|
||||||
if u not in remoteirc.channels[remotechan].users:
|
|
||||||
ts = irc.channels[channel].ts
|
|
||||||
prefixes = getPrefixModes(irc, remoteirc, channel, user)
|
|
||||||
userpair = (prefixes, u)
|
|
||||||
queued_users.append(userpair)
|
|
||||||
log.debug('(%s) relayJoins: joining %s to %s%s', irc.name, userpair, remoteirc.name, remotechan)
|
|
||||||
else:
|
|
||||||
log.debug('(%s) relayJoins: not joining %s to %s%s; they\'re already there!', irc.name,
|
|
||||||
u, remoteirc.name, remotechan)
|
|
||||||
if queued_users:
|
|
||||||
# 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]:
|
|
||||||
rsid = getRemoteSid(remoteirc, irc)
|
|
||||||
remoteirc.proto.sjoinServer(rsid, remotechan, queued_users, ts=ts)
|
|
||||||
relayModes(irc, remoteirc, getRemoteSid(irc, remoteirc), channel, irc.channels[channel].modes)
|
|
||||||
else:
|
|
||||||
remoteirc.proto.joinClient(queued_users[0][1], remotechan)
|
|
||||||
|
|
||||||
def relayPart(irc, channel, user):
|
def handle_endburst(irc, numeric, command, args):
|
||||||
for name, remoteirc in world.networkobjects.items():
|
if numeric == irc.uplink:
|
||||||
if name == irc.name or not remoteirc.connected.is_set():
|
initializeAll(irc)
|
||||||
# Don't relay things to their source network...
|
utils.add_hook(handle_endburst, "ENDBURST")
|
||||||
continue
|
|
||||||
remotechan = findRemoteChan(irc, remoteirc, channel)
|
|
||||||
log.debug('(%s) relayPart: looking for %s/%s on %s', irc.name, user, irc.name, remoteirc.name)
|
|
||||||
log.debug('(%s) relayPart: remotechan found as %s', irc.name, remotechan)
|
|
||||||
remoteuser = getRemoteUser(irc, remoteirc, user, spawnIfMissing=False)
|
|
||||||
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(remoteuser, remotechan, 'Channel delinked.')
|
|
||||||
if isRelayClient(remoteirc, remoteuser) and not remoteirc.users[remoteuser].channels:
|
|
||||||
remoteirc.proto.quitClient(remoteuser, 'Left all shared channels.')
|
|
||||||
del relayusers[(irc.name, user)][remoteirc.name]
|
|
||||||
|
|
||||||
def removeChannel(irc, channel):
|
def handle_disconnect(irc, numeric, command, args):
|
||||||
if irc is None:
|
for k, v in relayusers.copy().items():
|
||||||
return
|
if irc.name in v:
|
||||||
if channel not in map(str.lower, irc.serverdata['channels']):
|
del relayusers[k][irc.name]
|
||||||
irc.proto.partClient(irc.pseudoclient.uid, channel, 'Channel delinked.')
|
if k[0] == irc.name:
|
||||||
relay = findRelay((irc.name, channel))
|
del relayusers[k]
|
||||||
if relay:
|
for name, ircobj in world.networkobjects.items():
|
||||||
for user in irc.channels[channel].users.copy():
|
if name != irc.name:
|
||||||
if not isRelayClient(irc, user):
|
rsid = getRemoteSid(ircobj, irc)
|
||||||
relayPart(irc, channel, user)
|
ircobj.proto.squitServer(ircobj.sid, rsid, text='Home network lost connection.')
|
||||||
# Don't ever part the main client from any of its autojoin channels.
|
del relayservers[name][irc.name]
|
||||||
else:
|
del relayservers[irc.name]
|
||||||
if user == irc.pseudoclient.uid and channel in \
|
# handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Home network lost connection.'})
|
||||||
irc.serverdata['channels']:
|
|
||||||
continue
|
utils.add_hook(handle_disconnect, "PYLINK_DISCONNECT")
|
||||||
irc.proto.partClient(user, channel, 'Channel delinked.')
|
|
||||||
# Don't ever quit it either...
|
def handle_save(irc, numeric, command, args):
|
||||||
if user != irc.pseudoclient.uid and not irc.users[user].channels:
|
target = args['target']
|
||||||
remoteuser = getLocalUser(irc, user)
|
realuser = getLocalUser(irc, target)
|
||||||
del relayusers[remoteuser][irc.name]
|
log.debug('(%s) relay handle_save: %r got in a nick collision! Real user: %r',
|
||||||
irc.proto.quitClient(user, 'Left all shared channels.')
|
irc.name, target, realuser)
|
||||||
|
if isRelayClient(irc, target) and realuser:
|
||||||
|
# Nick collision!
|
||||||
|
# It's one of our relay clients; try to fix our nick to the next
|
||||||
|
# available normalized nick.
|
||||||
|
remotenet, remoteuser = realuser
|
||||||
|
remoteirc = world.networkobjects[remotenet]
|
||||||
|
nick = remoteirc.users[remoteuser].nick
|
||||||
|
# Limit how many times we can attempt to fix our nick, to prevent
|
||||||
|
# floods and such.
|
||||||
|
if savecache.setdefault(irc.name, 0) <= 5:
|
||||||
|
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(target, newnick)
|
||||||
|
else:
|
||||||
|
log.warning('(%s) SAVE received for relay client %r (%s), not '
|
||||||
|
'fixing nick again due to 5 failed attempts in '
|
||||||
|
'the last 10 seconds!', irc.name, target, nick)
|
||||||
|
savecache[irc.name] += 1
|
||||||
|
else:
|
||||||
|
# Somebody else on the network (not a PyLink client) had a nick collision;
|
||||||
|
# relay this as a nick change appropriately.
|
||||||
|
handle_nick(irc, target, 'SAVE', {'oldnick': None, 'newnick': target})
|
||||||
|
|
||||||
|
utils.add_hook(handle_save, "SAVE")
|
||||||
|
|
||||||
|
### PUBLIC COMMANDS
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def create(irc, source, args):
|
def create(irc, source, args):
|
||||||
@ -1015,78 +1137,6 @@ def delink(irc, source, args):
|
|||||||
else:
|
else:
|
||||||
irc.msg(source, 'Error: No such relay %r.' % channel)
|
irc.msg(source, 'Error: No such relay %r.' % channel)
|
||||||
|
|
||||||
def initializeAll(irc):
|
|
||||||
log.debug('(%s) initializeAll: waiting for world.started', irc.name)
|
|
||||||
world.started.wait()
|
|
||||||
for chanpair, entrydata in db.items():
|
|
||||||
network, channel = chanpair
|
|
||||||
initializeChannel(irc, channel)
|
|
||||||
for link in entrydata['links']:
|
|
||||||
network, channel = link
|
|
||||||
initializeChannel(irc, channel)
|
|
||||||
|
|
||||||
def main():
|
|
||||||
loadDB()
|
|
||||||
world.schedulers['relaydb'] = scheduler = sched.scheduler()
|
|
||||||
scheduler.enter(30, 1, exportDB, argument=(True,))
|
|
||||||
# Thread this because exportDB() queues itself as part of its
|
|
||||||
# execution, in order to get a repeating loop.
|
|
||||||
thread = threading.Thread(target=scheduler.run)
|
|
||||||
thread.daemon = True
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def handle_endburst(irc, numeric, command, args):
|
|
||||||
if numeric == irc.uplink:
|
|
||||||
initializeAll(irc)
|
|
||||||
utils.add_hook(handle_endburst, "ENDBURST")
|
|
||||||
|
|
||||||
def handle_disconnect(irc, numeric, command, args):
|
|
||||||
for k, v in relayusers.copy().items():
|
|
||||||
if irc.name in v:
|
|
||||||
del relayusers[k][irc.name]
|
|
||||||
if k[0] == irc.name:
|
|
||||||
del relayusers[k]
|
|
||||||
for name, ircobj in world.networkobjects.items():
|
|
||||||
if name != irc.name:
|
|
||||||
rsid = getRemoteSid(ircobj, irc)
|
|
||||||
ircobj.proto.squitServer(ircobj.sid, rsid, text='Home network lost connection.')
|
|
||||||
del relayservers[name][irc.name]
|
|
||||||
del relayservers[irc.name]
|
|
||||||
# handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Home network lost connection.'})
|
|
||||||
|
|
||||||
utils.add_hook(handle_disconnect, "PYLINK_DISCONNECT")
|
|
||||||
|
|
||||||
def handle_save(irc, numeric, command, args):
|
|
||||||
target = args['target']
|
|
||||||
realuser = getLocalUser(irc, target)
|
|
||||||
log.debug('(%s) relay handle_save: %r got in a nick collision! Real user: %r',
|
|
||||||
irc.name, target, realuser)
|
|
||||||
if isRelayClient(irc, target) and realuser:
|
|
||||||
# Nick collision!
|
|
||||||
# It's one of our relay clients; try to fix our nick to the next
|
|
||||||
# available normalized nick.
|
|
||||||
remotenet, remoteuser = realuser
|
|
||||||
remoteirc = world.networkobjects[remotenet]
|
|
||||||
nick = remoteirc.users[remoteuser].nick
|
|
||||||
# Limit how many times we can attempt to fix our nick, to prevent
|
|
||||||
# floods and such.
|
|
||||||
if savecache.setdefault(irc.name, 0) <= 5:
|
|
||||||
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(target, newnick)
|
|
||||||
else:
|
|
||||||
log.warning('(%s) SAVE received for relay client %r (%s), not '
|
|
||||||
'fixing nick again due to 5 failed attempts in '
|
|
||||||
'the last 10 seconds!', irc.name, target, nick)
|
|
||||||
savecache[irc.name] += 1
|
|
||||||
else:
|
|
||||||
# Somebody else on the network (not a PyLink client) had a nick collision;
|
|
||||||
# relay this as a nick change appropriately.
|
|
||||||
handle_nick(irc, target, 'SAVE', {'oldnick': None, 'newnick': target})
|
|
||||||
|
|
||||||
utils.add_hook(handle_save, "SAVE")
|
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def linked(irc, source, args):
|
def linked(irc, source, args):
|
||||||
"""takes no arguments.
|
"""takes no arguments.
|
||||||
@ -1105,41 +1155,6 @@ def linked(irc, source, args):
|
|||||||
s += '(no relays yet)'
|
s += '(no relays yet)'
|
||||||
irc.msg(source, s)
|
irc.msg(source, s)
|
||||||
|
|
||||||
def handle_away(irc, numeric, command, args):
|
|
||||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
|
||||||
remoteirc = world.networkobjects[netname]
|
|
||||||
remoteirc.proto.awayClient(user, args['text'])
|
|
||||||
utils.add_hook(handle_away, 'AWAY')
|
|
||||||
|
|
||||||
def handle_spawnmain(irc, numeric, command, args):
|
|
||||||
if args['olduser']:
|
|
||||||
# Kills to the main PyLink client force reinitialization; this makes sure
|
|
||||||
# it joins all the relay channels like it's supposed to.
|
|
||||||
initializeAll(irc)
|
|
||||||
utils.add_hook(handle_spawnmain, 'PYLINK_SPAWNMAIN')
|
|
||||||
|
|
||||||
def handle_invite(irc, source, command, args):
|
|
||||||
target = args['target']
|
|
||||||
channel = args['channel']
|
|
||||||
if isRelayClient(irc, target):
|
|
||||||
remotenet, remoteuser = getLocalUser(irc, target)
|
|
||||||
remoteirc = world.networkobjects[remotenet]
|
|
||||||
remotechan = findRemoteChan(irc, remoteirc, channel)
|
|
||||||
remotesource = getRemoteUser(irc, remoteirc, source, spawnIfMissing=False)
|
|
||||||
if remotesource is None:
|
|
||||||
irc.msg(source, 'Error: You must be in a common channel '
|
|
||||||
'with %s to invite them to channels.' % \
|
|
||||||
irc.users[target].nick,
|
|
||||||
notice=True)
|
|
||||||
elif remotechan is None:
|
|
||||||
irc.msg(source, 'Error: You cannot invite someone to a '
|
|
||||||
'channel not on their network!',
|
|
||||||
notice=True)
|
|
||||||
else:
|
|
||||||
remoteirc.proto.inviteClient(remotesource, remoteuser,
|
|
||||||
remotechan)
|
|
||||||
utils.add_hook(handle_invite, 'INVITE')
|
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
def linkacl(irc, source, args):
|
def linkacl(irc, source, args):
|
||||||
"""ALLOW|DENY|LIST <channel> <remotenet>
|
"""ALLOW|DENY|LIST <channel> <remotenet>
|
||||||
@ -1221,3 +1236,15 @@ def showuser(irc, source, args):
|
|||||||
relaychannels.append(''.join(relay))
|
relaychannels.append(''.join(relay))
|
||||||
if relaychannels and (utils.isOper(irc, source) or u == source):
|
if relaychannels and (utils.isOper(irc, source) or u == source):
|
||||||
irc.msg(source, "\x02Relay channels\x02: %s" % ' '.join(relaychannels))
|
irc.msg(source, "\x02Relay channels\x02: %s" % ' '.join(relaychannels))
|
||||||
|
|
||||||
|
@utils.add_cmd
|
||||||
|
def save(irc, source, args):
|
||||||
|
"""takes no arguments.
|
||||||
|
|
||||||
|
Saves the relay database to disk."""
|
||||||
|
if utils.isOper(irc, source):
|
||||||
|
exportDB()
|
||||||
|
irc.msg(source, 'Done.')
|
||||||
|
else:
|
||||||
|
irc.msg(source, 'Error: You are not authenticated!')
|
||||||
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user