Exemple #1
0
    def __init__(self, main):
        self.ism = IRCStateManager(main, self)
        self.server_name = None
        self.shutdown_deferred = None

        self.ping_dcall = None
        self.ping_waiting = False
Exemple #2
0
    def __init__(self, main):
        self.ism = IRCStateManager(main, self)
        self.server_name = None
        self.shutdown_deferred = None

        self.ping_dcall = None
        self.ping_waiting = False
Exemple #3
0
    def __init__(self, main):
        self.setupSID()
        self.uuid_counter = 0

        self.ism = IRCStateManager(main=main,
                                   ircs=self,
                                   uuid_generator=self.generateUUID)
        self.server_name = None
        self.shutdown_deferred = None

        self.ping_dcall = None
        self.ping_waiting = False
        self.capabs = {}

        # The earliest observed channel creation time.
        self.chan_time = int(time.time())

        # Generate a unique Q-line reason string, so we can detect if
        # another bridge races and steals our prefix.
        self.qline_reason = ("Reserved for Dtella (%08X)" %
                             random.randint(0, 0xFFFFFFFF))
Exemple #4
0
    def __init__(self, main):
        self.setupSID()
        self.uuid_counter = 0

        self.ism = IRCStateManager(
            main=main, ircs=self, uuid_generator=self.generateUUID)
        self.server_name = None
        self.shutdown_deferred = None

        self.ping_dcall = None
        self.ping_waiting = False
        self.capabs = {}

        # The earliest observed channel creation time.
        self.chan_time = int(time.time())

        # Generate a unique Q-line reason string, so we can detect if 
        # another bridge races and steals our prefix.
        self.qline_reason = (
            "Reserved for Dtella (%08X)" % random.randint(0, 0xFFFFFFFF))
Exemple #5
0
class UnrealIRCServer(LineOnlyReceiver):
    showirc = False

    def __init__(self, main):
        self.ism = IRCStateManager(main, self)
        self.server_name = None
        self.shutdown_deferred = None

        self.ping_dcall = None
        self.ping_waiting = False

    def connectionMade(self):
        scfg = getServiceConfig()
        LOG.info("Connected to IRC server.")
        self.sendLine("PASS :%s" % (scfg.sendpass,))
        self.sendLine(
            "SERVER %s 1 :%s" % (scfg.my_host, scfg.my_name))

    def sendLine(self, line):
        line = line.replace('\r', '').replace('\n', '')
        if self.showirc:
            LOG.log(5, "<: %s" % line)
        LineOnlyReceiver.sendLine(self, line)

    def lineReceived(self, line):
        if not line:
            return

        if self.showirc:
            LOG.log(5, ">: %s" % line)

        if line[0] == ':':
            try:
                prefix, line = line[1:].split(' ', 1)
            except ValueError:
                return
        else:
            prefix = ''

        try:
            line, trailing = line.split(' :', 1)
        except ValueError:
            args = line.split()
        else:
            args = line.split()
            args.append(trailing)

        try:
            f = getattr(self, 'handleCmd_%s' % args[0].upper())
        except AttributeError:
            pass
        else:
            f(prefix, args[1:])

    def handleCmd_PING(self, prefix, args):
        LOG.info("PING? PONG!")
        scfg = getServiceConfig()
        if len(args) == 1:
            self.sendLine("PONG %s :%s" % (scfg.my_host, args[0]))
        elif len(args) == 2:
            self.sendLine("PONG %s :%s" % (args[1], args[0]))

    def handleCmd_PONG(self, prefix, args):
        if self.ping_waiting:
            self.ping_waiting = False
            self.schedulePing()

    def handleCmd_NICK(self, prefix, args):
        oldnick = prefix
        newnick = args[0]

        if oldnick:
            self.ism.changeNick(self.ism.findUser(oldnick), newnick)
        else:
            self.ism.addUser(newnick)

    def handleCmd_SVSNICK(self, prefix, args):
        # :services.dhirc.com SVSNICK |foo Guest33400 :1236660594
        oldnick = args[0]
        newnick = args[1]

        # Is the source user a Dtella node?  Freak out.
        n = self.ism.findDtellaNode(inick=oldnick)
        if n:
            message = "Nick change to %s is impossible" % irc_to_dc(newnick)
            self.ism.kickDtellaNode(n, prefix, message)

    def handleCmd_JOIN(self, prefix, args):
        nick = prefix
        chans = args[0].split(',')

        scfg = getServiceConfig()
        if scfg.channel in chans:
            self.ism.joinChannel(self.ism.findUser(nick))

    def handleCmd_PART(self, prefix, args):
        nick = prefix
        chans = args[0].split(',')

        scfg = getServiceConfig()
        if scfg.channel in chans:
            CHECK(self.ism.partChannel(self.ism.findUser(nick)))

    def handleCmd_QUIT(self, prefix, args):
        nick = prefix
        self.ism.removeUser(self.ism.findUser(nick))

    def handleCmd_KICK(self, prefix, args):
        chan = args[0]
        l33t = prefix
        n00b = args[1]
        reason = irc_strip(args[2])

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

        if n00b.lower() == self.ism.bot_user.inick.lower():
            if self.ism.syncd:
                self.pushBotJoin()
            return

        n = self.ism.findDtellaNode(inick=n00b)
        if n:
            self.ism.kickDtellaNode(n, l33t, reason)
        else:
            message = (
                "%s has kicked %s: %s" %
                (irc_to_dc(l33t), irc_to_dc(n00b), reason))
            CHECK(self.ism.partChannel(self.ism.findUser(n00b), message))

    def handleCmd_KILL(self, prefix, args):
        # :darkhorse KILL }darkhorse :dhirc.com!darkhorse (TEST!!!)
        l33t = prefix
        n00b = args[0]
        reason = "KILL: " + irc_strip(args[1])

        if n00b.lower() == self.ism.bot_user.inick.lower():
            if self.ism.syncd:
                self.pushBotJoin(do_nick=True)
            return

        n = self.ism.findDtellaNode(inick=n00b)
        if n:
            self.ism.kickDtellaNode(n, l33t, reason, send_quit=False)
        else:
            message = (
                "%s has KILL'd %s: %s" %
                (irc_to_dc(l33t), irc_to_dc(n00b), reason))
            self.ism.removeUser(self.ism.findUser(n00b), message)

    # Treat SVSKILL the same as KILL.
    handleCmd_SVSKILL = handleCmd_KILL

    def handleCmd_TOPIC(self, prefix, args):
        # :Paul TOPIC #dtella Paul 1169420711 :Dtella :: Development Stage
        chan = args[0]
        whoset = args[1]
        text = irc_strip(args[-1])

        scfg = getServiceConfig()
        if chan == scfg.channel:
            self.ism.setTopic(whoset, text)

    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)

    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)

    def handleCmd_SERVER(self, prefix, args):
        if prefix:
            # Not from our connected server
            return

        if self.server_name:
            # Could be a dupe?  Ignore it.
            return

        # We got a reply from the our connected IRC server, so our password
        # was just accepted.  Send the Dtella state information into IRC.

        # Save server name
        CHECK(args[0])
        self.server_name = args[0]

        LOG.info("IRC Server Name: %s" % self.server_name)

        # Tell the ReconnectingClientFactory that we're cool
        self.factory.resetDelay()

        # This isn't very correct, because the Dtella nicks
        # haven't been sent yet, but it's the best we can practically do.
        scfg = getServiceConfig()
        cloak_checksum = scfg.hostmasker.getChecksum()
        self.sendLine("NETINFO 0 %d 0 %s 0 0 0 :%s" %
                      (time.time(), cloak_checksum, scfg.network_name))
        self.sendLine(":%s EOS" % scfg.my_host)

    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()

    def handleCmd_WHOIS(self, prefix, args):
        # Somewhat simplistic handling of WHOIS requests
        if not (prefix and len(args) >= 1):
            return

        src = prefix
        who = args[-1]
        scfg = getServiceConfig()

        if who.lower() == self.ism.bot_user.inick.lower():
            self.pushWhoisReply(
                311, src, who, B_USER, scfg.my_host, '*', B_REALNAME)
            self.pushWhoisReply(
                312, src, who, scfg.my_host, scfg.my_name)
            self.pushWhoisReply(
                319, src, who, scfg.channel)
        else:
            n = self.ism.findDtellaNode(inick=who)
            if not (n and hasattr(n, 'hostmask')):
                return

            self.pushWhoisReply(
                311, src, who, n_user(n.ipp), n.hostmask, '*',
                "Dtella %s" % n.dttag[3:])
            self.pushWhoisReply(
                312, src, who, scfg.my_host, scfg.my_name)
            self.pushWhoisReply(
                319, src, who, scfg.channel)

            if local.use_locations:
                self.pushWhoisReply(
                    320, src, who, "Location: %s"
                    % local.hostnameToLocation(n.hostname))

        self.pushWhoisReply(
            318, src, who, "End of /WHOIS list.")

    def pushWhoisReply(self, code, target, who, *strings):
        scfg = getServiceConfig()
        line = ":%s %d %s %s " % (scfg.my_host, code, target, who)
        strings = list(strings)
        strings[-1] = ":" + strings[-1]
        line += ' '.join(strings)
        self.sendLine(line)

    def handleCmd_PRIVMSG(self, prefix, args):
        src_nick = prefix
        target = args[0]
        text = args[1]
        flags = 0

        if (text[:8], text[-1:]) == ('\001ACTION ', '\001'):
            text = text[8:-1]
            flags |= core.SLASHME_BIT

        text = irc_strip(text)

        scfg = getServiceConfig()
        if target == scfg.channel:
            self.ism.sendChannelMessage(src_nick, text, flags)
	
        #Format> :Global PRIVMSG $irc3.dhirc.com :TESTING....
        #Handle global messages delivered to the bridge.
        elif target == "$" + scfg.my_host:
            flags |= core.NOTICE_BIT
            self.ism.sendChannelMessage(src_nick, text, flags)

        else:
            n = self.ism.findDtellaNode(inick=target)
            if n:
                self.ism.sendPrivateMessage(n, src_nick, text, flags)

    def handleCmd_NOTICE(self, prefix, args):
        src_nick = prefix
        target = args[0]
        text = irc_strip(args[1])
        flags = core.NOTICE_BIT

        scfg = getServiceConfig()
        if target == scfg.channel:
            self.ism.sendChannelMessage(src_nick, text, flags)
        else:
            n = self.ism.findDtellaNode(inick=target)
            if n:
                self.ism.sendPrivateMessage(n, src_nick, text, flags)

    def pushNick(self, nick, ident, host, modes, ip, name):
        # If an IP was provided, convert to a base64 parameter.
        if ip:
            ip = ' ' + binascii.b2a_base64(ip).rstrip()
        else:
            ip = ''

        scfg = getServiceConfig()
        self.sendLine(
            "NICK %s 1 %d %s %s %s 1 +%s *%s :%s" %
            (nick, time.time(), ident, host, scfg.my_host, modes, ip, name))

    def pushJoin(self, nick):
        scfg = getServiceConfig()
        self.sendLine(":%s JOIN %s" % (nick, scfg.channel))

    def pushQuit(self, nick, reason=""):
        self.sendLine(":%s QUIT :%s" % (nick, reason))

    def pushBotJoin(self, do_nick=False):
        scfg = getServiceConfig()
        if do_nick:
            self.pushNick(
                self.ism.bot_user.inick, B_USER, scfg.my_host, "Sq", None,
                B_REALNAME)

        # Join channel, and grant ops.
        self.pushJoin(self.ism.bot_user.inick)
        self.sendLine(
            ":%s MODE %s +ao %s %s" %
            (scfg.my_host, scfg.channel,
             self.ism.bot_user.inick, self.ism.bot_user.inick))

    def pushRemoveQLine(self, nickmask):
        scfg = getServiceConfig()
        LOG.info("Telling network to remove Q-line: %s" % nickmask)
        self.sendLine("TKL - Q * %s %s" % (nickmask, scfg.my_host))

    def schedulePing(self):
        if self.ping_dcall:
            self.ping_dcall.reset(60.0)
            return

        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)

        self.ping_dcall = reactor.callLater(60.0, cb)

    def event_AddDtNode(self, n, ident):
        self.pushNick(
            n.inick, ident, n.hostname, "iwx", n.ipp[:4],
            "Dtella %s" % n.dttag[3:])
        self.pushJoin(n.inick)

    def event_RemoveDtNode(self, n, reason):
        self.pushQuit(n.inick, reason)

    def event_KillUser(self, u):
        scfg = getServiceConfig()
        LOG.info("Killing nick: " + u.inick)
        self.sendLine(":%s KILL %s :%s (nick reserved for Dtella)"
                      % (scfg.my_host, u.inick, scfg.my_host))

    def event_NodeSetTopic(self, n, topic):
        scfg = getServiceConfig()
        self.sendLine(
            ":%s TOPIC %s %s %d :%s" %
            (n.inick, scfg.channel, n.inick, time.time(), topic))

    def event_Message(self, src_n, dst_u, text, action=False):
        scfg = getServiceConfig()
        if dst_u is None:
            target = scfg.channel
        else:
            target = dst_u.inick

        if action:
            text = "\001ACTION %s\001" % text

        self.sendLine(":%s PRIVMSG %s :%s" % (src_n.inick, target, text))

    def event_Notice(self, src_n, dst_u, text):
        scfg = getServiceConfig()
        if dst_u is None:
            target = scfg.channel
        else:
            target = dst_u.inick

        self.sendLine(":%s NOTICE %s :%s" % (src_n.nick, target, text))

    def shutdown(self):
        if self.shutdown_deferred:
            return self.shutdown_deferred

        # Remove nick ban
        self.pushRemoveQLine(cfg.dc_to_irc_prefix + "*")

        # Scream
        self.pushQuit(self.ism.bot_user.inick, "AIEEEEEEE!")

        # Send SQUIT for completeness
        scfg = getServiceConfig()
        self.sendLine(":%s SQUIT %s :Bridge Shutting Down"
                      % (scfg.my_host, scfg.my_host))

        # Close connection
        self.transport.loseConnection()

        # This will complete after loseConnection fires
        self.shutdown_deferred = defer.Deferred()
        return self.shutdown_deferred

    def connectionLost(self, result):
        LOG.info("Lost IRC connection.")
        if self.ism.syncd:
            self.ism.removeMeFromMain()

        if self.shutdown_deferred:
            self.shutdown_deferred.callback("Bye!")
