Exemple #1
0
    def testSignOnion(self, tor_manager):
        address = tor_manager.addOnion()

        # Sign
        sign = CryptRsa.sign("hello", tor_manager.getPrivatekey(address))
        assert len(sign) == 128

        # Verify
        publickey = CryptRsa.privatekeyToPublickey(tor_manager.getPrivatekey(address))
        assert len(publickey) == 140
        assert CryptRsa.verify("hello", publickey, sign)
        assert not CryptRsa.verify("not hello", publickey, sign)

        # Pub to address
        assert CryptRsa.publickeyToOnion(publickey) == address

        # Delete
        tor_manager.delOnion(address)
Exemple #2
0
    def testSignOnion(self, tor_manager):
        address = tor_manager.addOnion()

        # Sign
        sign = CryptRsa.sign("hello", tor_manager.getPrivatekey(address))
        assert len(sign) == 128

        # Verify
        publickey = CryptRsa.privatekeyToPublickey(tor_manager.getPrivatekey(address))
        assert len(publickey) == 140
        assert CryptRsa.verify("hello", publickey, sign)
        assert not CryptRsa.verify("not hello", publickey, sign)

        # Pub to address
        assert CryptRsa.publickeyToOnion(publickey) == address

        # Delete
        tor_manager.delOnion(address)
    def checkOnionSigns(self, onions, onion_signs, onion_sign_this):
        if not onion_signs or len(onion_signs) != len(set(onions)):
            return False

        if time.time() - float(onion_sign_this) > 3 * 60:
            return False  # Signed out of allowed 3 minutes

        onions_signed = []
        # Check onion signs
        for onion_publickey, onion_sign in onion_signs.items():
            if CryptRsa.verify(onion_sign_this.encode(), onion_publickey, onion_sign):
                onions_signed.append(CryptRsa.publickeyToOnion(onion_publickey))
            else:
                break

        # Check if the same onion addresses signed as the announced onces
        if sorted(onions_signed) == sorted(set(onions)):
            return True
        else:
            return False
    def announceTracker(self, tracker_protocol, tracker_address, fileserver_port=0, add_types=[], my_peer_id="", mode="start"):
        if tracker_protocol != "zero":
            return super(SitePlugin, self).announceTracker(
                tracker_protocol, tracker_address, fileserver_port, add_types, my_peer_id, mode
            )

        s = time.time()

        need_types = ["ip4"]
        if self.connection_server and self.connection_server.tor_manager and self.connection_server.tor_manager.enabled:
            need_types.append("onion")

        if mode == "start" or mode == "more":  # Single: Announce only this site
            sites = [self]
            full_announce = False
        else:  # Multi: Announce all currently serving site
            full_announce = True
            if time.time() - time_full_announced.get(tracker_address, 0) < 60 * 5:  # No reannounce all sites within 5 minute
                return True
            time_full_announced[tracker_address] = time.time()
            from Site import SiteManager
            sites = [site for site in SiteManager.site_manager.sites.values() if site.settings["serving"]]

        # Create request
        request = {
            "hashes": [], "onions": [], "port": fileserver_port, "need_types": need_types, "need_num": 20, "add": add_types
        }
        for site in sites:
            if "onion" in add_types:
                onion = self.connection_server.tor_manager.getOnion(site.address)
                request["onions"].append(onion)
            request["hashes"].append(hashlib.sha256(site.address).digest())

        # Tracker can remove sites that we don't announce
        if full_announce:
            request["delete"] = True

        # Sent request to tracker
        tracker = connection_pool.get(tracker_address)  # Re-use tracker connection if possible
        if not tracker:
            tracker_ip, tracker_port = tracker_address.split(":")
            tracker = Peer(tracker_ip, tracker_port, connection_server=self.connection_server)
            connection_pool[tracker_address] = tracker
        res = tracker.request("announce", request)

        if not res or "peers" not in res:
            self.log.debug("Announce to %s failed: %s" % (tracker_address, res))
            if full_announce:
                time_full_announced[tracker_address] = 0
            return False

        # Add peers from response to site
        site_index = 0
        for site_res in res["peers"]:
            site = sites[site_index]
            processPeerRes(site, site_res)
            site_index += 1

        # Check if we need to sign prove the onion addresses
        if "onion_sign_this" in res:
            self.log.debug("Signing %s for %s to add %s onions" % (res["onion_sign_this"], tracker_address, len(sites)))
            request["onion_signs"] = {}
            request["onion_sign_this"] = res["onion_sign_this"]
            request["need_num"] = 0
            for site in sites:
                onion = self.connection_server.tor_manager.getOnion(site.address)
                sign = CryptRsa.sign(res["onion_sign_this"], self.connection_server.tor_manager.getPrivatekey(onion))
                request["onion_signs"][self.connection_server.tor_manager.getPublickey(onion)] = sign
            res = tracker.request("announce", request)
            if not res or "onion_sign_this" in res:
                self.log.debug("Announce onion address to %s failed: %s" % (tracker_address, res))
                if full_announce:
                    time_full_announced[tracker_address] = 0
                return False

        if full_announce:
            tracker.remove()  # Close connection, we don't need it in next 5 minute

        return time.time() - s
