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:
commit
0610783479
@ -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.
|
||||||
|
|
||||||
|
@ -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
12
main.py
@ -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)
|
||||||
|
@ -144,12 +144,15 @@ 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
|
||||||
irc.proto.kickClient(irc, u, channel, targetu, reason)
|
if utils.isInternalServer(irc, u):
|
||||||
|
irc.proto.kickServer(irc, u, channel, targetu, reason)
|
||||||
|
else:
|
||||||
|
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'}])
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
|
@ -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)):
|
# Increase the separator length by 1 if the user was already tagged,
|
||||||
# The nick we want exists? Darn, create another one then, but only if
|
# but couldn't be created due to a nick conflict.
|
||||||
# the target isn't an internal client!
|
# This can happen when someone steals a relay user's nick.
|
||||||
# Increase the separator length by 1 if the user was already tagged,
|
|
||||||
# but couldn't be created due to a nick conflict.
|
# However, if the user is changing from, say, a long, cut-off nick to another long,
|
||||||
# This can happen when someone steals a relay user's nick.
|
# 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,10 +553,9 @@ 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)
|
|
||||||
else:
|
else:
|
||||||
remoteirc.proto.modeServer(remoteirc, remoteirc.sid, remotechan, supported_modes)
|
remoteirc.proto.modeServer(remoteirc, remoteirc.sid, 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
|
||||||
irc.proto.nickClient(irc, target, newnick)
|
# 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:
|
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.
|
||||||
|
Loading…
Reference in New Issue
Block a user