Exemple #6
0
class InspIRCdServer(LineOnlyReceiver):
    showirc = False

    def __init__(self, main):
        self.setupSID()
        self.uuid_counter = 0

        self.ism = IRCStateManager(
            main=main, ircs=self, uuid_generator=self.generateUUID)
        self.server_name = None
        self.shutdown_deferred = None

        self.ping_dcall = None
        self.ping_waiting = False
        self.capabs = {}

        # The earliest observed channel creation time.
        self.chan_time = int(time.time())

        # Generate a unique Q-line reason string, so we can detect if 
        # another bridge races and steals our prefix.
        self.qline_reason = (
            "Reserved for Dtella (%08X)" % random.randint(0, 0xFFFFFFFF))

    def setupSID(self):
        scfg = getServiceConfig()
        if scfg.sid:
            self.sid = scfg.sid
            return

        # Generate a 3-digit SID, based on some config constants.
        # This isn't the same algorithm as inspircd.cpp, but it doesn't
        # really matter.
        input_str = "%s\0%s" % (scfg.my_host, scfg.my_name)
        sid, = struct.unpack("!I", sha256(input_str).digest()[:4])
        self.sid = "%03d" % (sid % 999)

    def generateUUID(self):
        ctr = self.uuid_counter
        self.uuid_counter += 1

        uuid = ""
        for i in range(6):
            uuid = UUID_DIGITS[ctr % len(UUID_DIGITS)] + uuid
            ctr /= len(UUID_DIGITS)

        # There are over 2 billion UUIDs; just reconnect if we run out.
        if ctr > 0:
            self.transport.loseConnection()
            raise ValueError("Ran out of UUIDs")

        return self.sid + uuid

    def connectionMade(self):
        scfg = getServiceConfig()
        LOG.info("Connected to IRC server.")

        self.challenge = make_challenge()
        my_capabs = [
            ("NICKMAX", 32),
            ("HALFOP", 1),
            ("CHANMAX", 65),
            ("MAXMODES", 20),
            ("IDENTMAX", 12),
            ("MAXQUIT", 255),
            ("MAXTOPIC", 307),
            ("MAXKICK", 255),
            ("MAXGECOS", 128),
            ("MAXAWAY", 200),
            ("IP6SUPPORT", 1),
            ("PROTOCOL", 1201),
            ("CHALLENGE", self.challenge),
            ]
        my_capabs_str = ' '.join("%s=%s" % x for x in my_capabs)

        self.sendLine("CAPAB START")
        self.sendLine("CAPAB CAPABILITIES :%s" % my_capabs_str)
        self.sendLine("CAPAB END")

    def sendLine(self, line):
        line = line.replace('\r', '').replace('\n', '')
        print "<: %s" % line
        if self.showirc:
            LOG.log(5, "<: %s" % line)
        LineOnlyReceiver.sendLine(self, line)

    def lineReceived(self, line):
        if not line:
            return

        print ">: %s" % line
        if self.showirc:
            LOG.log(5, ">: %s" % line)

        if line[0] == ':':
            try:
                prefix, line = line[1:].split(' ', 1)
            except ValueError:
                return
        else:
            prefix = ''

        try:
            line, trailing = line.split(' :', 1)
        except ValueError:
            args = line.split()
        else:
            args = line.split()
            args.append(trailing)

        try:
            f = getattr(self, 'handleCmd_%s' % args[0].upper())
        except AttributeError:
            pass
        else:
            f(prefix, args[1:])

    def handleCmd_CAPAB(self, prefix, args):
        capab_mode = args[0]
        scfg = getServiceConfig()

        if capab_mode == "START":
            CHECK(not self.capabs)
        elif capab_mode == "CAPABILITIES":
            for capab in args[1].split():
                k, v = capab.split("=", 1)
                self.capabs[k] = v
        elif capab_mode == "END":
            challenge = self.capabs["CHALLENGE"]
            response = ihmac(scfg.sendpass, challenge)
            self.sendLine(
                "SERVER %s %s 0 %s :%s" %
                (scfg.my_host, response, self.sid, scfg.my_name))

    def handleCmd_PING(self, prefix, args):
        LOG.info("PING? PONG!")
        scfg = getServiceConfig()
        if len(args) == 1:
            self.sendLine(":%s PONG :%s" % (scfg.my_host, args[0]))
        elif len(args) == 2:
            self.sendLine("PONG %s :%s" % (args[1], args[0]))

    def handleCmd_PONG(self, prefix, args):
        if self.ping_waiting:
            self.ping_waiting = False
            self.schedulePing()

    def handleCmd_NICK(self, prefix, args):
        # :268AAAAAF NICK Paul 1238303566
        old_uuid = prefix
        new_nick = args[0]
        try:
            u = self.ism.findUser(old_uuid)
        except KeyError:
            # This might be an echo from our KICK.
            LOG.warning("NICK: can't find source: %s" % old_uuid)
            return
        self.ism.changeNick(u, new_nick)

    def handleCmd_UID(self, prefix, args):
        pass
        # :268 UID 268AAAAAF 1238242868 Paul <host> <host>
        #          paul <ip> 1238242873 +o :gecos
        nick = args[2]
        uuid = args[0]
        self.ism.addUser(nick, uuid)

    """
    def handleCmd_SVSNICK(self, prefix, args):
        # :services.dhirc.com SVSNICK |foo Guest33400 :1236660594
        oldnick = args[0]
        newnick = args[1]

        # Is the source user a Dtella node?  Freak out.
        n = self.ism.findDtellaNode(inick=oldnick)
        if n:
            message = "Nick change to %s is impossible" % irc_to_dc(newnick)
            self.ism.kickDtellaNode(n, prefix, message)
    """

    def handleCmd_JOIN(self, prefix, args):
        # :Paul JOIN #dtella 1237797449
        nick = prefix
        chans = args[0].split(',')

        scfg = getServiceConfig()
        if scfg.channel in chans:
            self.ism.joinChannel(self.ism.findUser(nick))

    def handleCmd_FJOIN(self, prefix, args):
        # :268 FJOIN #opers 1238298761 +nt :o,268AAAAAF v,268AAAAAE
        scfg = getServiceConfig()
        chan = args[0]
        if chan != scfg.channel:
            return

        # If we're not syncd yet, process the channel modes.
        if not self.ism.syncd:
            self.handleCmd_FMODE(prefix, args[:-1])

        # Keep track of earliest creation time for this channel.
        self.chan_time = min(self.chan_time, int(args[1]))

        for uinfo in args[-1].split():
            modes, uuid = uinfo.split(",", 1)

            u = self.ism.findUser(uuid)
            self.ism.joinChannel(u)

            changes = {}
            for m in modes:
                if m in scfg.chan_umodes.modes:
                    changes[m] = True

            if changes:
                self.ism.setChannelUserModes("", u, changes)

    def handleCmd_PART(self, prefix, args):
        uuid = prefix
        chans = args[0].split(',')

        scfg = getServiceConfig()
        if scfg.channel in chans:
            CHECK(self.ism.partChannel(self.ism.findUser(uuid)))

    def handleCmd_QUIT(self, prefix, args):
        uuid = prefix
        try:
            u = self.ism.findUser(uuid)
        except KeyError:
            LOG.warning("Can't quit user: %s" % uuid)
        else:
            self.ism.removeUser(u)

    def handleCmd_KICK(self, prefix, args):
        chan = args[0]
        l33t = prefix
        n00b = args[1]
        reason = irc_strip(args[2])

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

        # Rejoin the bot if it's killed.
        if self.ism.isMyBot(n00b):
            if self.ism.syncd:
                self.pushBotJoin()
            return

        l33t = self.ism.findUser(l33t).inick
        n = self.ism.findDtellaNode(inick=n00b)
        if n:
            self.ism.kickDtellaNode(n, l33t, reason)
        else:
            n00b_u = self.ism.findUser(n00b)
            message = (
                "%s has kicked %s: %s" %
                (irc_to_dc(l33t), irc_to_dc(n00b_u.inick), reason))
            CHECK(self.ism.partChannel(n00b_u, message))

    def handleCmd_KILL(self, prefix, args):
        l33t = prefix
        n00b = args[0]
        reason = irc_strip(args[1])

        # In most cases, n00b is a UUID, but Anope seems to still use a nick.
        # Thus, we have to try both everywhere :-/

        # Reconnect the bot if it's killed.
        if self.ism.isMyBot(n00b):
            if self.ism.syncd:
                self.pushBotJoin(do_nick=True)
            return

        l33t = self.ism.findUser(l33t).inick

        # If n00b is a Dtella node, kick 'em.
        n = self.ism.findDtellaNode(inick=n00b)
        if n:
            self.ism.kickDtellaNode(
                n, l33t, "KILL: " + reason, send_quit=False)
            return

        # If n00b is an IRC user, treat it like a QUIT.
        try:
            n00b_u = self.ism.findUser(n00b)
        except KeyError:
            LOG.warning("Tried to KILL unknown user: %s" % n00b)
            return
        message = (
            "%s has KILL'd %s: %s" %
            (irc_to_dc(l33t), irc_to_dc(n00b_u.inick), reason))
        self.ism.removeUser(n00b_u, message)

    """
    # Treat SVSKILL the same as KILL.
    handleCmd_SVSKILL = handleCmd_KILL
    """

    def handleCmd_TOPIC(self, prefix, args):
        # :268AAAAAO TOPIC #dtella :hello world
        whoset_uuid = prefix
        chan = args[0]
        text = irc_strip(args[-1])

        scfg = getServiceConfig()
        if chan == scfg.channel:
            whoset = self.ism.findUser(whoset_uuid).inick
            self.ism.setTopic(whoset, text)

    def handleCmd_FTOPIC(self, prefix, args):
        # :268 FTOPIC #dtella 1238306219 nick!host :hello
        chan = args[0]
        whoset = args[2].split('!', 1)[0]
        text = irc_strip(args[-1])

        scfg = getServiceConfig()
        if chan == scfg.channel:
            self.ism.setTopic(whoset, text)

    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)

    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)

    def handleCmd_DELLINE(self, prefix, args):
        # :268 DELLINE <linetype> <mask>
        kind = args[0]

        if kind == 'Z':
            cidr = args[1]
            self.ism.setNetworkBan(cidr, False)

        elif kind == 'Q':
            nickmask = args[1]

            # Ignore the unsetting of my own prefix.
            # It might be an echo from my own unsetting during startup.
            if self.ism.syncd and nickmask == cfg.dc_to_irc_prefix + "*":
                return

            self.ism.removeQLine(nickmask)

    def handleCmd_SERVER(self, prefix, args):
        # :foo.dhirc.com SERVER s.dhirc.com * 1 00A :Services
        hostname = args[0]
        password = args[1]
        sid = args[3]

        # Register each server as a user, because it's easier.
        LOG.info("Recording server: hostname=%s sid=%s" % (hostname, sid))
        self.ism.addUser(hostname, sid)

        if prefix:
            # Not my directly-connected server.
            return

        if self.server_name:
            # Could be a dupe?  Ignore it.
            return

        # We got a reply from the our connected IRC server, so our password
        # was just accepted.  Send the Dtella state information into IRC.

        scfg = getServiceConfig()

        # Save server name
        CHECK(args[0])
        self.server_name = args[0]

        # Verify challenge response.
        if scfg.recvpass is None:
            LOG.info("Skipping validation of recvpass")
        else:
            ch_response = args[1]
            if ch_response != ihmac(scfg.recvpass, self.challenge):
                raise ValueError("Incorrect recvpass")
            LOG.info("Correct recvpass")

        LOG.info("IRC Server Name: %s" % self.server_name)

        # Tell the ReconnectingClientFactory that we're cool
        self.factory.resetDelay()

        self.sendLine("BURST %d" % time.time())
        self.sendLine("ENDBURST")

    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))

    def handleCmd_BURST(self, prefix, args):
        return #FIXME
        if self.ism.syncd:
            LOG.error("Can't handle BURST after sync. Restarting.")
            self.transport.loseConnection()

    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()

    # FIXME: implement WHOIS
    """
    def handleCmd_WHOIS(self, prefix, args):
        # Somewhat simplistic handling of WHOIS requests
        if not (prefix and len(args) >= 1):
            return

        src = prefix
        who = args[-1]
        scfg = getServiceConfig()

        if who == cfg.dc_to_irc_bot:
            self.pushWhoisReply(
                311, src, who, B_USER, scfg.my_host, '*', B_REALNAME)
            self.pushWhoisReply(
                312, src, who, scfg.my_host, scfg.my_name)
            self.pushWhoisReply(
                319, src, who, scfg.channel)
        else:
            n = self.ism.findDtellaNode(inick=who)
            if not (n and hasattr(n, 'hostmask')):
                return

            self.pushWhoisReply(
                311, src, who, n_user(n.ipp), n.hostmask, '*',
                "Dtella %s" % n.dttag[3:])
            self.pushWhoisReply(
                312, src, who, scfg.my_host, scfg.my_name)
            self.pushWhoisReply(
                319, src, who, scfg.channel)

            if local.use_locations:
                self.pushWhoisReply(
                    320, src, who, "Location: %s"
                    % local.hostnameToLocation(n.hostname))

        self.pushWhoisReply(
            318, src, who, "End of /WHOIS list.")

    def pushWhoisReply(self, code, target, who, *strings):
        scfg = getServiceConfig()
        line = ":%s %d %s %s " % (scfg.my_host, code, target, who)
        strings = list(strings)
        strings[-1] = ":" + strings[-1]
        line += ' '.join(strings)
        self.sendLine(line)
    """

    def handleCmd_PRIVMSG(self, prefix, args):
        src_uuid = prefix
        target = args[0]
        text = args[1]
        flags = 0

        if (text[:8], text[-1:]) == ('\001ACTION ', '\001'):
            text = text[8:-1]
            flags |= core.SLASHME_BIT

        text = irc_strip(text)

        src_nick = self.ism.findUser(src_uuid).inick

        scfg = getServiceConfig()
        if target == scfg.channel:
            self.ism.sendChannelMessage(src_nick, text, flags)
	
        # :Global PRIVMSG $irc3.dhirc.com :TESTING....
        # Handle global messages delivered to the bridge.
        # FIXME: does this work with InspIRCd?
        elif target == "$" + scfg.my_host:
            flags |= core.NOTICE_BIT
            self.ism.sendChannelMessage(src_nick, text, flags)

        else:
            n = self.ism.findDtellaNode(inick=target)
            if n:
                self.ism.sendPrivateMessage(n, src_nick, text, flags)

    def handleCmd_NOTICE(self, prefix, args):
        src_uuid = prefix
        target = args[0]
        text = irc_strip(args[1])
        flags = core.NOTICE_BIT

        src_nick = self.ism.findUser(src_uuid).inick

        scfg = getServiceConfig()
        if target == scfg.channel:
            self.ism.sendChannelMessage(src_nick, text, flags)
        else:
            n = self.ism.findDtellaNode(inick=target)
            if n:
                self.ism.sendPrivateMessage(n, src_nick, text, flags)

    def pushUID(self, uuid, nick, ident, host, cloak_host, modes, ip, gecos):
        # If an IP was provided, convert to a base64 parameter.
        if not ip:
            ip = '0.0.0.0'

        if not cloak_host:
            cloak_host = host

        scfg = getServiceConfig()
        now = time.time()
        self.sendLine(
            ":%s UID %s %d %s %s %s %s %s %d +%s :%s" %
            (self.sid, uuid, now, nick, host, cloak_host, ident,
             ip, now, modes, gecos))

    def pushJoin(self, uuid, modes=""):
        scfg = getServiceConfig()
        self.sendLine(
            ":%s FJOIN %s %d + %s,%s" %
            (self.sid, scfg.channel, self.chan_time, modes, uuid))

    def pushQuit(self, uuid, reason=""):
        self.sendLine(":%s QUIT :%s" % (uuid, reason))

    def pushBotJoin(self, do_nick=False):
        scfg = getServiceConfig()
        uuid = self.ism.bot_user.uuid

        if do_nick:
            self.pushUID(
                uuid, self.ism.bot_user.inick,
                B_USER, scfg.my_host, None, "", None, B_REALNAME)

        # Join channel, and grant ops.
        self.pushJoin(uuid, "o")

    def pushRemoveQLine(self, nickmask):
        scfg = getServiceConfig()
        LOG.info("Telling network to remove Q-line: %s" % nickmask)
        self.sendLine(":%s DELLINE Q %s" % (self.sid, nickmask))

    def schedulePing(self):
        if self.ping_dcall:
            self.ping_dcall.reset(60.0)
            return

        def cb():
            self.ping_dcall = None

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

        self.ping_dcall = reactor.callLater(60.0, cb)

    def event_AddDtNode(self, n, ident):
        self.pushUID(
            n.uuid, n.inick, ident, n.hostname, n.hostmask, "iwx",
            Ad().setRawIPPort(n.ipp).getTextIP(),
            "Dtella %s" % n.dttag[3:])
        self.pushJoin(n.uuid)

    def event_RemoveDtNode(self, n, reason):
        self.pushQuit(n.uuid, reason)

    def event_KillUser(self, u):
        scfg = getServiceConfig()
        LOG.info("Killing nick: " + u.inick)
        self.sendLine(":%s KILL %s :%s (nick reserved for Dtella)"
                      % (self.sid, u.uuid, scfg.my_host))

    def event_NodeSetTopic(self, n, topic):
        scfg = getServiceConfig()
        self.sendLine(
            ":%s TOPIC %s :%s" %
            (n.uuid, scfg.channel, topic))

    def event_Message(self, src_n, dst_u, text, action=False):
        scfg = getServiceConfig()
        if dst_u is None:
            target = scfg.channel
        else:
            target = dst_u.uuid

        if action:
            text = "\001ACTION %s\001" % text

        self.sendLine(":%s PRIVMSG %s :%s" % (src_n.uuid, target, text))

    def event_Notice(self, src_n, dst_u, text):
        scfg = getServiceConfig()
        if dst_u is None:
            target = scfg.channel
        else:
            target = dst_u.uuid

        self.sendLine(":%s NOTICE %s :%s" % (src_n.uuid, target, text))

    def shutdown(self):
        if self.shutdown_deferred:
            return self.shutdown_deferred

        # Remove nick ban
        self.pushRemoveQLine(cfg.dc_to_irc_prefix + "*")

        # Scream
        self.pushQuit(self.ism.bot_user.uuid, "AIEEEEEEE!")

        # Send SQUIT for completeness
        scfg = getServiceConfig()
        self.sendLine(":%s SQUIT %s :Bridge Shutting Down"
                      % (self.sid, scfg.my_host))

        # Close connection
        self.transport.loseConnection()

        # This will complete after loseConnection fires
        self.shutdown_deferred = defer.Deferred()
        return self.shutdown_deferred

    def connectionLost(self, result):
        LOG.info("Lost IRC connection.")
        if self.ism.syncd:
            self.ism.removeMeFromMain()

        if self.shutdown_deferred:
            self.shutdown_deferred.callback("Bye!")
