Exemple #1
0
 def dump(self, f, now=None):
     """Write the current data to a file handle 'f'."""
     if now is None: now = time()
     try:
         self._lock.acquire()
         startTime = self.lastRotation
         endTime = now
         print >>f, "========== From %s to %s:" % (formatTime(startTime,1),
                                                   formatTime(endTime,1))
         for event in _EVENTS:
             count = self.count[event]
             if len(count) == 0:
                 print >>f, "  %s: 0" % event
                 continue
             elif len(count) == 1 and count.keys()[0] is None:
                 print >>f, "  %s: %s" % (event, count[None])
                 continue
             print >>f, "  %s:" % event
             total = 0
             args = count.keys()
             args.sort()
             length = max([ len(str(arg)) for arg in args ])
             length = max((length, 10))
             fmt = "    %"+str(length)+"s: %s"
             for arg in args:
                 v = count[arg]
                 if arg is None: arg = "{Unknown}"
                 print >>f, fmt % (arg, v)
                 total += v
             print >>f, fmt % ("Total", total)
     finally:
         self._lock.release()
Exemple #2
0
    def checkDescriptorConsistency(self, regen=1):
        """Check whether the server descriptors in this keyring are
           consistent with the server's configuration.  If 'regen' is
           true, inconsistent descriptors are regenerated."""
        identity = None
        state = []
        for _, _, ks in self.keySets:
            ok = ks.checkConsistency(self.config, 0)
            if ok == 'good':
                continue
            state.append((ok, ks))

        if not state:
            return

        LOG.warn("Some generated keysets do not match "
                 "current configuration...")

        for ok, ks in state:
            va, vu = ks.getLiveness()
            LOG.warn("Keyset %s (%s--%s):", ks.keyname, formatTime(va, 1),
                     formatTime(vu, 1))
            ks.checkConsistency(self.config, 1)
            if regen and ok == 'bad':
                if not identity: identity = self.getIdentityKey()
                ks.regenerateServerDescriptor(self.config, identity)
Exemple #3
0
    def checkDescriptorConsistency(self, regen=1):
        """Check whether the server descriptors in this keyring are
           consistent with the server's configuration.  If 'regen' is
           true, inconsistent descriptors are regenerated."""
        identity = None
        state = []
        for _,_,ks in self.keySets:
            ok = ks.checkConsistency(self.config, 0)
            if ok == 'good':
                continue
            state.append((ok, ks))

        if not state:
            return

        LOG.warn("Some generated keysets do not match "
                  "current configuration...")

        for ok, ks in state:
            va,vu = ks.getLiveness()
            LOG.warn("Keyset %s (%s--%s):",ks.keyname,formatTime(va,1),
                     formatTime(vu,1))
            ks.checkConsistency(self.config, 1)
            if regen and ok == 'bad':
                if not identity: identity = self.getIdentityKey()
                ks.regenerateServerDescriptor(self.config, identity)
Exemple #4
0
 def dump(self, f, now=None):
     """Write the current data to a file handle 'f'."""
     if now is None: now = time()
     try:
         self._lock.acquire()
         startTime = self.lastRotation
         endTime = now
         print >> f, "========== From %s to %s:" % (formatTime(
             startTime, 1), formatTime(endTime, 1))
         for event in _EVENTS:
             count = self.count[event]
             if len(count) == 0:
                 print >> f, "  %s: 0" % event
                 continue
             elif len(count) == 1 and count.keys()[0] is None:
                 print >> f, "  %s: %s" % (event, count[None])
                 continue
             print >> f, "  %s:" % event
             total = 0
             args = count.keys()
             args.sort()
             length = max([len(str(arg)) for arg in args])
             length = max((length, 10))
             fmt = "    %" + str(length) + "s: %s"
             for arg in args:
                 v = count[arg]
                 if arg is None: arg = "{Unknown}"
                 print >> f, fmt % (arg, v)
                 total += v
             print >> f, fmt % ("Total", total)
     finally:
         self._lock.release()