Exemple #5
0
    def announceTrackerZero(self, tracker_address, mode="start", num_want=10):
        global time_full_announced
        s = time.time()

        need_types = ["ip4"]
        if self.site.connection_server.tor_manager.enabled:
            need_types.append("onion")

        if mode == "start" or mode == "more":  # Single: Announce only this site
            sites = [self.site]
            full_announce = False
        else:  # Multi: Announce all currently serving site
            full_announce = True
            if time.time() - time_full_announced.get(
                    tracker_address,
                    0) < 60 * 15:  # No reannounce all sites within short time
                return None
            time_full_announced[tracker_address] = time.time()
            from Site import SiteManager
            sites = [
                site for site in SiteManager.site_manager.sites.values()
                if site.settings["serving"]
            ]

        # Create request
        add_types = self.getOpenedServiceTypes()
        request = {
            "hashes": [],
            "onions": [],
            "port": self.fileserver_port,
            "need_types": need_types,
            "need_num": 20,
            "add": add_types
        }
        for site in sites:
            if "onion" in add_types:
                onion = self.site.connection_server.tor_manager.getOnion(
                    site.address)
                request["onions"].append(onion)
            request["hashes"].append(site.address_hash)

        # Tracker can remove sites that we don't announce
        if full_announce:
            request["delete"] = True

        # Sent request to tracker
        tracker_peer = connection_pool.get(
            tracker_address)  # Re-use tracker connection if possible
        if not tracker_peer:
            tracker_ip, tracker_port = tracker_address.split(":")
            tracker_peer = Peer(tracker_ip,
                                tracker_port,
                                connection_server=self.site.connection_server)
            tracker_peer.is_tracker_connection = True
            connection_pool[tracker_address] = tracker_peer

        res = tracker_peer.request("announce", request)

        if not res or "peers" not in res:
            if full_announce:
                time_full_announced[tracker_address] = 0
            raise AnnounceError("Invalid response: %s" % res)

        # Add peers from response to site
        site_index = 0
        peers_added = 0
        for site_res in res["peers"]:
            site = sites[site_index]
            peers_added += processPeerRes(tracker_address, site, site_res)
            site_index += 1

        # Check if we need to sign prove the onion addresses
        if "onion_sign_this" in res:
            self.site.log.debug(
                "Signing %s for %s to add %s onions" %
                (res["onion_sign_this"], tracker_address, len(sites)))
            request["onion_signs"] = {}
            request["onion_sign_this"] = res["onion_sign_this"]
            request["need_num"] = 0
            for site in sites:
                onion = self.site.connection_server.tor_manager.getOnion(
                    site.address)
                publickey = self.site.connection_server.tor_manager.getPublickey(
                    onion)
                if publickey not in request["onion_signs"]:
                    sign = CryptRsa.sign(
                        res["onion_sign_this"],
                        self.site.connection_server.tor_manager.getPrivatekey(
                            onion))
                    request["onion_signs"][publickey] = sign
            res = tracker_peer.request("announce", request)
            if not res or "onion_sign_this" in res:
                if full_announce:
                    time_full_announced[tracker_address] = 0
                raise AnnounceError("Announce onion address to failed: %s" %
                                    res)

        if full_announce:
            tracker_peer.remove(
            )  # Close connection, we don't need it in next 5 minute

        self.site.log.debug(
            "Tracker announce result: zero://%s (sites: %s, new peers: %s) in %.3fs"
            % (tracker_address, site_index, peers_added, time.time() - s))

        return True
