mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-30 14:49:28 +01:00
relay: fix many bugs with nick collision handling, and add flood prevention for fixing nicks
Closes #85. Nick collisions caused by internal clients are handled fine now, including the following cases: - Abusing spawnClient to create a user with the same nick as a relay client, but with a lower TS. - When both an UID (e.g. 42XAAAAAA) and a tagged UID (_42XAAAAAA) exist on the same network (the two will both try to take the same nick of _42XAAAAAA/net over the relay). The case where changing NICK from a long cut-off nick to another long cut-off nick is also mitigated. somelongnick/net won't show nick changes to somelongnic//net if the old and new nicks have give the same normalized relay nick. This introduces a new dependency, expiringdict, from https://pypi.python.org/pypi/expiringdict/1.1.2, which is used as a cache for flood prevention.
This commit is contained in:
parent
1e95f4b3df
commit
20474dabac
@ -14,6 +14,7 @@ Dependencies currently include:
|
||||
|
||||
* Python 3.4+
|
||||
* PyYAML (`pip install pyyaml` or `apt-get install python3-yaml`)
|
||||
* *For the relay plugin only*: expiringdict (`pip install expiringdict`/`apt-get install python3-expiringdict`)
|
||||
|
||||
#### Supported IRCds
|
||||
|
||||
@ -23,7 +24,7 @@ Dependencies currently include:
|
||||
|
||||
### Installation
|
||||
|
||||
1) Rename `config.yml.example` to `config.yml` and configure your instance there. Not all options are properly implemented yet, and the configuration schema isn't finalized yet - this means your configuration may break in an update!
|
||||
1) Rename `config.yml.example` to `config.yml` and configure your instance there. Not all options are properly implemented yet, and the configuration schema isn't finalized yet - this means that your configuration may break in an update!
|
||||
|
||||
2) Run `main.py` from the command line.
|
||||
|
||||
|
@ -8,6 +8,8 @@ import threading
|
||||
import string
|
||||
from collections import defaultdict
|
||||
|
||||
from expiringdict import ExpiringDict
|
||||
|
||||
import utils
|
||||
from log import log
|
||||
from conf import confname
|
||||
@ -19,6 +21,7 @@ dbname += '.db'
|
||||
|
||||
relayusers = defaultdict(dict)
|
||||
spawnlocks = defaultdict(threading.Lock)
|
||||
savecache = ExpiringDict(max_len=5, max_age_seconds=10)
|
||||
|
||||
def relayWhoisHandlers(irc, target):
|
||||
user = irc.users[target]
|
||||
@ -31,41 +34,46 @@ def relayWhoisHandlers(irc, target):
|
||||
remotenick)]
|
||||
utils.whois_handlers.append(relayWhoisHandlers)
|
||||
|
||||
def normalizeNick(irc, netname, nick, separator=None, oldnick=''):
|
||||
def normalizeNick(irc, netname, nick, separator=None, uid=''):
|
||||
separator = separator or irc.serverdata.get('separator') or "/"
|
||||
log.debug('(%s) normalizeNick: using %r as separator.', irc.name, separator)
|
||||
orig_nick = nick
|
||||
protoname = irc.proto.__name__
|
||||
maxnicklen = irc.maxnicklen
|
||||
if not protoname.startswith(('insp', 'unreal')):
|
||||
# Charybdis doesn't allow / in usernames, and will quit with
|
||||
# a protocol violation if there is one.
|
||||
# Charybdis doesn't allow / in usernames, and will SQUIT with
|
||||
# a protocol violation if it sees one.
|
||||
separator = separator.replace('/', '|')
|
||||
nick = nick.replace('/', '|')
|
||||
if nick.startswith(tuple(string.digits)):
|
||||
# On TS6 IRCds, nicks that start with 0-9 are only allowed if
|
||||
# they match the UID of the originating server. Otherwise, you'll
|
||||
# get nasty protocol violations!
|
||||
# get nasty protocol violation SQUITs!
|
||||
nick = '_' + nick
|
||||
tagnicks = True
|
||||
|
||||
suffix = separator + netname
|
||||
nick = nick[:maxnicklen]
|
||||
# Maximum allowed length of a nickname.
|
||||
# Maximum allowed length of a nickname, minus the obligatory /network tag.
|
||||
allowedlength = maxnicklen - len(suffix)
|
||||
# If a nick is too long, the real nick portion must be cut off, but the
|
||||
# /network suffix must remain the same.
|
||||
|
||||
# If a nick is too long, the real nick portion will be cut off, but the
|
||||
# /network suffix MUST remain the same.
|
||||
nick = nick[:allowedlength]
|
||||
nick += suffix
|
||||
# FIXME: factorize
|
||||
while utils.nickToUid(irc, nick) or utils.nickToUid(irc, oldnick) and not \
|
||||
isRelayClient(irc, utils.nickToUid(irc, nick)):
|
||||
# The nick we want exists? Darn, create another one then, but only if
|
||||
# the target isn't an internal client!
|
||||
# Increase the separator length by 1 if the user was already tagged,
|
||||
# but couldn't be created due to a nick conflict.
|
||||
# This can happen when someone steals a relay user's nick.
|
||||
|
||||
# The nick we want exists? Darn, create another one then.
|
||||
# Increase the separator length by 1 if the user was already tagged,
|
||||
# but couldn't be created due to a nick conflict.
|
||||
# This can happen when someone steals a relay user's nick.
|
||||
|
||||
# However, if the user is changing from, say, a long, cut-off nick to another long,
|
||||
# cut-off nick, we don't need to check for duplicates and tag the nick twice.
|
||||
|
||||
# somecutoffnick/net would otherwise be erroneous NICK'ed to somecutoffnic//net,
|
||||
# even though there would be no collision because the old and new nicks are from
|
||||
# the same client.
|
||||
while utils.nickToUid(irc, nick) and utils.nickToUid(irc, nick) != uid:
|
||||
new_sep = separator + separator[-1]
|
||||
log.debug('(%s) normalizeNick: nick %r is in use; using %r as new_sep.', irc.name, nick, new_sep)
|
||||
nick = normalizeNick(irc, netname, orig_nick, separator=new_sep)
|
||||
@ -271,7 +279,7 @@ utils.add_hook(handle_squit, 'SQUIT')
|
||||
def handle_nick(irc, numeric, command, args):
|
||||
for netname, user in relayusers[(irc.name, numeric)].items():
|
||||
remoteirc = utils.networkobjects[netname]
|
||||
newnick = normalizeNick(remoteirc, irc.name, args['newnick'])
|
||||
newnick = normalizeNick(remoteirc, irc.name, args['newnick'], uid=user)
|
||||
if remoteirc.users[user].nick != newnick:
|
||||
remoteirc.proto.nickClient(remoteirc, user, newnick)
|
||||
utils.add_hook(handle_nick, 'NICK')
|
||||
@ -912,8 +920,18 @@ def handle_save(irc, numeric, command, args):
|
||||
remotenet, remoteuser = realuser
|
||||
remoteirc = utils.networkobjects[remotenet]
|
||||
nick = remoteirc.users[remoteuser].nick
|
||||
newnick = normalizeNick(irc, remotenet, nick, oldnick=args['oldnick'])
|
||||
irc.proto.nickClient(irc, target, newnick)
|
||||
# Limit how many times we can attempt to fix our nick, to prevent
|
||||
# floods and such.
|
||||
if savecache.setdefault(target, 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(irc, 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[target] += 1
|
||||
else:
|
||||
# Somebody else on the network (not a PyLink client) had a nick collision;
|
||||
# relay this as a nick change appropriately.
|
||||
|
Loading…
Reference in New Issue
Block a user