Exemple #5
0
 def regenerateServerDescriptor(self, config, identityKey):
     """Regenerate the server descriptor for this keyset, keeping the
        original keys."""
     self.load()
     self.markAsUnpublished()
     validAt,validUntil = self.getLiveness()
     LOG.info("Regenerating descriptor for keyset %s (%s--%s)",
              self.keyname, formatTime(validAt,1),
              formatTime(validUntil,1))
     generateServerDescriptorAndKeys(config, identityKey,
                      self.keyroot, self.keyname, self.hashroot,
                      validAt=validAt, validUntil=validUntil,
                      useServerKeys=1)
     self.serverinfo = self.validAfter = self.validUntil = None
Exemple #6
0
    def deliveryFailed(self, handle, retriable=0, now=None):
        """Removes a message from the outgoing queue, or requeues it
           for delivery at a later time.  This method should be
           invoked after the corresponding message has been
           unsuccessfully delivered."""
        assert self.retrySchedule is not None
        LOG.trace("DeliveryQueue failed to deliver %s from %s",
                  handle, self.qname)
        try:
            self._lock.acquire()
            try:
                ds = self.store.getMetadata(handle)
            except KeyError:
                ds = None
            except CorruptedFile:
                return

            if ds is None:
                # This should never happen
                LOG.error_exc(sys.exc_info(),
                              "Handle %s had no state", handle)
                ds = _DeliveryState(now)
                ds.setNextAttempt(self.retrySchedule, now)
                self.store.setMetadata(handle, ds)
                return

            if not ds.isPending():
                LOG.error("Handle %s was not pending", handle)
                return

            last = ds.pending
            ds.setNonPending()

            if retriable:
                # If we can retry the message, update the deliveryState
                # with the most recent attempt, and see if there's another
                # attempt in the future.
                ds.setLastAttempt(last)
                ds.setNextAttempt(self.retrySchedule, now)
                if ds.nextAttempt is not None:
                    # There is another scheduled delivery attempt.  Remember
                    # it, mark the message sendable again, and save our state.
                    LOG.trace("     (We'll try %s again at %s)", handle,
                              formatTime(ds.nextAttempt, 1))

                    self.store.setMetadata(handle, ds)
                    return
                else:
                    assert ds.isRemovable()
                # Otherwise, fallthrough.

            # If we reach this point, the message is undeliverable, either
            # because 'retriable' is false, or because we've run out of
            # retries.
            LOG.trace("     (Giving up on %s)", handle)
            self.removeMessage(handle)
        finally:
            self._lock.release()
Exemple #7
0
    def getNextKeygen(self):
        """Return the time (in seconds) when we should next generate keys.
           If -1 is returned, keygen should occur immediately.
        """
        if not self.keySets:
            return -1

        # Our last current key expires at 'lastExpiry'.
        lastExpiry = self.keySets[-1][1]
        # We want to have keys in the directory valid for
        # PREPUBLICATION_INTERVAL seconds after that, and we assume that
        # a key takes up to PUBLICATION_LATENCY seconds to make it into the
        # directory.
        nextKeygen = lastExpiry - PUBLICATION_LATENCY - PREPUBLICATION_INTERVAL

        LOG.info("Last expiry at %s; next keygen at %s",
                 formatTime(lastExpiry, 1), formatTime(nextKeygen, 1))
        return nextKeygen
