Esempio n. 1
0
 def setNetworkBan(self, cidr, on_off):
     # See if this is a valid 1.2.3.4/5 CIDR string.
     try:
         ipmask = ipv4.CidrStringToIPMask(cidr)
     except ValueError, e:
         LOG.error("Bad CIDR string: %s")
         return
Esempio n. 2
0
 def setNetworkBan(self, cidr, on_off):
     # See if this is a valid 1.2.3.4/5 CIDR string.
     try:
         ipmask = ipv4.CidrStringToIPMask(cidr)
     except ValueError, e:
         LOG.error("Bad CIDR string: %s")
         return
Esempio n. 3
0
    def handleCmd_ENDBURST(self, prefix, args):
        if self.ism.syncd:
            # FIXME
            LOG.warning("Ignoring ENDBURST")
            return

        CHECK(self.server_name)
        LOG.info("Finished receiving IRC sync data.")

        self.showirc = True

        # Check for conflicting bridges.
        if self.ism.findConflictingBridge():
            LOG.error("My nick prefix is in use! Terminating.")
            self.transport.loseConnection()
            reactor.stop()
            return

        # Set up nick reservation
        scfg = getServiceConfig()

        self.ism.killConflictingUsers()
        self.sendLine(":%s ADDLINE Q %s* %s %d 0 :%s" %
                      (self.sid, cfg.dc_to_irc_prefix, scfg.my_host,
                       time.time(), self.qline_reason))

        # Send my own bridge nick
        self.pushBotJoin(do_nick=True)

        # When we enter the syncd state, register this instance with Dtella.
        # This will eventually trigger event_DtellaUp, where we send our state.
        self.schedulePing()
        self.ism.addMeToMain()
Esempio n. 4
0
    def handleCmd_EOS(self, prefix, args):
        if prefix != self.server_name:
            return
        if self.ism.syncd:
            return

        LOG.info("Finished receiving IRC sync data.")

        self.showirc = True

        # Check for conflicting bridges.
        if self.ism.findConflictingBridge():
            LOG.error("My nick prefix is in use! Terminating.")
            self.transport.loseConnection()
            reactor.stop()
            return

        # Set up nick reservation
        scfg = getServiceConfig()
        self.sendLine("TKL + Q * %s* %s 0 %d :Reserved for Dtella" %
                      (cfg.dc_to_irc_prefix, scfg.my_host, time.time()))
        self.ism.killConflictingUsers()

        # Send my own bridge nick
        self.pushBotJoin(do_nick=True)

        # When we enter the syncd state, register this instance with Dtella.
        # This will eventually trigger event_DtellaUp, where we send our state.
        self.schedulePing()
        self.ism.addMeToMain()
Esempio n. 5
0
    def handleCmd_ENDBURST(self, prefix, args):
        if self.ism.syncd:
            # FIXME
            LOG.warning("Ignoring ENDBURST")
            return

        CHECK(self.server_name)
        LOG.info("Finished receiving IRC sync data.")

        self.showirc = True

        # Check for conflicting bridges.
        if self.ism.findConflictingBridge():
            LOG.error("My nick prefix is in use! Terminating.")
            self.transport.loseConnection()
            reactor.stop()
            return

        # Set up nick reservation
        scfg = getServiceConfig()

        self.ism.killConflictingUsers()
        self.sendLine(
            ":%s ADDLINE Q %s* %s %d 0 :%s" %
            (self.sid, cfg.dc_to_irc_prefix, scfg.my_host,
             time.time(), self.qline_reason))

        # Send my own bridge nick
        self.pushBotJoin(do_nick=True)

        # When we enter the syncd state, register this instance with Dtella.
        # This will eventually trigger event_DtellaUp, where we send our state.
        self.schedulePing()
        self.ism.addMeToMain()
