Example #1
0
    def sjoinServer(self, server, channel, users, ts=None):
        """Sends an SJOIN for a group of users to a channel.

        The sender should always be a server (SID). TS is optional, and defaults
        to the one we've stored in the channel state if not given.
        <users> is a list of (prefix mode, UID) pairs:

        Example uses:
            sjoinServer('100', '#test', [('', '100AAABBC'), ('o', 100AAABBB'), ('v', '100AAADDD')])
            sjoinServer(self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])

        Note that for UnrealIRCd, no mode data is sent in an SJOIN command, only
        The channel name, TS, and user list.
        """
        # <- :001 SJOIN 1444361345 #endlessvoid :001DJ1O02
        # The nicklist consists of users joining the channel, with status prefixes for
        # their status ('@+', '@', '+' or ''), for example:
        # '@+1JJAAAAAB +2JJAAAA4C 1JJAAAADS'.
        channel = utils.toLower(self.irc, channel)
        server = server or self.irc.sid
        assert users, "sjoinServer: No users sent?"
        if not server:
            raise LookupError('No such PyLink server exists.')

        orig_ts = self.irc.channels[channel].ts
        ts = ts or orig_ts
        self.updateTS(channel, ts)

        changedmodes = []
        uids = []
        namelist = []
        for userpair in users:
            assert len(
                userpair) == 2, "Incorrect format of userpair: %r" % userpair
            prefixes, user = userpair
            # Unreal uses slightly different prefixes in SJOIN. +q is * instead of ~,
            # and +a is ~ instead of &.
            # &, ", and ' are used for bursting bans.
            sjoin_prefixes = {'q': '*', 'a': '~', 'o': '@', 'h': '%', 'v': '+'}
            prefixchars = ''.join(
                [sjoin_prefixes.get(prefix, '') for prefix in prefixes])
            if prefixchars:
                changedmodes + [('+%s' % prefix, user) for prefix in prefixes]
            namelist.append(prefixchars + user)
            uids.append(user)
            try:
                self.irc.users[user].channels.add(channel)
            except KeyError:  # Not initialized yet?
                log.debug(
                    "(%s) sjoinServer: KeyError trying to add %r to %r's channel list?",
                    self.irc.name, channel, user)
        namelist = ' '.join(namelist)
        self._send(
            server, "SJOIN {ts} {channel} :{users}".format(ts=ts,
                                                           users=namelist,
                                                           channel=channel))
        self.irc.channels[channel].users.update(uids)
        if ts <= orig_ts:
            # Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
            utils.applyModes(self.irc, channel, changedmodes)
Example #2
0
    def handle_euid(self, numeric, command, args):
        """Handles incoming EUID commands (user introduction)."""
        # <- :42X EUID GL 1 1437505322 +ailoswz ~gl 127.0.0.1 127.0.0.1 42XAAAAAB * * :realname
        nick = args[0]
        ts, modes, ident, host, ip, uid, realhost = args[2:9]
        if realhost == '*':
            realhost = None
        realname = args[-1]
        log.debug(
            '(%s) handle_euid got args: nick=%s ts=%s uid=%s ident=%s '
            'host=%s realname=%s realhost=%s ip=%s', self.irc.name, nick, ts,
            uid, ident, host, realname, realhost, ip)

        self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname,
                                      realhost, ip)
        parsedmodes = utils.parseModes(self.irc, uid, [modes])
        log.debug('Applying modes %s for %s', parsedmodes, uid)
        utils.applyModes(self.irc, uid, parsedmodes)
        self.irc.servers[numeric].users.add(uid)
        # Call the OPERED UP hook if +o is being added to the mode list.
        if ('+o', None) in parsedmodes:
            otype = 'Server_Administrator' if (
                '+a', None) in parsedmodes else 'IRC_Operator'
            self.irc.callHooks([uid, 'PYLINK_CLIENT_OPERED', {'text': otype}])
        return {
            'uid': uid,
            'ts': ts,
            'nick': nick,
            'realhost': realhost,
            'host': host,
            'ident': ident,
            'ip': ip
        }