Exemple #8
0
    def deliveryFailed(self, handle, retriable=0, now=None):
        """Removes a message from the outgoing queue, or requeues it
           for delivery at a later time.  This method should be
           invoked after the corresponding message has been
           unsuccessfully delivered."""
        assert self.retrySchedule is not None
        LOG.trace("DeliveryQueue failed to deliver %s from %s", handle,
                  self.qname)
        try:
            self._lock.acquire()
            try:
                ds = self.store.getMetadata(handle)
            except KeyError:
                ds = None
            except CorruptedFile:
                return

            if ds is None:
                # This should never happen
                LOG.error_exc(sys.exc_info(), "Handle %s had no state", handle)
                ds = _DeliveryState(now)
                ds.setNextAttempt(self.retrySchedule, now)
                self.store.setMetadata(handle, ds)
                return

            if not ds.isPending():
                LOG.error("Handle %s was not pending", handle)
                return

            last = ds.pending
            ds.setNonPending()

            if retriable:
                # If we can retry the message, update the deliveryState
                # with the most recent attempt, and see if there's another
                # attempt in the future.
                ds.setLastAttempt(last)
                ds.setNextAttempt(self.retrySchedule, now)
                if ds.nextAttempt is not None:
                    # There is another scheduled delivery attempt.  Remember
                    # it, mark the message sendable again, and save our state.
                    LOG.trace("     (We'll try %s again at %s)", handle,
                              formatTime(ds.nextAttempt, 1))

                    self.store.setMetadata(handle, ds)
                    return
                else:
                    assert ds.isRemovable()
                # Otherwise, fallthrough.

            # If we reach this point, the message is undeliverable, either
            # because 'retriable' is false, or because we've run out of
            # retries.
            LOG.trace("     (Giving up on %s)", handle)
            self.removeMessage(handle)
        finally:
            self._lock.release()
Exemple #9
0
 def regenerateServerDescriptor(self, config, identityKey):
     """Regenerate the server descriptor for this keyset, keeping the
        original keys."""
     self.load()
     self.markAsUnpublished()
     validAt, validUntil = self.getLiveness()
     LOG.info("Regenerating descriptor for keyset %s (%s--%s)",
              self.keyname, formatTime(validAt,
                                       1), formatTime(validUntil, 1))
     generateServerDescriptorAndKeys(config,
                                     identityKey,
                                     self.keyroot,
                                     self.keyname,
                                     self.hashroot,
                                     validAt=validAt,
                                     validUntil=validUntil,
                                     useServerKeys=1)
     self.serverinfo = self.validAfter = self.validUntil = None
Exemple #10
0
    def getNextKeygen(self):
        """Return the time (in seconds) when we should next generate keys.
           If -1 is returned, keygen should occur immediately.
        """
        if not self.keySets:
            return -1

        # Our last current key expires at 'lastExpiry'.
        lastExpiry = self.keySets[-1][1]
        # We want to have keys in the directory valid for
        # PREPUBLICATION_INTERVAL seconds after that, and we assume that
        # a key takes up to PUBLICATION_LATENCY seconds to make it into the
        # directory.
        nextKeygen = lastExpiry - PUBLICATION_LATENCY - PREPUBLICATION_INTERVAL

        LOG.info("Last expiry at %s; next keygen at %s",
                 formatTime(lastExpiry,1), formatTime(nextKeygen, 1))
        return nextKeygen
Exemple #11
0
    def getNextKeyRotation(self, curKeys=None):
        """Calculate the next time at which we should change the set of live
           keys."""
        if self.nextUpdate is None:
            if curKeys is None:
                if self.currentKeys is None:
                    curKeys = self.getServerKeysets()
                else:
                    curKeys = self.currentKeys
            events = []
            curNames = {}
            # For every current keyset, we'll remove it at keyOverlap
            # seconds after its stated expiry time.
            for k in curKeys:
                va, vu = k.getLiveness()
                events.append((vu + self.keyOverlap, "RM"))
                curNames[k.keyname] = 1
            # For every other keyset, we'll add it when it becomes valid.
            for va, vu, k in self.keySets:
                if curNames.has_key(k.keyname): continue
                events.append((va, "ADD"))

            # Which even happens first?
            events.sort()
            if not events:
                LOG.info("No future key rotation events.")
                self.nextUpdate = sys.maxint
                return self.nextUpdate

            self.nextUpdate, eventType = events[0]
            if eventType == "RM":
                LOG.info("Next key event: old key is removed at %s",
                         formatTime(self.nextUpdate, 1))
            else:
                assert eventType == "ADD"
                LOG.info("Next key event: new key becomes valid at %s",
                         formatTime(self.nextUpdate, 1))

        return self.nextUpdate