Exemple #6
0
    def testAddOnion(self, file_server, site, bootstrapper_db, tor_manager):
        onion1 = tor_manager.addOnion()
        onion2 = tor_manager.addOnion()
        peer = Peer("127.0.0.1", 1544, connection_server=file_server)
        hash1 = hashlib.sha256("site1").digest()
        hash2 = hashlib.sha256("site2").digest()
        hash3 = hashlib.sha256("site3").digest()

        bootstrapper_db.peerAnnounce(ip4="1.2.3.4",
                                     port=1234,
                                     hashes=[hash1, hash2, hash3])
        res = peer.request(
            "announce", {
                "onions": [onion1, onion1, onion2],
                "hashes": [hash1, hash2, hash3],
                "port": 15441,
                "need_types": ["ip4", "onion"],
                "need_num": 10,
                "add": ["onion"]
            })
        assert len(res["peers"][0]["ip4"]) == 1

        # Onion address not added yet
        site_peers = bootstrapper_db.peerList(ip4="1.2.3.4",
                                              port=1234,
                                              hash=hash1)
        assert len(site_peers["onion"]) == 0
        assert "onion_sign_this" in res

        # Sign the nonces
        sign1 = CryptRsa.sign(res["onion_sign_this"],
                              tor_manager.getPrivatekey(onion1))
        sign2 = CryptRsa.sign(res["onion_sign_this"],
                              tor_manager.getPrivatekey(onion2))

        # Bad sign (different address)
        res = peer.request(
            "announce", {
                "onions": [onion1],
                "onion_sign_this": res["onion_sign_this"],
                "onion_signs": {
                    tor_manager.getPublickey(onion2): sign2
                },
                "hashes": [hash1],
                "port": 15441,
                "need_types": ["ip4", "onion"],
                "need_num": 10,
                "add": ["onion"]
            })
        assert "onion_sign_this" in res
        site_peers1 = bootstrapper_db.peerList(ip4="1.2.3.4",
                                               port=1234,
                                               hash=hash1)
        assert len(site_peers1["onion"]) == 0  # Not added

        # Bad sign (missing one)
        res = peer.request(
            "announce", {
                "onions": [onion1, onion1, onion2],
                "onion_sign_this": res["onion_sign_this"],
                "onion_signs": {
                    tor_manager.getPublickey(onion1): sign1
                },
                "hashes": [hash1, hash2, hash3],
                "port": 15441,
                "need_types": ["ip4", "onion"],
                "need_num": 10,
                "add": ["onion"]
            })
        assert "onion_sign_this" in res
        site_peers1 = bootstrapper_db.peerList(ip4="1.2.3.4",
                                               port=1234,
                                               hash=hash1)
        assert len(site_peers1["onion"]) == 0  # Not added

        # Good sign
        res = peer.request(
            "announce", {
                "onions": [onion1, onion1, onion2],
                "onion_sign_this": res["onion_sign_this"],
                "onion_signs": {
                    tor_manager.getPublickey(onion1): sign1,
                    tor_manager.getPublickey(onion2): sign2
                },
                "hashes": [hash1, hash2, hash3],
                "port": 15441,
                "need_types": ["ip4", "onion"],
                "need_num": 10,
                "add": ["onion"]
            })
        assert "onion_sign_this" not in res

        # Onion addresses added
        site_peers1 = bootstrapper_db.peerList(ip4="1.2.3.4",
                                               port=1234,
                                               hash=hash1)
        assert len(site_peers1["onion"]) == 1
        site_peers2 = bootstrapper_db.peerList(ip4="1.2.3.4",
                                               port=1234,
                                               hash=hash2)
        assert len(site_peers2["onion"]) == 1
        site_peers3 = bootstrapper_db.peerList(ip4="1.2.3.4",
                                               port=1234,
                                               hash=hash3)
        assert len(site_peers3["onion"]) == 1

        assert site_peers1["onion"][0] == site_peers2["onion"][0]
        assert site_peers2["onion"][0] != site_peers3["onion"][0]
        assert helper.unpackOnionAddress(
            site_peers1["onion"][0])[0] == onion1 + ".onion"
        assert helper.unpackOnionAddress(
            site_peers2["onion"][0])[0] == onion1 + ".onion"
        assert helper.unpackOnionAddress(
            site_peers3["onion"][0])[0] == onion2 + ".onion"

        tor_manager.delOnion(onion1)
        tor_manager.delOnion(onion2)
