mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-28 05:29:25 +01:00
Merge branch 'master' into wip/relay-no-duplicate-modes
This commit is contained in:
commit
25da086a6b
@ -18,7 +18,7 @@ Dependencies currently include:
|
|||||||
#### Supported IRCds
|
#### Supported IRCds
|
||||||
|
|
||||||
* InspIRCd 2.0.x - module: `inspircd`
|
* InspIRCd 2.0.x - module: `inspircd`
|
||||||
* charybdis (3.5.x / git master) - module: `ts6`
|
* charybdis (3.5.x / git master) - module: `ts6` (**experimental**)
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
from collections import defaultdict
|
|
||||||
import threading
|
import threading
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ import time
|
|||||||
class IrcUser():
|
class IrcUser():
|
||||||
def __init__(self, nick, ts, uid, ident='null', host='null',
|
def __init__(self, nick, ts, uid, ident='null', host='null',
|
||||||
realname='PyLink dummy client', realhost='null',
|
realname='PyLink dummy client', realhost='null',
|
||||||
ip='0.0.0.0', modes=set()):
|
ip='0.0.0.0'):
|
||||||
self.nick = nick
|
self.nick = nick
|
||||||
self.ts = ts
|
self.ts = ts
|
||||||
self.uid = uid
|
self.uid = uid
|
||||||
@ -18,7 +17,7 @@ class IrcUser():
|
|||||||
self.realhost = realhost
|
self.realhost = realhost
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.realname = realname
|
self.realname = realname
|
||||||
self.modes = modes
|
self.modes = set()
|
||||||
|
|
||||||
self.identified = False
|
self.identified = False
|
||||||
self.channels = set()
|
self.channels = set()
|
||||||
@ -39,7 +38,6 @@ class IrcServer():
|
|||||||
self.users = []
|
self.users = []
|
||||||
self.internal = internal
|
self.internal = internal
|
||||||
self.name = name.lower()
|
self.name = name.lower()
|
||||||
self.has_bursted = False
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return repr(self.__dict__)
|
return repr(self.__dict__)
|
||||||
|
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
|
# This is a sample configuration file for PyLink. You'll likely want to rename it to config.yml
|
||||||
|
# and begin your configuration there.
|
||||||
|
|
||||||
|
# Note: lines starting with a "#" are comments and will be ignored.
|
||||||
|
|
||||||
bot:
|
bot:
|
||||||
# Sets nick, user/ident, and real name.
|
# Sets nick, user/ident, and real name.
|
||||||
nick: pylink
|
nick: pylink
|
||||||
|
@ -36,3 +36,61 @@ def handle_commands(irc, source, command, args):
|
|||||||
utils.msg(irc, source, 'Uncaught exception in command %r: %s: %s' % (cmd, type(e).__name__, str(e)))
|
utils.msg(irc, source, 'Uncaught exception in command %r: %s: %s' % (cmd, type(e).__name__, str(e)))
|
||||||
return
|
return
|
||||||
utils.add_hook(handle_commands, 'PRIVMSG')
|
utils.add_hook(handle_commands, 'PRIVMSG')
|
||||||
|
|
||||||
|
# Return WHOIS replies to IRCds that use them.
|
||||||
|
def handle_whois(irc, source, command, args):
|
||||||
|
target = args['target']
|
||||||
|
user = irc.users.get(target)
|
||||||
|
if user is None:
|
||||||
|
log.warning('(%s) Got a WHOIS request for %r from %r, but the target doesn\'t exist in irc.users!', irc.name, target, source)
|
||||||
|
f = irc.proto.numericServer
|
||||||
|
server = utils.clientToServer(irc, target) or irc.sid
|
||||||
|
nick = user.nick
|
||||||
|
sourceisOper = ('o', None) in irc.users[source].modes
|
||||||
|
# https://www.alien.net.au/irc/irc2numerics.html
|
||||||
|
# 311: sends nick!user@host information
|
||||||
|
f(irc, server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname))
|
||||||
|
# 312: sends the server the target is on, and the name
|
||||||
|
f(irc, server, 312, source, "%s %s :PyLink Server" % (nick, irc.serverdata['hostname']))
|
||||||
|
# 313: sends a string denoting the target's operator privilege;
|
||||||
|
# we'll only send it if the user has umode +o.
|
||||||
|
if ('o', None) in user.modes:
|
||||||
|
f(irc, server, 313, source, "%s :is an IRC Operator" % nick)
|
||||||
|
# 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
|
||||||
|
# Only shown to opers!
|
||||||
|
if sourceisOper:
|
||||||
|
f(irc, server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes)))
|
||||||
|
# 319: RPL_WHOISCHANNELS, shows channel list
|
||||||
|
public_chans = []
|
||||||
|
for chan in user.channels:
|
||||||
|
# Here, we'll want to hide secret/private channels from non-opers
|
||||||
|
# who are not in them.
|
||||||
|
c = irc.channels[chan]
|
||||||
|
if ((irc.cmodes.get('secret'), None) in c.modes or \
|
||||||
|
(irc.cmodes.get('private'), None) in c.modes) \
|
||||||
|
and not (sourceisOper or source in c.users):
|
||||||
|
continue
|
||||||
|
# TODO: show prefix modes like a regular IRCd does.
|
||||||
|
public_chans.append(chan)
|
||||||
|
if public_chans:
|
||||||
|
f(irc, server, 319, source, '%s :%s' % (nick, ' '.join(public_chans)))
|
||||||
|
# 317: shows idle and signon time. Though we don't track the user's real
|
||||||
|
# idle time; we just return 0.
|
||||||
|
# 317 GL GL 15 1437632859 :seconds idle, signon time
|
||||||
|
f(irc, server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
|
||||||
|
try:
|
||||||
|
# Iterate over plugin-created WHOIS handlers. They return a tuple
|
||||||
|
# or list with two arguments: the numeric, and the text to send.
|
||||||
|
for func in utils.whois_handlers:
|
||||||
|
res = func(irc, target)
|
||||||
|
if res:
|
||||||
|
num, text = res
|
||||||
|
f(irc, server, num, source, text)
|
||||||
|
except Exception as e:
|
||||||
|
# Again, we wouldn't want this to crash our service, in case
|
||||||
|
# something goes wrong!
|
||||||
|
log.exception('Error caught in WHOIS handler: %s', e)
|
||||||
|
finally:
|
||||||
|
# 318: End of WHOIS.
|
||||||
|
f(irc, server, 318, source, "%s :End of /WHOIS list" % nick)
|
||||||
|
utils.add_hook(handle_whois, 'WHOIS')
|
||||||
|
7
main.py
7
main.py
@ -61,7 +61,7 @@ class Irc():
|
|||||||
self.sid = self.serverdata["sid"]
|
self.sid = self.serverdata["sid"]
|
||||||
self.botdata = conf['bot']
|
self.botdata = conf['bot']
|
||||||
self.proto = proto
|
self.proto = proto
|
||||||
self.pingfreq = self.serverdata.get('pingfreq') or 10
|
self.pingfreq = self.serverdata.get('pingfreq') or 30
|
||||||
self.pingtimeout = self.pingfreq * 2
|
self.pingtimeout = self.pingfreq * 2
|
||||||
|
|
||||||
self.initVars()
|
self.initVars()
|
||||||
@ -84,7 +84,9 @@ class Irc():
|
|||||||
self.socket.settimeout(self.pingtimeout)
|
self.socket.settimeout(self.pingtimeout)
|
||||||
self.proto.connect(self)
|
self.proto.connect(self)
|
||||||
self.spawnMain()
|
self.spawnMain()
|
||||||
|
log.info('(%s) Starting ping schedulers....', self.name)
|
||||||
self.schedulePing()
|
self.schedulePing()
|
||||||
|
log.info('(%s) Server ready; listening for data.', self.name)
|
||||||
self.run()
|
self.run()
|
||||||
except (socket.error, classes.ProtocolError, ConnectionError) as e:
|
except (socket.error, classes.ProtocolError, ConnectionError) as e:
|
||||||
log.warning('(%s) Disconnected from IRC: %s: %s',
|
log.warning('(%s) Disconnected from IRC: %s: %s',
|
||||||
@ -183,7 +185,8 @@ class Irc():
|
|||||||
nick = self.botdata.get('nick') or 'PyLink'
|
nick = self.botdata.get('nick') or 'PyLink'
|
||||||
ident = self.botdata.get('ident') or 'pylink'
|
ident = self.botdata.get('ident') or 'pylink'
|
||||||
host = self.serverdata["hostname"]
|
host = self.serverdata["hostname"]
|
||||||
self.pseudoclient = self.proto.spawnClient(self, nick, ident, host, modes={("o", None)})
|
log.info('(%s) Connected! Spawning main client %s.', self.name, nick)
|
||||||
|
self.pseudoclient = self.proto.spawnClient(self, nick, ident, host, modes={("+o", None)})
|
||||||
for chan in self.serverdata['channels']:
|
for chan in self.serverdata['channels']:
|
||||||
self.proto.joinClient(self, self.pseudoclient.uid, chan)
|
self.proto.joinClient(self, self.pseudoclient.uid, chan)
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ def spawnclient(irc, source, args):
|
|||||||
def quit(irc, source, args):
|
def quit(irc, source, args):
|
||||||
"""<target> [<reason>]
|
"""<target> [<reason>]
|
||||||
|
|
||||||
Admin-only. Quits the PyLink client <target>, if it exists."""
|
Admin-only. Quits the PyLink client with nick <target>, if one exists."""
|
||||||
checkauthenticated(irc, source)
|
checkauthenticated(irc, source)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
@ -66,7 +66,7 @@ def quit(irc, source, args):
|
|||||||
def joinclient(irc, source, args):
|
def joinclient(irc, source, args):
|
||||||
"""<target> <channel1>,[<channel2>], etc.
|
"""<target> <channel1>,[<channel2>], etc.
|
||||||
|
|
||||||
Admin-only. Joins <target>, a PyLink client, to a comma-separated list of channels."""
|
Admin-only. Joins <target>, the nick of a PyLink client, to a comma-separated list of channels."""
|
||||||
checkauthenticated(irc, source)
|
checkauthenticated(irc, source)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
@ -108,7 +108,7 @@ def nick(irc, source, args):
|
|||||||
def part(irc, source, args):
|
def part(irc, source, args):
|
||||||
"""<target> <channel1>,[<channel2>],... [<reason>]
|
"""<target> <channel1>,[<channel2>],... [<reason>]
|
||||||
|
|
||||||
Admin-only. Parts <target>, a PyLink client, from a comma-separated list of channels."""
|
Admin-only. Parts <target>, the nick of a PyLink client, from a comma-separated list of channels."""
|
||||||
checkauthenticated(irc, source)
|
checkauthenticated(irc, source)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
@ -128,7 +128,7 @@ def part(irc, source, args):
|
|||||||
def kick(irc, source, args):
|
def kick(irc, source, args):
|
||||||
"""<source> <channel> <user> [<reason>]
|
"""<source> <channel> <user> [<reason>]
|
||||||
|
|
||||||
Admin-only. Kicks <user> from <channel> via <source>, where <source> is a PyLink client."""
|
Admin-only. Kicks <user> from <channel> via <source>, where <source> is the nick of a PyLink client."""
|
||||||
checkauthenticated(irc, source)
|
checkauthenticated(irc, source)
|
||||||
try:
|
try:
|
||||||
nick = args[0]
|
nick = args[0]
|
||||||
@ -186,7 +186,7 @@ def showchan(irc, source, args):
|
|||||||
def mode(irc, source, args):
|
def mode(irc, source, args):
|
||||||
"""<source> <target> <modes>
|
"""<source> <target> <modes>
|
||||||
|
|
||||||
Admin-only. Sets modes <modes> on <target>."""
|
Admin-only. Sets modes <modes> on <target> from <source>, where <source> is the nick of a PyLink client."""
|
||||||
checkauthenticated(irc, source)
|
checkauthenticated(irc, source)
|
||||||
try:
|
try:
|
||||||
modesource, target, modes = args[0], args[1], args[2:]
|
modesource, target, modes = args[0], args[1], args[2:]
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# commands.py: base PyLink commands
|
# commands.py: base PyLink commands
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import logging
|
|
||||||
|
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
import utils
|
import utils
|
||||||
|
152
plugins/relay.py
152
plugins/relay.py
@ -5,7 +5,6 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||||||
import pickle
|
import pickle
|
||||||
import sched
|
import sched
|
||||||
import threading
|
import threading
|
||||||
import time
|
|
||||||
import string
|
import string
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
@ -15,6 +14,17 @@ from log import log
|
|||||||
dbname = "pylinkrelay.db"
|
dbname = "pylinkrelay.db"
|
||||||
relayusers = defaultdict(dict)
|
relayusers = defaultdict(dict)
|
||||||
|
|
||||||
|
def relayWhoisHandlers(irc, target):
|
||||||
|
user = irc.users[target]
|
||||||
|
orig = getLocalUser(irc, target)
|
||||||
|
if orig:
|
||||||
|
network, remoteuid = orig
|
||||||
|
remotenick = utils.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)]
|
||||||
|
utils.whois_handlers.append(relayWhoisHandlers)
|
||||||
|
|
||||||
def normalizeNick(irc, netname, nick, separator=None):
|
def normalizeNick(irc, netname, nick, separator=None):
|
||||||
# Block until we know the IRC network's nick length (after capabilities
|
# Block until we know the IRC network's nick length (after capabilities
|
||||||
# are sent)
|
# are sent)
|
||||||
@ -97,12 +107,11 @@ def save(irc, source, args):
|
|||||||
def getPrefixModes(irc, remoteirc, channel, user):
|
def getPrefixModes(irc, remoteirc, channel, user):
|
||||||
modes = ''
|
modes = ''
|
||||||
for pmode in ('owner', 'admin', 'op', 'halfop', 'voice'):
|
for pmode in ('owner', 'admin', 'op', 'halfop', 'voice'):
|
||||||
if pmode not in remoteirc.cmodes: # Mode not supported by IRCd
|
if pmode in remoteirc.cmodes: # Mode supported by IRCd
|
||||||
continue
|
mlist = irc.channels[channel].prefixmodes[pmode+'s']
|
||||||
mlist = irc.channels[channel].prefixmodes[pmode+'s']
|
log.debug('(%s) getPrefixModes: checking if %r is in %r', irc.name, user, mlist)
|
||||||
log.debug('(%s) getPrefixModes: checking if %r is in %r', irc.name, user, mlist)
|
if user in mlist:
|
||||||
if user in mlist:
|
modes += remoteirc.cmodes[pmode]
|
||||||
modes += remoteirc.cmodes[pmode]
|
|
||||||
return modes
|
return modes
|
||||||
|
|
||||||
def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
||||||
@ -133,7 +142,7 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
|
|||||||
modes = getSupportedUmodes(irc, remoteirc, userobj.modes)
|
modes = getSupportedUmodes(irc, remoteirc, userobj.modes)
|
||||||
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
|
u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
|
||||||
host=host, realname=realname,
|
host=host, realname=realname,
|
||||||
modes=modes).uid
|
modes=modes, ts=userobj.ts).uid
|
||||||
remoteirc.users[u].remote = irc.name
|
remoteirc.users[u].remote = irc.name
|
||||||
relayusers[(irc.name, user)][remoteirc.name] = u
|
relayusers[(irc.name, user)][remoteirc.name] = u
|
||||||
remoteirc.users[u].remote = irc.name
|
remoteirc.users[u].remote = irc.name
|
||||||
@ -218,6 +227,7 @@ def initializeChannel(irc, channel):
|
|||||||
all_links = db[relay]['links'].copy()
|
all_links = db[relay]['links'].copy()
|
||||||
all_links.update((relay,))
|
all_links.update((relay,))
|
||||||
log.debug('(%s) initializeChannel: all_links: %s', irc.name, all_links)
|
log.debug('(%s) initializeChannel: all_links: %s', irc.name, all_links)
|
||||||
|
# Iterate over all the remote channels linked in this relay.
|
||||||
for link in all_links:
|
for link in all_links:
|
||||||
modes = []
|
modes = []
|
||||||
remotenet, remotechan = link
|
remotenet, remotechan = link
|
||||||
@ -229,20 +239,9 @@ def initializeChannel(irc, channel):
|
|||||||
rc = remoteirc.channels[remotechan]
|
rc = remoteirc.channels[remotechan]
|
||||||
if not (remoteirc.connected and findRemoteChan(remoteirc, irc, remotechan)):
|
if not (remoteirc.connected and findRemoteChan(remoteirc, irc, remotechan)):
|
||||||
continue # They aren't connected, don't bother!
|
continue # They aren't connected, don't bother!
|
||||||
for user in remoteirc.channels[remotechan].users:
|
# Join their (remote) users and set their modes.
|
||||||
# Don't spawn our pseudoclients again.
|
relayJoins(remoteirc, remotechan, rc.users,
|
||||||
if not utils.isInternalClient(remoteirc, user):
|
rc.ts, rc.modes)
|
||||||
log.debug('(%s) initializeChannel: should be joining %s/%s to %s', irc.name, user, remotenet, channel)
|
|
||||||
localuser = getRemoteUser(remoteirc, irc, user)
|
|
||||||
if localuser is None:
|
|
||||||
log.warning('(%s) got None for local user for %s/%s', irc.name, user, remotenet)
|
|
||||||
continue
|
|
||||||
userpair = (getPrefixModes(remoteirc, irc, remotechan, user), localuser)
|
|
||||||
log.debug('(%s) initializeChannel: adding %s to queued_users for %s', irc.name, userpair, channel)
|
|
||||||
queued_users.append(userpair)
|
|
||||||
if queued_users:
|
|
||||||
irc.proto.sjoinServer(irc, irc.sid, channel, queued_users, ts=rc.ts)
|
|
||||||
relayModes(remoteirc, irc, remoteirc.sid, remotechan)
|
|
||||||
relayModes(irc, remoteirc, irc.sid, channel)
|
relayModes(irc, remoteirc, irc.sid, channel)
|
||||||
topic = remoteirc.channels[relay[1]].topic
|
topic = remoteirc.channels[relay[1]].topic
|
||||||
# Only update the topic if it's different from what we already have,
|
# Only update the topic if it's different from what we already have,
|
||||||
@ -251,6 +250,7 @@ def initializeChannel(irc, channel):
|
|||||||
irc.proto.topicServer(irc, irc.sid, channel, topic)
|
irc.proto.topicServer(irc, irc.sid, channel, topic)
|
||||||
|
|
||||||
log.debug('(%s) initializeChannel: joining our users: %s', irc.name, c.users)
|
log.debug('(%s) initializeChannel: joining our users: %s', irc.name, c.users)
|
||||||
|
# After that's done, we'll send our users to them.
|
||||||
relayJoins(irc, channel, c.users, c.ts, c.modes)
|
relayJoins(irc, channel, c.users, c.ts, c.modes)
|
||||||
irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
|
irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
|
||||||
|
|
||||||
@ -262,7 +262,6 @@ def handle_join(irc, numeric, command, args):
|
|||||||
modes = args['modes']
|
modes = args['modes']
|
||||||
ts = args['ts']
|
ts = args['ts']
|
||||||
users = set(args['users'])
|
users = set(args['users'])
|
||||||
# users.update(irc.channels[channel].users)
|
|
||||||
relayJoins(irc, channel, users, ts, modes)
|
relayJoins(irc, channel, users, ts, modes)
|
||||||
utils.add_hook(handle_join, 'JOIN')
|
utils.add_hook(handle_join, 'JOIN')
|
||||||
|
|
||||||
@ -296,6 +295,8 @@ def handle_part(irc, numeric, command, args):
|
|||||||
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
for netname, user in relayusers[(irc.name, numeric)].copy().items():
|
||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = utils.networkobjects[netname]
|
||||||
remotechan = findRemoteChan(irc, remoteirc, channel)
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
|
if remotechan is None:
|
||||||
|
continue
|
||||||
remoteirc.proto.partClient(remoteirc, user, remotechan, text)
|
remoteirc.proto.partClient(remoteirc, user, remotechan, text)
|
||||||
if not remoteirc.users[user].channels:
|
if not remoteirc.users[user].channels:
|
||||||
remoteirc.proto.quitClient(remoteirc, user, 'Left all shared channels.')
|
remoteirc.proto.quitClient(remoteirc, user, 'Left all shared channels.')
|
||||||
@ -310,7 +311,10 @@ def handle_privmsg(irc, numeric, command, args):
|
|||||||
return
|
return
|
||||||
sent = 0
|
sent = 0
|
||||||
relay = findRelay((irc.name, target))
|
relay = findRelay((irc.name, target))
|
||||||
if utils.isChannel(target) and relay and not db[relay]['links']:
|
# Don't send any "you must be in common channels" if we're not part
|
||||||
|
# of a relay, or we are but there are no links!
|
||||||
|
if utils.isChannel(target) and ((relay and not db[relay]['links']) or \
|
||||||
|
relay is None):
|
||||||
return
|
return
|
||||||
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]
|
||||||
@ -416,6 +420,12 @@ def handle_kick(irc, source, command, args):
|
|||||||
text = "(<unknown kicker>@%s) %s" % (irc.name, text)
|
text = "(<unknown kicker>@%s) %s" % (irc.name, text)
|
||||||
remoteirc.proto.kickServer(remoteirc, remoteirc.sid,
|
remoteirc.proto.kickServer(remoteirc, remoteirc.sid,
|
||||||
remotechan, real_target, text)
|
remotechan, real_target, text)
|
||||||
|
|
||||||
|
if target != irc.pseudoclient.uid and not irc.users[target].channels:
|
||||||
|
irc.proto.quitClient(irc, target, 'Left all shared channels.')
|
||||||
|
remoteuser = getLocalUser(irc, target)
|
||||||
|
del relayusers[remoteuser][irc.name]
|
||||||
|
|
||||||
utils.add_hook(handle_kick, 'KICK')
|
utils.add_hook(handle_kick, 'KICK')
|
||||||
|
|
||||||
def handle_chgclient(irc, source, command, args):
|
def handle_chgclient(irc, source, command, args):
|
||||||
@ -434,8 +444,10 @@ def handle_chgclient(irc, source, command, args):
|
|||||||
remoteirc = utils.networkobjects[netname]
|
remoteirc = utils.networkobjects[netname]
|
||||||
try:
|
try:
|
||||||
remoteirc.proto.updateClient(remoteirc, user, field, text)
|
remoteirc.proto.updateClient(remoteirc, user, field, text)
|
||||||
except ValueError: # IRCd doesn't support changing the field we want
|
except NotImplementedError: # IRCd doesn't support changing the field we want
|
||||||
logging.debug('(%s) Error raised changing field %r of %s on %s (for %s/%s)', irc.name, field, user, target, remotenet, irc.name)
|
log.debug('(%s) Ignoring changing field %r of %s on %s (for %s/%s);'
|
||||||
|
' remote IRCd doesn\'t support it', irc.name, field,
|
||||||
|
user, target, netname, irc.name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for c in ('CHGHOST', 'CHGNAME', 'CHGIDENT'):
|
for c in ('CHGHOST', 'CHGNAME', 'CHGIDENT'):
|
||||||
@ -446,10 +458,10 @@ whitelisted_cmodes = {'admin', 'allowinvite', 'autoop', 'ban', 'banexception',
|
|||||||
'limit', 'moderated', 'noctcp', 'noextmsg', 'nokick',
|
'limit', 'moderated', 'noctcp', 'noextmsg', 'nokick',
|
||||||
'noknock', 'nonick', 'nonotice', 'op', 'operonly',
|
'noknock', 'nonick', 'nonotice', 'op', 'operonly',
|
||||||
'opmoderated', 'owner', 'private', 'regonly',
|
'opmoderated', 'owner', 'private', 'regonly',
|
||||||
'regmoderated', 'secret', 'sslonly',
|
'regmoderated', 'secret', 'sslonly', 'adminonly',
|
||||||
'stripcolor', 'topiclock', 'voice'}
|
'stripcolor', 'topiclock', 'voice'}
|
||||||
whitelisted_umodes = {'bot', 'hidechans', 'hideoper', 'invisible', 'oper',
|
whitelisted_umodes = {'bot', 'hidechans', 'hideoper', 'invisible', 'oper',
|
||||||
'regdeaf', 'u_stripcolor', 'servprotect', 'u_noctcp'}
|
'regdeaf', 'u_stripcolor', 'u_noctcp', 'wallops'}
|
||||||
def relayModes(irc, remoteirc, sender, channel, modes=None):
|
def relayModes(irc, remoteirc, sender, channel, modes=None):
|
||||||
remotechan = findRemoteChan(irc, remoteirc, channel)
|
remotechan = findRemoteChan(irc, remoteirc, channel)
|
||||||
log.debug('(%s) Relay mode: remotechan for %s on %s is %s', irc.name, channel, irc.name, remotechan)
|
log.debug('(%s) Relay mode: remotechan for %s on %s is %s', irc.name, channel, irc.name, remotechan)
|
||||||
@ -590,30 +602,40 @@ utils.add_hook(handle_topic, 'TOPIC')
|
|||||||
def handle_kill(irc, numeric, command, args):
|
def handle_kill(irc, numeric, command, args):
|
||||||
target = args['target']
|
target = args['target']
|
||||||
userdata = args['userdata']
|
userdata = args['userdata']
|
||||||
if numeric not in irc.users:
|
|
||||||
# A server's sending kill? Uh oh, this can't be good.
|
|
||||||
return
|
|
||||||
# We don't allow killing over the relay, so we must spawn the client.
|
|
||||||
# all over again and rejoin it to its channels.
|
|
||||||
realuser = getLocalUser(irc, target)
|
realuser = getLocalUser(irc, target)
|
||||||
del relayusers[realuser][irc.name]
|
log.debug('(%s) relay handle_kill: realuser is %r', irc.name, realuser)
|
||||||
remoteirc = utils.networkobjects[realuser[0]]
|
# Target user was remote:
|
||||||
for channel in remoteirc.channels:
|
if realuser and realuser[0] != irc.name:
|
||||||
remotechan = findRemoteChan(remoteirc, irc, channel)
|
# We don't allow killing over the relay, so we must respawn the affected
|
||||||
if remotechan:
|
# client and rejoin it to its channels.
|
||||||
modes = getPrefixModes(remoteirc, irc, remotechan, realuser[1])
|
del relayusers[realuser][irc.name]
|
||||||
log.debug('(%s) handle_kill: userpair: %s, %s', irc.name, modes, realuser)
|
remoteirc = utils.networkobjects[realuser[0]]
|
||||||
client = getRemoteUser(remoteirc, irc, realuser[1])
|
for channel in remoteirc.channels:
|
||||||
irc.proto.sjoinServer(irc, irc.sid, remotechan, [(modes, client)])
|
remotechan = findRemoteChan(remoteirc, irc, channel)
|
||||||
utils.msg(irc, numeric, "Your kill has to %s been blocked "
|
if remotechan:
|
||||||
"because PyLink does not allow killing"
|
modes = getPrefixModes(remoteirc, irc, remotechan, realuser[1])
|
||||||
" users over the relay at this time." % \
|
log.debug('(%s) relay handle_kill: userpair: %s, %s', irc.name, modes, realuser)
|
||||||
userdata.nick, notice=True)
|
client = getRemoteUser(remoteirc, irc, realuser[1])
|
||||||
|
irc.proto.sjoinServer(irc, irc.sid, remotechan, [(modes, client)])
|
||||||
|
if userdata and numeric in irc.users:
|
||||||
|
utils.msg(irc, numeric, "Your kill has to %s been blocked "
|
||||||
|
"because PyLink does not allow killing"
|
||||||
|
" users over the relay at this time." % \
|
||||||
|
userdata.nick, notice=True)
|
||||||
|
# Target user was local.
|
||||||
|
else:
|
||||||
|
# IMPORTANT: some IRCds (charybdis) don't send explicit QUIT messages
|
||||||
|
# for locally killed clients, while others (inspircd) do!
|
||||||
|
# If we receive a user object in 'userdata' instead of None, it means
|
||||||
|
# that the KILL hasn't been handled by a preceding QUIT message.
|
||||||
|
if userdata:
|
||||||
|
handle_quit(irc, target, 'KILL', {'text': args['text']})
|
||||||
|
|
||||||
utils.add_hook(handle_kill, 'KILL')
|
utils.add_hook(handle_kill, 'KILL')
|
||||||
|
|
||||||
def relayJoins(irc, channel, users, ts, modes):
|
def relayJoins(irc, channel, users, ts, modes):
|
||||||
queued_users = []
|
|
||||||
for name, remoteirc in utils.networkobjects.items():
|
for name, remoteirc in utils.networkobjects.items():
|
||||||
|
queued_users = []
|
||||||
if name == irc.name:
|
if name == irc.name:
|
||||||
# Don't relay things to their source network...
|
# Don't relay things to their source network...
|
||||||
continue
|
continue
|
||||||
@ -622,18 +644,19 @@ def relayJoins(irc, channel, users, ts, modes):
|
|||||||
# If there is no link on our network for the user, don't
|
# If there is no link on our network for the user, don't
|
||||||
# bother spawning it.
|
# bother spawning it.
|
||||||
continue
|
continue
|
||||||
|
log.debug('(%s) relayJoins: got %r for users', irc.name, users)
|
||||||
for user in users.copy():
|
for user in users.copy():
|
||||||
|
if utils.isInternalClient(irc, user) or user not in irc.users:
|
||||||
|
# We don't need to clone PyLink pseudoclients... That's
|
||||||
|
# meaningless.
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
if irc.users[user].remote:
|
if irc.users[user].remote:
|
||||||
# Is the .remote attribute set? If so, don't relay already
|
# Is the .remote attribute set? If so, don't relay already
|
||||||
# relayed clients; that'll trigger an endless loop!
|
# relayed clients; that'll trigger an endless loop!
|
||||||
continue
|
continue
|
||||||
except (AttributeError, KeyError): # Nope, it isn't.
|
except AttributeError: # Nope, it isn't.
|
||||||
pass
|
pass
|
||||||
if utils.isInternalClient(irc, user) or user not in irc.users:
|
|
||||||
# We don't need to clone PyLink pseudoclients... That's
|
|
||||||
# meaningless.
|
|
||||||
continue
|
|
||||||
log.debug('Okay, spawning %s/%s everywhere', user, irc.name)
|
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)
|
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)
|
u = getRemoteUser(irc, remoteirc, user)
|
||||||
@ -673,9 +696,14 @@ def removeChannel(irc, channel):
|
|||||||
for user in irc.channels[channel].users.copy():
|
for user in irc.channels[channel].users.copy():
|
||||||
if not utils.isInternalClient(irc, user):
|
if not utils.isInternalClient(irc, user):
|
||||||
relayPart(irc, channel, user)
|
relayPart(irc, channel, user)
|
||||||
|
# Don't ever part the main client from any of its autojoin channels.
|
||||||
else:
|
else:
|
||||||
|
if user == irc.pseudoclient.uid and channel in \
|
||||||
|
irc.serverdata['channels']:
|
||||||
|
continue
|
||||||
irc.proto.partClient(irc, user, channel, 'Channel delinked.')
|
irc.proto.partClient(irc, user, channel, 'Channel delinked.')
|
||||||
if not irc.users[user].channels:
|
# Don't ever quit it either...
|
||||||
|
if user != irc.pseudoclient.uid and not irc.users[user].channels:
|
||||||
irc.proto.quitClient(irc, user, 'Left all shared channels.')
|
irc.proto.quitClient(irc, user, 'Left all shared channels.')
|
||||||
remoteuser = getLocalUser(irc, user)
|
remoteuser = getLocalUser(irc, user)
|
||||||
del relayusers[remoteuser][irc.name]
|
del relayusers[remoteuser][irc.name]
|
||||||
@ -853,18 +881,24 @@ def handle_disconnect(irc, numeric, command, args):
|
|||||||
utils.add_hook(handle_disconnect, "PYLINK_DISCONNECT")
|
utils.add_hook(handle_disconnect, "PYLINK_DISCONNECT")
|
||||||
|
|
||||||
def handle_save(irc, numeric, command, args):
|
def handle_save(irc, numeric, command, args):
|
||||||
# Nick collision! Try to change our nick to the next available normalized
|
|
||||||
# nick.
|
|
||||||
target = args['target']
|
target = args['target']
|
||||||
if utils.isInternalClient(irc, target):
|
realuser = getLocalUser(irc, target)
|
||||||
realuser = getLocalUser(irc, target)
|
log.debug('(%s) relay handle_save: %r got in a nick collision! Real user: %r',
|
||||||
if realuser is None:
|
irc.name, target, realuser)
|
||||||
return
|
if utils.isInternalClient(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
|
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)
|
newnick = normalizeNick(irc, remotenet, nick)
|
||||||
irc.proto.nickClient(irc, target, newnick)
|
irc.proto.nickClient(irc, target, newnick)
|
||||||
|
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_hook(handle_save, "SAVE")
|
||||||
|
|
||||||
@utils.add_cmd
|
@utils.add_cmd
|
||||||
|
@ -10,6 +10,8 @@ from log import log
|
|||||||
|
|
||||||
from classes import *
|
from classes import *
|
||||||
|
|
||||||
|
casemapping = 'rfc1459'
|
||||||
|
|
||||||
# Raw commands sent from servers vary from protocol to protocol. Here, we map
|
# Raw commands sent from servers vary from protocol to protocol. Here, we map
|
||||||
# non-standard names to our hook handlers, so plugins get the information they need.
|
# non-standard names to our hook handlers, so plugins get the information they need.
|
||||||
|
|
||||||
@ -23,7 +25,7 @@ def _send(irc, sid, msg):
|
|||||||
irc.send(':%s %s' % (sid, msg))
|
irc.send(':%s %s' % (sid, msg))
|
||||||
|
|
||||||
def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(),
|
def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||||
server=None, ip='0.0.0.0', realname=None):
|
server=None, ip='0.0.0.0', realname=None, ts=None):
|
||||||
server = server or irc.sid
|
server = server or irc.sid
|
||||||
if not utils.isInternalServer(irc, server):
|
if not utils.isInternalServer(irc, server):
|
||||||
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
||||||
@ -32,12 +34,13 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set()
|
|||||||
if server not in irc.uidgen:
|
if server not in irc.uidgen:
|
||||||
irc.uidgen[server] = utils.TS6UIDGenerator(server)
|
irc.uidgen[server] = utils.TS6UIDGenerator(server)
|
||||||
uid = irc.uidgen[server].next_uid()
|
uid = irc.uidgen[server].next_uid()
|
||||||
ts = int(time.time())
|
ts = ts or int(time.time())
|
||||||
realname = realname or irc.botdata['realname']
|
realname = realname or irc.botdata['realname']
|
||||||
realhost = realhost or host
|
realhost = realhost or host
|
||||||
raw_modes = utils.joinModes(modes)
|
raw_modes = utils.joinModes(modes)
|
||||||
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip, modes=modes)
|
realhost=realhost, ip=ip)
|
||||||
|
utils.applyModes(irc, uid, modes)
|
||||||
irc.servers[server].users.append(uid)
|
irc.servers[server].users.append(uid)
|
||||||
_send(irc, server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
|
_send(irc, server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
|
||||||
" {ts} {modes} + :{realname}".format(ts=ts, host=host,
|
" {ts} {modes} + :{realname}".format(ts=ts, host=host,
|
||||||
@ -273,16 +276,16 @@ def updateClient(irc, numeric, field, text):
|
|||||||
Changes the <field> field of <target> PyLink PseudoClient <client numeric>."""
|
Changes the <field> field of <target> PyLink PseudoClient <client numeric>."""
|
||||||
field = field.upper()
|
field = field.upper()
|
||||||
if field == 'IDENT':
|
if field == 'IDENT':
|
||||||
handle_fident(irc, numeric, 'PYLINK_UPDATECLIENT_IDENT', [text])
|
irc.users[numeric].ident = text
|
||||||
_send(irc, numeric, 'FIDENT %s' % text)
|
_send(irc, numeric, 'FIDENT %s' % text)
|
||||||
elif field == 'HOST':
|
elif field == 'HOST':
|
||||||
handle_fhost(irc, numeric, 'PYLINK_UPDATECLIENT_HOST', [text])
|
irc.users[numeric].host = text
|
||||||
_send(irc, numeric, 'FHOST %s' % text)
|
_send(irc, numeric, 'FHOST %s' % text)
|
||||||
elif field in ('REALNAME', 'GECOS'):
|
elif field in ('REALNAME', 'GECOS'):
|
||||||
handle_fname(irc, numeric, 'PYLINK_UPDATECLIENT_GECOS', [text])
|
irc.users[numeric].realname = text
|
||||||
_send(irc, numeric, 'FNAME :%s' % text)
|
_send(irc, numeric, 'FNAME :%s' % text)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Changing field %r of a client is unsupported by this protocol." % field)
|
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
|
||||||
|
|
||||||
def pingServer(irc, source=None, target=None):
|
def pingServer(irc, source=None, target=None):
|
||||||
source = source or irc.sid
|
source = source or irc.sid
|
||||||
@ -290,6 +293,12 @@ def pingServer(irc, source=None, target=None):
|
|||||||
if not (target is None or source is None):
|
if not (target is None or source is None):
|
||||||
_send(irc, source, 'PING %s %s' % (source, target))
|
_send(irc, source, 'PING %s %s' % (source, target))
|
||||||
|
|
||||||
|
def numericServer(irc, source, numeric, text):
|
||||||
|
raise NotImplementedError("Numeric sending is not yet implemented by this "
|
||||||
|
"protocol module. WHOIS requests are handled "
|
||||||
|
"locally by InspIRCd servers, so there is no "
|
||||||
|
"need for PyLink to send numerics directly yet.")
|
||||||
|
|
||||||
def connect(irc):
|
def connect(irc):
|
||||||
ts = irc.start_ts
|
ts = irc.start_ts
|
||||||
irc.uidgen = {}
|
irc.uidgen = {}
|
||||||
@ -318,8 +327,9 @@ def handle_privmsg(irc, source, command, args):
|
|||||||
|
|
||||||
def handle_kill(irc, source, command, args):
|
def handle_kill(irc, source, command, args):
|
||||||
killed = args[0]
|
killed = args[0]
|
||||||
data = irc.users[killed]
|
data = irc.users.get(killed)
|
||||||
removeClient(irc, killed)
|
if data:
|
||||||
|
removeClient(irc, killed)
|
||||||
return {'target': killed, 'text': args[1], 'userdata': data}
|
return {'target': killed, 'text': args[1], 'userdata': data}
|
||||||
|
|
||||||
def handle_kick(irc, source, command, args):
|
def handle_kick(irc, source, command, args):
|
||||||
@ -467,7 +477,7 @@ def handle_events(irc, data):
|
|||||||
# Each server message looks something like this:
|
# Each server message looks something like this:
|
||||||
# :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :v,1SRAAESWE
|
# :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :v,1SRAAESWE
|
||||||
# :<sid> <command> <argument1> <argument2> ... :final multi word argument
|
# :<sid> <command> <argument1> <argument2> ... :final multi word argument
|
||||||
args = data.split()
|
args = data.split(" ")
|
||||||
if not args:
|
if not args:
|
||||||
# No data??
|
# No data??
|
||||||
return
|
return
|
||||||
@ -559,7 +569,7 @@ def handle_events(irc, data):
|
|||||||
if parsed_args is not None:
|
if parsed_args is not None:
|
||||||
return [numeric, command, parsed_args]
|
return [numeric, command, parsed_args]
|
||||||
|
|
||||||
def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server', endburst=True):
|
def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server'):
|
||||||
# -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver
|
# -> :0AL SERVER test.server * 1 0AM :some silly pseudoserver
|
||||||
uplink = uplink or irc.sid
|
uplink = uplink or irc.sid
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
@ -578,13 +588,8 @@ def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server', endburst
|
|||||||
raise ValueError('Invalid server name %r' % name)
|
raise ValueError('Invalid server name %r' % name)
|
||||||
_send(irc, uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc))
|
_send(irc, uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc))
|
||||||
irc.servers[sid] = IrcServer(uplink, name, internal=True)
|
irc.servers[sid] = IrcServer(uplink, name, internal=True)
|
||||||
if endburst:
|
|
||||||
endburstServer(irc, sid)
|
|
||||||
return sid
|
|
||||||
|
|
||||||
def endburstServer(irc, sid):
|
|
||||||
_send(irc, sid, 'ENDBURST')
|
_send(irc, sid, 'ENDBURST')
|
||||||
irc.servers[sid].has_bursted = True
|
return sid
|
||||||
|
|
||||||
def handle_ftopic(irc, numeric, command, args):
|
def handle_ftopic(irc, numeric, command, args):
|
||||||
# <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic
|
# <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic
|
||||||
|
@ -2,7 +2,6 @@ import time
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from copy import copy
|
|
||||||
|
|
||||||
curdir = os.path.dirname(__file__)
|
curdir = os.path.dirname(__file__)
|
||||||
sys.path += [curdir, os.path.dirname(curdir)]
|
sys.path += [curdir, os.path.dirname(curdir)]
|
||||||
@ -17,13 +16,14 @@ from inspircd import handle_privmsg, handle_kill, handle_kick, handle_error, \
|
|||||||
handle_quit, handle_nick, handle_save, handle_squit, handle_mode, handle_topic, \
|
handle_quit, handle_nick, handle_save, handle_squit, handle_mode, handle_topic, \
|
||||||
handle_notice
|
handle_notice
|
||||||
|
|
||||||
|
casemapping = 'rfc1459'
|
||||||
hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE'}
|
hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE'}
|
||||||
|
|
||||||
def _send(irc, sid, msg):
|
def _send(irc, sid, msg):
|
||||||
irc.send(':%s %s' % (sid, msg))
|
irc.send(':%s %s' % (sid, msg))
|
||||||
|
|
||||||
def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(),
|
def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set(),
|
||||||
server=None, ip='0.0.0.0', realname=None):
|
server=None, ip='0.0.0.0', realname=None, ts=None):
|
||||||
server = server or irc.sid
|
server = server or irc.sid
|
||||||
if not utils.isInternalServer(irc, server):
|
if not utils.isInternalServer(irc, server):
|
||||||
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
raise ValueError('Server %r is not a PyLink internal PseudoServer!' % server)
|
||||||
@ -32,20 +32,22 @@ def spawnClient(irc, nick, ident='null', host='null', realhost=None, modes=set()
|
|||||||
if server not in irc.uidgen:
|
if server not in irc.uidgen:
|
||||||
irc.uidgen[server] = utils.TS6UIDGenerator(server)
|
irc.uidgen[server] = utils.TS6UIDGenerator(server)
|
||||||
uid = irc.uidgen[server].next_uid()
|
uid = irc.uidgen[server].next_uid()
|
||||||
# UID:
|
# EUID:
|
||||||
# parameters: nickname, hopcount, nickTS, umodes, username,
|
# parameters: nickname, hopcount, nickTS, umodes, username,
|
||||||
# visible hostname, IP address, UID, gecos
|
# visible hostname, IP address, UID, real hostname, account name, gecos
|
||||||
ts = int(time.time())
|
ts = ts or int(time.time())
|
||||||
realname = realname or irc.botdata['realname']
|
realname = realname or irc.botdata['realname']
|
||||||
realhost = realhost or host
|
realhost = realhost or host
|
||||||
raw_modes = utils.joinModes(modes)
|
raw_modes = utils.joinModes(modes)
|
||||||
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
u = irc.users[uid] = IrcUser(nick, ts, uid, ident=ident, host=host, realname=realname,
|
||||||
realhost=realhost, ip=ip, modes=modes)
|
realhost=realhost, ip=ip)
|
||||||
|
utils.applyModes(irc, uid, modes)
|
||||||
irc.servers[server].users.append(uid)
|
irc.servers[server].users.append(uid)
|
||||||
_send(irc, server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
_send(irc, server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
|
||||||
":{realname}".format(ts=ts, host=host,
|
"{realhost} * :{realname}".format(ts=ts, host=host,
|
||||||
nick=nick, ident=ident, uid=uid,
|
nick=nick, ident=ident, uid=uid,
|
||||||
modes=raw_modes, ip=ip, realname=realname))
|
modes=raw_modes, ip=ip, realname=realname,
|
||||||
|
realhost=realhost))
|
||||||
return u
|
return u
|
||||||
|
|
||||||
def joinClient(irc, client, channel):
|
def joinClient(irc, client, channel):
|
||||||
@ -214,7 +216,7 @@ def updateClient(irc, numeric, field, text):
|
|||||||
Changes the <field> field of <target> PyLink PseudoClient <client numeric>."""
|
Changes the <field> field of <target> PyLink PseudoClient <client numeric>."""
|
||||||
field = field.upper()
|
field = field.upper()
|
||||||
if field == 'HOST':
|
if field == 'HOST':
|
||||||
handle_chghost(irc, numeric, 'PYLINK_UPDATECLIENT_HOST', [text])
|
irc.users[numeric].host = text
|
||||||
_send(irc, irc.sid, 'CHGHOST %s :%s' % (numeric, text))
|
_send(irc, irc.sid, 'CHGHOST %s :%s' % (numeric, text))
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
|
raise NotImplementedError("Changing field %r of a client is unsupported by this protocol." % field)
|
||||||
@ -228,6 +230,9 @@ def pingServer(irc, source=None, target=None):
|
|||||||
else:
|
else:
|
||||||
_send(irc, source, 'PING %s' % source)
|
_send(irc, source, 'PING %s' % source)
|
||||||
|
|
||||||
|
def numericServer(irc, source, numeric, target, text):
|
||||||
|
_send(irc, source, '%s %s %s' % (numeric, target, text))
|
||||||
|
|
||||||
def connect(irc):
|
def connect(irc):
|
||||||
ts = irc.start_ts
|
ts = irc.start_ts
|
||||||
irc.uidgen = {}
|
irc.uidgen = {}
|
||||||
@ -375,8 +380,12 @@ def handle_euid(irc, numeric, command, args):
|
|||||||
irc.servers[numeric].users.append(uid)
|
irc.servers[numeric].users.append(uid)
|
||||||
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
|
return {'uid': uid, 'ts': ts, 'nick': nick, 'realhost': realhost, 'host': host, 'ident': ident, 'ip': ip}
|
||||||
|
|
||||||
|
def handle_uid(irc, numeric, command, args):
|
||||||
|
raise ProtocolError("Servers must use EUID to send users! This is a "
|
||||||
|
"requested capability; plain UID (received) is not "
|
||||||
|
"handled by us at all!")
|
||||||
|
|
||||||
def handle_server(irc, numeric, command, args):
|
def handle_server(irc, numeric, command, args):
|
||||||
# SERVER is sent by our uplink or any other server to introduce others.
|
|
||||||
# parameters: server name, hopcount, sid, server description
|
# parameters: server name, hopcount, sid, server description
|
||||||
servername = args[0].lower()
|
servername = args[0].lower()
|
||||||
try:
|
try:
|
||||||
@ -390,6 +399,8 @@ def handle_server(irc, numeric, command, args):
|
|||||||
irc.servers[sid] = IrcServer(numeric, servername)
|
irc.servers[sid] = IrcServer(numeric, servername)
|
||||||
return {'name': servername, 'sid': sid, 'text': sdesc}
|
return {'name': servername, 'sid': sid, 'text': sdesc}
|
||||||
|
|
||||||
|
handle_sid = handle_server
|
||||||
|
|
||||||
def handle_tmode(irc, numeric, command, args):
|
def handle_tmode(irc, numeric, command, args):
|
||||||
# <- :42XAAAAAB TMODE 1437450768 #endlessvoid -c+lkC 3 agte4
|
# <- :42XAAAAAB TMODE 1437450768 #endlessvoid -c+lkC 3 agte4
|
||||||
channel = args[1].lower()
|
channel = args[1].lower()
|
||||||
@ -403,7 +414,7 @@ def handle_events(irc, data):
|
|||||||
# TS6 messages:
|
# TS6 messages:
|
||||||
# :42X COMMAND arg1 arg2 :final long arg
|
# :42X COMMAND arg1 arg2 :final long arg
|
||||||
# :42XAAAAAA PRIVMSG #somewhere :hello!
|
# :42XAAAAAA PRIVMSG #somewhere :hello!
|
||||||
args = data.split()
|
args = data.split(" ")
|
||||||
if not args:
|
if not args:
|
||||||
# No data??
|
# No data??
|
||||||
return
|
return
|
||||||
@ -428,6 +439,12 @@ def handle_events(irc, data):
|
|||||||
# According to the TS6 protocol documentation, we should send SVINFO
|
# According to the TS6 protocol documentation, we should send SVINFO
|
||||||
# when we get our uplink's SERVER command.
|
# when we get our uplink's SERVER command.
|
||||||
irc.send('SVINFO 6 6 0 :%s' % int(time.time()))
|
irc.send('SVINFO 6 6 0 :%s' % int(time.time()))
|
||||||
|
elif args[0] == 'SQUIT':
|
||||||
|
# What? Charybdis send this in a different format!
|
||||||
|
# <- SQUIT 00A :Remote host closed the connection
|
||||||
|
split_server = args[1]
|
||||||
|
res = handle_squit(irc, split_server, 'SQUIT', [split_server])
|
||||||
|
irc.callHooks([split_server, 'SQUIT', res])
|
||||||
elif args[0] == 'CAPAB':
|
elif args[0] == 'CAPAB':
|
||||||
# We only get a list of keywords here. Charybdis obviously assumes that
|
# We only get a list of keywords here. Charybdis obviously assumes that
|
||||||
# we know what modes it supports (indeed, this is a standard list).
|
# we know what modes it supports (indeed, this is a standard list).
|
||||||
@ -447,6 +464,10 @@ def handle_events(irc, data):
|
|||||||
|
|
||||||
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L80
|
# https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L80
|
||||||
chary_cmodes = { # TS6 generic modes:
|
chary_cmodes = { # TS6 generic modes:
|
||||||
|
# Note: charybdis +p has the effect of being both
|
||||||
|
# noknock AND private. Surprisingly, mapping it twice
|
||||||
|
# works pretty well: setting +p on a charybdis relay
|
||||||
|
# server sets +pK on an InspIRCd network.
|
||||||
'op': 'o', 'voice': 'v', 'ban': 'b', 'key': 'k', 'limit':
|
'op': 'o', 'voice': 'v', 'ban': 'b', 'key': 'k', 'limit':
|
||||||
'l', 'moderated': 'm', 'noextmsg': 'n', 'noknock': 'p',
|
'l', 'moderated': 'm', 'noextmsg': 'n', 'noknock': 'p',
|
||||||
'secret': 's', 'topiclock': 't',
|
'secret': 's', 'topiclock': 't',
|
||||||
@ -454,7 +475,9 @@ def handle_events(irc, data):
|
|||||||
'quiet': 'q', 'redirect': 'f', 'freetarget': 'F',
|
'quiet': 'q', 'redirect': 'f', 'freetarget': 'F',
|
||||||
'joinflood': 'j', 'largebanlist': 'L', 'permanent': 'P',
|
'joinflood': 'j', 'largebanlist': 'L', 'permanent': 'P',
|
||||||
'c_noforwards': 'Q', 'stripcolor': 'c', 'allowinvite':
|
'c_noforwards': 'Q', 'stripcolor': 'c', 'allowinvite':
|
||||||
'g', 'opmoderated': 'z',
|
'g', 'opmoderated': 'z', 'noctcp': 'C',
|
||||||
|
# charybdis-specific modes provided by EXTENSIONS
|
||||||
|
'operonly': 'O', 'adminonly': 'A', 'sslonly': 'S',
|
||||||
# Now, map all the ABCD type modes:
|
# Now, map all the ABCD type modes:
|
||||||
'*A': 'beI', '*B': 'k', '*C': 'l', '*D': 'mnprst'}
|
'*A': 'beI', '*B': 'k', '*C': 'l', '*D': 'mnprst'}
|
||||||
if 'EX' in caps:
|
if 'EX' in caps:
|
||||||
@ -526,7 +549,7 @@ def handle_events(irc, data):
|
|||||||
if parsed_args is not None:
|
if parsed_args is not None:
|
||||||
return [numeric, command, parsed_args]
|
return [numeric, command, parsed_args]
|
||||||
|
|
||||||
def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server', endburst=True):
|
def spawnServer(irc, name, sid=None, uplink=None, desc='PyLink Server'):
|
||||||
# -> :0AL SERVER test.server 1 0XY :some silly pseudoserver
|
# -> :0AL SERVER test.server 1 0XY :some silly pseudoserver
|
||||||
uplink = uplink or irc.sid
|
uplink = uplink or irc.sid
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
@ -584,3 +607,23 @@ def handle_bmask(irc, numeric, command, args):
|
|||||||
modes.append(('+%s' % mode, ban))
|
modes.append(('+%s' % mode, ban))
|
||||||
utils.applyModes(irc, channel, modes)
|
utils.applyModes(irc, channel, modes)
|
||||||
return {'target': channel, 'modes': modes, 'ts': ts}
|
return {'target': channel, 'modes': modes, 'ts': ts}
|
||||||
|
|
||||||
|
def handle_whois(irc, numeric, command, args):
|
||||||
|
# <- :42XAAAAAB WHOIS 5PYAAAAAA :pylink-devel
|
||||||
|
return {'target': args[0]}
|
||||||
|
|
||||||
|
def handle_472(irc, numeric, command, args):
|
||||||
|
# <- :charybdis.midnight.vpn 472 GL|devel O :is an unknown mode char to me
|
||||||
|
# 472 is sent to us when one of our clients tries to set a mode the server
|
||||||
|
# doesn't support. In this case, we'll raise a warning to alert the user
|
||||||
|
# about it.
|
||||||
|
badmode = args[1]
|
||||||
|
reason = args[-1]
|
||||||
|
setter = args[0]
|
||||||
|
charlist = {'A': 'chm_adminonly', 'O': 'chm_operonly', 'S': 'chm_sslonly'}
|
||||||
|
if badmode in charlist:
|
||||||
|
log.warning('(%s) User %r attempted to set channel mode %r, but the '
|
||||||
|
'extension providing it isn\'t loaded! To prevent possible'
|
||||||
|
' desyncs, try adding the line "loadmodule "extensions/%s.so";" to '
|
||||||
|
'your IRCd configuration.', irc.name, setter, badmode,
|
||||||
|
charlist[badmode])
|
||||||
|
@ -2,7 +2,6 @@ import sys
|
|||||||
import os
|
import os
|
||||||
sys.path += [os.getcwd(), os.path.join(os.getcwd(), 'protocols')]
|
sys.path += [os.getcwd(), os.path.join(os.getcwd(), 'protocols')]
|
||||||
import unittest
|
import unittest
|
||||||
import time
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import inspircd
|
import inspircd
|
||||||
@ -72,7 +71,6 @@ class TestProtoInspIRCd(unittest.TestCase):
|
|||||||
self.assertNotIn(u, self.irc.channels['#channel'].users)
|
self.assertNotIn(u, self.irc.channels['#channel'].users)
|
||||||
self.assertNotIn(u, self.irc.users)
|
self.assertNotIn(u, self.irc.users)
|
||||||
self.assertNotIn(u, self.irc.servers[self.irc.sid].users)
|
self.assertNotIn(u, self.irc.servers[self.irc.sid].users)
|
||||||
pass
|
|
||||||
|
|
||||||
def testKickClient(self):
|
def testKickClient(self):
|
||||||
target = self.proto.spawnClient(self.irc, 'soccerball', 'soccerball', 'abcd').uid
|
target = self.proto.spawnClient(self.irc, 'soccerball', 'soccerball', 'abcd').uid
|
||||||
|
29
utils.py
29
utils.py
@ -12,6 +12,7 @@ command_hooks = defaultdict(list)
|
|||||||
networkobjects = {}
|
networkobjects = {}
|
||||||
schedulers = {}
|
schedulers = {}
|
||||||
plugins = []
|
plugins = []
|
||||||
|
whois_handlers = []
|
||||||
started = threading.Event()
|
started = threading.Event()
|
||||||
|
|
||||||
class TS6UIDGenerator():
|
class TS6UIDGenerator():
|
||||||
@ -97,8 +98,6 @@ class TS6SIDGenerator():
|
|||||||
self.iters[pos] = iter(self.allowedchars[pos])
|
self.iters[pos] = iter(self.allowedchars[pos])
|
||||||
next(self.iters[pos])
|
next(self.iters[pos])
|
||||||
self.increment(pos-1)
|
self.increment(pos-1)
|
||||||
else:
|
|
||||||
print('NEXT')
|
|
||||||
|
|
||||||
def next_sid(self):
|
def next_sid(self):
|
||||||
sid = ''.join(self.output)
|
sid = ''.join(self.output)
|
||||||
@ -122,9 +121,18 @@ def add_hook(func, command):
|
|||||||
command = command.upper()
|
command = command.upper()
|
||||||
command_hooks[command].append(func)
|
command_hooks[command].append(func)
|
||||||
|
|
||||||
|
def toLower(irc, text):
|
||||||
|
if irc.proto.casemapping == 'rfc1459':
|
||||||
|
text = text.replace('{', '[')
|
||||||
|
text = text.replace('}', ']')
|
||||||
|
text = text.replace('|', '\\')
|
||||||
|
text = text.replace('~', '^')
|
||||||
|
return text.lower()
|
||||||
|
|
||||||
def nickToUid(irc, nick):
|
def nickToUid(irc, nick):
|
||||||
|
nick = toLower(irc, nick)
|
||||||
for k, v in irc.users.items():
|
for k, v in irc.users.items():
|
||||||
if v.nick == nick:
|
if toLower(irc, v.nick) == nick:
|
||||||
return k
|
return k
|
||||||
|
|
||||||
def clientToServer(irc, numeric):
|
def clientToServer(irc, numeric):
|
||||||
@ -170,16 +178,18 @@ def parseModes(irc, target, args):
|
|||||||
if usermodes:
|
if usermodes:
|
||||||
log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes)
|
log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes)
|
||||||
supported_modes = irc.umodes
|
supported_modes = irc.umodes
|
||||||
|
oldmodes = irc.users[target].modes
|
||||||
else:
|
else:
|
||||||
log.debug('(%s) Using irc.cmodes for this query: %s', irc.name, irc.cmodes)
|
log.debug('(%s) Using irc.cmodes for this query: %s', irc.name, irc.cmodes)
|
||||||
supported_modes = irc.cmodes
|
supported_modes = irc.cmodes
|
||||||
|
oldmodes = irc.channels[target].modes
|
||||||
res = []
|
res = []
|
||||||
for mode in modestring:
|
for mode in modestring:
|
||||||
if mode in '+-':
|
if mode in '+-':
|
||||||
prefix = mode
|
prefix = mode
|
||||||
else:
|
else:
|
||||||
if not prefix:
|
if not prefix:
|
||||||
raise ValueError('Invalid query %r: mode char given without preceding prefix.' % modestring)
|
prefix = '+'
|
||||||
arg = None
|
arg = None
|
||||||
log.debug('Current mode: %s%s; args left: %s', prefix, mode, args)
|
log.debug('Current mode: %s%s; args left: %s', prefix, mode, args)
|
||||||
try:
|
try:
|
||||||
@ -187,6 +197,17 @@ def parseModes(irc, target, args):
|
|||||||
# Must have parameter.
|
# Must have parameter.
|
||||||
log.debug('Mode %s: This mode must have parameter.', mode)
|
log.debug('Mode %s: This mode must have parameter.', mode)
|
||||||
arg = args.pop(0)
|
arg = args.pop(0)
|
||||||
|
if prefix == '-' and mode in supported_modes['*B'] and arg == '*':
|
||||||
|
# Charybdis allows unsetting +k without actually
|
||||||
|
# knowing the key by faking the argument when unsetting
|
||||||
|
# as a single "*".
|
||||||
|
# We'd need to know the real argument of +k for us to
|
||||||
|
# be able to unset the mode.
|
||||||
|
oldargs = [m[1] for m in oldmodes if m[0] == mode]
|
||||||
|
if oldargs:
|
||||||
|
# Set the arg to the old one on the channel.
|
||||||
|
arg = oldargs[0]
|
||||||
|
log.debug("Mode %s: coersing argument of '*' to %r.", mode, arg)
|
||||||
elif mode in irc.prefixmodes and not usermodes:
|
elif mode in irc.prefixmodes and not usermodes:
|
||||||
# We're setting a prefix mode on someone (e.g. +o user1)
|
# We're setting a prefix mode on someone (e.g. +o user1)
|
||||||
log.debug('Mode %s: This mode is a prefix mode.', mode)
|
log.debug('Mode %s: This mode is a prefix mode.', mode)
|
||||||
|
Loading…
Reference in New Issue
Block a user