Example #1
0
    def addUser(self, inick, uuid=None):
        # Start tracking a new IRC user.
        CHECK(inick.lower() not in self.users)

        if self.uuid_generator:
            CHECK(number_prefix(uuid))
            CHECK(uuid.lower() not in self.users)

            # If the nick is a uuid, it must be its own uuid.
            if number_prefix(inick):
                CHECK(inick.lower() == uuid.lower())
        else:
            CHECK(uuid is None)

        u = User(inick, uuid)

        # Don't allow nicks which match my prefix.
        if self.syncd and matches_dc_to_irc_prefix(u.inick):
            if self.ircs:
                self.ircs.event_KillUser(u)
            return

        self.users[inick.lower()] = u
        if self.uuid_generator:
            self.users[uuid.lower()] = u
        return u
Example #2
0
    def event_KickMe(self, lines, rejoin_time):
        # Sequence of events during a kick:
        # 1. event_RemoveNick(*)
        # 2. event_DtellaDown()
        # 3. event_KickMe()
        # 4. stateChange_ObserverDown()

        # Node will become visible again if:
        # - Dtella node loses its connection
        # - User types !REJOIN
        # - DC client reconnects (creates a new DCHandler)

        CHECK(self.state == 'ready')
        self.state = 'invisible'

        for line in lines:
            self.pushStatus(line)

        if rejoin_time is None:
            return

        # Automatically rejoin the chat after a timeout period.
        def cb():
            self.autoRejoin_dcall = None
            self.pushStatus("Automatically rejoining...")
            self.doRejoin()

        CHECK(self.autoRejoin_dcall is None)
        self.autoRejoin_dcall = reactor.callLater(rejoin_time, cb)
Example #3
0
    def removeUser(self, u, message=None):
        self.partChannel(u, message)

        # If inick is not a uuid, remove from index.
        if not (self.uuid_generator and number_prefix(u.inick)):
            CHECK(self.users.pop(u.inick.lower()) is u)

        # If a uuid is defined, remove from index.
        if self.uuid_generator:
            CHECK(self.users.pop(u.uuid.lower()) is u)
Example #4
0
    def changeNick(self, u, new_inick):
        old_inick = u.inick
        if old_inick.lower() == new_inick.lower():
            # TODO: report case changes to Dtella?
            u.inick = new_inick
            return

        if self.uuid_generator and number_prefix(new_inick):
            # If nick is changing to a uuid, it must be its own uuid.
            CHECK(new_inick.lower() == u.uuid.lower())
        else:
            # Otherwise, the nick must not already exist.
            CHECK(new_inick.lower() not in self.users)

        if not (self.uuid_generator and number_prefix(u.inick)):
            # If old nick is not a uuid, remove it from the index.
            CHECK(self.users.pop(u.inick.lower()) is u)

        # Don't allow IRC nicks which match my prefix.
        conflicted = (self.syncd and matches_dc_to_irc_prefix(new_inick))

        if conflicted:
            # Report exit from Dtella before updating nick.
            self.partChannel(u)

        # Update nick.  Note that UUID is unchanged.
        u.inick = new_inick
        self.users[new_inick.lower()] = u

        if conflicted:
            # Nick matches my prefix, diediedie!
            if self.ircs:
                self.ircs.event_KillUser(u)
            self.removeUser(u)
            return

        scfg = getServiceConfig()

        # Report the change to Dtella.
        if u in self.chanusers:
            try:
                osm = self.getOnlineStateManager()
            except NotOnline:
                return

            infoindex = scfg.chan_umodes.getUserInfoIndex(u)

            chunks = []
            osm.bsm.addChatChunk(
                chunks, cfg.irc_to_dc_bot, "%s is now known as %s" %
                (irc_to_dc(old_inick), irc_to_dc(new_inick)))
            osm.bsm.addNickChunk(chunks, irc_to_dc(old_inick), 0xFF)
            osm.bsm.addNickChunk(chunks, irc_to_dc(new_inick), infoindex)
            osm.bsm.sendBridgeChange(chunks)