Exemple #7
0
 def getPublickey(self, address):
     return CryptRsa.privatekeyToPublickey(self.privatekeys[address])
Exemple #8
0
 def getPublickey(self, address):
     return CryptRsa.privatekeyToPublickey(self.privatekeys[address])
    def actionAnnounce(self, params):
        time_started = time.time()
        s = time.time()
        hashes = params["hashes"]

        if "onion_signs" in params and len(params["onion_signs"]) == len(set(params["onions"])):
            # Check if all sign is correct
            if time.time() - float(params["onion_sign_this"]) < 3*60:  # Peer has 3 minute to sign the message
                onions_signed = []
                # Check onion signs
                for onion_publickey, onion_sign in params["onion_signs"].items():
                    if CryptRsa.verify(params["onion_sign_this"], onion_publickey, onion_sign):
                        onions_signed.append(CryptRsa.publickeyToOnion(onion_publickey))
                    else:
                        break
                # Check if the same onion addresses signed as the announced onces
                if sorted(onions_signed) == sorted(set(params["onions"])):
                    all_onions_signed = True
                else:
                    all_onions_signed = False
            else:
                # Onion sign this out of 3 minute
                all_onions_signed = False
        else:
            # Incorrect signs number
            all_onions_signed = False

        time_onion_check = time.time() - s

        if "ip4" in params["add"] and self.connection.ip != "127.0.0.1" and not self.connection.ip.endswith(".onion"):
            ip4 = self.connection.ip
        else:
            ip4 = None

        s = time.time()
        # Separatley add onions to sites or at once if no onions present
        i = 0
        onion_to_hash = {}
        for onion in params.get("onions", []):
            if onion not in onion_to_hash:
                onion_to_hash[onion] = []
            onion_to_hash[onion].append(hashes[i])
            i += 1

        hashes_changed = 0
        db.execute("BEGIN")
        for onion, onion_hashes in onion_to_hash.iteritems():
            hashes_changed += db.peerAnnounce(
                onion=onion,
                port=params["port"],
                hashes=onion_hashes,
                onion_signed=all_onions_signed
            )
        db.execute("END")
        time_db_onion = time.time() - s

        s = time.time()
        # Announce all sites if ip4 defined
        if ip4:
            hashes_changed += db.peerAnnounce(
                ip4=ip4,
                port=params["port"],
                hashes=hashes,
                delete_missing_hashes=params.get("delete")
            )
        time_db_ip4 = time.time() - s

        s = time.time()
        # Query sites
        back = {}
        peers = []
        if params.get("onions") and not all_onions_signed and hashes_changed:
            back["onion_sign_this"] = "%.0f" % time.time()  # Send back nonce for signing

        if len(hashes) > 500:
            limit = 5
            order = False
        else:
            limit = 30
            order = True
        for hash in hashes:
            if time.time() - time_started > 1:  # 1 sec limit on request
                self.connection.log("Announce time limit exceeded after %s/%s sites" % (len(peers), len(hashes)))
                break

            hash_peers = db.peerList(
                hash,
                ip4=self.connection.ip, onions=onion_to_hash.keys(), port=params["port"],
                limit=min(limit, params["need_num"]), need_types=params["need_types"], order=order
            )
            peers.append(hash_peers)
        time_peerlist = time.time() - s


        back["peers"] = peers
        self.connection.log(
            "Announce %s sites (onions: %s, onion_check: %.3fs, db_onion: %.3fs, db_ip4: %.3fs, peerlist: %.3fs)" %
            (len(hashes), len(onion_to_hash), time_onion_check, time_db_onion, time_db_ip4, time_peerlist)
        )
        self.response(back)