Example #3
0
    def handle_fjoin(self, servernumeric, command, args):
        """Handles incoming FJOIN commands (InspIRCd equivalent of JOIN/SJOIN)."""
        # :70M FJOIN #chat 1423790411 +AFPfjnt 6:5 7:5 9:5 :o,1SRAABIT4 v,1IOAAF53R <...>
        channel = utils.toLower(self.irc, args[0])
        # InspIRCd sends each channel's users in the form of 'modeprefix(es),UID'
        userlist = args[-1].split()

        their_ts = int(args[1])
        our_ts = self.irc.channels[channel].ts
        self.updateTS(channel, their_ts)

        modestring = args[2:-1] or args[2]
        parsedmodes = utils.parseModes(self.irc, channel, modestring)
        utils.applyModes(self.irc, channel, parsedmodes)
        namelist = []
        for user in userlist:
            modeprefix, user = user.split(',', 1)
            namelist.append(user)
            self.irc.users[user].channels.add(channel)
            if their_ts <= our_ts:
                utils.applyModes(self.irc, channel,
                                 [('+%s' % mode, user) for mode in modeprefix])
            self.irc.channels[channel].users.add(user)
        return {
            'channel': channel,
            'users': namelist,
            'modes': parsedmodes,
            'ts': their_ts
        }
Example #4
0
    def handle_mode(self, numeric, command, args):
        # <- :unreal.midnight.vpn MODE #endlessvoid +bb test!*@* *!*@bad.net
        # <- :unreal.midnight.vpn MODE #endlessvoid +q GL 1444361345
        # <- :unreal.midnight.vpn MODE #endlessvoid +ntCo GL 1444361345
        # <- :unreal.midnight.vpn MODE #endlessvoid +mntClfo 5 [10t]:5  GL 1444361345
        # <- :GL MODE #services +v GL

        # This seems pretty relatively inconsistent - why do some commands have a TS at the end while others don't?
        # Answer: the first syntax (MODE sent by SERVER) is used for channel bursts - according to Unreal 3.2 docs,
        # the last argument should be interpreted as a timestamp ONLY if it is a number and the sender is a server.
        # Ban bursting does not give any TS, nor do normal users setting modes. SAMODE is special though, it will
        # send 0 as a TS argument (which should be ignored unless breaking the internal channel TS is desired).

        # Also, we need to get rid of that extra space following the +f argument. :|
        if utils.isChannel(args[0]):
            channel = utils.toLower(self.irc, args[0])
            oldobj = self.irc.channels[channel].deepcopy()
            modes = list(filter(None, args[1:]))  # normalize whitespace
            parsedmodes = utils.parseModes(self.irc, channel, modes)
            if parsedmodes:
                utils.applyModes(self.irc, channel, parsedmodes)
            if numeric in self.irc.servers and args[-1].isdigit():
                # Sender is a server AND last arg is number. Perform TS updates.
                their_ts = int(args[-1])
                if their_ts > 0:
                    self.updateTS(channel, their_ts)
            return {'target': channel, 'modes': parsedmodes, 'oldchan': oldobj}
        else:
            log.warning("(%s) received MODE for non-channel target: %r",
                        self.irc.name, args)
            raise NotImplementedError
Example #5
0
 def handle_svsmode(self, numeric, command, args):
     """Handle SVSMODE/SVS2MODE, used for setting user modes on others (services)."""
     # <- :source SVSMODE target +usermodes
     target = self._getNick(args[0])
     modes = args[1:]
     parsedmodes = utils.parseModes(self.irc, target, modes)
     utils.applyModes(self.irc, target, parsedmodes)
     return {'target': numeric, 'modes': parsedmodes}
