mirror of
				https://github.com/jlu5/PyLink.git
				synced 2025-11-04 00:47:21 +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
 | 
			
		||||
 | 
			
		||||
* InspIRCd 2.0.x - module: `inspircd`
 | 
			
		||||
* charybdis (3.5.x / git master) - module: `ts6`
 | 
			
		||||
* charybdis (3.5.x / git master) - module: `ts6` (**experimental**)
 | 
			
		||||
 | 
			
		||||
### Installation
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
import threading
 | 
			
		||||
from random import randint
 | 
			
		||||
 | 
			
		||||
@ -9,7 +8,7 @@ import time
 | 
			
		||||
class IrcUser():
 | 
			
		||||
    def __init__(self, nick, ts, uid, ident='null', host='null',
 | 
			
		||||
                 realname='PyLink dummy client', realhost='null',
 | 
			
		||||
                 ip='0.0.0.0', modes=set()):
 | 
			
		||||
                 ip='0.0.0.0'):
 | 
			
		||||
        self.nick = nick
 | 
			
		||||
        self.ts = ts
 | 
			
		||||
        self.uid = uid
 | 
			
		||||
@ -18,7 +17,7 @@ class IrcUser():
 | 
			
		||||
        self.realhost = realhost
 | 
			
		||||
        self.ip = ip
 | 
			
		||||
        self.realname = realname
 | 
			
		||||
        self.modes = modes
 | 
			
		||||
        self.modes = set()
 | 
			
		||||
 | 
			
		||||
        self.identified = False
 | 
			
		||||
        self.channels = set()
 | 
			
		||||
@ -39,7 +38,6 @@ class IrcServer():
 | 
			
		||||
        self.users = []
 | 
			
		||||
        self.internal = internal
 | 
			
		||||
        self.name = name.lower()
 | 
			
		||||
        self.has_bursted = False
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        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:
 | 
			
		||||
    # Sets nick, user/ident, and real name.
 | 
			
		||||
    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)))
 | 
			
		||||
            return
 | 
			
		||||
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.botdata = conf['bot']
 | 
			
		||||
        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.initVars()
 | 
			
		||||
@ -84,7 +84,9 @@ class Irc():
 | 
			
		||||
                self.socket.settimeout(self.pingtimeout)
 | 
			
		||||
                self.proto.connect(self)
 | 
			
		||||
                self.spawnMain()
 | 
			
		||||
                log.info('(%s) Starting ping schedulers....', self.name)
 | 
			
		||||
                self.schedulePing()
 | 
			
		||||
                log.info('(%s) Server ready; listening for data.', self.name)
 | 
			
		||||
                self.run()
 | 
			
		||||
            except (socket.error, classes.ProtocolError, ConnectionError) as e:
 | 
			
		||||
                log.warning('(%s) Disconnected from IRC: %s: %s',
 | 
			
		||||
@ -183,7 +185,8 @@ class Irc():
 | 
			
		||||
        nick = self.botdata.get('nick') or 'PyLink'
 | 
			
		||||
        ident = self.botdata.get('ident') or 'pylink'
 | 
			
		||||
        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']:
 | 
			
		||||
            self.proto.joinClient(self, self.pseudoclient.uid, chan)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -49,7 +49,7 @@ def spawnclient(irc, source, args):
 | 
			
		||||
def quit(irc, source, args):
 | 
			
		||||
    """<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)
 | 
			
		||||
    try:
 | 
			
		||||
        nick = args[0]
 | 
			
		||||
@ -66,7 +66,7 @@ def quit(irc, source, args):
 | 
			
		||||
def joinclient(irc, source, args):
 | 
			
		||||
    """<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)
 | 
			
		||||
    try:
 | 
			
		||||
        nick = args[0]
 | 
			
		||||
@ -108,7 +108,7 @@ def nick(irc, source, args):
 | 
			
		||||