Exemple #10
0
    def testAddOnion(self, file_server, site, bootstrapper_db, tor_manager):
        onion1 = tor_manager.addOnion()
        onion2 = tor_manager.addOnion()
        peer = Peer("127.0.0.1", 1544, connection_server=file_server)
        hash1 = hashlib.sha256("site1").digest()
        hash2 = hashlib.sha256("site2").digest()

        bootstrapper_db.peerAnnounce(ip4="1.2.3.4", port=1234, hashes=[hash1, hash2])
        res = peer.request("announce", {
            "onions": [onion1, onion2],
            "hashes": [hash1, hash2], "port": 15441, "need_types": ["ip4", "onion"], "need_num": 10, "add": ["onion"]
        })
        assert len(res["peers"][0]["ip4"]) == 1
        assert "onion_sign_this" in res

        # Onion address not added yet
        site_peers = bootstrapper_db.peerList(ip4="1.2.3.4", port=1234, hash=hash1)
        assert len(site_peers["onion"]) == 0
        assert "onion_sign_this" in res

        # Sign the nonces
        sign1 = CryptRsa.sign(res["onion_sign_this"], tor_manager.getPrivatekey(onion1))
        sign2 = CryptRsa.sign(res["onion_sign_this"], tor_manager.getPrivatekey(onion2))

        # Bad sign (different address)
        res = peer.request("announce", {
            "onions": [onion1], "onion_sign_this": res["onion_sign_this"],
            "onion_signs": {tor_manager.getPublickey(onion2): sign2},
            "hashes": [hash1], "port": 15441, "need_types": ["ip4", "onion"], "need_num": 10, "add": ["onion"]
        })
        assert "onion_sign_this" in res
        site_peers1 = bootstrapper_db.peerList(ip4="1.2.3.4", port=1234, hash=hash1)
        assert len(site_peers1["onion"]) == 0  # Not added

        # Bad sign (missing one)
        res = peer.request("announce", {
            "onions": [onion1, onion2], "onion_sign_this": res["onion_sign_this"],
            "onion_signs": {tor_manager.getPublickey(onion1): sign1},
            "hashes": [hash1, hash2], "port": 15441, "need_types": ["ip4", "onion"], "need_num": 10, "add": ["onion"]
        })
        assert "onion_sign_this" in res
        site_peers1 = bootstrapper_db.peerList(ip4="1.2.3.4", port=1234, hash=hash1)
        assert len(site_peers1["onion"]) == 0  # Not added

        # Good sign
        res = peer.request("announce", {
            "onions": [onion1, onion2], "onion_sign_this": res["onion_sign_this"],
            "onion_signs": {tor_manager.getPublickey(onion1): sign1, tor_manager.getPublickey(onion2): sign2},
            "hashes": [hash1, hash2], "port": 15441, "need_types": ["ip4", "onion"], "need_num": 10, "add": ["onion"]
        })
        assert "onion_sign_this" not in res

        # Onion addresses added
        site_peers1 = bootstrapper_db.peerList(ip4="1.2.3.4", port=1234, hash=hash1)
        assert len(site_peers1["onion"]) == 1
        site_peers2 = bootstrapper_db.peerList(ip4="1.2.3.4", port=1234, hash=hash2)
        assert len(site_peers2["onion"]) == 1

        assert site_peers1["onion"][0] != site_peers2["onion"][0]
        assert helper.unpackOnionAddress(site_peers1["onion"][0])[0] == onion1+".onion"
        assert helper.unpackOnionAddress(site_peers2["onion"][0])[0] == onion2+".onion"

        tor_manager.delOnion(onion1)
        tor_manager.delOnion(onion2)