Esempio n. 6
0
    def handleCmd_TKL(self, prefix, args):
        addrem = args[0]
        kind = args[1]

        if addrem == '+':
            on_off = True
        elif addrem == '-':
            on_off = False
        else:
            LOG.error("TKL: invalid modifier: '%s'" % addrem)
            return

        # :irc1.dhirc.com TKL + Z * 128.10.12.0/24 [email protected] 0 1171427130 :no reason
        if kind == 'Z' and args[2] == '*':
            cidr = args[3]
            self.ism.setNetworkBan(cidr, on_off)

        # :%s TKL + Q * %s* %s 0 %d :Reserved for Dtella
        elif kind == 'Q':
            nickmask = args[3]
            if on_off:
                reason = args[-1]
                self.ism.addQLine(nickmask, reason)
            else:
                self.ism.removeQLine(nickmask)
Esempio n. 7
0
    def handleCmd_EOS(self, prefix, args):
        if prefix != self.server_name:
            return
        if self.ism.syncd:
            return

        LOG.info("Finished receiving IRC sync data.")

        self.showirc = True

        # Check for conflicting bridges.
        if self.ism.findConflictingBridge():
            LOG.error("My nick prefix is in use! Terminating.")
            self.transport.loseConnection()
            reactor.stop()
            return

        # Set up nick reservation
        scfg = getServiceConfig()
        self.sendLine(
            "TKL + Q * %s* %s 0 %d :Reserved for Dtella" %
            (cfg.dc_to_irc_prefix, scfg.my_host, time.time()))
        self.ism.killConflictingUsers()

        # Send my own bridge nick
        self.pushBotJoin(do_nick=True)

        # When we enter the syncd state, register this instance with Dtella.
        # This will eventually trigger event_DtellaUp, where we send our state.
        self.schedulePing()
        self.ism.addMeToMain()
Esempio n. 8
0
    def handleCmd_TKL(self, prefix, args):
        addrem = args[0]
        kind = args[1]

        if addrem == '+':
            on_off = True
        elif addrem == '-':
            on_off = False
        else:
            LOG.error("TKL: invalid modifier: '%s'" % addrem)
            return

        # :irc1.dhirc.com TKL + Z * 128.10.12.0/24 [email protected] 0 1171427130 :no reason
        if kind == 'Z' and args[2] == '*':
            cidr = args[3]
            self.ism.setNetworkBan(cidr, on_off)

        # :%s TKL + Q * %s* %s 0 %d :Reserved for Dtella
        elif kind == 'Q':
            nickmask = args[3]
            if on_off:
                reason = args[-1]
                self.ism.addQLine(nickmask, reason)
            else:
                self.ism.removeQLine(nickmask)
Esempio n. 9
0
    def removeQLine(self, nickmask):
        self.qlines.pop(nickmask, None)
        LOG.info("Removed Q-line: " + nickmask)

        # If some other bridge removes our reservation, abort.
        if self.syncd and (nickmask == cfg.dc_to_irc_prefix + "*"):
            LOG.error("My own Q-line was removed! Terminating.")
            if self.ircs:
                self.ircs.transport.loseConnection()
            reactor.stop()
Esempio n. 10
0
    def removeQLine(self, nickmask):
        self.qlines.pop(nickmask, None)
        LOG.info("Removed Q-line: " + nickmask)

        # If some other bridge removes our reservation, abort.
        if self.syncd and (nickmask == cfg.dc_to_irc_prefix + "*"):
            LOG.error("My own Q-line was removed! Terminating.")
            if self.ircs:
                self.ircs.transport.loseConnection()
            reactor.stop()
Esempio n. 11
0
        def cb():
            self.ping_dcall = None

            if self.ping_waiting:
                LOG.error("Ping timeout!")
                self.transport.loseConnection()
            else:
                scfg = getServiceConfig()
                self.sendLine("PING :%s" % scfg.my_host)
                self.ping_waiting = True
                self.ping_dcall = reactor.callLater(60.0, cb)