Example #5
0
    def findConflictingBridge(self):
        # Determine if another bridge conflicts with me.
        # Return True if we need to abort.
        CHECK(self.ircs and not self.syncd)

        stale_qlines = []
        for nickmask, (q, reason) in self.qlines.iteritems():
            # Look for Q-lines which conflict with my prefix.
            if not q.match(cfg.dc_to_irc_prefix):
                continue
            LOG.info("Found a conflicting Q-line: %s" % nickmask)

            # If any nicks exist under that Q-line, we'll need to abort.
            for u in set(self.users.itervalues()):
                if q.match(u.inick):
                    LOG.info("... and a nick to go with it: %s" % u.inick)
                    return True

            stale_qlines.append(nickmask)

        # Remove all stale Q-lines from the network.
        LOG.info("Stale qlines: %r" % stale_qlines)
        for nickmask in stale_qlines:
            del self.qlines[nickmask]
            self.ircs.pushRemoveQLine(nickmask)

        # Conflict has been neutralized.
        return False
Example #6
0
    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)
Example #7
0
    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)))
Example #8
0
        def cb():
            self.sendState_dcall = None

            osm = self.main.osm

            CHECK(osm and osm.syncd)

            # Decide when to retransmit next
            when = 60 * 5
            self.sendState_dcall = reactor.callLater(when, cb)

            # Broadcast header
            packet = osm.mrm.broadcastHeader('BS', osm.me.ipp)

            # The meat
            block_hashes, blocks = self.getStateData(packet)

            # Signature
            self.signPacket(packet, broadcast=True)

            # Broadcast status message
            osm.mrm.newMessage(''.join(packet), tries=8)

            # Broadcast data blocks
            # This could potentially be a bottleneck for slow connections
            for b in blocks:
                packet = osm.mrm.broadcastHeader('BB', osm.me.ipp)
                packet.append(self.nextPktNum())
                packet.append(struct.pack("!H", len(b)))
                packet.append(b)
                osm.mrm.newMessage(''.join(packet), tries=4)
Example #9
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()
Example #10
0
    def broadcastChatMessage(self, flags, text):

        CHECK(self.isOnline())

        osm = self.main.osm

        if osm.isModerated():
            # If the channel went moderated with something in the queue,
            # wipe it out and don't send.
            del self.chatq[:]
            return

        packet = osm.mrm.broadcastHeader('CH', osm.me.ipp)
        packet.append(struct.pack('!I', osm.mrm.getPacketNumber_chat()))

        packet.append(osm.me.nickHash())
        packet.append(struct.pack('!BH', flags, len(text)))
        packet.append(text)

        osm.mrm.newMessage(''.join(packet), tries=4)

        # Echo back to the DC client
        if flags & core.SLASHME_BIT:
            nick = "*"
            text = "%s %s" % (osm.me.nick, text)
        else:
            nick = osm.me.nick

        self.pushChatMessage(nick, text)
Example #11
0
    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))
Example #12
0
    def event_DtellaDown(self):
        CHECK(self.isOnline())

        # Wipe out the topic
        self.pushTopic()

        # Wipe out my outgoing chat queue
        del self.chatq[:]
Example #13
0
    def event_DtellaUp(self):
        CHECK(self.isOnline())
        self.d_GetNickList()

        # Grab the current topic from Dtella.
        tm = self.main.osm.tm
        self.pushTopic(tm.topic)
        if tm.topic:
            self.pushStatus(tm.getFormattedTopic())
Example #14
0
    def __init__(self, *data):
        # *data is a sequence of (mode, friendly_name, info),
        # in order of decreasing awesomeness.
        #
        # mode is:
        # - A single character, for normal user modes.
        # - ":P", for plain users who have no modes assigned.
        # - ":V" for virtual nicks, not present in the channel.
        #
        # friendly_name is a string used in mode-change messages.
        # info is a DC-formatted info string.
        #
        # All modes must be unique. "" and None must be present.

        # string of mode characters, decreasing in awesomeness.
        self.modes = ""

        # Mapping from 'mode' -> infoindex.
        # Includes ":P" for plain, and ":V" for virtual.
        self.mode_to_index = {}

        self.friendly = []
        self.info = []

        for i, (mode, friendly, info) in enumerate(data):
            if len(mode) == 1:
                # Keep sorted string of normal modes.
                self.modes += mode
            elif mode in (":P", ":V"):
                pass
            else:
                raise ValueError("Invalid mode: " + mode)

            CHECK(mode not in self.mode_to_index)
            self.mode_to_index[mode] = i

            self.friendly.append(friendly)
            self.info.append(info)

        # Make sure plain and virtual were defined.
        CHECK(":P" in self.mode_to_index)
        CHECK(":V" in self.mode_to_index)