Exemple #12
0
    def getNextKeyRotation(self, curKeys=None):
        """Calculate the next time at which we should change the set of live
           keys."""
        if self.nextUpdate is None:
            if curKeys is None:
                if self.currentKeys is None:
                    curKeys = self.getServerKeysets()
                else:
                    curKeys = self.currentKeys
            events = []
            curNames = {}
            # For every current keyset, we'll remove it at keyOverlap
            # seconds after its stated expiry time.
            for k in curKeys:
                va, vu = k.getLiveness()
                events.append((vu+self.keyOverlap, "RM"))
                curNames[k.keyname] = 1
            # For every other keyset, we'll add it when it becomes valid.
            for va, vu, k in self.keySets:
                if curNames.has_key(k.keyname): continue
                events.append((va, "ADD"))

            # Which even happens first?
            events.sort()
            if not events:
                LOG.info("No future key rotation events.")
                self.nextUpdate = sys.maxint
                return self.nextUpdate

            self.nextUpdate, eventType = events[0]
            if eventType == "RM":
                LOG.info("Next key event: old key is removed at %s",
                         formatTime(self.nextUpdate,1))
            else:
                assert eventType == "ADD"
                LOG.info("Next key event: new key becomes valid at %s",
                         formatTime(self.nextUpdate,1))

        return self.nextUpdate
Exemple #13
0
    def format(self):
        """DOCDOC"""
        import mixminion.ServerInfo
        digest = self.getHexDigest()
        expiry = formatTime(self.timestamp)
        if self.routingType == SWAP_FWD_IPV4_TYPE:
            routing = parseIPV4Info(self.routingInfo)
        elif self.routingType == SWAP_FWD_HOST_TYPE:
            routing = parseMMTPHostInfo(self.routingInfo)
        else:
            routing = None
        server = mixminion.ServerInfo.displayServerByRouting(routing)
        return """Reply block hash: %s
Expires at: %s GMT
First server is: %s""" % (digest, expiry, server)
Exemple #14
0
    def format(self):
        """DOCDOC"""
        import mixminion.ServerInfo
        digest = self.getHexDigest()
        expiry = formatTime(self.timestamp)
        if self.routingType == SWAP_FWD_IPV4_TYPE:
            routing = parseIPV4Info(self.routingInfo)
        elif self.routingType == SWAP_FWD_HOST_TYPE:
            routing = parseMMTPHostInfo(self.routingInfo)
        else:
            routing = None
        server = mixminion.ServerInfo.displayServerByRouting(routing)
        return """Reply block hash: %s
Expires at: %s GMT
First server is: %s""" % (digest, expiry, server)
Exemple #15
0
 def markAsPublished(self):
     """Mark this keyset as published."""
     contents = "%s\n"%formatTime(time.time(),1)
     writeFile(self.publishedFile, contents, mode=0600)
     self.published = 1