def part(irc, source, args):
 | 
			
		||||
    """<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)
 | 
			
		||||
    try:
 | 
			
		||||
        nick = args[0]
 | 
			
		||||
@ -128,7 +128,7 @@ def part(irc, source, args):
 | 
			
		||||
def kick(irc, source, args):
 | 
			
		||||
    """<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)
 | 
			
		||||
    try:
 | 
			
		||||
        nick = args[0]
 | 
			
		||||
@ -186,7 +186,7 @@ def showchan(irc, source, args):
 | 
			
		||||
def mode(irc, source, args):
 | 
			
		||||
    """<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)
 | 
			
		||||
    try:
 | 
			
		||||
        modesource, target, modes = args[0], args[1], args[2:]
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,6 @@
 | 
			
		||||
# commands.py: base PyLink commands
 | 
			
		||||
import sys
 | 
			
		||||
import os
 | 
			
		||||
import logging
 | 
			
		||||
 | 
			
		||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
			
		||||
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 sched
 | 
			
		||||
import threading
 | 
			
		||||
import time
 | 
			
		||||
import string
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
@ -15,6 +14,17 @@ from log import log
 | 
			
		||||
dbname = "pylinkrelay.db"
 | 
			
		||||
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):
 | 
			
		||||
    # Block until we know the IRC network's nick length (after capabilities
 | 
			
		||||
    # are sent)
 | 
			
		||||
@ -97,12 +107,11 @@ def save(irc, source, args):
 | 
			
		||||
def getPrefixModes(irc, remoteirc, channel, user):
 | 
			
		||||
    modes = ''
 | 
			
		||||
    for pmode in ('owner', 'admin', 'op', 'halfop', 'voice'):
 | 
			
		||||
        if pmode not in remoteirc.cmodes:  # Mode not supported by IRCd
 | 
			
		||||
            continue
 | 
			
		||||
        mlist = irc.channels[channel].prefixmodes[pmode+'s']
 | 
			
		||||
        log.debug('(%s) getPrefixModes: checking if %r is in %r', irc.name, user, mlist)
 | 
			
		||||
        if user in mlist:
 | 
			
		||||
            modes += remoteirc.cmodes[pmode]
 | 
			
		||||
        if pmode in remoteirc.cmodes:  # Mode supported by IRCd
 | 
			
		||||
            mlist = irc.channels[channel].prefixmodes[pmode+'s']
 | 
			
		||||
            log.debug('(%s) getPrefixModes: checking if %r is in %r', irc.name, user, mlist)
 | 
			
		||||
            if user in mlist:
 | 
			
		||||
                modes += remoteirc.cmodes[pmode]
 | 
			
		||||
    return modes
 | 
			
		||||
 | 
			
		||||
def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
 | 
			
		||||
@ -133,7 +142,7 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
 | 
			
		||||
        modes = getSupportedUmodes(irc, remoteirc, userobj.modes)
 | 
			
		||||
        u = remoteirc.proto.spawnClient(remoteirc, nick, ident=ident,
 | 
			
		||||
                                        host=host, realname=realname,
 | 
			
		||||
                                        modes=modes).uid
 | 
			
		||||
                                        modes=modes, ts=userobj.ts).uid
 | 
			
		||||
        remoteirc.users[u].remote = irc.name
 | 
			
		||||
    relayusers[(irc.name, user)][remoteirc.name] = u
 | 
			
		||||
    remoteirc.users[u].remote = irc.name
 | 
			
		||||
@ -218,6 +227,7 @@ def initializeChannel(irc, channel):
 | 
			
		||||
        all_links = db[relay]['links'].copy()
 | 
			
		||||
        all_links.update((relay,))
 | 
			
		||||
        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:
 | 
			
		||||
            modes = []
 | 
			
		||||
            remotenet, remotechan = link
 | 
			
		||||
@ -229,20 +239,9 @@ def initializeChannel(irc, channel):
 | 
			
		||||
            rc = remoteirc.channels[remotechan]
 | 
			
		||||
            if not (remoteirc.connected and findRemoteChan(remoteirc, irc, remotechan)):
 | 
			
		||||
                continue  # They aren't connected, don't bother!
 | 
			
		||||
            for user in remoteirc.channels[remotechan].users:
 | 
			
		||||
                # Don't spawn our pseudoclients again.
 | 
			
		||||
                if not utils.isInternalClient(remoteirc, user):
 | 
			
		||||
                    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)
 | 
			
		||||
            # Join their (remote) users and set their modes.
 | 
			
		||||
            relayJoins(remoteirc, remotechan, rc.users,
 | 
			
		||||
                       rc.ts, rc.modes)
 | 
			
		||||
            relayModes(irc, remoteirc, irc.sid, channel)
 | 
			
		||||
            topic = remoteirc.channels[relay[1]].topic
 | 
			
		||||
            # 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)
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
        irc.proto.joinClient(irc, irc.pseudoclient.uid, channel)
 | 
			
		||||
 | 
			
		||||
@ -262,7 +262,6 @@ def handle_join(irc, numeric, command, args):
 | 
			
		||||
    modes = args['modes']
 | 
			
		||||
    ts = args['ts']
 | 
			
		||||
    users = set(args['users'])
 | 
			
		||||
    # users.update(irc.channels[channel].users)
 | 
			
		||||
    relayJoins(irc, channel, users, ts, modes)
 | 
			
		||||
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():
 | 
			
		||||
            remoteirc = utils.networkobjects[netname]
 | 
			
		||||
            remotechan = findRemoteChan(irc, remoteirc, channel)
 | 
			
		||||
            if remotechan is None:
 | 
			
		||||
                continue
 | 
			
		||||
            remoteirc.proto.partClient(remoteirc, user, remotechan, text)
 | 
			
		||||
            if not remoteirc.users[user].channels:
 | 
			
		||||
                remoteirc.proto.quitClient(remoteirc, user, 'Left all shared channels.')
 | 
			
		||||
@ -310,7 +311,10 @@ def handle_privmsg(irc, numeric, command, args):
 | 
			
		||||
        return
 | 
			
		||||
    sent = 0
 | 
			
		||||
    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
 | 
			
		||||
    for netname, user in relayusers[(irc.name, numeric)].items():
 | 
			
		||||
        remoteirc = utils.networkobjects[netname]
 | 
			
		||||
@ -416,6 +420,12 @@ def handle_kick(irc, source, command, args):
 | 
			
		||||
                text = "(<unknown kicker>@%s) %s" % (irc.name, text)
 | 
			
		||||
            remoteirc.proto.kickServer(remoteirc, remoteirc.sid,
 | 
			
		||||
                                       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')
 | 
			
		||||
 | 
			
		||||
def handle_chgclient(irc, source, command, args):
 | 
			
		||||
@ -434,8 +444,10 @@ def handle_chgclient(irc, source, command, args):
 | 
			
		||||
            remoteirc = utils.networkobjects[netname]
 | 
			
		||||
            try:
 | 
			
		||||
                remoteirc.proto.updateClient(remoteirc, user, field, text)
 | 
			
		||||
            except ValueError:  # 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)
 | 
			
		||||
            except NotImplementedError:  # IRCd doesn't support changing the field we want
 | 
			
		||||
                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
 | 
			
		||||
 | 
			
		||||
for c in ('CHGHOST', 'CHGNAME', 'CHGIDENT'):
 | 
			
		||||
@ -446,10 +458,10 @@ whitelisted_cmodes = {'admin', 'allowinvite', 'autoop', 'ban', 'banexception',
 | 
			
		||||
                      'limit', 'moderated', 'noctcp', 'noextmsg', 'nokick',
 | 
			
		||||
                      'noknock', 'nonick', 'nonotice', 'op', 'operonly',
 | 
			
		||||
                      'opmoderated', 'owner', 'private', 'regonly',
 | 
			
		||||
                      'regmoderated', 'secret', 'sslonly',
 | 
			
		||||
                      'regmoderated', 'secret', 'sslonly', 'adminonly',
 | 
			
		||||
                      'stripcolor', 'topiclock', 'voice'}
 | 
			
		||||
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):
 | 
			
		||||
    remotechan = findRemoteChan(irc, remoteirc, channel)
 | 
			
		||||
    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):
 | 
			
		||||
    target = args['target']
 | 
			
		||||
    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)
 | 
			
		||||
    del relayusers[realuser][irc.name]
 | 
			
		||||
    remoteirc = utils.networkobjects[realuser[0]]
 | 
			
		||||
    for channel in remoteirc.channels:
 | 
			
		||||
        remotechan = findRemoteChan(remoteirc, irc, channel)
 | 
			
		||||
        if remotechan:
 | 
			
		||||
            modes = getPrefixModes(remoteirc, irc, remotechan, realuser[1])
 | 
			
		||||
            log.debug('(%s) handle_kill: userpair: %s, %s', irc.name, modes, realuser)
 | 
			
		||||
            client = getRemoteUser(remoteirc, irc, realuser[1])
 | 
			
		||||
            irc.proto.sjoinServer(irc, irc.sid, remotechan, [(modes, client)])
 | 
			
		||||
            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)
 | 
			
		||||
    log.debug('(%s) relay handle_kill: realuser is %r', irc.name, realuser)
 | 
			
		||||
    # Target user was remote:
 | 
			
		||||
    if realuser and realuser[0] != irc.name:
 | 
			
		||||
        # We don't allow killing over the relay, so we must respawn the affected
 | 
			
		||||
        # client and rejoin it to its channels.
 | 
			
		||||
        del relayusers[realuser][irc.name]
 | 
			
		||||
        remoteirc = utils.networkobjects[realuser[0]]
 | 
			
		||||
        for channel in remoteirc.channels:
 | 
			
		||||
            remotechan = findRemoteChan(remoteirc, irc, channel)
 | 
			
		||||
            if remotechan:
 | 
			
		||||
                modes = getPrefixModes(remoteirc, irc, remotechan, realuser[1])
 | 
			
		||||
                log.debug('(%s) relay handle_kill: userpair: %s, %s', irc.name, modes, realuser)
 | 
			
		||||
                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')
 | 
			
		||||
 | 
			
		||||
def relayJoins(irc, channel, users, ts, modes):
 | 
			
		||||
    queued_users = []
 | 
			
		||||
    for name, remoteirc in utils.networkobjects.items():
 | 
			
		||||
        queued_users = []
 | 
			
		||||
        if name == irc.name:
 | 
			
		||||
            # Don't relay things to their source network...
 | 
			
		||||
            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
 | 
			
		||||
            # bother spawning it.
 | 
			
		||||
            continue
 | 
			
		||||
        log.debug('(%s) relayJoins: got %r for users', irc.name, users)
 | 
			
		||||
        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:
 | 
			
		||||
                if irc.users[user].remote:
 | 
			
		||||
                    # Is the .remote attribute set? If so, don't relay already
 | 
			
		||||
                    # relayed clients; that'll trigger an endless loop!
 | 
			
		||||
                    continue
 | 
			
		||||
            except (AttributeError, KeyError):  # Nope, it isn't.
 | 
			
		||||
            except AttributeError:  # Nope, it isn't.
 | 
			
		||||
                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)
 | 
			
		||||
            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)
 | 
			
		||||
@ -673,9 +696,14 @@ def removeChannel(irc, channel):
 | 
			
		||||
        for user in irc.channels[channel].users.copy():
 | 
			
		||||
            if not utils.isInternalClient(irc, user):
 | 
			
		||||
                relayPart(irc, channel, user)
 | 
			
		||||
            # Don't ever part the main client from any of its autojoin channels.
 | 
			
		||||
            else:
 | 
			
		||||
                if user == irc.pseudoclient.uid and channel in \
 | 
			
		||||
                        irc.serverdata['channels']:
 | 
			
		||||
                    continue
 | 
			
		||||
                irc.proto.partClient(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.')
 | 
			
		||||
                    remoteuser = getLocalUser(irc, user)
 | 
			
		||||
                    del relayusers[remoteuser][irc.name]
 | 
			
		||||
@ -853,18 +881,24 @@ def handle_disconnect(irc, numeric, command, args):
 | 
			
		||||
utils.add_hook(handle_disconnect, "PYLINK_DISCONNECT")
 | 
			
		||||
 | 
			
		||||
def handle_save(irc, numeric, command, args):
 | 
			
		||||
    # Nick collision! Try to change our nick to the next available normalized
 | 
			
		||||
    # nick.
 | 
			
		||||
    target = args['target']
 | 
			
		||||
    if utils.isInternalClient(irc, target):
 | 
			
		||||
        realuser = getLocalUser(irc, target)
 | 
			
		||||
        if realuser is None:
 | 
			
		||||
            return
 | 
			
		||||
    realuser = getLocalUser(irc, target)
 | 
			
		||||
    log.debug('(%s) relay handle_save: %r got in a nick collision! Real user: %r',
 | 
			
		||||
                  irc.name, target, realuser)
 | 
			
		||||
    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
 | 
			
		||||
        remoteirc = utils.networkobjects[remotenet]
 | 
			
		||||
        nick = remoteirc.users[remoteuser].nick
 | 
			
		||||
        newnick = normalizeNick(irc, remotenet, nick)
 | 
			
		||||
        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_cmd
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,8 @@ from log import log
 | 
			
		||||
 | 
			
		||||
from classes import *
 | 
			
		||||
 | 
			
		||||
casemapping = 'rfc1459'
 | 
			
		||||
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
@ -23,7 +25,7 @@ def _send(irc, sid, msg):
 | 
			
		||||
    irc.send(':%s %s' % (sid, msg))
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    if not utils.isInternalServer(irc, 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:
 | 
			
		||||
        irc.uidgen[server] = utils.TS6UIDGenerator(server)
 | 
			
		||||
    uid = irc.uidgen[server].next_uid()
 | 
			
		||||
    ts = int(time.time())
 | 
			
		||||
    ts = ts or int(time.time())
 | 
			
		||||
    realname = realname or irc.botdata['realname']
 | 
			
		||||
    realhost = realhost or host
 | 
			
		||||
    raw_modes = utils.joinModes(modes)
 | 
			
		||||
    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)
 | 
			
		||||
    _send(irc, server, "UID {uid} {ts} {nick} {realhost} {host} {ident} {ip}"
 | 
			
		||||
                    " {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>."""
 | 
			
		||||
    field = field.upper()
 | 
			
		||||
    if field == 'IDENT':
 | 
			
		||||
        handle_fident(irc, numeric, 'PYLINK_UPDATECLIENT_IDENT', [text])
 | 
			
		||||
        irc.users[numeric].ident = text
 | 
			
		||||
        _send(irc, numeric, 'FIDENT %s' % text)
 | 
			
		||||
    elif field == 'HOST':
 | 
			
		||||
        handle_fhost(irc, numeric, 'PYLINK_UPDATECLIENT_HOST', [text])
 | 
			
		||||
        irc.users[numeric].host = text
 | 
			
		||||
        _send(irc, numeric, 'FHOST %s' % text)
 | 
			
		||||
    elif field in ('REALNAME', 'GECOS'):
 | 
			
		||||
        handle_fname(irc, numeric, 'PYLINK_UPDATECLIENT_GECOS', [text])
 | 
			
		||||
        irc.users[numeric].realname = text
 | 
			
		||||
        _send(irc, numeric, 'FNAME :%s' % text)
 | 
			
		||||
    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):
 | 
			
		||||
    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):
 | 
			
		||||
        _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):
 | 
			
		||||
    ts = irc.start_ts
 | 
			
		||||
    irc.uidgen = {}
 | 
			
		||||