Example #15
0
 def killConflictingUsers(self):
     # Find any reserved nicks, and KILL them.
     CHECK(self.ircs and not self.syncd)
     bad_users = [
         u for u in set(self.users.itervalues())
         if matches_dc_to_irc_prefix(u.inick)
     ]
     LOG.info("Conflicting users: %r" % bad_users)
     for u in bad_users:
         self.ircs.event_KillUser(u)
         self.removeUser(u)
Example #16
0
    def removeIRCStateManager(self, ism):
        CHECK(ism and (self.ism is ism))

        self.ism = None

        # Send empty IRC state to Dtella.
        self.stateChange_ObserverDown()

        osm = self.osm
        if osm:
            # Rebuild ban table.
            osm.banm.scheduleRebuildBans()
Example #17
0
    def event_ChatMessage(self, n, nick, text, flags):
        CHECK(not hasattr(n, 'dns_pending'))

        if not self.ircs:
            return

        if flags & core.NOTICE_BIT:
            self.ircs.event_Notice(n, None, text)
        elif flags & core.SLASHME_BIT:
            self.ircs.event_Message(n, None, text, action=True)
        else:
            self.ircs.event_Message(n, None, text)
Example #18
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()
Example #19
0
    def bridgeevent_TopicChange(self, n, topic):
        # Topic change, from a Dtella node.
        # Return True if successful.
        CHECK(self.syncd)

        if self.topic_locked:
            return False

        if self.ircs:
            self.ircs.event_NodeSetTopic(n, topic)

        self.setTopic(n.inick, topic)
        return True
Example #20
0
    def addDCHandler(self, dch):

        CHECK(not self.dch)
        CHECK(dch.state == 'ready')

        self.dch = dch

        # Cancel the disconnect timeout
        dcall_discard(self, 'disconnect_dcall')

        # Start connecting, or get status of current connection
        text = self.startConnecting()
        if text:
            # We must already be connecting/online.
            # Show the last status message.
            LOG.debug(text)
            dch.pushStatus(text)

            # Send a message if there's a newer version
            self.dcfg.resetReportedVersion()
            self.dcfg.reportNewVersion()

        self.stateChange_ObserverUp()
Example #21
0
    def sendBridgeChange(self, chunks):
        osm = self.main.osm
        CHECK(osm and osm.syncd)

        packet = osm.mrm.broadcastHeader('BC', osm.me.ipp)
        packet.append(self.nextPktNum())

        chunks = ''.join(chunks)
        packet.append(struct.pack("!H", len(chunks)))
        packet.append(chunks)

        self.signPacket(packet, broadcast=True)

        osm.mrm.newMessage(''.join(packet), tries=4)
Example #22
0
    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))
Example #23
0
    def bridgeevent_PrivMsg(self, n, dst_inick, text):
        # Send a private message from Dtella to IRC.
        # Return True if successful.
        CHECK(self.syncd)

        try:
            u = self.findUser(dst_inick)
        except KeyError:
            return False

        if self.ircs:
            self.ircs.event_Message(n, u, text)
            return True

        return False
Example #24
0
    def queryLocation(self, my_ipp):
        # Try to convert the IP address into a human-readable location name.
        # This might be slightly more complicated than it really needs to be.

        CHECK(local.use_locations)

        ad = Ad().setRawIPPort(my_ipp)
        my_ip = ad.getTextIP()

        # Set my local ip in the state
        # self.state.local_ip = my_ip
        # self.state.saveState()

        skip = False
        for ip, loc in self.location.items():
            if ip == my_ip:
                skip = True
            elif loc:
                # Forget old entries
                del self.location[ip]

        # If we already had an entry for this IP, then don't start
        # another lookup.
        if skip:
            return

        # A location of None indicates that a lookup is in progress
        self.location[my_ip] = None

        def cb(hostname):

            # Use local_config to transform this hostname into a
            # human-readable location
            loc = local.hostnameToLocation(hostname)

            # If we got a location, save it, otherwise dump the
            # dictionary entry
            if loc:
                self.location[my_ip] = loc
            else:
                del self.location[my_ip]

            # Maybe send an info update
            if self.osm:
                self.osm.updateMyInfo()

        # Start lookup
        ipToHostname(ad).addCallback(cb)