Exemple #16
0
def generateServerDescriptorAndKeys(config,
                                    identityKey,
                                    keydir,
                                    keyname,
                                    hashdir,
                                    validAt=None,
                                    now=None,
                                    useServerKeys=0,
                                    validUntil=None):
    """Generate and sign a new server descriptor, and generate all the keys to
       go with it.

          config -- Our ServerConfig object.
          identityKey -- This server's private identity key
          keydir -- The root directory for storing key sets.
          keyname -- The name of this new key set within keydir
          hashdir -- The root directory for storing hash logs.
          validAt -- The starting time (in seconds) for this key's lifetime.
          useServerKeys -- If true, try to read an existing keyset from
               (keydir,keyname,hashdir) rather than generating a fresh one.
          validUntil -- Time at which the generated descriptor should
               expire.
    """
    if useServerKeys:
        serverKeys = ServerKeyset(keydir, keyname, hashdir)
        serverKeys.load()
        packetKey = serverKeys.packetKey
    else:
        # First, we generate both of our short-term keys...
        packetKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES * 8)

        # ...and save them to disk, setting up our directory structure while
        # we're at it.
        serverKeys = ServerKeyset(keydir, keyname, hashdir)
        serverKeys.packetKey = packetKey
        serverKeys.save()

    # FFFF unused
    # allowIncoming = config['Incoming/MMTP'].get('Enabled', 0)

    # Now, we pull all the information we need from our configuration.
    nickname = config['Server']['Nickname']
    contact = config['Server']['Contact-Email']
    fingerprint = config['Server']['Contact-Fingerprint']
    comments = config['Server']['Comments']
    if not now:
        now = time.time()
    if not validAt:
        validAt = now

    insecurities = config.getInsecurities()
    if insecurities:
        secure = "no"
    else:
        secure = "yes"

    # Calculate descriptor and X509 certificate lifetimes.
    # (Round validAt to previous midnight.)
    validAt = mixminion.Common.previousMidnight(validAt + 30)
    if not validUntil:
        keyLifetime = config['Server']['PublicKeyLifetime'].getSeconds()
        validUntil = previousMidnight(validAt + keyLifetime + 30)

    mmtpProtocolsIn = mixminion.server.MMTPServer.MMTPServerConnection \
                      .PROTOCOL_VERSIONS[:]
    mmtpProtocolsOut = mixminion.server.MMTPServer.MMTPClientConnection \
                       .PROTOCOL_VERSIONS[:]
    mmtpProtocolsIn.sort()
    mmtpProtocolsOut.sort()
    mmtpProtocolsIn = ",".join(mmtpProtocolsIn)
    mmtpProtocolsOut = ",".join(mmtpProtocolsOut)

    #XXXX009 remove: hasn't been checked since 007 or used since 005.
    identityKeyID = formatBase64(
        mixminion.Crypto.sha1(
            mixminion.Crypto.pk_encode_public_key(identityKey)))

    fields = {
        # XXXX009 remove: hasn't been checked since 007.
        "IP":
        config['Incoming/MMTP'].get('IP', "0.0.0.0"),
        "Hostname":
        config['Incoming/MMTP'].get('Hostname', None),
        "Port":
        config['Incoming/MMTP'].get('Port', 0),
        "Nickname":
        nickname,
        "Identity":
        formatBase64(mixminion.Crypto.pk_encode_public_key(identityKey)),
        "Published":
        formatTime(now),
        "ValidAfter":
        formatDate(validAt),
        "ValidUntil":
        formatDate(validUntil),
        "PacketKey":
        formatBase64(mixminion.Crypto.pk_encode_public_key(packetKey)),
        "KeyID":
        identityKeyID,
        "MMTPProtocolsIn":
        mmtpProtocolsIn,
        "MMTPProtocolsOut":
        mmtpProtocolsOut,
        "PacketVersion":
        mixminion.Packet.PACKET_VERSION,
        "mm_version":
        mixminion.__version__,
        "Secure":
        secure,
        "Contact":
        contact,
    }

    # If we don't know our IP address, try to guess
    if fields['IP'] == '0.0.0.0':  #XXXX008 remove; not needed since 005.
        try:
            fields['IP'] = _guessLocalIP()
            LOG.warn("No IP configured; guessing %s", fields['IP'])
        except IPGuessError, e:
            LOG.error("Can't guess IP: %s", str(e))
            raise UIError("Can't guess IP: %s" % str(e))