Esempio n. 12
0
        def cb():
            self.ping_dcall = None

            if self.ping_waiting:
                LOG.error("Ping timeout!")
                self.transport.loseConnection()
            else:
                scfg = getServiceConfig()
                self.sendLine("PING :%s" % scfg.my_host)
                self.ping_waiting = True
                self.ping_dcall = reactor.callLater(60.0, cb)
Esempio n. 13
0
 def startConnecting(self):
     udp_state = self.ph.getSocketState()
     if udp_state == 'dead':
         try:
             reactor.listenUDP(cfg.udp_port, self.ph)
         except twisted.internet.error.BindError:
             LOG.error("Failed to bind UDP port!")
             raise SystemExit
     elif udp_state == 'dying':
         return
     
     CHECK(self.ph.getSocketState() == 'alive')
     self.startInitialContact()
Esempio n. 14
0
    def startConnecting(self):
        udp_state = self.ph.getSocketState()
        if udp_state == 'dead':
            try:
                reactor.listenUDP(cfg.udp_port, self.ph)
            except twisted.internet.error.BindError:
                LOG.error("Failed to bind UDP port!")
                raise SystemExit
        elif udp_state == 'dying':
            return

        CHECK(self.ph.getSocketState() == 'alive')
        self.startInitialContact()
Esempio n. 15
0
 def cb(first):
     try:
         reactor.listenTCP(dc_port, dfactory, interface='127.0.0.1')
     except twisted.internet.error.CannotListenError:
         if first:
             LOG.warning("TCP bind failed.  Killing old process...")
             if terminate(dc_port):
                 LOG.info("Ok.  Sleeping...")
                 reactor.callLater(2.0, cb, False)
             else:
                 LOG.error("Kill failed.  Giving up.")
                 reactor.stop()
         else:
             LOG.error("Bind failed again.  Giving up.")
             reactor.stop()
     else:
         LOG.info("Listening on 127.0.0.1:%d" % dc_port)
         dtMain.startConnecting()
Esempio n. 16
0
 def cb(first):
     try:
         reactor.listenTCP(dc_port, dfactory, interface='127.0.0.1')
     except twisted.internet.error.CannotListenError:
         if first:
             LOG.warning("TCP bind failed.  Killing old process...")
             if terminate(dc_port):
                 LOG.info("Ok.  Sleeping...")
                 reactor.callLater(2.0, cb, False)
             else:
                 LOG.error("Kill failed.  Giving up.")
                 reactor.stop()
         else:
             LOG.error("Bind failed again.  Giving up.")
             reactor.stop()
     else:
         LOG.info("Listening on 127.0.0.1:%d" % dc_port)
         dtMain.startConnecting()
Esempio n. 17
0
    def joinChannel(self, u):
        if u in self.chanusers:
            LOG.error("joinChannel: %r already in channel." % u)
            return

        self.chanusers.add(u)
        u.chanmodes.clear()

        try:
            osm = self.getOnlineStateManager()
        except NotOnline:
            return

        scfg = getServiceConfig()
        infoindex = scfg.chan_umodes.getUserInfoIndex(u)
        chunks = []
        osm.bsm.addNickChunk(chunks, irc_to_dc(u.inick), infoindex)
        osm.bsm.sendBridgeChange(chunks)
Esempio n. 18
0
    def joinChannel(self, u):
        if u in self.chanusers:
            LOG.error("joinChannel: %r already in channel." % u)
            return

        self.chanusers.add(u)
        u.chanmodes.clear()

        try:
            osm = self.getOnlineStateManager()
        except NotOnline:
            return

        scfg = getServiceConfig()
        infoindex = scfg.chan_umodes.getUserInfoIndex(u)
        chunks = []
        osm.bsm.addNickChunk(
            chunks, irc_to_dc(u.inick), infoindex)
        osm.bsm.sendBridgeChange(chunks)