Example #25
0
    def bindUDPPort(self):
        # Returns True if the UDP port is bound

        udp_state = self.ph.getSocketState()

        if udp_state == 'alive':
            # Port already bound, yay!
            return True
        elif udp_state == 'dying':
            # Port is busy disconnecting.  Wait.
            return False

        # Otherwise, the port is dead, so try to rebind it.
        CHECK(udp_state == 'dead')

        # Anhad addition, Uncomment for debugging
        try:
            #    print 'port debugging'
            #    print self.state.udp_port
            self.state.udp_port = local.default_udpport
        #    print 'done port debugging'
        except:
            print 'Error setting default port'

        try:
            reactor.listenUDP(self.state.udp_port, self.ph)

        except twisted.internet.error.BindError:

            self.showLoginStatus("*** FAILED TO BIND UDP PORT ***")

            text = (
                "Dtella was not able to listen on UDP port %d. One possible "
                "reason for this is that you've tried to make your DC "
                "client use the same UDP port as Dtella. Two programs "
                "are not allowed listen on the same port.  To tell Dtella "
                "to use a different port, type !UDP followed by a number. "
                "Note that if you have a firewall or router, you will have "
                "to tell it to let traffic through on this port." %
                self.state.udp_port)

            for line in word_wrap(text):
                self.showLoginStatus(line)

        return self.ph.getSocketState() == 'alive'
Example #26
0
    def setDNSIPCache(self, data):

        CHECK(len(data) % 6 == 4)

        # 2011-08-21: New nodes ignore the value of 'when'.
        when, = struct.unpack("!I", data[:4])
        ipps = [data[i:i + 6] for i in range(4, len(data), 6)]

        self.dns_ipcache = when, ipps

        # If DNS contains a foreign IP, add it to the exemption
        # list, so that it can function as a bridge or cache node.

        self.exempt_ips.clear()

        for ipp in ipps:
            ad = Ad().setRawIPPort(ipp)
            self.addExemptIP(ad)
Example #27
0
    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")
Example #28
0
    def sendPrivateBridgeChange(self, n, chunks):
        osm = self.main.osm
        ph = self.main.ph
        CHECK(osm and osm.syncd)

        chunks = ''.join(chunks)

        ack_key = self.nextPktNum()

        packet = ['bC']
        packet.append(osm.me.ipp)
        packet.append(ack_key)
        packet.append(n.nickHash())
        packet.append(struct.pack('!H', len(chunks)))
        packet.append(chunks)
        self.signPacket(packet, broadcast=False)
        packet = ''.join(packet)

        def fail_cb(detail):
            LOG.debug("bC failed: %s" % detail)

        n.sendPrivateMessage(ph, ack_key, packet, fail_cb)
Example #29
0
    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))
Example #30
0
    def showStats(self, out, title, compute, format, peers_only):

        CHECK(self.dch.isOnline())

        # Count users and bytes
        ucount = {}
        bcount = {}

        # Collect user count and share size
        for n in self.main.osm.nkm.nickmap.values():

            if peers_only and not n.is_peer:
                continue

            try:
                ucount[n.location] += 1
                bcount[n.location] += n.shared
            except KeyError:
                ucount[n.location] = 1
                bcount[n.location] = n.shared

        # Collect final values
        values = {}
        for loc in ucount:
            values[loc] = compute(ucount[loc], bcount[loc])

        # Sort by value, in descending order
        locs = values.keys()
        locs.sort(key=lambda loc: values[loc], reverse=True)

        overall = compute(sum(ucount.values()), sum(bcount.values()))

        # Build info string and send it
        out("/== %s, by Location ==\\" % title)
        for loc in locs:
            out("| %s <= %s" % (format(values[loc]), loc))
        out("|")
        out("\\_ Overall: %s _/" % format(overall))