Exemple #17
0
def generateServerDescriptorAndKeys(config, identityKey, keydir, keyname,
                                    hashdir, validAt=None, now=None,
                                    useServerKeys=0, validUntil=None):
    """Generate and sign a new server descriptor, and generate all the keys to
       go with it.

          config -- Our ServerConfig object.
          identityKey -- This server's private identity key
          keydir -- The root directory for storing key sets.
          keyname -- The name of this new key set within keydir
          hashdir -- The root directory for storing hash logs.
          validAt -- The starting time (in seconds) for this key's lifetime.
          useServerKeys -- If true, try to read an existing keyset from
               (keydir,keyname,hashdir) rather than generating a fresh one.
          validUntil -- Time at which the generated descriptor should
               expire.
    """
    if useServerKeys:
        serverKeys = ServerKeyset(keydir, keyname, hashdir)
        serverKeys.load()
        packetKey = serverKeys.packetKey
    else:
        # First, we generate both of our short-term keys...
        packetKey = mixminion.Crypto.pk_generate(PACKET_KEY_BYTES*8)

        # ...and save them to disk, setting up our directory structure while
        # we're at it.
        serverKeys = ServerKeyset(keydir, keyname, hashdir)
        serverKeys.packetKey = packetKey
        serverKeys.save()

    # FFFF unused
    # allowIncoming = config['Incoming/MMTP'].get('Enabled', 0)

    # Now, we pull all the information we need from our configuration.
    nickname = config['Server']['Nickname']
    contact = config['Server']['Contact-Email']
    fingerprint = config['Server']['Contact-Fingerprint']
    comments = config['Server']['Comments']
    if not now:
        now = time.time()
    if not validAt:
        validAt = now

    insecurities = config.getInsecurities()
    if insecurities:
        secure = "no"
    else:
        secure = "yes"

    # Calculate descriptor and X509 certificate lifetimes.
    # (Round validAt to previous midnight.)
    validAt = mixminion.Common.previousMidnight(validAt+30)
    if not validUntil:
        keyLifetime = config['Server']['PublicKeyLifetime'].getSeconds()
        validUntil = previousMidnight(validAt + keyLifetime + 30)

    mmtpProtocolsIn = mixminion.server.MMTPServer.MMTPServerConnection \
                      .PROTOCOL_VERSIONS[:]
    mmtpProtocolsOut = mixminion.server.MMTPServer.MMTPClientConnection \
                       .PROTOCOL_VERSIONS[:]
    mmtpProtocolsIn.sort()
    mmtpProtocolsOut.sort()
    mmtpProtocolsIn = ",".join(mmtpProtocolsIn)
    mmtpProtocolsOut = ",".join(mmtpProtocolsOut)


    #XXXX009 remove: hasn't been checked since 007 or used since 005.
    identityKeyID = formatBase64(
                      mixminion.Crypto.sha1(
                          mixminion.Crypto.pk_encode_public_key(identityKey)))

    fields = {
        # XXXX009 remove: hasn't been checked since 007.
        "IP": config['Incoming/MMTP'].get('IP', "0.0.0.0"),
        "Hostname": config['Incoming/MMTP'].get('Hostname', None),
        "Port": config['Incoming/MMTP'].get('Port', 0),
        "Nickname": nickname,
        "Identity":
           formatBase64(mixminion.Crypto.pk_encode_public_key(identityKey)),
        "Published": formatTime(now),
        "ValidAfter": formatDate(validAt),
        "ValidUntil": formatDate(validUntil),
        "PacketKey":
           formatBase64(mixminion.Crypto.pk_encode_public_key(packetKey)),
        "KeyID": identityKeyID,
        "MMTPProtocolsIn" : mmtpProtocolsIn,
        "MMTPProtocolsOut" : mmtpProtocolsOut,
        "PacketVersion" : mixminion.Packet.PACKET_VERSION,
        "mm_version" : mixminion.__version__,
        "Secure" : secure,
        "Contact" : contact,
        }

    # If we don't know our IP address, try to guess
    if fields['IP'] == '0.0.0.0': #XXXX008 remove; not needed since 005.
        try:
            fields['IP'] = _guessLocalIP()
            LOG.warn("No IP configured; guessing %s",fields['IP'])
        except IPGuessError, e:
            LOG.error("Can't guess IP: %s", str(e))
            raise UIError("Can't guess IP: %s" % str(e))
Exemple #18
0
 def markAsPublished(self):
     """Mark this keyset as published."""
     contents = "%s\n" % formatTime(time.time(), 1)
     writeFile(self.publishedFile, contents, mode=0600)
     self.published = 1