Exemple #7
0
class UnrealIRCServer(LineOnlyReceiver):
    showirc = False

    def __init__(self, main):
        self.ism = IRCStateManager(main, self)
        self.server_name = None
        self.shutdown_deferred = None

        self.ping_dcall = None
        self.ping_waiting = False

    def connectionMade(self):
        scfg = getServiceConfig()
        LOG.info("Connected to IRC server.")
        self.sendLine("PASS :%s" % (scfg.sendpass, ))
        self.sendLine("SERVER %s 1 :%s" % (scfg.my_host, scfg.my_name))

    def sendLine(self, line):
        line = line.replace('\r', '').replace('\n', '')
        if self.showirc:
            LOG.log(5, "<: %s" % line)
        LineOnlyReceiver.sendLine(self, line)

    def lineReceived(self, line):
        if not line:
            return

        if self.showirc:
            LOG.log(5, ">: %s" % line)

        if line[0] == ':':
            try:
                prefix, line = line[1:].split(' ', 1)
            except ValueError:
                return
        else:
            prefix = ''

        try:
            line, trailing = line.split(' :', 1)
        except ValueError:
            args = line.split()
        else:
            args = line.split()
            args.append(trailing)

        try:
            f = getattr(self, 'handleCmd_%s' % args[0].upper())
        except AttributeError:
            pass
        else:
            f(prefix, args[1:])

    def handleCmd_PING(self, prefix, args):
        LOG.info("PING? PONG!")
        scfg = getServiceConfig()
        if len(args) == 1:
            self.sendLine("PONG %s :%s" % (scfg.my_host, args[0]))
        elif len(args) == 2:
            self.sendLine("PONG %s :%s" % (args[1], args[0]))

    def handleCmd_PONG(self, prefix, args):
        if self.ping_waiting:
            self.ping_waiting = False
            self.schedulePing()

    def handleCmd_NICK(self, prefix, args):
        oldnick = prefix
        newnick = args[0]

        if oldnick:
            self.ism.changeNick(self.ism.findUser(oldnick), newnick)
        else:
            self.ism.addUser(newnick)

    def handleCmd_SVSNICK(self, prefix, args):
        # :services.dhirc.com SVSNICK |foo Guest33400 :1236660594
        oldnick = args[0]
        newnick = args[1]

        # Is the source user a Dtella node?  Freak out.
        n = self.ism.findDtellaNode(inick=oldnick)
        if n:
            message = "Nick change to %s is impossible" % irc_to_dc(newnick)
            self.ism.kickDtellaNode(n, prefix, message)

    def handleCmd_JOIN(self, prefix, args):
        nick = prefix
        chans = args[0].split(',')

        scfg = getServiceConfig()
        if scfg.channel in chans:
            self.ism.joinChannel(self.ism.findUser(nick))

    def handleCmd_PART(self, prefix, args):
        nick = prefix
        chans = args[0].split(',')

        scfg = getServiceConfig()
        if scfg.channel in chans:
            CHECK(self.ism.partChannel(self.ism.findUser(nick)))

    def handleCmd_QUIT(self, prefix, args):
        nick = prefix
        self.ism.removeUser(self.ism.findUser(nick))

    def handleCmd_KICK(self, prefix, args):
        chan = args[0]
        l33t = prefix
        n00b = args[1]
        reason = irc_strip(args[2])

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

        if n00b.lower() == self.ism.bot_user.inick.lower():
            if self.ism.syncd:
                self.pushBotJoin()
            return

        n = self.ism.findDtellaNode(inick=n00b)
        if n:
            self.ism.kickDtellaNode(n, l33t, reason)
        else:
            message = ("%s has kicked %s: %s" %
                       (irc_to_dc(l33t), irc_to_dc(n00b), reason))
            CHECK(self.ism.partChannel(self.ism.findUser(n00b), message))

    def handleCmd_KILL(self, prefix, args):
        # :darkhorse KILL }darkhorse :dhirc.com!darkhorse (TEST!!!)
        l33t = prefix
        n00b = args[0]
        reason = "KILL: " + irc_strip(args[1])

        if n00b.lower() == self.ism.bot_user.inick.lower():
            if self.ism.syncd:
                self.pushBotJoin(do_nick=True)
            return

        n = self.ism.findDtellaNode(inick=n00b)
        if n:
            self.ism.kickDtellaNode(n, l33t, reason, send_quit=False)
        else:
            message = ("%s has KILL'd %s: %s" %
                       (irc_to_dc(l33t), irc_to_dc(n00b), reason))
            self.ism.removeUser(self.ism.findUser(n00b), message)

    # Treat SVSKILL the same as KILL.
    handleCmd_SVSKILL = handleCmd_KILL

    def handleCmd_TOPIC(self, prefix, args):
        # :Paul TOPIC #dtella Paul 1169420711 :Dtella :: Development Stage
        chan = args[0]
        whoset = args[1]
        text = irc_strip(args[-1])

        scfg = getServiceConfig()
        if chan == scfg.channel:
            self.ism.setTopic(whoset, text)

    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)

    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)

    def handleCmd_SERVER(self, prefix, args):
        if prefix:
            # Not from our connected server
            return

        if self.server_name:
            # Could be a dupe?  Ignore it.
            return

        # We got a reply from the our connected IRC server, so our password
        # was just accepted.  Send the Dtella state information into IRC.

        # Save server name
        CHECK(args[0])
        self.server_name = args[0]

        LOG.info("IRC Server Name: %s" % self.server_name)

        # Tell the ReconnectingClientFactory that we're cool
        self.factory.resetDelay()

        # This isn't very correct, because the Dtella nicks
        # haven't been sent yet, but it's the best we can practically do.
        scfg = getServiceConfig()
        cloak_checksum = scfg.hostmasker.getChecksum()
        self.sendLine("NETINFO 0 %d 0 %s 0 0 0 :%s" %
                      (time.time(), cloak_checksum, scfg.network_name))
        self.sendLine(":%s EOS" % scfg.my_host)

    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()

    def handleCmd_WHOIS(self, prefix, args):
        # Somewhat simplistic handling of WHOIS requests
        if not (prefix and len(args) >= 1):
            return

        src = prefix
        who = args[-1]
        scfg = getServiceConfig()

        if who.lower() == self.ism.bot_user.inick.lower():
            self.pushWhoisReply(311, src, who, B_USER, scfg.my_host, '*',
                                B_REALNAME)
            self.pushWhoisReply(312, src, who, scfg.my_host, scfg.my_name)
            self.pushWhoisReply(319, src, who, scfg.channel)
        else:
            n = self.ism.findDtellaNode(inick=who)
            if not (n and hasattr(n, 'hostmask')):
                return

            self.pushWhoisReply(311, src, who, n_user(n.ipp), n.hostmask, '*',
                                "Dtella %s" % n.dttag[3:])
            self.pushWhoisReply(312, src, who, scfg.my_host, scfg.my_name)
            self.pushWhoisReply(319, src, who, scfg.channel)

            if local.use_locations:
                self.pushWhoisReply(
                    320, src, who,
                    "Location: %s" % local.hostnameToLocation(n.hostname))

        self.pushWhoisReply(318, src, who, "End of /WHOIS list.")

    def pushWhoisReply(self, code, target, who, *strings):
        scfg = getServiceConfig()
        line = ":%s %d %s %s " % (scfg.my_host, code, target, who)
        strings = list(strings)
        strings[-1] = ":" + strings[-1]
        line += ' '.join(strings)
        self.sendLine(line)

    def handleCmd_PRIVMSG(self, prefix, args):
        src_nick = prefix
        target = args[0]
        text = args[1]
        flags = 0

        if (text[:8], text[-1:]) == ('\001ACTION ', '\001'):
            text = text[8:-1]
            flags |= core.SLASHME_BIT

        text = irc_strip(text)

        scfg = getServiceConfig()
        if target == scfg.channel:
            self.ism.sendChannelMessage(src_nick, text, flags)

        #Format> :Global PRIVMSG $irc3.dhirc.com :TESTING....
        #Handle global messages delivered to the bridge.
        elif target == "$" + scfg.my_host:
            flags |= core.NOTICE_BIT
            self.ism.sendChannelMessage(src_nick, text, flags)

        else:
            n = self.ism.findDtellaNode(inick=target)
            if n:
                self.ism.sendPrivateMessage(n, src_nick, text, flags)

    def handleCmd_NOTICE(self, prefix, args):
        src_nick = prefix
        target = args[0]
        text = irc_strip(args[1])
        flags = core.NOTICE_BIT

        scfg = getServiceConfig()
        if target == scfg.channel:
            self.ism.sendChannelMessage(src_nick, text, flags)
        else:
            n = self.ism.findDtellaNode(inick=target)
            if n:
                self.ism.sendPrivateMessage(n, src_nick, text, flags)

    def pushNick(self, nick, ident, host, modes, ip, name):
        # If an IP was provided, convert to a base64 parameter.
        if ip:
            ip = ' ' + binascii.b2a_base64(ip).rstrip()
        else:
            ip = ''

        scfg = getServiceConfig()
        self.sendLine(
            "NICK %s 1 %d %s %s %s 1 +%s *%s :%s" %
            (nick, time.time(), ident, host, scfg.my_host, modes, ip, name))

    def pushJoin(self, nick):
        scfg = getServiceConfig()
        self.sendLine(":%s JOIN %s" % (nick, scfg.channel))

    def pushQuit(self, nick, reason=""):
        self.sendLine(":%s QUIT :%s" % (nick, reason))

    def pushBotJoin(self, do_nick=False):
        scfg = getServiceConfig()
        if do_nick:
            self.pushNick(self.ism.bot_user.inick, B_USER, scfg.my_host, "Sq",
                          None, B_REALNAME)

        # Join channel, and grant ops.
        self.pushJoin(self.ism.bot_user.inick)
        self.sendLine(":%s MODE %s +ao %s %s" %
                      (scfg.my_host, scfg.channel, self.ism.bot_user.inick,
                       self.ism.bot_user.inick))

    def pushRemoveQLine(self, nickmask):
        scfg = getServiceConfig()
        LOG.info("Telling network to remove Q-line: %s" % nickmask)
        self.sendLine("TKL - Q * %s %s" % (nickmask, scfg.my_host))

    def schedulePing(self):
        if self.ping_dcall:
            self.ping_dcall.reset(60.0)
            return

        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)

        self.ping_dcall = reactor.callLater(60.0, cb)

    def event_AddDtNode(self, n, ident):
        self.pushNick(n.inick, ident, n.hostname, "iwx", n.ipp[:4],
                      "Dtella %s" % n.dttag[3:])
        self.pushJoin(n.inick)

    def event_RemoveDtNode(self, n, reason):
        self.pushQuit(n.inick, reason)

    def event_KillUser(self, u):
        scfg = getServiceConfig()
        LOG.info("Killing nick: " + u.inick)
        self.sendLine(":%s KILL %s :%s (nick reserved for Dtella)" %
                      (scfg.my_host, u.inick, scfg.my_host))

    def event_NodeSetTopic(self, n, topic):
        scfg = getServiceConfig()
        self.sendLine(":%s TOPIC %s %s %d :%s" %
                      (n.inick, scfg.channel, n.inick, time.time(), topic))

    def event_Message(self, src_n, dst_u, text, action=False):
        scfg = getServiceConfig()
        if dst_u is None:
            target = scfg.channel
        else:
            target = dst_u.inick

        if action:
            text = "\001ACTION %s\001" % text

        self.sendLine(":%s PRIVMSG %s :%s" % (src_n.inick, target, text))

    def event_Notice(self, src_n, dst_u, text):
        scfg = getServiceConfig()
        if dst_u is None:
            target = scfg.channel
        else:
            target = dst_u.inick

        self.sendLine(":%s NOTICE %s :%s" % (src_n.nick, target, text))

    def shutdown(self):
        if self.shutdown_deferred:
            return self.shutdown_deferred

        # Remove nick ban
        self.pushRemoveQLine(cfg.dc_to_irc_prefix + "*")

        # Scream
        self.pushQuit(self.ism.bot_user.inick, "AIEEEEEEE!")

        # Send SQUIT for completeness
        scfg = getServiceConfig()
        self.sendLine(":%s SQUIT %s :Bridge Shutting Down" %
                      (scfg.my_host, scfg.my_host))

        # Close connection
        self.transport.loseConnection()

        # This will complete after loseConnection fires
        self.shutdown_deferred = defer.Deferred()
        return self.shutdown_deferred

    def connectionLost(self, result):
        LOG.info("Lost IRC connection.")
        if self.ism.syncd:
            self.ism.removeMeFromMain()

        if self.shutdown_deferred:
            self.shutdown_deferred.callback("Bye!")
