3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-01 09:19:23 +01:00

Merge branch 'devel' into wip/document-everything

This commit is contained in:
James Lu 2015-08-24 10:09:41 -07:00
commit 0610783479
5 changed files with 72 additions and 31 deletions

View File

@ -14,6 +14,7 @@ Dependencies currently include:
* Python 3.4+ * Python 3.4+
* PyYAML (`pip install pyyaml` or `apt-get install python3-yaml`) * 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 #### Supported IRCds
@ -23,7 +24,7 @@ Dependencies currently include:
### Installation ### 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. 2) Run `main.py` from the command line.

View File

@ -31,6 +31,12 @@ servers:
# The first char must be a digit [0-9], and the remaining two chars may be letters [A-Z] or digits. # The first char must be a digit [0-9], and the remaining two chars may be letters [A-Z] or digits.
sid: "0AL" sid: "0AL"
# SID range - the range of SIDs PyLink is allowed to use to generate server IDs. On TS6,
# this should be a combination of digits, letters, and #'s. Each # denotes a range (0-9A-Z)
# of characters that can be used by PyLink. You will want to make sure no other servers
# are using this range. There must be at least one # in the entry.
sidrange: "8##"
# Autojoin channels # Autojoin channels
channels: ["#pylink"] channels: ["#pylink"]

12
main.py
View File

@ -278,10 +278,10 @@ if __name__ == '__main__':
pl = imp.load_source(plugin, moduleinfo[1]) pl = imp.load_source(plugin, moduleinfo[1])
utils.plugins.append(pl) utils.plugins.append(pl)
except ImportError as e: except ImportError as e:
if str(e).startswith('No module named'): if str(e) == ('No module named %r' % plugin):
log.error('Failed to load plugin %r: the plugin could not be found.', plugin) log.error('Failed to load plugin %r: The plugin could not be found.', plugin)
else: else:
log.error('Failed to load plugin %r: import error %s', plugin, str(e)) log.error('Failed to load plugin %r: ImportError: %s', plugin, str(e))
else: else:
if hasattr(pl, 'main'): if hasattr(pl, 'main'):
log.debug('Calling main() function of plugin %r', pl) log.debug('Calling main() function of plugin %r', pl)
@ -293,10 +293,10 @@ if __name__ == '__main__':
moduleinfo = imp.find_module(protoname, protocols_folder) moduleinfo = imp.find_module(protoname, protocols_folder)
proto = imp.load_source(protoname, moduleinfo[1]) proto = imp.load_source(protoname, moduleinfo[1])
except ImportError as e: except ImportError as e:
if str(e).startswith('No module named'): if str(e) == ('No module named %r' % protoname):
log.critical('Failed to load protocol module %r: the file could not be found.', protoname) log.critical('Failed to load protocol module %r: The file could not be found.', protoname)
else: else:
log.critical('Failed to load protocol module: import error %s', protoname, str(e)) log.critical('Failed to load protocol module: ImportError: %s', protoname, str(e))
sys.exit(2) sys.exit(2)
else: else:
utils.networkobjects[network] = Irc(network, proto, conf.conf) utils.networkobjects[network] = Irc(network, proto, conf.conf)

View File