Example #6
0
    def sjoinServer(self, server, channel, users, ts=None):
        """Sends an SJOIN for a group of users to a channel.

        The sender should always be a Server ID (SID). TS is optional, and defaults
        to the one we've stored in the channel state if not given.
        <users> is a list of (prefix mode, UID) pairs:

        Example uses:
            sjoinServer('100', '#test', [('', '100AAABBC'), ('qo', 100AAABBB'), ('h', '100AAADDD')])
            sjoinServer(self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])
        """
        channel = utils.toLower(self.irc, channel)
        server = server or self.irc.sid
        assert users, "sjoinServer: No users sent?"
        log.debug('(%s) sjoinServer: got %r for users', self.irc.name, users)
        if not server:
            raise LookupError('No such PyLink PseudoClient exists.')

        orig_ts = self.irc.channels[channel].ts
        ts = ts or orig_ts
        self.updateTS(channel, ts)

        log.debug("sending SJOIN to %s%s with ts %s (that's %r)", channel,
                  self.irc.name, ts, time.strftime("%c", time.localtime(ts)))
        # Strip out list-modes, they shouldn't ever be sent in FJOIN (protocol rules).
        modes = [
            m for m in self.irc.channels[channel].modes
            if m[0] not in self.irc.cmodes['*A']
        ]
        uids = []
        changedmodes = []
        namelist = []
        # We take <users> as a list of (prefixmodes, uid) pairs.
        for userpair in users:
            assert len(
                userpair) == 2, "Incorrect format of userpair: %r" % userpair
            prefixes, user = userpair
            namelist.append(','.join(userpair))
            uids.append(user)
            for m in prefixes:
                changedmodes.append(('+%s' % m, user))
            try:
                self.irc.users[user].channels.add(channel)
            except KeyError:  # Not initialized yet?
                log.debug(
                    "(%s) sjoinServer: KeyError trying to add %r to %r's channel list?",
                    self.irc.name, channel, user)
        if ts <= orig_ts:
            # Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
            utils.applyModes(self.irc, channel, changedmodes)
        namelist = ' '.join(namelist)
        self._send(
            server, "FJOIN {channel} {ts} {modes} :{users}".format(
                ts=ts,
                users=namelist,
                channel=channel,
                modes=utils.joinModes(modes)))
        self.irc.channels[channel].users.update(uids)
Example #7
0
    def handle_uid(self, numeric, command, args):
        # <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname
        # <- :001 UID GL| 0 1441389007 gl 10.120.0.6 001ZO8F03 0 +iwx * 391A9CB9.26A16454.D9847B69.IP CngABg== :realname
        # arguments: nick, hopcount???, ts, ident, real-host, UID, number???, modes,
        #            displayed host, cloaked (+x) host, base64-encoded IP, and realname
        # TODO: find out what all the "???" fields mean.
        nick = args[0]
        ts, ident, realhost, uid = args[2:6]
        modestring = args[7]
        host = args[8]
        if host == '*':
            # A single * means that there is no displayed/virtual host, and
            # that it's the same as the real host
            host = args[9]
        raw_ip = args[10].encode()  # codecs.decode only takes bytes, not str
        if raw_ip == b'*':  # Dummy IP (for services, etc.)
            ip = '0.0.0.0'
        else:
            # First, decode the Base64 string into a packed binary IP address.
            ip = codecs.decode(raw_ip, "base64")

            try:  # IPv4 address.
                ip = socket.inet_ntop(socket.AF_INET, ip)
            except ValueError:  # IPv6 address.
                ip = socket.inet_ntop(socket.AF_INET6, ip)
                # HACK: make sure a leading ":" in the IPv6 address (e.g. ::1)
                # doesn't cause it to be misinterpreted as the last argument
                # in a line, should it be mirrored to other networks.
                if ip.startswith(':'):
                    ip = '0' + ip
            else:
                raise ProtocolError(
                    "Invalid number of bits in IP address field (got %s, expected 4 or 16)."
                    % len(ipbits))
        realname = args[-1]
        self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname,
                                      realhost, ip)
        parsedmodes = utils.parseModes(self.irc, uid, [modestring])
        utils.applyModes(self.irc, uid, parsedmodes)
        self.irc.servers[numeric].users.add(uid)

        # The cloaked (+x) host is completely separate from the displayed host
        # and real host in that it is ONLY shown if the user is +x (cloak mode
        # enabled) but NOT +t (vHost set). We'll store this separately for now,
        # but more handling is needed so that plugins can update the cloak host
        # appropriately.
        self.irc.users[uid].cloaked_host = args[9]

        return {
            'uid': uid,
            'ts': ts,
            'nick': nick,
            'realhost': realhost,
            'host': host,
            'ident': ident,
            'ip': ip
        }