Esempio n. 19
0
    def setChannelUserModes(self, whoset, u, changes):
        # changes: dict of {mode -> on_off}
        if u not in self.chanusers:
            LOG.error("setChannelUserModes: %r not in channel." % u)
            return

        scfg = getServiceConfig()

        # Save old index, apply changes, and get new index.
        old_infoindex = scfg.chan_umodes.getUserInfoIndex(u)
        for mode, on_off in changes.iteritems():
            if on_off:
                u.chanmodes.add(mode)
            else:
                u.chanmodes.discard(mode)
        new_infoindex = scfg.chan_umodes.getUserInfoIndex(u)

        try:
            osm = self.getOnlineStateManager()
        except NotOnline:
            return

        chunks = []
        if new_infoindex == old_infoindex:
            friendly_change = "well that was pointless"
        else:
            friendly_change = "%s -> %s" % (
                scfg.chan_umodes.friendly[old_infoindex],
                scfg.chan_umodes.friendly[new_infoindex])
            osm.bsm.addNickChunk(
                chunks, irc_to_dc(u.inick), new_infoindex)

        osm.bsm.addChatChunk(
            chunks, cfg.irc_to_dc_bot,
            "%s set mode %s for %s: %s" % (
                irc_to_dc(whoset),
                self.formatChannelUserModes(changes),
                irc_to_dc(u.inick),
                friendly_change))

        osm.bsm.sendBridgeChange(chunks)
Esempio n. 20
0
    def handleCmd_SQUIT(self, prefix, args):
        # :n.dhirc.com SQUIT remote.dtella.org :Remote host closed
        hostname = args[0]
        try:
            # All servers have been registered as users.
            sid = self.ism.findUser(hostname).uuid.lower()
            if len(sid) != 3:
                raise KeyError
        except KeyError:
            LOG.error("SQUIT: unknown server: %s" % hostname)
            return

        # Find all the users belonging to this server.  This should
        # never match nicks, because only UUIDs start with a number.
        remove_uuids = [uuid for uuid in self.ism.users
                        if uuid.startswith(sid)]

        # Drop them off the network.
        for uuid in remove_uuids:
            LOG.info("SQUIT: removing user: %s" % uuid)
            self.ism.removeUser(self.ism.findUser(uuid))
Esempio n. 21
0
    def handleCmd_SQUIT(self, prefix, args):
        # :n.dhirc.com SQUIT remote.dtella.org :Remote host closed
        hostname = args[0]
        try:
            # All servers have been registered as users.
            sid = self.ism.findUser(hostname).uuid.lower()
            if len(sid) != 3:
                raise KeyError
        except KeyError:
            LOG.error("SQUIT: unknown server: %s" % hostname)
            return

        # Find all the users belonging to this server.  This should
        # never match nicks, because only UUIDs start with a number.
        remove_uuids = [
            uuid for uuid in self.ism.users if uuid.startswith(sid)
        ]

        # Drop them off the network.
        for uuid in remove_uuids:
            LOG.info("SQUIT: removing user: %s" % uuid)
            self.ism.removeUser(self.ism.findUser(uuid))
Esempio n. 22
0
    def setChannelUserModes(self, whoset, u, changes):
        # changes: dict of {mode -> on_off}
        if u not in self.chanusers:
            LOG.error("setChannelUserModes: %r not in channel." % u)
            return

        scfg = getServiceConfig()

        # Save old index, apply changes, and get new index.
        old_infoindex = scfg.chan_umodes.getUserInfoIndex(u)
        for mode, on_off in changes.iteritems():
            if on_off:
                u.chanmodes.add(mode)
            else:
                u.chanmodes.discard(mode)
        new_infoindex = scfg.chan_umodes.getUserInfoIndex(u)

        try:
            osm = self.getOnlineStateManager()
        except NotOnline:
            return

        chunks = []
        if new_infoindex == old_infoindex:
            friendly_change = "well that was pointless"
        else:
            friendly_change = "%s -> %s" % (
                scfg.chan_umodes.friendly[old_infoindex],
                scfg.chan_umodes.friendly[new_infoindex])
            osm.bsm.addNickChunk(chunks, irc_to_dc(u.inick), new_infoindex)

        osm.bsm.addChatChunk(
            chunks, cfg.irc_to_dc_bot, "%s set mode %s for %s: %s" %
            (irc_to_dc(whoset), self.formatChannelUserModes(changes),
             irc_to_dc(u.inick), friendly_change))

        osm.bsm.sendBridgeChange(chunks)