@ -318,8 +327,9 @@ def handle_privmsg(irc, source, command, args):
 | 
			
		||||
 | 
			
		||||
def handle_kill(irc, source, command, args):
 | 
			
		||||
    killed = args[0]
 | 
			
		||||
    data = irc.users[killed]
 | 
			
		||||
    removeClient(irc, killed)
 | 
			
		||||
    data = irc.users.get(killed)
 | 
			
		||||
    if data:
 | 
			
		||||
        removeClient(irc, killed)
 | 
			
		||||
    return {'target': killed, 'text': args[1], 'userdata': data}
 | 
			
		||||
 | 
			
		||||
def handle_kick(irc, source, command, args):
 | 
			
		||||
@ -467,7 +477,7 @@ def handle_events(irc, data):
 | 
			
		||||
    # Each server message looks something like this:
 | 
			
		||||
    # :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :v,1SRAAESWE
 | 
			
		||||
    # :<sid> <command> <argument1> <argument2> ... :final multi word argument
 | 
			
		||||
    args = data.split()
 | 
			
		||||
    args = data.split(" ")
 | 
			
		||||
    if not args:
 | 
			
		||||
        # No data??
 | 
			
		||||
        return
 | 
			
		||||
@ -559,7 +569,7 @@ def handle_events(irc, data):
 | 
			
		||||
        if parsed_args is not None:
 | 
			
		||||
            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
 | 
			
		||||
    uplink = uplink or irc.sid
 | 
			
		||||
    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)
 | 
			
		||||
    _send(irc, uplink, 'SERVER %s * 1 %s :%s' % (name, sid, desc))
 | 
			
		||||
    irc.servers[sid] = IrcServer(uplink, name, internal=True)
 | 
			
		||||
    if endburst:
 | 
			
		||||
        endburstServer(irc, sid)
 | 
			
		||||
    return sid
 | 
			
		||||
 | 
			
		||||