Example #8
0
 def handle_mode(self, numeric, command, args):
     """Handles incoming user mode changes."""
     # In InspIRCd, MODE is used for setting user modes and
     # FMODE is used for channel modes:
     # <- :70MAAAAAA MODE 70MAAAAAA -i+xc
     target = args[0]
     modestrings = args[1:]
     changedmodes = utils.parseModes(self.irc, numeric, modestrings)
     utils.applyModes(self.irc, target, changedmodes)
     return {'target': target, 'modes': changedmodes}
Example #9
0
    def spawnClient(self,
                    nick,
                    ident='null',
                    host='null',
                    realhost=None,
                    modes=set(),
                    server=None,
                    ip='0.0.0.0',
                    realname=None,
                    ts=None,
                    opertype=None,
                    manipulatable=False):
        """Spawns a client with nick <nick> on the given IRC connection.

        Note: No nick collision / valid nickname checks are done here; it is
        up to plugins to make sure they don't introduce anything invalid."""
        server = server or self.irc.sid
        if not utils.isInternalServer(self.irc, server):
            raise ValueError(
                'Server %r is not a PyLink internal PseudoServer!' % server)
        # Create an UIDGenerator instance for every SID, so that each gets
        # distinct values.
        uid = self.uidgen.setdefault(server,
                                     utils.TS6UIDGenerator(server)).next_uid()
        # EUID:
        # parameters: nickname, hopcount, nickTS, umodes, username,
        # visible hostname, IP address, UID, real hostname, account name, gecos
        ts = ts or int(time.time())
        realname = realname or self.irc.botdata['realname']
        realhost = realhost or host
        raw_modes = utils.joinModes(modes)
        u = self.irc.users[uid] = IrcUser(nick,
                                          ts,
                                          uid,
                                          ident=ident,
                                          host=host,
                                          realname=realname,
                                          realhost=realhost,
                                          ip=ip,
                                          manipulatable=manipulatable)
        utils.applyModes(self.irc, uid, modes)
        self.irc.servers[server].users.add(uid)
        self._send(
            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
Example #10
0
 def handle_bmask(self, numeric, command, args):
     """Handles incoming BMASK commands (ban propagation on burst)."""
     # <- :42X BMASK 1424222769 #dev b :*!test@*.isp.net *!badident@*
     # This is used for propagating bans, not TMODE!
     channel = args[1].lower()
     mode = args[2]
     ts = int(args[0])
     modes = []
     for ban in args[-1].split():
         modes.append(('+%s' % mode, ban))
     utils.applyModes(self.irc, channel, modes)
     return {'target': channel, 'modes': modes, 'ts': ts}
Example #11
0
    def handle_sjoin(self, numeric, command, args):
        """Handles the UnrealIRCd SJOIN command."""
        # <- :001 SJOIN 1444361345 #endlessvoid :001DJ1O02
        # memberlist should be a list of UIDs with their channel status prefixes, as
        # in ":001AAAAAA @001AAAAAB +001AAAAAC".
        # Interestingly, no modes are ever sent in this command as far as I've seen.
        channel = utils.toLower(self.irc, args[1])
        userlist = args[-1].split()

        our_ts = self.irc.channels[channel].ts
        their_ts = int(args[0])
        self.updateTS(channel, their_ts)

        namelist = []
        log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name,
                  userlist, channel)
        for userpair in userlist:
            if userpair.startswith("&\"'"):  # TODO: handle ban bursts too
                # &, ", and ' entries are used for bursting bans:
                # https://www.unrealircd.org/files/docs/technical/serverprotocol.html#S5_1
                break
            r = re.search(r'([^\d]*)(.*)', userpair)
            user = r.group(2)
            # Unreal uses slightly different prefixes in SJOIN. +q is * instead of ~,
            # and +a is ~ instead of &.
            modeprefix = (r.group(1) or '').replace("~", "&").replace("*", "~")
            finalprefix = ''
            assert user, 'Failed to get the UID from %r; our regex needs updating?' % userpair
            log.debug('(%s) handle_sjoin: got modeprefix %r for user %r',
                      self.irc.name, modeprefix, user)
            for m in modeprefix:
                # Iterate over the mapping of prefix chars to prefixes, and
                # find the characters that match.
                for char, prefix in self.irc.prefixmodes.items():
                    if m == prefix:
                        finalprefix += char
            namelist.append(user)
            self.irc.users[user].channels.add(channel)
            # Only merge the remote's prefix modes if their TS is smaller or equal to ours.
            if their_ts <= our_ts:
                utils.applyModes(self.irc, channel, [('+%s' % mode, user)
                                                     for mode in finalprefix])
            self.irc.channels[channel].users.add(user)
        return {
            'channel': channel,
            'users': namelist,
            'modes': self.irc.channels[channel].modes,
            'ts': their_ts
        }