@ -144,11 +144,14 @@ def kick(irc, source, args):
except IndexError: except IndexError:
utils.msg(irc, source, "Error: not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).") utils.msg(irc, source, "Error: not enough arguments. Needs 3-4: source nick, channel, target, reason (optional).")
return return
u = utils.nickToUid(irc, nick) u = utils.nickToUid(irc, nick) or nick
targetu = utils.nickToUid(irc, target) targetu = utils.nickToUid(irc, target)
if not utils.isChannel(channel): if not utils.isChannel(channel):
utils.msg(irc, source, "Error: Invalid channel name %r." % channel) utils.msg(irc, source, "Error: Invalid channel name %r." % channel)
return return
if utils.isInternalServer(irc, u):
irc.proto.kickServer(irc, u, channel, targetu, reason)
else:
irc.proto.kickClient(irc, u, channel, targetu, reason) irc.proto.kickClient(irc, u, channel, targetu, reason)
irc.callHooks([u, 'PYLINK_ADMIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}]) irc.callHooks([u, 'PYLINK_ADMIN_KICK', {'channel': channel, 'target': targetu, 'text': reason, 'parse_as': 'KICK'}])

View File

@ -8,6 +8,8 @@ import threading
import string import string
from collections import defaultdict from collections import defaultdict
from expiringdict import ExpiringDict
import utils import utils
from log import log from log import log
from conf import confname from conf import confname
@ -19,6 +21,7 @@ dbname += '.db'
relayusers = defaultdict(dict) relayusers = defaultdict(dict)
spawnlocks = defaultdict(threading.Lock) spawnlocks = defaultdict(threading.Lock)
savecache = ExpiringDict(max_len=5, max_age_seconds=10)
def relayWhoisHandlers(irc, target): def relayWhoisHandlers(irc, target):
user = irc.users[target] user = irc.users[target]
@ -31,41 +34,46 @@ def relayWhoisHandlers(irc, target):
remotenick)] remotenick)]
utils.whois_handlers.append(relayWhoisHandlers) 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 "/" 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
protoname = irc.proto.__name__ protoname = irc.proto.__name__
maxnicklen = irc.maxnicklen maxnicklen = irc.maxnicklen
if not protoname.startswith(('insp', 'unreal')): if not protoname.startswith(('insp', 'unreal')):
# Charybdis doesn't allow / in usernames, and will quit with # Charybdis doesn't allow / in usernames, and will SQUIT with
# a protocol violation if there is one. # a protocol violation if it sees one.
separator = separator.replace('/', '|') separator = separator.replace('/', '|')
nick = nick.replace('/', '|') nick = nick.replace('/', '|')
if nick.startswith(tuple(string.digits)): if nick.startswith(tuple(string.digits)):
# On TS6 IRCds, nicks that start with 0-9 are only allowed if # On TS6 IRCds, nicks that start with 0-9 are only allowed if
# they match the UID of the originating server. Otherwise, you'll # they match the UID of the originating server. Otherwise, you'll
# get nasty protocol violations! # get nasty protocol violation SQUITs!
nick = '_' + nick nick = '_' + nick
tagnicks = True tagnicks = True
suffix = separator + netname suffix = separator + netname
nick = nick[:maxnicklen] nick = nick[:maxnicklen]
# Maximum allowed length of a nickname. # Maximum allowed length of a nickname, minus the obligatory /network tag.
allowedlength = maxnicklen - len(suffix) 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 = nick[:allowedlength]
nick += suffix nick += suffix
# FIXME: factorize
while utils.nickToUid(irc, nick) or utils.nickToUid(irc, oldnick) and not \ # The nick we want exists? Darn, create another one then.
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, # Increase the separator length by 1 if the user was already tagged,
# but couldn't be created due to a nick conflict. # but couldn't be created due to a nick conflict.
# This can happen when someone steals a relay user's nick. # 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] new_sep = separator + separator[-1]
log.debug('(%s) normalizeNick: nick %r is in use; using %r as new_sep.', irc.name, nick, new_sep) 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) 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): def handle_nick(irc, numeric, command, args):
for netname, user in relayusers[(irc.name, numeric)].items(): for netname, user in relayusers[(irc.name, numeric)].items():
remoteirc = utils.networkobjects[netname] 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: if remoteirc.users[user].nick != newnick:
remoteirc.proto.nickClient(remoteirc, user, newnick) remoteirc.proto.nickClient(remoteirc, user, newnick)
utils.add_hook(handle_nick, 'NICK') utils.add_hook(handle_nick, 'NICK')
@ -396,9 +404,16 @@ def handle_kick(irc, source, command, args):
# Join the kicked client back with its respective modes. # Join the kicked client back with its respective modes.
irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)]) irc.proto.sjoinServer(irc, irc.sid, channel, [(modes, target)])
if kicker in irc.users: if kicker in irc.users:
log.info('(%s) Blocked KICK (reason %r) from %s to relay client %s/%s on %s.',
irc.name, args['text'], irc.users[source].nick,
remoteirc.users[real_target].nick, remoteirc.name, channel)
utils.msg(irc, kicker, "This channel is claimed; your kick to " utils.msg(irc, kicker, "This channel is claimed; your kick to "
"%s has been blocked because you are not " "%s has been blocked because you are not "
"(half)opped." % channel, notice=True) "(half)opped." % channel, notice=True)
else:
log.info('(%s) Blocked KICK (reason %r) from server %s to relay client %s/%s on %s.',
irc.name, args['text'], irc.servers[source].name,
remoteirc.users[real_target].nick, remoteirc.name, channel)
return return
if not real_target: if not real_target:
@ -538,7 +553,6 @@ def relayModes(irc, remoteirc, sender, channel, modes=None):
# Don't send anything if there are no supported modes left after filtering. # Don't send anything if there are no supported modes left after filtering.
if supported_modes: if supported_modes:
# Check if the sender is a user; remember servers are allowed to set modes too. # Check if the sender is a user; remember servers are allowed to set modes too.
if sender in irc.users:
u = getRemoteUser(irc, remoteirc, sender, spawnIfMissing=False) u = getRemoteUser(irc, remoteirc, sender, spawnIfMissing=False)
if u: if u:
remoteirc.proto.modeClient(remoteirc, u, remotechan, supported_modes) remoteirc.proto.modeClient(remoteirc, u, remotechan, supported_modes)
@ -627,10 +641,17 @@ def handle_kill(irc, numeric, command, args):
client = getRemoteUser(remoteirc, irc, realuser[1]) client = getRemoteUser(remoteirc, irc, realuser[1])
irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)]) irc.proto.sjoinServer(irc, irc.sid, localchan, [(modes, client)])
if userdata and numeric in irc.users: if userdata and numeric in irc.users:
log.info('(%s) Blocked KILL (reason %r) from %s to relay client %s/%s.',
irc.name, args['text'], irc.users[numeric].nick,
remoteirc.users[realuser[1]].nick, realuser[0])
utils.msg(irc, numeric, "Your kill to %s has been blocked " utils.msg(irc, numeric, "Your kill to %s has been blocked "
"because PyLink does not allow killing" "because PyLink does not allow killing"
" users over the relay at this time." % \ " users over the relay at this time." % \
userdata.nick, notice=True) userdata.nick, notice=True)
else:
log.info('(%s) Blocked KILL (reason %r) from server %s to relay client %s/%s.',
irc.name, args['text'], irc.servers[numeric].name,
remoteirc.users[realuser[1]].nick, realuser[0])
# Target user was local. # Target user was local.
else: else:
# IMPORTANT: some IRCds (charybdis) don't send explicit QUIT messages # IMPORTANT: some IRCds (charybdis) don't send explicit QUIT messages
@ -913,8 +934,18 @@ def handle_save(irc, numeric, command, args):
remotenet, remoteuser = realuser remotenet, remoteuser = realuser
remoteirc = utils.networkobjects[remotenet] remoteirc = utils.networkobjects[remotenet]
nick = remoteirc.users[remoteuser].nick nick = remoteirc.users[remoteuser].nick
newnick = normalizeNick(irc, remotenet, nick, oldnick=args['oldnick']) # 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) 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: else:
# Somebody else on the network (not a PyLink client) had a nick collision; # Somebody else on the network (not a PyLink client) had a nick collision;
# relay this as a nick change appropriately. # relay this as a nick change appropriately.