Exemple #8
0
class InspIRCdServer(LineOnlyReceiver):
    showirc = False

    def __init__(self, main):
        self.setupSID()
        self.uuid_counter = 0

        self.ism = IRCStateManager(main=main,
                                   ircs=self,
                                   uuid_generator=self.generateUUID)
        self.server_name = None
        self.shutdown_deferred = None

        self.ping_dcall = None
        self.ping_waiting = False
        self.capabs = {}

        # The earliest observed channel creation time.
        self.chan_time = int(time.time())

        # Generate a unique Q-line reason string, so we can detect if
        # another bridge races and steals our prefix.
        self.qline_reason = ("Reserved for Dtella (%08X)" %
                             random.randint(0, 0xFFFFFFFF))

    def setupSID(self):
        scfg = getServiceConfig()
        if scfg.sid:
            self.sid = scfg.sid
            return

        # Generate a 3-digit SID, based on some config constants.
        # This isn't the same algorithm as inspircd.cpp, but it doesn't
        # really matter.
        input_str = "%s\0%s" % (scfg.my_host, scfg.my_name)
        sid, = struct.unpack("!I", sha256(input_str).digest()[:4])
        self.sid = "%03d" % (sid % 999)

    def generateUUID(self):
        ctr = self.uuid_counter
        self.uuid_counter += 1

        uuid = ""
        for i in range(6):
            uuid = UUID_DIGITS[ctr % len(UUID_DIGITS)] + uuid
            ctr /= len(UUID_DIGITS)

        # There are over 2 billion UUIDs; just reconnect if we run out.
        if ctr > 0:
            self.transport.loseConnection()
            raise ValueError("Ran out of UUIDs")

        return self.sid + uuid

    def connectionMade(self):
        scfg = getServiceConfig()
        LOG.info("Connected to IRC server.")

        self.challenge = make_challenge()
        my_capabs = [
            ("NICKMAX", 32),
            ("HALFOP", 1),
            ("CHANMAX", 65),
            ("MAXMODES", 20),
            ("IDENTMAX", 12),
            ("MAXQUIT", 255),
            ("MAXTOPIC", 307),
            ("MAXKICK", 255),
            ("MAXGECOS", 128),
            ("MAXAWAY", 200),
            ("IP6SUPPORT", 1),
            ("PROTOCOL", 1201),
            ("CHALLENGE", self.challenge),
        ]
        my_capabs_str = ' '.join("%s=%s" % x for x in my_capabs)

        self.sendLine("CAPAB START")
        self.sendLine("CAPAB CAPABILITIES :%s" % my_capabs_str)
        self.sendLine("CAPAB END")

    def sendLine(self, line):
        line = line.replace('\r', '').replace('\n', '')
        print "<: %s" % line
        if self.showirc:
            LOG.log(5, "<: %s" % line)
        LineOnlyReceiver.sendLine(self, line)

    def lineReceived(self, line):
        if not line:
            return

        print ">: %s" % line
        if self.showirc:
            LOG.log(5, ">: %s" % line)

        if line[0] == ':':
            try:
                prefix, line = line[1:].split(' ', 1)
            except ValueError:
                return
        else:
            prefix = ''

        try:
            line, trailing = line.split(' :', 1)
        except ValueError:
            args = line.split()
        else:
            args = line.split()
            args.append(trailing)

        try:
            f = getattr(self, 'handleCmd_%s' % args[0].upper())
        except AttributeError:
            pass
        else:
            f(prefix, args[1:])

    def handleCmd_CAPAB(self, prefix, args):
        capab_mode = args[0]
        scfg = getServiceConfig()

        if capab_mode == "START":
            CHECK(not self.capabs)
        elif capab_mode == "CAPABILITIES":
            for capab in args[1].split():
                k, v = capab.split("=", 1)
                self.capabs[k] = v
        elif capab_mode == "END":
            challenge = self.capabs["CHALLENGE"]
            response = ihmac(scfg.sendpass, challenge)
            self.sendLine("SERVER %s %s 0 %s :%s" %
                          (scfg.my_host, response, self.sid, scfg.my_name))

    def handleCmd_PING(self, prefix, args):
        LOG.info("PING? PONG!")
        scfg = getServiceConfig()
        if len(args) == 1:
            self.sendLine(":%s PONG :%s" % (scfg.my_host, args[0]))
        elif len(args) == 2:
            self.sendLine("PONG %s :%s" % (args[1], args[0]))

    def handleCmd_PONG(self, prefix, args):
        if self.ping_waiting:
            self.ping_waiting = False
            self.schedulePing()

    def handleCmd_NICK(self, prefix, args):
        # :268AAAAAF NICK Paul 1238303566
        old_uuid = prefix
        new_nick = args[0]
        try:
            u = self.ism.findUser(old_uuid)
        except KeyError:
            # This might be an echo from our KICK.
            LOG.warning("NICK: can't find source: %s" % old_uuid)
            return
        self.ism.changeNick(u, new_nick)

    def handleCmd_UID(self, prefix, args):
        pass
        # :268 UID 268AAAAAF 1238242868 Paul <host> <host>
        #          paul <ip> 1238242873 +o :gecos
        nick = args[2]
        uuid = args[0]
        self.ism.addUser(nick, uuid)

    """
    def handleCmd_SVSNICK(self, prefix, args):
        # :services.dhirc.com SVSNICK |foo Guest33400 :1236660594
        oldnick = args[0]
        newnick = args[1]

        # Is the source user a Dtella node?  Freak out.
        n = self.ism.findDtellaNode(inick=oldnick)
        if n:
            message = "Nick change to %s is impossible" % irc_to_dc(newnick)
            self.ism.kickDtellaNode(n, prefix, message)
    """

    def handleCmd_JOIN(self, prefix, args):
        # :Paul JOIN #dtella 1237797449
        nick = prefix
        chans = args[0].split(',')

        scfg = getServiceConfig()
        if scfg.channel in chans:
            self.ism.joinChannel(self.ism.findUser(nick))

    def handleCmd_FJOIN(self, prefix, args):
        # :268 FJOIN #opers 1238298761 +nt :o,268AAAAAF v,268AAAAAE
        scfg = getServiceConfig()
        chan = args[0]
        if chan != scfg.channel:
            return

        # If we're not syncd yet, process the channel modes.
        if not self.ism.syncd:
            self.handleCmd_FMODE(prefix, args[:-1])

        # Keep track of earliest creation time for this channel.
        self.chan_time = min(self.chan_time, int(args[1]))

        for uinfo in args[-1].split():
            modes, uuid = uinfo.split(",", 1)

            u = self.ism.findUser(uuid)
            self.ism.joinChannel(u)

            changes = {}
            for m in modes:
                if m in scfg.chan_umodes.modes:
                    changes[m] = True

            if changes:
                self.ism.setChannelUserModes("", u, changes)

    def handleCmd_PART(self, prefix, args):
        uuid = prefix
        chans = args[0].split(',')

        scfg = getServiceConfig()
        if scfg.channel in chans:
            CHECK(self.ism.partChannel(self.ism.findUser(uuid)))

    def handleCmd_QUIT(self, prefix, args):
        uuid = prefix
        try:
            u = self.ism.findUser(uuid)
        except KeyError:
            LOG.warning("Can't quit user: %s" % uuid)
        else:
            self.ism.removeUser(u)

    def handleCmd_KICK(self, prefix, args):
        chan = args[0]
        l33t = prefix
        n00b = args[1]
        reason = irc_strip(args[2])

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

        # Rejoin the bot if it's killed.
        if self.ism.isMyBot(n00b):
            if self.ism.syncd:
                self.pushBotJoin()
            return

        l33t = self.ism.findUser(l33t).inick
        n = self.ism.findDtellaNode(inick=n00b)
        if n:
            self.ism.kickDtellaNode(n, l33t, reason)
        else:
            n00b_u = self.ism.findUser(n00b)
            message = ("%s has kicked %s: %s" %
                       (irc_to_dc(l33t), irc_to_dc(n00b_u.inick), reason))
            CHECK(self.ism.partChannel(n00b_u, message))

    def handleCmd_KILL(self, prefix, args):
        l33t = prefix
        n00b = args[0]
        reason = irc_strip(args[1])

        # In most cases, n00b is a UUID, but Anope seems to still use a nick.
        # Thus, we have to try both everywhere :-/

        # Reconnect the bot if it's killed.
        if self.ism.isMyBot(n00b):
            if self.ism.syncd:
                self.pushBotJoin(do_nick=True)
            return

        l33t = self.ism.findUser(l33t).inick

        # If n00b is a Dtella node, kick 'em.
        n = self.ism.findDtellaNode(inick=n00b)
        if n:
            self.ism.kickDtellaNode(n,
                                    l33t,
                                    "KILL: " + reason,
                                    send_quit=False)
            return

        # If n00b is an IRC user, treat it like a QUIT.
        try:
            n00b_u = self.ism.findUser(n00b)
        except KeyError:
            LOG.warning("Tried to KILL unknown user: %s" % n00b)
            return
        message = ("%s has KILL'd %s: %s" %
                   (irc_to_dc(l33t), irc_to_dc(n00b_u.inick), reason))
        self.ism.removeUser(n00b_u, message)

    """
    # Treat SVSKILL the same as KILL.
    handleCmd_SVSKILL = handleCmd_KILL
    """

    def handleCmd_TOPIC(self, prefix, args):
        # :268AAAAAO TOPIC #dtella :hello world
        whoset_uuid = prefix
        chan = args[0]
        text = irc_strip(args[-1])

        scfg = getServiceConfig()
        if chan == scfg.channel:
            whoset = self.ism.findUser(whoset_uuid).inick
            self.ism.setTopic(whoset, text)

    def handleCmd_FTOPIC(self, prefix, args):
        # :268 FTOPIC #dtella 1238306219 nick!host :hello
        chan = args[0]
        whoset = args[2].split('!', 1)[0]
        text = irc_strip(args[-1])

        scfg = getServiceConfig()
        if chan == scfg.channel:
            self.ism.setTopic(whoset, text)

    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)

    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)

    def handleCmd_DELLINE(self, prefix, args):
        # :268 DELLINE <linetype> <mask>
        kind = args[0]

        if kind == 'Z':
            cidr = args[1]
            self.ism.setNetworkBan(cidr, False)

        elif kind == 'Q':
            nickmask = args[1]

            # Ignore the unsetting of my own prefix.
            # It might be an echo from my own unsetting during startup.
            if self.ism.syncd and nickmask == cfg.dc_to_irc_prefix + "*":
                return

            self.ism.removeQLine(nickmask)

    def handleCmd_SERVER(self, prefix, args):
        # :foo.dhirc.com SERVER s.dhirc.com * 1 00A :Services
        hostname = args[0]
        password = args[1]
        sid = args[3]

        # Register each server as a user, because it's easier.
        LOG.info("Recording server: hostname=%s sid=%s" % (hostname, sid))
        self.ism.addUser(hostname, sid)

        if prefix:
            # Not my directly-connected server.
            return

        if self.server_name:
            # Could be a dupe?  Ignore it.
            return

        # We got a reply from the our connected IRC server, so our password
        # was just accepted.  Send the Dtella state information into IRC.

        scfg = getServiceConfig()

        # Save server name
        CHECK(args[0])
        self.server_name = args[0]

        # Verify challenge response.
        if scfg.recvpass is None:
            LOG.info("Skipping validation of recvpass")
        else:
            ch_response = args[1]
            if ch_response != ihmac(scfg.recvpass, self.challenge):
                raise ValueError("Incorrect recvpass")
            LOG.info("Correct recvpass")

        LOG.info("IRC Server Name: %s" % self.server_name)

        # Tell the ReconnectingClientFactory that we're cool
        self.factory.resetDelay()

        self.sendLine("BURST %d" % time.time())
        self.sendLine("ENDBURST")

    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))

    def handleCmd_BURST(self, prefix, args):
        return  #FIXME
        if self.ism.syncd:
            LOG.error("Can't handle BURST after sync. Restarting.")
            self.transport.loseConnection()

    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()

    # FIXME: implement WHOIS
    """
    def handleCmd_WHOIS(self, prefix, args):
        # Somewhat simplistic handling of WHOIS requests
        if not (prefix and len(args) >= 1):
            return

        src = prefix
        who = args[-1]
        scfg = getServiceConfig()

        if who == cfg.dc_to_irc_bot:
            self.pushWhoisReply(
                311, src, who, B_USER, scfg.my_host, '*', B_REALNAME)
            self.pushWhoisReply(
                312, src, who, scfg.my_host, scfg.my_name)
            self.pushWhoisReply(
                319, src, who, scfg.channel)
        else:
            n = self.ism.findDtellaNode(inick=who)
            if not (n and hasattr(n, 'hostmask')):
                return

            self.pushWhoisReply(
                311, src, who, n_user(n.ipp), n.hostmask, '*',
                "Dtella %s" % n.dttag[3:])
            self.pushWhoisReply(
                312, src, who, scfg.my_host, scfg.my_name)
            self.pushWhoisReply(
                319, src, who, scfg.channel)

            if local.use_locations:
                self.pushWhoisReply(
                    320, src, who, "Location: %s"
                    % local.hostnameToLocation(n.hostname))

        self.pushWhoisReply(
            318, src, who, "End of /WHOIS list.")

    def pushWhoisReply(self, code, target, who, *strings):
        scfg = getServiceConfig()
        line = ":%s %d %s %s " % (scfg.my_host, code, target, who)
        strings = list(strings)
        strings[-1] = ":" + strings[-1]
        line += ' '.join(strings)
        self.sendLine(line)
    """

    def handleCmd_PRIVMSG(self, prefix, args):
        src_uuid = prefix
        target = args[0]
        text = args[1]
        flags = 0

        if (text[:8], text[-1:]) == ('\001ACTION ', '\001'):
            text = text[8:-1]
            flags |= core.SLASHME_BIT

        text = irc_strip(text)

        src_nick = self.ism.findUser(src_uuid).inick

        scfg = getServiceConfig()
        if target == scfg.channel:
            self.ism.sendChannelMessage(src_nick, text, flags)

        # :Global PRIVMSG $irc3.dhirc.com :TESTING....
        # Handle global messages delivered to the bridge.
        # FIXME: does this work with InspIRCd?
        elif target == "$" + scfg.my_host:
            flags |= core.NOTICE_BIT
            self.ism.sendChannelMessage(src_nick, text, flags)

        else:
            n = self.ism.findDtellaNode(inick=target)
            if n:
                self.ism.sendPrivateMessage(n, src_nick, text, flags)

    def handleCmd_NOTICE(self, prefix, args):
        src_uuid = prefix
        target = args[0]
        text = irc_strip(args[1])
        flags = core.NOTICE_BIT

        src_nick = self.ism.findUser(src_uuid).inick

        scfg = getServiceConfig()
        if target == scfg.channel:
            self.ism.sendChannelMessage(src_nick, text, flags)
        else:
            n = self.ism.findDtellaNode(inick=target)
            if n:
                self.ism.sendPrivateMessage(n, src_nick, text, flags)

    def pushUID(self, uuid, nick, ident, host, cloak_host, modes, ip, gecos):
        # If an IP was provided, convert to a base64 parameter.
        if not ip:
            ip = '0.0.0.0'

        if not cloak_host:
            cloak_host = host

        scfg = getServiceConfig()
        now = time.time()
        self.sendLine(":%s UID %s %d %s %s %s %s %s %d +%s :%s" %
                      (self.sid, uuid, now, nick, host, cloak_host, ident, ip,
                       now, modes, gecos))

    def pushJoin(self, uuid, modes=""):
        scfg = getServiceConfig()
        self.sendLine(":%s FJOIN %s %d + %s,%s" %
                      (self.sid, scfg.channel, self.chan_time, modes, uuid))

    def pushQuit(self, uuid, reason=""):
        self.sendLine(":%s QUIT :%s" % (uuid, reason))

    def pushBotJoin(self, do_nick=False):
        scfg = getServiceConfig()
        uuid = self.ism.bot_user.uuid

        if do_nick:
            self.pushUID(uuid, self.ism.bot_user.inick, B_USER, scfg.my_host,
                         None, "", None, B_REALNAME)

        # Join channel, and grant ops.
        self.pushJoin(uuid, "o")

    def pushRemoveQLine(self, nickmask):
        scfg = getServiceConfig()
        LOG.info("Telling network to remove Q-line: %s" % nickmask)
        self.sendLine(":%s DELLINE Q %s" % (self.sid, nickmask))

    def schedulePing(self):
        if self.ping_dcall:
            self.ping_dcall.reset(60.0)
            return

        def cb():
            self.ping_dcall = None

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

        self.ping_dcall = reactor.callLater(60.0, cb)

    def event_AddDtNode(self, n, ident):
        self.pushUID(n.uuid, n.inick, ident, n.hostname, n.hostmask, "iwx",
                     Ad().setRawIPPort(n.ipp).getTextIP(),
                     "Dtella %s" % n.dttag[3:])
        self.pushJoin(n.uuid)

    def event_RemoveDtNode(self, n, reason):
        self.pushQuit(n.uuid, reason)

    def event_KillUser(self, u):
        scfg = getServiceConfig()
        LOG.info("Killing nick: " + u.inick)
        self.sendLine(":%s KILL %s :%s (nick reserved for Dtella)" %
                      (self.sid, u.uuid, scfg.my_host))

    def event_NodeSetTopic(self, n, topic):
        scfg = getServiceConfig()
        self.sendLine(":%s TOPIC %s :%s" % (n.uuid, scfg.channel, topic))

    def event_Message(self, src_n, dst_u, text, action=False):
        scfg = getServiceConfig()
        if dst_u is None:
            target = scfg.channel
        else:
            target = dst_u.uuid

        if action:
            text = "\001ACTION %s\001" % text

        self.sendLine(":%s PRIVMSG %s :%s" % (src_n.uuid, target, text))

    def event_Notice(self, src_n, dst_u, text):
        scfg = getServiceConfig()
        if dst_u is None:
            target = scfg.channel
        else:
            target = dst_u.uuid

        self.sendLine(":%s NOTICE %s :%s" % (src_n.uuid, target, text))

    def shutdown(self):
        if self.shutdown_deferred:
            return self.shutdown_deferred

        # Remove nick ban
        self.pushRemoveQLine(cfg.dc_to_irc_prefix + "*")

        # Scream
        self.pushQuit(self.ism.bot_user.uuid, "AIEEEEEEE!")

        # Send SQUIT for completeness
        scfg = getServiceConfig()
        self.sendLine(":%s SQUIT %s :Bridge Shutting Down" %
                      (self.sid, scfg.my_host))

        # Close connection
        self.transport.loseConnection()

        # This will complete after loseConnection fires
        self.shutdown_deferred = defer.Deferred()
        return self.shutdown_deferred

    def connectionLost(self, result):
        LOG.info("Lost IRC connection.")
        if self.ism.syncd:
            self.ism.removeMeFromMain()

        if self.shutdown_deferred:
            self.shutdown_deferred.callback("Bye!")