Example #12
0
 def handle_tmode(self, numeric, command, args):
     """Handles incoming TMODE commands (channel mode change)."""
     # <- :42XAAAAAB TMODE 1437450768 #endlessvoid -c+lkC 3 agte4
     channel = utils.toLower(self.irc, args[1])
     oldobj = self.irc.channels[channel].deepcopy()
     modes = args[2:]
     changedmodes = utils.parseModes(self.irc, channel, modes)
     utils.applyModes(self.irc, channel, changedmodes)
     ts = int(args[0])
     return {
         'target': channel,
         'modes': changedmodes,
         'ts': ts,
         'oldchan': oldobj
     }
Example #13
0
 def handle_fmode(self, numeric, command, args):
     """Handles the FMODE command, used for channel mode changes."""
     # <- :70MAAAAAA FMODE #chat 1433653462 +hhT 70MAAAAAA 70MAAAAAD
     channel = utils.toLower(self.irc, args[0])
     oldobj = self.irc.channels[channel].deepcopy()
     modes = args[2:]
     changedmodes = utils.parseModes(self.irc, channel, modes)
     utils.applyModes(self.irc, channel, changedmodes)
     ts = int(args[1])
     return {
         'target': channel,
         'modes': changedmodes,
         'ts': ts,
         'oldchan': oldobj
     }
Example #14
0
 def handle_mode(self, numeric, command, args):
     """Handles incoming user mode changes."""
     # <- :70MAAAAAA MODE 70MAAAAAA -i+xc
     target = args[0]
     modestrings = args[1:]
     changedmodes = utils.parseModes(self.irc, numeric, modestrings)
     utils.applyModes(self.irc, target, changedmodes)
     # Call the OPERED UP hook if +o is being set.
     if ('+o', None) in changedmodes:
         otype = 'Server_Administrator' if (
             'a', None) in self.irc.users[target].modes else 'IRC_Operator'
         self.irc.callHooks(
             [target, 'PYLINK_CLIENT_OPERED', {
                 'text': otype
             }])
     return {'target': target, 'modes': changedmodes}
Example #15
0
 def _sendModes(self, numeric, target, modes, ts=None):
     """Internal function to send mode changes from a PyLink client/server."""
     # -> :9PYAAAAAA FMODE #pylink 1433653951 +os 9PYAAAAAA
     # -> :9PYAAAAAA MODE 9PYAAAAAA -i+w
     log.debug('(%s) inspircd._sendModes: received %r for mode list',
               self.irc.name, modes)
     if ('+o', None) in modes and not utils.isChannel(target):
         # https://github.com/inspself.ircd/inspself.ircd/blob/master/src/modules/m_spanningtree/opertype.cpp#L26-L28
         # Servers need a special command to set umode +o on people.
         self._operUp(target)
     utils.applyModes(self.irc, target, modes)
     joinedmodes = utils.joinModes(modes)
     if utils.isChannel(target):
         ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
         self._send(numeric, 'FMODE %s %s %s' % (target, ts, joinedmodes))
     else:
         self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
Example #16
0
 def _sendModes(self, numeric, target, modes, ts=None):
     """Internal function to send mode changes from a PyLink client/server."""
     # <- :unreal.midnight.vpn MODE #endlessvoid +ntCo GL 1444361345
     utils.applyModes(self.irc, target, modes)
     joinedmodes = utils.joinModes(modes)
     if utils.isChannel(target):
         # The MODE command is used for channel mode changes only
         ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
         self._send(numeric, 'MODE %s %s %s' % (target, joinedmodes, ts))
     else:
         # For user modes, the only way to set modes (for non-U:Lined servers)
         # is through UMODE2, which sets the modes on the caller.
         # U:Lines can use SVSMODE/SVS2MODE, but I won't expect people to
         # U:Line a PyLink daemon...
         if not utils.isInternalClient(self.irc, target):
             raise ProtocolError(
                 'Cannot force mode change on external clients!')
         self._send(target, 'UMODE2 %s' % joinedmodes)