Exemple #19
0
    def generateDirectory(self,
                          startAt,
                          endAt,
                          extraTime,
                          identityKey,
                          publicationTime=None,
                          badServers=(),
                          excludeServers=()):
        """Generate and sign a new directory, to be effective from <startAt>
           through <endAt>.  It includes all servers that are valid at
           any time between <startAt> and <endAt>+<extraTime>.  The directory
           is signed with <identityKey>.

           Any servers whose nicknames appear in 'badServers' are marked as
           not recommended; any servers whose nicknames appear in
           'excludeServers' are left off the directory entirely.
        """
        try:
            self._lock()
            self.clean()
            if publicationTime is None:
                publicationTime = time.time()
            if previousMidnight(startAt) >= previousMidnight(endAt):
                raise MixError("Validity range does not contain a full day.")

            excludeServers = [nickname.lower() for nickname in excludeServers]

            # First, sort all servers by nickname.
            includedByNickname = {}
            for fn, s in self.servers.items():
                nickname = s.getNickname().lower()
                if nickname in excludeServers: continue
                includedByNickname.setdefault(nickname, []).append((s, fn))

            # Second, find all servers that are valid for part of the period,
            # and that aren't superseded for the whole period.
            timeRange = IntervalSet([(previousMidnight(startAt),
                                      endAt + extraTime)])

            for nickname, ss in includedByNickname.items():
                # We prefer the most-recently-published descriptor.  If two
                # are published at the same time, we prefer the one that
                # expires last.
                ss = [(s['Server']['Published'], s['Server']['Valid-Until'], s,
                       fn) for s, fn in ss]
                ss.sort()
                ss.reverse()
                uncovered = timeRange.copy()
                included = []
                for _, _, s, fn in ss:
                    valid = s.getIntervalSet()
                    if (uncovered * valid):
                        included.append((s, fn))
                        uncovered -= valid
                includedByNickname[nickname] = included

            # Now sort the remaining servers by nickname, then by valid-after.
            included = []
            for ss in includedByNickname.values():
                for s, fn in ss:
                    nickname = s.getNickname()
                    validAfter = s['Server']['Valid-After']
                    included.append((nickname, validAfter, fn))
            included.sort()

            # FFFF We should probably not do all of this in RAM, but
            # FFFF what the hey.  It will only matter if we have many, many
            # FFFF servers in the system.
            contents = []
            for _, _, fn in included:
                txt = readFile(os.path.join(self.serverDir, fn))
                contents.append(txt)

            goodServers = [n for n, _, _ in included if n not in badServers]
            g = {}
            for n in goodServers:
                g[n] = 1
            goodServers = g.keys()
            goodServers.sort()
            goodServers = ", ".join(goodServers)

            clientVersions = self.config['Directory']['ClientVersions']
            serverVersions = self.config['Directory']['ServerVersions']

            #FFFF Support for multiple signatures
            header = """\
            [Directory]
            Version: 0.2
            Published: %s
            Valid-After: %s
            Valid-Until: %s
            Recommended-Servers: %s
            [Signature]
            DirectoryIdentity: %s
            DirectoryDigest:
            DirectorySignature:
            [Recommended-Software]
            MixminionClient: %s
            MixminionServer: %s
            """ % (formatTime(publicationTime), formatDate(startAt),
                   formatDate(endAt), goodServers,
                   formatBase64(pk_encode_public_key(identityKey)),
                   ", ".join(clientVersions), ", ".join(serverVersions))

            directory = header + "".join(contents)
            directory = _getDirectoryDigestImpl(directory, identityKey)

            # Make sure that the directory checks out
            # FFFF remove this once we are _very_ confident.
            if 1:
                parsed = ServerDirectory(string=directory)
                includedDigests = {}
                for _, _, fn in included:
                    includedDigests[self.servers[fn]['Server']['Digest']] = 1
                foundDigests = {}
                for s in parsed.getAllServers():
                    foundDigests[s['Server']['Digest']] = 1
                assert foundDigests == includedDigests

            writeFile(os.path.join(self.baseDir, "directory"),
                      directory,
                      mode=0644)

            f, _ = openUnique(
                os.path.join(self.dirArchiveDir, "dir-" + formatFnameTime()))
            f.write(directory)
            f.close()
        finally:
            self._unlock()