Esempio n. 23
0
    def handleCmd_ADDLINE(self, prefix, args):
        kind = args[0]

        # :268 ADDLINE Z 69.69.69.69 <Config> 1237917434 0 :hello
        if kind == 'Z':
            cidr = args[1]
            self.ism.setNetworkBan(cidr, True)

        # :268 ADDLINE Q [P]* setter 1238300707 0 :Reserved
        elif kind == 'Q':
            nickmask = args[1]
            timeset = int(args[3])
            reason = args[-1]

            if self.ism.syncd and nickmask == cfg.dc_to_irc_prefix + "*":
                # If reason matches, it's a self-echo.  Otherwise, someone
                # took my prefix first.
                if reason != self.qline_reason:
                    LOG.error("Someone stole my Q-line! Terminating.")
                    self.transport.loseConnection()
                    reactor.stop()
                return

            self.ism.addQLine(nickmask, reason)
Esempio n. 24
0
    def handleCmd_ADDLINE(self, prefix, args):
        kind = args[0]

        # :268 ADDLINE Z 69.69.69.69 <Config> 1237917434 0 :hello
        if kind == 'Z':
            cidr = args[1]
            self.ism.setNetworkBan(cidr, True)

        # :268 ADDLINE Q [P]* setter 1238300707 0 :Reserved
        elif kind == 'Q':
            nickmask = args[1]
            timeset = int(args[3])
            reason = args[-1]

            if self.ism.syncd and nickmask == cfg.dc_to_irc_prefix + "*":
                # If reason matches, it's a self-echo.  Otherwise, someone
                # took my prefix first.
                if reason != self.qline_reason:
                    LOG.error("Someone stole my Q-line! Terminating.")
                    self.transport.loseConnection()
                    reactor.stop()
                return

            self.ism.addQLine(nickmask, reason)
Esempio n. 25
0
    def handleCmd_MODE(self, prefix, args):
        # :Paul MODE #dtella +vv aaahhh Big_Guy
        whoset = prefix
        chan = args[0]
        change = args[1]
        nicks = args[2:]

        scfg = getServiceConfig()
        if chan != scfg.channel:
            return

        on_off = True
        i = 0

        # User() -> {mode -> on_off}
        user_changes = {}

        # Dtella node modes that need unsetting.
        unset_modes = []
        unset_nicks = []

        for c in change:
            if c == '+':
                on_off = True
            elif c == '-':
                on_off = False
            elif c == 't':
                self.ism.setTopicLocked(whoset, on_off)
            elif c == 'm':
                self.ism.setModerated(whoset, on_off)
            elif c == 'k':
                # Skip over channel key
                i += 1
            elif c == 'l':
                # Skip over channel user limit
                i += 1
            elif c == 'b':
                banmask = nicks[i]
                i += 1
                self.ism.setChannelBan(whoset, on_off, banmask)
            elif c in scfg.chan_umodes.modes:
                # Grab affected nick
                nick = nicks[i]
                i += 1

                n = self.ism.findDtellaNode(inick=nick)
                if n:
                    # If someone set a mode for a Dt node, unset it.
                    if on_off:
                        unset_modes.append(c)
                        unset_nicks.append(nick)
                    continue

                # Get the IRC user we're modifying.
                try:
                    u = self.ism.findUser(nick)
                except KeyError:
                    LOG.error("MODE: unknown nick: %s" % nick)
                    continue

                # Schedule a mode change for this user.
                user_changes.setdefault(u, {})[c] = on_off

        # Undo mode changes for Dtella nodes.
        if unset_modes:
            self.sendLine(
                ":%s MODE %s -%s %s" % (
                    self.ism.bot_user.inick, scfg.channel,
                    ''.join(unset_modes), ' '.join(unset_nicks)))

        # Send IRC user mode changes to Dtella
        for u, changes in user_changes.iteritems():
            self.ism.setChannelUserModes(whoset, u, changes)