Example #17
0
    def handle_opertype(self, numeric, command, args):
        """Handles incoming OPERTYPE, which is used to denote an oper up.

        This calls the internal hook PYLINK_CLIENT_OPERED, sets the internal
        opertype of the client, and assumes setting user mode +o on the caller."""
        # This is used by InspIRCd to denote an oper up; there is no MODE
        # command sent for it.
        # <- :70MAAAAAB OPERTYPE Network_Owner
        omode = [('+o', None)]
        self.irc.users[numeric].opertype = opertype = args[0].replace("_", " ")
        utils.applyModes(self.irc, numeric, omode)
        # OPERTYPE is essentially umode +o and metadata in one command;
        # we'll call that too.
        self.irc.callHooks(
            [numeric, 'PYLINK_CLIENT_OPERED', {
                'text': opertype
            }])
        return {'target': numeric, 'modes': omode}
Example #18
0
    def handle_sjoin(self, servernumeric, command, args):
        """Handles incoming SJOIN commands."""
        # parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist
        channel = utils.toLower(self.irc, args[1])
        userlist = args[-1].split()
        their_ts = int(args[0])
        our_ts = self.irc.channels[channel].ts

        self.updateTS(channel, their_ts)

        modestring = args[2:-1] or args[2]
        parsedmodes = utils.parseModes(self.irc, channel, modestring)
        utils.applyModes(self.irc, channel, parsedmodes)
        namelist = []
        log.debug('(%s) handle_sjoin: got userlist %r for %r', self.irc.name,
                  userlist, channel)
        for userpair in userlist:
            # charybdis sends this in the form "@+UID1, +UID2, UID3, @UID4"
            r = re.search(r'([^\d]*)(.*)', userpair)
            user = r.group(2)
            modeprefix = r.group(1) or ''
            finalprefix = ''
            assert user, 'Failed to get the UID from %r; our regex needs updating?' % userpair
            log.debug('(%s) handle_sjoin: got modeprefix %r for user %r',
                      self.irc.name, modeprefix, user)
            for m in modeprefix:
                # Iterate over the mapping of prefix chars to prefixes, and
                # find the characters that match.
                for char, prefix in self.irc.prefixmodes.items():
                    if m == prefix:
                        finalprefix += char
            namelist.append(user)
            self.irc.users[user].channels.add(channel)
            if their_ts <= our_ts:
                utils.applyModes(self.irc, channel, [('+%s' % mode, user)
                                                     for mode in finalprefix])
            self.irc.channels[channel].users.add(user)
        return {
            'channel': channel,
            'users': namelist,
            'modes': parsedmodes,
            'ts': their_ts
        }
Example #19
0
 def handle_uid(self, numeric, command, args):
     """Handles incoming UID commands (user introduction)."""
     # :70M UID 70MAAAAAB 1429934638 GL 0::1 hidden-7j810p.9mdf.lrek.0000.0000.IP gl 0::1 1429934638 +Wioswx +ACGKNOQXacfgklnoqvx :realname
     uid, ts, nick, realhost, host, ident, ip = args[0:7]
     realname = args[-1]
     self.irc.users[uid] = IrcUser(nick, ts, uid, ident, host, realname,
                                   realhost, ip)
     parsedmodes = utils.parseModes(self.irc, uid, [args[8], args[9]])
     log.debug('Applying modes %s for %s', parsedmodes, uid)
     utils.applyModes(self.irc, uid, parsedmodes)
     self.irc.servers[numeric].users.add(uid)
     return {
         'uid': uid,
         'ts': ts,
         'nick': nick,
         'realhost': realhost,
         'host': host,
         'ident': ident,
         'ip': ip
     }
