Ejemplo n.º 1
0
def showuser(irc, source, args):
    """<user>

    Shows information about <user>."""
    try:
        target = args[0]
    except IndexError:
        irc.reply("Error: Not enough arguments. Needs 1: nick.")
        return
    u = utils.nickToUid(irc, target) or target
    # Only show private info if the person is calling 'showuser' on themselves,
    # or is an oper.
    verbose = utils.isOper(irc, source) or u == source
    if u not in irc.users:
        irc.reply('Error: Unknown user %r.' % target)
        return

    f = lambda s: irc.msg(source, s)
    userobj = irc.users[u]
    f('Information on user \x02%s\x02 (%s@%s): %s' %
      (userobj.nick, userobj.ident, userobj.host, userobj.realname))
    sid = utils.clientToServer(irc, u)
    serverobj = irc.servers[sid]
    ts = userobj.ts
    f('\x02Home server\x02: %s (%s); \x02Signon time:\x02 %s (%s)' % \
      (serverobj.name, sid, ctime(float(ts)), ts))
    if verbose:
        f('\x02Protocol UID\x02: %s; \x02PyLink identification\x02: %s' % \
          (u, userobj.identified))
        f('\x02User modes\x02: %s' % utils.joinModes(userobj.modes))
        f('\x02Real host\x02: %s; \x02IP\x02: %s; \x02Away status\x02: %s' % \
          (userobj.realhost, userobj.ip, userobj.away or '\x1D(not set)\x1D'))
        f('\x02Channels\x02: %s' %
          (' '.join(userobj.channels).strip() or '\x1D(none)\x1D'))
Ejemplo n.º 2
0
 def joinClient(self, client, channel):
     """Joins a PyLink client to a channel."""
     # InspIRCd doesn't distinguish between burst joins and regular joins,
     # so what we're actually doing here is sending FJOIN from the server,
     # on behalf of the clients that are joining.
     channel = utils.toLower(self.irc, channel)
     server = utils.isInternalClient(self.irc, client)
     if not server:
         log.error(
             '(%s) Error trying to join client %r to %r (no such pseudoclient exists)',
             self.irc.name, client, channel)
         raise LookupError('No such PyLink PseudoClient exists.')
     # Strip out list-modes, they shouldn't be ever sent in FJOIN.
     modes = [
         m for m in self.irc.channels[channel].modes
         if m[0] not in self.irc.cmodes['*A']
     ]
     self._send(
         server, "FJOIN {channel} {ts} {modes} :,{uid}".format(
             ts=self.irc.channels[channel].ts,
             uid=client,
             channel=channel,
             modes=utils.joinModes(modes)))
     self.irc.channels[channel].users.add(client)
     self.irc.users[client].channels.add(channel)
Ejemplo n.º 3
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)
Ejemplo n.º 4
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
Ejemplo n.º 5
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))
Ejemplo n.º 6
0
def showchan(irc, source, args):
    """<channel>

    Shows information about <channel>."""
    try:
        channel = utils.toLower(irc, args[0])
    except IndexError:
        irc.reply("Error: Not enough arguments. Needs 1: channel.")
        return
    if channel not in irc.channels:
        irc.reply('Error: Unknown channel %r.' % channel)
        return

    f = lambda s: irc.msg(source, s)
    c = irc.channels[channel]
    # Only show verbose info if caller is oper or is in the target channel.
    verbose = source in c.users or utils.isOper(irc, source)
    secret = ('s', None) in c.modes
    if secret and not verbose:
        # Hide secret channels from normal users.
        irc.msg(source, 'Error: Unknown channel %r.' % channel)
        return

    nicks = [irc.users[u].nick for u in c.users]
    pmodes = ('owner', 'admin', 'op', 'halfop', 'voice')

    f('Information on channel \x02%s\x02:' % channel)
    f('\x02Channel topic\x02: %s' % c.topic)
    f('\x02Channel creation time\x02: %s (%s)' % (ctime(c.ts), c.ts))
    # Show only modes that aren't list-style modes.
    modes = utils.joinModes(
        [m for m in c.modes if m[0] not in irc.cmodes['*A']])
    f('\x02Channel modes\x02: %s' % modes)
    if verbose:
        nicklist = []
        # Iterate over the user list, sorted by nick.
        for user, nick in sorted(zip(c.users, nicks),
                                 key=lambda userpair: userpair[1].lower()):
            prefixmodes = [
                irc.prefixmodes.get(irc.cmodes.get(pmode, ''), '')
                for pmode in pmodes if user in c.prefixmodes[pmode + 's']
            ]
            nicklist.append(''.join(prefixmodes) + nick)

        while nicklist[:20]:  # 20 nicks per line to prevent message cutoff.
            f('\x02User list\x02: %s' % ' '.join(nicklist[:20]))
            nicklist = nicklist[20:]