Esempio n. 26
0
 def handleCmd_BURST(self, prefix, args):
     return  #FIXME
     if self.ism.syncd:
         LOG.error("Can't handle BURST after sync. Restarting.")
         self.transport.loseConnection()
Esempio n. 27
0
 def handleCmd_BURST(self, prefix, args):
     return #FIXME
     if self.ism.syncd:
         LOG.error("Can't handle BURST after sync. Restarting.")
         self.transport.loseConnection()
Esempio n. 28
0
    def handleCmd_FMODE(self, prefix, args):
        # :268AAAAAF FMODE #dtella 1238298761 +h-o 268AAAAAF 268AAAAAF
        whoset_uuid = prefix
        chan = args[0]
        change = args[2]
        margs = args[3:]

        scfg = getServiceConfig()
        if chan != scfg.channel:
            return

        try:
            whoset = self.ism.findUser(whoset_uuid).inick
        except KeyError:
            # Could be a server?
            whoset = ""

        on_off = True
        i = 0

        # User() -> {mode -> on_off}
        user_changes = {}

        # Dtella node modes that need unsetting.
        unset_modes = []
        unset_uuids = []

        for c in change:
            if c == '+':
                on_off = True
            elif c == '-':
                on_off = False
            elif c == 't':
                self.ism.setTopicLocked(whoset, on_off)
            elif c == 'm':
                self.ism.setModerated(whoset, on_off)
            elif c == 'k':
                # Skip over channel key
                i += 1
            elif c == 'l':
                # Skip over channel user limit
                i += 1
            elif c == 'b':
                banmask = margs[i]
                i += 1
                self.ism.setChannelBan(whoset, on_off, banmask)
            elif c in scfg.chan_umodes.modes:
                # Grab affected user
                uuid = margs[i]
                i += 1

                n = self.ism.findDtellaNode(inick=uuid)
                if n:
                    # If someone set a mode for a Dt node, unset it.
                    if on_off:
                        unset_modes.append(c)
                        unset_uuids.append(uuid)
                    continue

                # Get the IRC user we're modifying.
                try:
                    u = self.ism.findUser(uuid)
                except KeyError:
                    LOG.error("MODE: unknown user: %s" % uuid)
                    continue

                # Schedule a mode change for this user.
                user_changes.setdefault(u, {})[c] = on_off

        # Undo mode changes for Dtella nodes.
        if unset_modes:
            self.sendLine(
                ":%s FMODE %s %d -%s %s" %
                (self.ism.bot_user.uuid, scfg.channel, self.chan_time,
                 ''.join(unset_modes), ' '.join(unset_uuids)))

        # Send IRC user mode changes to Dtella
        for u, changes in user_changes.iteritems():
            self.ism.setChannelUserModes(whoset, u, changes)