Example #20
0
    def _sendModes(self, numeric, target, modes, ts=None):
        """Internal function to send mode changes from a PyLink client/server."""
        utils.applyModes(self.irc, target, modes)
        modes = list(modes)
        if utils.isChannel(target):
            ts = ts or self.irc.channels[utils.toLower(self.irc, target)].ts
            # TMODE:
            # parameters: channelTS, channel, cmode changes, opt. cmode parameters...

            # On output, at most ten cmode parameters should be sent; if there are more,
            # multiple TMODE messages should be sent.
            while modes[:9]:
                joinedmodes = utils.joinModes(modes=[
                    m for m in modes[:9] if m[0] not in self.irc.cmodes['*A']
                ])
                modes = modes[9:]
                self._send(numeric,
                           'TMODE %s %s %s' % (ts, target, joinedmodes))
        else:
            joinedmodes = utils.joinModes(modes)
            self._send(numeric, 'MODE %s %s' % (target, joinedmodes))
Example #21
0
    def testReverseModesExisting(self):
        utils.applyModes(self.irc, '#test', [('+m', None), ('+l', '50'), ('+k', 'supersecret'),
                                             ('+o', '9PYAAAAAA')])

        self._reverseModes({('+i', None), ('+l', '3')}, {('-i', None), ('+l', '50')})
        self._reverseModes('-n', '+n')
        self._reverseModes('-l', '+l 50')
        self._reverseModes('+k derp', '+k supersecret')
        self._reverseModes('-mk *', '+mk supersecret')

        # Existing modes are ignored.
        self._reverseModes([('+t', None)], set())
        self._reverseModes('+n', '+')
        self._reverseModes('+oo GLolol 9PYAAAAAA', '-o GLolol')
        self._reverseModes('+o 9PYAAAAAA', '+')
        self._reverseModes('+vvvvM test abcde atat abcd', '-vvvvM test abcde atat abcd')

        # Ignore unsetting prefixmodes/list modes that were never set.
        self._reverseModes([('-v', '10XAAAAAA')], set())
        self._reverseModes('-ob 10XAAAAAA derp!*@*', '+')
        utils.applyModes(self.irc, '#test', [('+o', 'GLolol'), ('+b', '*[email protected]')])
        self._reverseModes('-voo GLolol GLolol 10XAAAAAA', '+o GLolol')
        self._reverseModes('-bb *!*@* *[email protected]', '+b *[email protected]')
Example #22
0
 def handle_umode2(self, numeric, command, args):
     """Handles UMODE2, used to set user modes on oneself."""
     # <- :GL UMODE2 +W
     parsedmodes = utils.parseModes(self.irc, numeric, args)
     utils.applyModes(self.irc, numeric, parsedmodes)
     return {'target': numeric, 'modes': parsedmodes}
Example #23
0
    def spawnClient(self,
                    nick,
                    ident='null',
                    host='null',
                    realhost=None,
                    modes=set(),
                    server=None,
                    ip='0.0.0.0',
                    realname=None,
                    ts=None,
                    opertype=None,
                    manipulatable=False):
        """Spawns a client with nick <nick> on the given IRC connection.

        Note: No nick collision / valid nickname checks are done here; it is
        up to plugins to make sure they don't introduce anything invalid."""
        server = server or self.irc.sid
        if not utils.isInternalServer(self.irc, server):
            raise ValueError(
                'Server %r is not a PyLink internal PseudoServer!' % server)
        # Unreal 3.4 uses TS6-style UIDs. They don't start from AAAAAA like other IRCd's
        # do, but we can do that fine...
        uid = self.uidgen.setdefault(server,
                                     utils.TS6UIDGenerator(server)).next_uid()
        ts = ts or int(time.time())
        realname = realname or self.irc.botdata['realname']
        realhost = realhost or host
        raw_modes = utils.joinModes(modes)
        u = self.irc.users[uid] = IrcUser(nick,
                                          ts,
                                          uid,
                                          ident=ident,
                                          host=host,
                                          realname=realname,
                                          realhost=realhost,
                                          ip=ip,
                                          manipulatable=manipulatable)
        utils.applyModes(self.irc, uid, modes)
        self.irc.servers[server].users.add(uid)

        # UnrealIRCd requires encoding the IP by first packing it into a binary format,
        # and then encoding the binary with Base64.
        if ip == '0.0.0.0':  # Dummy IP (for services, etc.) use a single *.
            encoded_ip = '*'
        else:
            try:  # Try encoding as IPv4 first.
                binary_ip = socket.inet_pton(socket.AF_INET, ip)
            except OSError:
                try:  # That failed, try IPv6 next.
                    binary_ip = socket.inet_pton(socket.AF_INET6, ip)
                except OSError:
                    raise ValueError("Invalid IPv4 or IPv6 address %r." % ip)

            # Encode in Base64.
            encoded_ip = codecs.encode(binary_ip, "base64")
            # Now, strip the trailing \n and decode into a string again.
            encoded_ip = encoded_ip.strip().decode()

        # <- :001 UID GL 0 1441306929 gl localhost 0018S7901 0 +iowx * midnight-1C620195 fwAAAQ== :realname
        self._send(
            server, "UID {nick} 0 {ts} {ident} {realhost} {uid} 0 {modes} "
            "{host} * {ip} :{realname}".format(ts=ts,
                                               host=host,
                                               nick=nick,
                                               ident=ident,
                                               uid=uid,
                                               modes=raw_modes,
                                               realname=realname,
                                               realhost=realhost,
                                               ip=encoded_ip))

        # Force the virtual hostname to show correctly by running SETHOST on
        # the user. Otherwise, Unreal will show the real host of the person
        # instead, which is probably not what we want.
        self.updateClient(uid, 'HOST', host)

        return u