Ejemplo n.º 7
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))
Ejemplo n.º 8
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)
Ejemplo n.º 9
0
    def testJoinModes(self):
        res = utils.joinModes({('+l', '50'), ('+n', None), ('+t', None)})
        # Sets are orderless, so the end mode could be scrambled in a number of ways.
        # Basically, we're looking for a string that looks like '+ntl 50' or '+lnt 50'.
        possible = ['+%s 50' % ''.join(x) for x in itertools.permutations('lnt', 3)]
        self.assertIn(res, possible)

        # Without any arguments, make sure there is no trailing space.
        self.assertEqual(utils.joinModes({('+t', None)}), '+t')

        # The +/- in the mode is not required; if it doesn't exist, assume we're
        # adding modes always.
        self.assertEqual(utils.joinModes([('t', None), ('n', None)]), '+tn')

        # An empty query should return just '+'
        self.assertEqual(utils.joinModes(set()), '+')

        # More complex query now with both + and - modes being set
        res = utils.joinModes([('+l', '50'), ('-n', None)])
        self.assertEqual(res, '+l-n 50')

        # If one modepair in the list lacks a +/- prefix, just follow the
        # previous one's.
        res = utils.joinModes([('+l', '50'), ('-n', None), ('m', None)])
        self.assertEqual(res, '+l-nm 50')
        res = utils.joinModes([('+l', '50'), ('m', None)])
        self.assertEqual(res, '+lm 50')
        res = utils.joinModes([('l', '50'), ('-m', None)])
        self.assertEqual(res, '+l-m 50')

        # Rarely in real life will we get a mode string this complex.
        # Let's make sure it works, just in case.
        res = utils.joinModes([('-o', '9PYAAAAAA'), ('+l', '50'), ('-n', None),
                               ('-m', None), ('+k', 'hello'),
                               ('+b', '*!*@*.badisp.net')])
        self.assertEqual(res, '-o+l-nm+kb 9PYAAAAAA 50 hello *!*@*.badisp.net')
Ejemplo n.º 10
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
Ejemplo n.º 11
0
def handle_whois(irc, source, command, args):
    """Handle WHOIS queries, for IRCds that send them across servers (charybdis, UnrealIRCd; NOT InspIRCd)."""
    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)
        return
    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(server, 311, source, "%s %s %s * :%s" % (nick, user.ident, user.host, user.realname))
    # 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
        # Show prefix modes like a regular IRCd does.
        for prefixmode, prefixchar in irc.prefixmodes.items():
            modename = [mname for mname, char in irc.cmodes.items() if char == prefixmode]
            if modename and target in c.prefixmodes[modename[0]+'s']:
                chan = prefixchar + chan
        public_chans.append(chan)
    if public_chans:
        f(server, 319, source, '%s :%s' % (nick, ' '.join(public_chans)))
    # 312: sends the server the target is on, and its server description.
    f(server, 312, source, "%s %s :%s" % (nick, irc.servers[server].name,
      irc.servers[server].desc))
    # 313: sends a string denoting the target's operator privilege,
    # only if they have umode +o.
    if ('o', None) in user.modes:
        if hasattr(user, 'opertype'):
            opertype = user.opertype
        else:
            opertype = "IRC Operator"
        # Let's be gramatically correct.
        n = 'n' if opertype[0].lower() in 'aeiou' else ''
        f(server, 313, source, "%s :is a%s %s" % (nick, n, opertype))
    # 379: RPL_WHOISMODES, used by UnrealIRCd and InspIRCd.
    # Only show this to opers!
    if sourceisOper:
        f(server, 378, source, "%s :is connecting from %s@%s %s" % (nick, user.ident, user.realhost, user.ip))
        f(server, 379, source, '%s :is using modes %s' % (nick, utils.joinModes(user.modes)))
    # 317: shows idle and signon time. However, we don't track the user's real
    # idle time, so we simply return 0.
    # <- 317 GL GL 15 1437632859 :seconds idle, signon time
    f(server, 317, source, "%s 0 %s :seconds idle, signon time" % (nick, user.ts))
    for func in world.whois_handlers:
    # Iterate over custom plugin WHOIS handlers. They return a tuple
    # or list with two arguments: the numeric, and the text to send.
        try:
            res = func(irc, target)
            if res:
                num, text = res
                f(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('(%s) Error caught in WHOIS handler: %s', irc.name, e)
    # 318: End of WHOIS.
    f(server, 318, source, "%s :End of /WHOIS list" % nick)
Ejemplo n.º 12
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)