Esempio n. 29
0
    def handleCmd_FMODE(self, prefix, args):
        # :268AAAAAF FMODE #dtella 1238298761 +h-o 268AAAAAF 268AAAAAF
        whoset_uuid = prefix
        chan = args[0]
        change = args[2]
        margs = args[3:]

        scfg = getServiceConfig()
        if chan != scfg.channel:
            return

        try:
            whoset = self.ism.findUser(whoset_uuid).inick
        except KeyError:
            # Could be a server?
            whoset = ""

        on_off = True
        i = 0

        # User() -> {mode -> on_off}
        user_changes = {}

        # Dtella node modes that need unsetting.
        unset_modes = []
        unset_uuids = []

        for c in change:
            if c == '+':
                on_off = True
            elif c == '-':
                on_off = False
            elif c == 't':
                self.ism.setTopicLocked(whoset, on_off)
            elif c == 'm':
                self.ism.setModerated(whoset, on_off)
            elif c == 'k':
                # Skip over channel key
                i += 1
            elif c == 'l':
                # Skip over channel user limit
                i += 1
            elif c == 'b':
                banmask = margs[i]
                i += 1
                self.ism.setChannelBan(whoset, on_off, banmask)
            elif c in scfg.chan_umodes.modes:
                # Grab affected user
                uuid = margs[i]
                i += 1

                n = self.ism.findDtellaNode(inick=uuid)
                if n:
                    # If someone set a mode for a Dt node, unset it.
                    if on_off:
                        unset_modes.append(c)
                        unset_uuids.append(uuid)
                    continue

                # Get the IRC user we're modifying.
                try:
                    u = self.ism.findUser(uuid)
                except KeyError:
                    LOG.error("MODE: unknown user: %s" % uuid)
                    continue

                # Schedule a mode change for this user.
                user_changes.setdefault(u, {})[c] = on_off

        # Undo mode changes for Dtella nodes.
        if unset_modes:
            self.sendLine(
                ":%s FMODE %s %d -%s %s" %
                (self.ism.bot_user.uuid, scfg.channel, self.chan_time,
                 ''.join(unset_modes), ' '.join(unset_uuids)))

        # Send IRC user mode changes to Dtella
        for u, changes in user_changes.iteritems():
            self.ism.setChannelUserModes(whoset, u, changes)
Esempio n. 30
0
    def handleCmd_MODE(self, prefix, args):
        # :Paul MODE #dtella +vv aaahhh Big_Guy
        whoset = prefix
        chan = args[0]
        change = args[1]
        nicks = args[2:]

        scfg = getServiceConfig()
        if chan != scfg.channel:
            return

        on_off = True
        i = 0

        # User() -> {mode -> on_off}
        user_changes = {}

        # Dtella node modes that need unsetting.
        unset_modes = []
        unset_nicks = []

        for c in change:
            if c == '+':
                on_off = True
            elif c == '-':
                on_off = False
            elif c == 't':
                self.ism.setTopicLocked(whoset, on_off)
            elif c == 'm':
                self.ism.setModerated(whoset, on_off)
            elif c == 'k':
                # Skip over channel key
                i += 1
            elif c == 'l':
                # Skip over channel user limit
                i += 1
            elif c == 'b':
                banmask = nicks[i]
                i += 1
                self.ism.setChannelBan(whoset, on_off, banmask)
            elif c in scfg.chan_umodes.modes:
                # Grab affected nick
                nick = nicks[i]
                i += 1

                n = self.ism.findDtellaNode(inick=nick)
                if n:
                    # If someone set a mode for a Dt node, unset it.
                    if on_off:
                        unset_modes.append(c)
                        unset_nicks.append(nick)
                    continue

                # Get the IRC user we're modifying.
                try:
                    u = self.ism.findUser(nick)
                except KeyError:
                    LOG.error("MODE: unknown nick: %s" % nick)
                    continue

                # Schedule a mode change for this user.
                user_changes.setdefault(u, {})[c] = on_off

        # Undo mode changes for Dtella nodes.
        if unset_modes:
            self.sendLine(":%s MODE %s -%s %s" %
                          (self.ism.bot_user.inick, scfg.channel,
                           ''.join(unset_modes), ' '.join(unset_nicks)))

        # Send IRC user mode changes to Dtella
        for u, changes in user_changes.iteritems():
            self.ism.setChannelUserModes(whoset, u, changes)