Example #24
0
    def sjoinServer(self, server, channel, users, ts=None):
        """Sends an SJOIN for a group of users to a channel.

        The sender should always be a Server ID (SID). TS is optional, and defaults
        to the one we've stored in the channel state if not given.
        <users> is a list of (prefix mode, UID) pairs:

        Example uses:
            sjoinServer('100', '#test', [('', '100AAABBC'), ('o', 100AAABBB'), ('v', '100AAADDD')])
            sjoinServer(self.irc.sid, '#test', [('o', self.irc.pseudoclient.uid)])
        """
        # https://github.com/grawity/irc-docs/blob/master/server/ts6.txt#L821
        # parameters: channelTS, channel, simple modes, opt. mode parameters..., nicklist

        # Broadcasts a channel creation or bursts a channel.

        # The nicklist consists of users joining the channel, with status prefixes for
        # their status ('@+', '@', '+' or ''), for example:
        # '@+1JJAAAAAB +2JJAAAA4C 1JJAAAADS'. All users must be behind the source server
        # so it is not possible to use this message to force users to join a channel.
        channel = utils.toLower(self.irc, channel)
        server = server or self.irc.sid
        assert users, "sjoinServer: No users sent?"
        log.debug('(%s) sjoinServer: got %r for users', self.irc.name, users)
        if not server:
            raise LookupError('No such PyLink PseudoClient exists.')

        orig_ts = self.irc.channels[channel].ts
        ts = ts or orig_ts
        self.updateTS(channel, ts)

        log.debug("(%s) sending SJOIN to %s with ts %s (that's %r)",
                  self.irc.name, channel, ts,
                  time.strftime("%c", time.localtime(ts)))
        modes = [
            m for m in self.irc.channels[channel].modes
            if m[0] not in self.irc.cmodes['*A']
        ]
        changedmodes = []
        while users[:10]:
            uids = []
            namelist = []
            # We take <users> as a list of (prefixmodes, uid) pairs.
            for userpair in users[:10]:
                assert len(
                    userpair
                ) == 2, "Incorrect format of userpair: %r" % userpair
                prefixes, user = userpair
                prefixchars = ''
                for prefix in prefixes:
                    pr = self.irc.prefixmodes.get(prefix)
                    if pr:
                        prefixchars += pr
                        changedmodes.append(('+%s' % prefix, user))
                namelist.append(prefixchars + user)
                uids.append(user)
                try:
                    self.irc.users[user].channels.add(channel)
                except KeyError:  # Not initialized yet?
                    log.debug(
                        "(%s) sjoinServer: KeyError trying to add %r to %r's channel list?",
                        self.irc.name, channel, user)
            users = users[10:]
            namelist = ' '.join(namelist)
            self._send(
                server, "SJOIN {ts} {channel} {modes} :{users}".format(
                    ts=ts,
                    users=namelist,
                    channel=channel,
                    modes=utils.joinModes(modes)))
            self.irc.channels[channel].users.update(uids)
        if ts <= orig_ts:
            # Only save our prefix modes in the channel state if our TS is lower than or equal to theirs.
            utils.applyModes(self.irc, channel, changedmodes)