Exemple #20
0
    def generateDirectory(self,
                          startAt, endAt, extraTime,
                          identityKey,
                          publicationTime=None,
                          badServers=(),
                          excludeServers=()):
        """Generate and sign a new directory, to be effective from <startAt>
           through <endAt>.  It includes all servers that are valid at
           any time between <startAt> and <endAt>+<extraTime>.  The directory
           is signed with <identityKey>.

           Any servers whose nicknames appear in 'badServers' are marked as
           not recommended; any servers whose nicknames appear in
           'excludeServers' are left off the directory entirely.
        """
        try:
            self._lock()
            self.clean()
            if publicationTime is None:
                publicationTime = time.time()
            if previousMidnight(startAt) >= previousMidnight(endAt):
                raise MixError("Validity range does not contain a full day.")

            excludeServers = [ nickname.lower() for nickname in excludeServers]

            # First, sort all servers by nickname.
            includedByNickname =  {}
            for fn, s in self.servers.items():
                nickname = s.getNickname().lower()
                if nickname in excludeServers: continue
                includedByNickname.setdefault(nickname, []).append((s, fn))

            # Second, find all servers that are valid for part of the period,
            # and that aren't superseded for the whole period.
            timeRange = IntervalSet([(previousMidnight(startAt),
                                      endAt+extraTime)])

            for nickname, ss in includedByNickname.items():
                # We prefer the most-recently-published descriptor.  If two
                # are published at the same time, we prefer the one that
                # expires last.
                ss = [ (s['Server']['Published'],
                        s['Server']['Valid-Until'],
                        s, fn) for s,fn in ss]
                ss.sort()
                ss.reverse()
                uncovered = timeRange.copy()
                included = []
                for _, _, s, fn in ss:
                    valid = s.getIntervalSet()
                    if (uncovered * valid):
                        included.append((s, fn))
                        uncovered -= valid
                includedByNickname[nickname] = included

            # Now sort the remaining servers by nickname, then by valid-after.
            included = []
            for ss in includedByNickname.values():
                for s,fn in ss:
                    nickname = s.getNickname()
                    validAfter = s['Server']['Valid-After']
                    included.append((nickname, validAfter, fn))
            included.sort()

            # FFFF We should probably not do all of this in RAM, but
            # FFFF what the hey.  It will only matter if we have many, many
            # FFFF servers in the system.
            contents = [ ]
            for _, _, fn in included:
                txt = readFile(os.path.join(self.serverDir, fn))
                contents.append(txt)

            goodServers = [n for n,_,_ in included if n not in badServers]
            g = {}
            for n in goodServers: g[n]=1
            goodServers = g.keys()
            goodServers.sort()
            goodServers = ", ".join(goodServers)

            clientVersions = self.config['Directory']['ClientVersions']
            serverVersions = self.config['Directory']['ServerVersions']

            #FFFF Support for multiple signatures
            header = """\
            [Directory]
            Version: 0.2
            Published: %s
            Valid-After: %s
            Valid-Until: %s
            Recommended-Servers: %s
            [Signature]
            DirectoryIdentity: %s
            DirectoryDigest:
            DirectorySignature:
            [Recommended-Software]
            MixminionClient: %s
            MixminionServer: %s
            """ % (formatTime(publicationTime),
                   formatDate(startAt),
                   formatDate(endAt),
                   goodServers,
                   formatBase64(pk_encode_public_key(identityKey)),
                   ", ".join(clientVersions),
                   ", ".join(serverVersions))

            directory = header+"".join(contents)
            directory = _getDirectoryDigestImpl(directory, identityKey)

            # Make sure that the directory checks out
            # FFFF remove this once we are _very_ confident.
            if 1:
                parsed = ServerDirectory(string=directory)
                includedDigests = {}
                for _, _, fn in included:
                    includedDigests[self.servers[fn]['Server']['Digest']] = 1
                foundDigests = {}
                for s in parsed.getAllServers():
                    foundDigests[s['Server']['Digest']] = 1
                assert foundDigests == includedDigests

            writeFile(os.path.join(self.baseDir, "directory"),
                      directory,
                      mode=0644)

            f, _ = openUnique(os.path.join(self.dirArchiveDir,
                                            "dir-"+formatFnameTime()))
            f.write(directory)
            f.close()
        finally:
            self._unlock()