Exemple #11
0
    def actionAnnounce(self, params):
        hashes = params["hashes"]

        if "onion_signs" in params and len(params["onion_signs"]) == len(
                set(params["onions"])):
            # Check if all sign is correct
            if time.time() - float(
                    params["onion_sign_this"]
            ) < 3 * 60:  # Peer has 3 minute to sign the message
                onions_signed = []
                # Check onion signs
                for onion_publickey, onion_sign in params["onion_signs"].items(
                ):
                    if CryptRsa.verify(params["onion_sign_this"],
                                       onion_publickey, onion_sign):
                        onions_signed.append(
                            CryptRsa.publickeyToOnion(onion_publickey))
                    else:
                        break
                # Check if the same onion addresses signed as the announced onces
                if sorted(onions_signed) == sorted(set(params["onions"])):
                    all_onions_signed = True
                else:
                    all_onions_signed = False
            else:
                # Onion sign this out of 3 minute
                all_onions_signed = False
        else:
            # Incorrect signs number
            all_onions_signed = False

        if "ip4" in params[
                "add"] and self.connection.ip != "127.0.0.1" and not self.connection.ip.endswith(
                    ".onion"):
            ip4 = self.connection.ip
        else:
            ip4 = None

        # Separatley add onions to sites or at once if no onions present
        hashes_changed = 0
        i = 0
        for onion in params.get("onions", []):
            hashes_changed += db.peerAnnounce(onion=onion,
                                              port=params["port"],
                                              hashes=[hashes[i]],
                                              onion_signed=all_onions_signed)
            i += 1

        # Announce all sites if ip4 defined
        if ip4:
            hashes_changed += db.peerAnnounce(
                ip4=ip4,
                port=params["port"],
                hashes=hashes,
                delete_missing_hashes=params.get("delete"))

        # Query sites
        back = {}
        peers = []
        if params.get("onions") and not all_onions_signed and hashes_changed:
            back["onion_sign_this"] = "%.0f" % time.time(
            )  # Send back nonce for signing

        for hash in hashes:
            hash_peers = db.peerList(hash,
                                     ip4=self.connection.ip,
                                     onions=params.get("onions"),
                                     port=params["port"],
                                     limit=min(30, params["need_num"]),
                                     need_types=params["need_types"])
            peers.append(hash_peers)

        back["peers"] = peers
        self.response(back)