def endburstServer(irc, sid):
 | 
			
		||||
    _send(irc, sid, 'ENDBURST')
 | 
			
		||||
    irc.servers[sid].has_bursted = True
 | 
			
		||||
    return sid
 | 
			
		||||
 | 
			
		||||
def handle_ftopic(irc, numeric, command, args):
 | 
			
		||||
    # <- :70M FTOPIC #channel 1434510754 GLo|o|!GLolol@escape.the.dreamland.ca :Some channel topic
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ import time
 | 
			
		||||
import sys
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
from copy import copy
 | 
			
		||||
 | 
			
		||||
curdir = os.path.dirname(__file__)
 | 
			
		||||
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_notice
 | 
			
		||||
 | 
			
		||||
casemapping = 'rfc1459'
 | 
			
		||||
hook_map = {'SJOIN': 'JOIN', 'TB': 'TOPIC', 'TMODE': 'MODE', 'BMASK': 'MODE'}
 | 
			
		||||
 | 
			
		||||
def _send(irc, sid, msg):
 | 
			
		||||
    irc.send(':%s %s' % (sid, msg))
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    if not utils.isInternalServer(irc, 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:
 | 
			
		||||
        irc.uidgen[server] = utils.TS6UIDGenerator(server)
 | 
			
		||||
    uid = irc.uidgen[server].next_uid()
 | 
			
		||||
    # UID:
 | 
			
		||||
    # EUID:
 | 
			
		||||
    # parameters: nickname, hopcount, nickTS, umodes, username,
 | 
			
		||||
    # visible hostname, IP address, UID, gecos
 | 
			
		||||
    ts = int(time.time())
 | 
			
		||||
    # visible hostname, IP address, UID, real hostname, account name, gecos
 | 
			
		||||
    ts = ts or int(time.time())
 | 
			
		||||
    realname = realname or irc.botdata['realname']
 | 
			
		||||
    realhost = realhost or host
 | 
			
		||||
    raw_modes = utils.joinModes(modes)
 | 
			
		||||
    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)
 | 
			
		||||
    _send(irc, server, "UID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
 | 
			
		||||
                    ":{realname}".format(ts=ts, host=host,
 | 
			
		||||
                                         nick=nick, ident=ident, uid=uid,
 | 
			
		||||
                                         modes=raw_modes, ip=ip, realname=realname))
 | 
			
		||||
    _send(irc, server, "EUID {nick} 1 {ts} {modes} {ident} {host} {ip} {uid} "
 | 
			
		||||
            "{realhost} * :{realname}".format(ts=ts, host=host,
 | 
			
		||||
            nick=nick, ident=ident, uid=uid,
 | 
			
		||||
            modes=raw_modes, ip=ip, realname=realname,
 | 
			
		||||
            realhost=realhost))
 | 
			
		||||
    return u
 | 
			
		||||
 | 
			
		||||
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>."""
 | 
			
		||||
    field = field.upper()
 | 
			
		||||
    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))
 | 
			
		||||
    else:
 | 
			
		||||
        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:
 | 
			
		||||
        _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):
 | 
			
		||||
    ts = irc.start_ts
 | 
			
		||||
    irc.uidgen = {}
 | 
			
		||||
@ -375,8 +380,12 @@ def handle_euid(irc, numeric, command, args):
 | 
			
		||||
    irc.servers[numeric].users.append(uid)
 | 
			
		||||
    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):
 | 
			
		||||
    # SERVER is sent by our uplink or any other server to introduce others.
 | 
			
		||||
    # parameters: server name, hopcount, sid, server description
 | 
			
		||||
    servername = args[0].lower()
 | 
			
		||||
    try:
 | 
			
		||||
@ -390,6 +399,8 @@ def handle_server(irc, numeric, command, args):
 | 
			
		||||
    irc.servers[sid] = IrcServer(numeric, servername)
 | 
			
		||||
    return {'name': servername, 'sid': sid, 'text': sdesc}
 | 
			
		||||
 | 
			
		||||
handle_sid = handle_server
 | 
			
		||||
 | 
			
		||||
def handle_tmode(irc, numeric, command, args):
 | 
			
		||||
    # <- :42XAAAAAB TMODE 1437450768 #endlessvoid -c+lkC 3 agte4
 | 
			
		||||
    channel = args[1].lower()
 | 
			
		||||
@ -403,7 +414,7 @@ def handle_events(irc, data):
 | 
			
		||||
    # TS6 messages:
 | 
			
		||||
    # :42X COMMAND arg1 arg2 :final long arg
 | 
			
		||||
    # :42XAAAAAA PRIVMSG #somewhere :hello!
 | 
			
		||||
    args = data.split()
 | 
			
		||||
    args = data.split(" ")
 | 
			
		||||
    if not args:
 | 
			
		||||
        # No data??
 | 
			
		||||
        return
 | 
			
		||||
@ -428,6 +439,12 @@ def handle_events(irc, data):
 | 
			
		||||
        # According to the TS6 protocol documentation, we should send SVINFO
 | 
			
		||||
        # when we get our uplink's SERVER command.
 | 
			
		||||
        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':
 | 
			
		||||
        # We only get a list of keywords here. Charybdis obviously assumes that
 | 
			
		||||
        # 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
 | 
			
		||||
        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':
 | 
			
		||||
                        'l', 'moderated': 'm', 'noextmsg': 'n', 'noknock': 'p',
 | 
			
		||||
                        'secret': 's', 'topiclock': 't',
 | 
			
		||||
@ -454,7 +475,9 @@ def handle_events(irc, data):
 | 
			
		||||
                         'quiet': 'q', 'redirect': 'f', 'freetarget': 'F',
 | 
			
		||||
                         'joinflood': 'j', 'largebanlist': 'L', 'permanent': 'P',
 | 
			
		||||
                         '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:
 | 
			
		||||
                         '*A': 'beI', '*B': 'k', '*C': 'l', '*D': 'mnprst'}
 | 
			
		||||
        if 'EX' in caps:
 | 
			
		||||
@ -526,7 +549,7 @@ def handle_events(irc, data):
 | 
			
		||||
        if parsed_args is not None:
 | 
			
		||||
            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
 | 
			
		||||
    uplink = uplink or irc.sid
 | 
			
		||||
    name = name.lower()
 | 
			
		||||
@ -584,3 +607,23 @@ def handle_bmask(irc, numeric, command, args):
 | 
			
		||||
        modes.append(('+%s' % mode, ban))
 | 
			
		||||
    utils.applyModes(irc, channel, modes)
 | 
			
		||||
    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
 | 
			
		||||
sys.path += [os.getcwd(), os.path.join(os.getcwd(), 'protocols')]
 | 
			
		||||
import unittest
 | 
			
		||||
import time
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
 | 
			
		||||
import inspircd
 | 
			
		||||
@ -72,7 +71,6 @@ class TestProtoInspIRCd(unittest.TestCase):
 | 
			
		||||
        self.assertNotIn(u, self.irc.channels['#channel'].users)
 | 
			
		||||
        self.assertNotIn(u, self.irc.users)
 | 
			
		||||
        self.assertNotIn(u, self.irc.servers[self.irc.sid].users)
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def testKickClient(self):
 | 
			
		||||
        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 = {}
 | 
			
		||||
schedulers = {}
 | 
			
		||||
plugins = []
 | 
			
		||||
whois_handlers = []
 | 
			
		||||
started = threading.Event()
 | 
			
		||||
 | 
			
		||||
class TS6UIDGenerator():
 | 
			
		||||
@ -97,8 +98,6 @@ class TS6SIDGenerator():
 | 
			
		||||
            self.iters[pos] = iter(self.allowedchars[pos])
 | 
			
		||||
            next(self.iters[pos])
 | 
			
		||||
            self.increment(pos-1)
 | 
			
		||||
        else:
 | 
			
		||||
            print('NEXT')
 | 
			
		||||
 | 
			
		||||
    def next_sid(self):
 | 
			
		||||
        sid = ''.join(self.output)
 | 
			
		||||
@ -122,9 +121,18 @@ def add_hook(func, command):
 | 
			
		||||
    command = command.upper()
 | 
			
		||||
    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):
 | 
			
		||||
    nick = toLower(irc, nick)
 | 
			
		||||
    for k, v in irc.users.items():
 | 
			
		||||
        if v.nick == nick:
 | 
			
		||||
        if toLower(irc, v.nick) == nick:
 | 
			
		||||
            return k
 | 
			
		||||
 | 
			
		||||
def clientToServer(irc, numeric):
 | 
			
		||||
@ -170,16 +178,18 @@ def parseModes(irc, target, args):
 | 
			
		||||
    if usermodes:
 | 
			
		||||
        log.debug('(%s) Using irc.umodes for this query: %s', irc.name, irc.umodes)
 | 
			
		||||
        supported_modes = irc.umodes
 | 
			
		||||
        oldmodes = irc.users[target].modes
 | 
			
		||||
    else:
 | 
			
		||||
        log.debug('(%s) Using irc.cmodes for this query: %s', irc.name, irc.cmodes)
 | 
			
		||||
        supported_modes = irc.cmodes
 | 
			
		||||
        oldmodes = irc.channels[target].modes
 | 
			
		||||
    res = []
 | 
			
		||||
    for mode in modestring:
 | 
			
		||||
        if mode in '+-':
 | 
			
		||||
            prefix = mode
 | 
			
		||||
        else:
 | 
			
		||||
            if not prefix:
 | 
			
		||||
                raise ValueError('Invalid query %r: mode char given without preceding prefix.' % modestring)
 | 
			
		||||
                prefix = '+'
 | 
			
		||||
            arg = None
 | 
			
		||||
            log.debug('Current mode: %s%s; args left: %s', prefix, mode, args)
 | 
			
		||||
            try:
 | 
			
		||||
@ -187,6 +197,17 @@ def parseModes(irc, target, args):
 | 
			
		||||
                    # Must have parameter.
 | 
			
		||||
                    log.debug('Mode %s: This mode must have parameter.', mode)
 | 
			
		||||
                    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:
 | 
			
		||||
                    # We're setting a prefix mode on someone (e.g. +o user1)
 | 
			
		||||
                    log.debug('Mode %s: This mode is a prefix mode.', mode)
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user