Exemple #12
0
    def actionAnnounce(self, params):
        hashes = params["hashes"]

        if "onion_signs" in params and len(params["onion_signs"]) == len(hashes):
            # Check if all sign is correct
            if time.time() - float(params["onion_sign_this"]) < 3*60:  # Peer has 3 minute to sign the message
                onions_signed = []
                # Check onion signs
                for onion_publickey, onion_sign in params["onion_signs"].items():
                    if CryptRsa.verify(params["onion_sign_this"], onion_publickey, onion_sign):
                        onions_signed.append(CryptRsa.publickeyToOnion(onion_publickey))
                    else:
                        break
                # Check if the same onion addresses signed as the announced onces
                if sorted(onions_signed) == sorted(params["onions"]):
                    all_onions_signed = True
                else:
                    all_onions_signed = False
            else:
                # Onion sign this out of 3 minute
                all_onions_signed = False
        else:
            # Incorrect signs number
            all_onions_signed = False

        if "ip4" in params["add"] and self.connection.ip != "127.0.0.1" and not self.connection.ip.endswith(".onion"):
            ip4 = self.connection.ip
        else:
            ip4 = None

        # Separatley add onions to sites or at once if no onions present
        hashes_changed = 0
        i = 0
        for onion in params.get("onions", []):
            hashes_changed += db.peerAnnounce(
                onion=onion,
                port=params["port"],
                hashes=[hashes[i]],
                onion_signed=all_onions_signed
            )
            i += 1
        # Announce all sites if ip4 defined
        if ip4:
            hashes_changed += db.peerAnnounce(
                ip4=ip4,
                port=params["port"],
                hashes=hashes,
                delete_missing_hashes=params.get("delete")
            )

        # Query sites
        back = {}
        peers = []
        if params.get("onions") and not all_onions_signed and hashes_changed:
            back["onion_sign_this"] = "%.0f" % time.time()  # Send back nonce for signing

        for hash in hashes:
            hash_peers = db.peerList(
                hash,
                ip4=self.connection.ip, onions=params.get("onions"), port=params["port"],
                limit=min(30, params["need_num"]), need_types=params["need_types"]
            )
            peers.append(hash_peers)

        back["peers"] = peers
        self.response(back)
    def actionAnnounce(self, params):
        time_started = time.time()
        s = time.time()
        hashes = params["hashes"]

        if "onion_signs" in params and len(params["onion_signs"]) == len(
                set(params["onions"])):
            # Check if all sign is correct
            if time.time() - float(
                    params["onion_sign_this"]
            ) < 3 * 60:  # Peer has 3 minute to sign the message
                onions_signed = []
                # Check onion signs
                for onion_publickey, onion_sign in params["onion_signs"].items(
                ):
                    if CryptRsa.verify(params["onion_sign_this"],
                                       onion_publickey, onion_sign):
                        onions_signed.append(
                            CryptRsa.publickeyToOnion(onion_publickey))
                    else:
                        break
                # Check if the same onion addresses signed as the announced onces
                if sorted(onions_signed) == sorted(set(params["onions"])):
                    all_onions_signed = True
                else:
                    all_onions_signed = False
            else:
                # Onion sign this out of 3 minute
                all_onions_signed = False
        else:
            # Incorrect signs number
            all_onions_signed = False

        time_onion_check = time.time() - s

        if "ip4" in params[
                "add"] and self.connection.ip != "127.0.0.1" and not self.connection.ip.endswith(
                    ".onion"):
            ip4 = self.connection.ip
        else:
            ip4 = None

        s = time.time()
        # Separatley add onions to sites or at once if no onions present
        i = 0
        onion_to_hash = {}
        for onion in params.get("onions", []):
            if onion not in onion_to_hash:
                onion_to_hash[onion] = []
            onion_to_hash[onion].append(hashes[i])
            i += 1

        hashes_changed = 0
        db.execute("BEGIN")
        for onion, onion_hashes in onion_to_hash.iteritems():
            hashes_changed += db.peerAnnounce(onion=onion,
                                              port=params["port"],
                                              hashes=onion_hashes,
                                              onion_signed=all_onions_signed)
        db.execute("END")
        time_db_onion = time.time() - s

        s = time.time()
        # Announce all sites if ip4 defined
        if ip4:
            hashes_changed += db.peerAnnounce(
                ip4=ip4,
                port=params["port"],
                hashes=hashes,
                delete_missing_hashes=params.get("delete"))
        time_db_ip4 = time.time() - s

        s = time.time()
        # Query sites
        back = {}
        peers = []
        if params.get("onions") and not all_onions_signed and hashes_changed:
            back["onion_sign_this"] = "%.0f" % time.time(
            )  # Send back nonce for signing

        if len(hashes) > 500 or not hashes_changed:
            limit = 5
            order = False
        else:
            limit = 30
            order = True
        for hash in hashes:
            if time.time() - time_started > 1:  # 1 sec limit on request
                self.connection.log(
                    "Announce time limit exceeded after %s/%s sites" %
                    (len(peers), len(hashes)))
                break

            hash_peers = db.peerList(hash,
                                     ip4=self.connection.ip,
                                     onions=onion_to_hash.keys(),
                                     port=params["port"],
                                     limit=min(limit, params["need_num"]),
                                     need_types=params["need_types"],
                                     order=order)
            peers.append(hash_peers)
        time_peerlist = time.time() - s

        back["peers"] = peers
        self.connection.log(
            "Announce %s sites (onions: %s, onion_check: %.3fs, db_onion: %.3fs, db_ip4: %.3fs, peerlist: %.3fs, limit: %s)"
            % (len(hashes), len(onion_to_hash), time_onion_check,
               time_db_onion, time_db_ip4, time_peerlist, limit))
        self.response(back)
    def announceTrackerZero(self, tracker_address, mode="start", num_want=10):
        global time_full_announced
        s = time.time()

        need_types = ["ip4"]
        if self.site.connection_server.tor_manager.enabled:
            need_types.append("onion")

        if mode == "start" or mode == "more":  # Single: Announce only this site
            sites = [self.site]
            full_announce = False
        else:  # Multi: Announce all currently serving site
            full_announce = True
            if time.time() - time_full_announced.get(tracker_address, 0) < 60 * 5:  # No reannounce all sites within 5 minute
                return []
            time_full_announced[tracker_address] = time.time()
            from Site import SiteManager
            sites = [site for site in SiteManager.site_manager.sites.values() if site.settings["serving"]]

        # Create request
        add_types = self.getOpenedServiceTypes()
        request = {
            "hashes": [], "onions": [], "port": self.fileserver_port, "need_types": need_types, "need_num": 20, "add": add_types
        }
        for site in sites:
            if "onion" in add_types:
                onion = self.site.connection_server.tor_manager.getOnion(site.address)
                request["onions"].append(onion)
            request["hashes"].append(site.address_hash)

        # Tracker can remove sites that we don't announce
        if full_announce:
            request["delete"] = True

        # Sent request to tracker
        tracker = connection_pool.get(tracker_address)  # Re-use tracker connection if possible
        if not tracker:
            tracker_ip, tracker_port = tracker_address.split(":")
            tracker = Peer(tracker_ip, tracker_port, connection_server=self.site.connection_server)
            connection_pool[tracker_address] = tracker
        res = tracker.request("announce", request)

        if not res or "peers" not in res:
            if full_announce:
                time_full_announced[tracker_address] = 0
            raise AnnounceError("Invalid response: %s" % res)

        # Add peers from response to site
        site_index = 0
        peers_added = 0
        for site_res in res["peers"]:
            site = sites[site_index]
            peers_added += processPeerRes(tracker_address, site, site_res)
            site_index += 1

        # Check if we need to sign prove the onion addresses
        if "onion_sign_this" in res:
            self.site.log.debug("Signing %s for %s to add %s onions" % (res["onion_sign_this"], tracker_address, len(sites)))
            request["onion_signs"] = {}
            request["onion_sign_this"] = res["onion_sign_this"]
            request["need_num"] = 0
            for site in sites:
                onion = self.site.connection_server.tor_manager.getOnion(site.address)
                publickey = self.site.connection_server.tor_manager.getPublickey(onion)
                if publickey not in request["onion_signs"]:
                    sign = CryptRsa.sign(res["onion_sign_this"], self.site.connection_server.tor_manager.getPrivatekey(onion))
                    request["onion_signs"][publickey] = sign
            res = tracker.request("announce", request)
            if not res or "onion_sign_this" in res:
                if full_announce:
                    time_full_announced[tracker_address] = 0
                raise AnnounceError("Announce onion address to failed: %s" % res)

        if full_announce:
            tracker.remove()  # Close connection, we don't need it in next 5 minute

        self.site.log.debug(
            "Tracker announce result: zero://%s (sites: %s, new peers: %s) in %.3fs" %
            (tracker_address, site_index, peers_added, time.time() - s)
        )

        return None