HelloZeroNet / ZeroNet

ZeroNet - Decentralized websites using Bitcoin crypto and BitTorrent network
https://zeronet.io
Other
18.39k stars 2.27k forks source link

Spammers attack ZeroNet with hostile forks in order to defraud ZeroNet users. #2823

Closed ghost closed 1 year ago

ghost commented 1 year ago

@caryoscelus is still spamming this repository with her ostensibly "zeronet conservancy" fork, which is nothing more than a scam in which she simply changed the donation addresses without making any significant changes.

You should avoid using the fork promoted by @caryoscelus; she has also spammed the ZeroNet Wikipedia page with links to her fork.

While the name implies "preserving ZeroNet," the entire fork is about defrauding ZeroNet users and "rewriting ZeroNet" from Python to a creepy language called Rust, which was created in 2010. She formed a phoney organization called "Riza committee" with some seriously deranged individuals with the goal of completely destroying ZeroNet.

Except for the word "conservation," "ZeroNet conservancy" refers to everything. It's a giant scam.

You're mistaken if you think you can hide behind @imachug.

Imachug is a retard who attacked ZeroNet users and faked a license change, literally manipulating legal voting to ensure that ZeroNet will have a GPLv3 license rather than a GPLv3+, then @imachug pretended he is standing with RMS (Richard Stallman) and even became the creator of the "Stand with RMS" campaign on GitHub, which has since been archived.

If, as he claims, @imachug has changed over the years, he should speak out against your hostile fork @caryoscelus. Even if he doesn't, I will, so prepare yourself.

I will not fail ZeroNet; I did not fail when the license was changed, and I will not fail now.

paboum commented 1 year ago

Which fork do you recommend then? Where is the project page, the list of commits that introduce value, a community of happy users that will confirm the quality of the project?

caryoscelus commented 1 year ago

@paboum don't pay attention to the troll ;) they've been spreading FUD around zeronet-conservancy for a while now . you can easily read its redme and compare commits to any other fork and see their post is just a badly put together collection of blatant lies

ghost commented 1 year ago

@caryoscelus You con artist, prove me wrong.

Show me what significant changes you made to ZeroNet other than replacing the donation addresses.

I've seen several forks that are far superior, and the developers of those forks have been working on ZeroNet for many years. Some of them aren't even on GitHub.

You even claimed to be the first to add Tor v3 support, which is completely false; the forks I saw in 2020 already had Tor v3, even before zeronet-enhanced! You're nothing more than a liar and a dirty con artist!

@paboum Until I figure out how to contact the other developer(s), you can use zeronet-enhanced.

Ps.: I'm sure these two have planned to come here and troll on the same day.

caryoscelus commented 1 year ago

Show me what significant changes you made to ZeroNet other than replacing the donation addresses.

read changelog

You even claimed to be the first to add Tor v3 support

you're either delusional and obsessed with my fork for some reason or a blatant propagandist and liar. anyway, thanks for promoting my fork! :D to be a little formal here: please provide a link where i make such a claim

caryoscelus commented 1 year ago

the entire fork is about defrauding ZeroNet users and "rewriting ZeroNet" from Python to a creepy language called Rust

since there are supposedly other users reading this fantasy writer, let me clear that there's and there never was a plan to rewrite zeronet-conservancy into rust

ghost commented 1 year ago

This is something we discussed not long ago. Your memory appears to be very short, madam want to be developer.

@caryoscelus, pay close attention. Your WikiPedia edits, trolling and attacks on @canewsin, and even @wandrien https://www.reddit.com/r/zeronet/comments/s907uv/comment/htn7asn/ is is totally disgusting.

When confronted with reality, you're doing some knee-jerk smear here.

Several people who have been involved in ZeroNet for a long time have made it abundantly clear that you are not welcome in this community.

If you continue to troll, I will suspend your account and delete the fork that you are spamming everywhere while attacking others.

paboum commented 1 year ago

Since there is no "official" fork of ZeroNet, neither there is a fork with proven excellence track (commits), nor a fork recommended by vast community, I will simply continue not to use it. Tor and Freenet don't have these issues, I suggest everyone learns to explore at least one of these.

ghost commented 1 year ago

Official ZeroNet fork? It'd be amusing. More likely that the FBI arrest you than your ZeroNet fork becomes "official".

@caryoscelus is on her way to prison.

ghost commented 1 year ago

The biggest mistake you can make with ZeroNet forks is to advertise it in your own name; you will almost certainly be killed or imprisoned.

milahu commented 1 year ago

so many claims, so little evidence ...

at least its suspicious that zeronet-conservancy is

1453 commits ahead, 51 commits behind HelloZeroNet:master.

question as always: stupid or evil?

lets read some diff ...

spoiler: just stupid, not evil. stupid because lots of diff noise, which makes auditing harder

clone, diff ```sh cd $(mktemp -d) git clone https://github.com/HelloZeroNet/ZeroNet cd ZeroNet/ git remote add conservancy https://github.com/zeronet-conservancy/zeronet-conservancy git fetch conservancy master:conservancy git config core.whitespace cr-at-eol # hide diff noise from dos line endings git diff py3 conservancy | wc -l # 11056 ## too much to read git diff py3 conservancy --diff-filter="AM" | wc -l # 3790 ## better git diff py3 conservancy --diff-filter="AM" --ignore-cr-at-eol --ignore-space-at-eol --ignore-space-change --ignore-blank-lines -- . :^.github/ :^.dockerignore :^.gitignore :^.travis.yml ":^*.md" ":^*/languages/*" | wc -l # 2406 ## better ```
diff with my comments (2406 lines) ```diff diff --git a/Dockerfile b/Dockerfile index 7839cfa0..955ce752 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,17 @@ -FROM alpine:3.11 +FROM python:3.10.4-alpine -#Base settings -ENV HOME /root +RUN apk --update --no-cache --no-progress add gcc libffi-dev musl-dev make openssl g++ -COPY requirements.txt /root/requirements.txt +WORKDIR /app +COPY . . # wtf? -#Install ZeroNet -RUN apk --update --no-cache --no-progress add python3 python3-dev gcc libffi-dev musl-dev make tor openssl \ - && pip3 install -r /root/requirements.txt \ - && apk del python3-dev gcc libffi-dev musl-dev make \ - && echo "ControlPort 9051" >> /etc/tor/torrc \ - && echo "CookieAuthentication 1" >> /etc/tor/torrc +RUN python3 -m venv venv \ + && source venv/bin/activate \ + && python3 -m pip install -r requirements.txt -RUN python3 -V \ - && python3 -m pip list \ - && tor --version \ - && openssl version +CMD source venv/bin/activate \ + && python3 zeronet.py --ui_ip "*" --fileserver_port 26552 \ + --tor $TOR_ENABLED --tor_controller tor:$TOR_CONTROL_PORT \ + --tor_proxy tor:$TOR_SOCKS_PORT --tor_password $TOR_CONTROL_PASSWD main -#Add Zeronet source -COPY . /root -VOLUME /root/data - -#Control if Tor proxy is started -ENV ENABLE_TOR false - -WORKDIR /root - -#Set upstart command -CMD (! ${ENABLE_TOR} || tor&) && python3 zeronet.py --ui_ip 0.0.0.0 --fileserver_port 26552 - -#Expose ports EXPOSE 43110 26552 diff --git a/Dockerfile.integrated_tor b/Dockerfile.integrated_tor new file mode 100644 index 00000000..20c4425a --- /dev/null +++ b/Dockerfile.integrated_tor @@ -0,0 +1,18 @@ +FROM python:3.10.4-alpine + +RUN apk --update --no-cache --no-progress add tor gcc libffi-dev musl-dev make openssl g++ \ + && echo "ControlPort 9051" >> /etc/tor/torrc \ + && echo "CookieAuthentication 1" >> /etc/tor/torrc + +WORKDIR /app +COPY . . # wtf? + +RUN python3 -m venv venv \ + && source venv/bin/activate \ + && python3 -m pip install -r requirements.txt + +CMD (tor&) \ + && source venv/bin/activate \ + && python3 zeronet.py --ui_ip "*" --fileserver_port 26552 + +EXPOSE 43110 26552 diff --git a/Dockerfile.tor b/Dockerfile.tor new file mode 100644 index 00000000..b085747f --- /dev/null +++ b/Dockerfile.tor @@ -0,0 +1,10 @@ +FROM alpine:3.16.0 + +RUN apk --update --no-cache --no-progress add tor + +CMD hashed_control_password=$(tor --quiet --hash-password $TOR_CONTROL_PASSWD) \ + && tor --SocksPort 0.0.0.0:$TOR_SOCKS_PORT \ + --ControlPort 0.0.0.0:$TOR_CONTROL_PORT \ + --HashedControlPassword $hashed_control_password + +EXPOSE $TOR_SOCKS_PORT $TOR_CONTROL_PORT diff --git a/LICENSE b/LICENSE index 0d17b72d..9b5df6d9 100644 --- a/LICENSE +++ b/LICENSE @@ -11,7 +11,16 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -Additional Conditions : +All contributions after commit 2a4927b77b55dea9c40d14ce2cfa8a0bf0a90b80 +(and/or made after 2022-05-22 UTC 00:00:00) can additionally be used +under GNU General Public License as published by the Free Software +Foundation, version 3 or (at your option) any later version. + + +Additional Conditions (please note that these are likely to not be legally bounding, +but before this is cleared we're leaving this note) applying to contributions from +commit 1de748585846e935d9d88bc7f22c69c84f7b8252 till commit +2a4927b77b55dea9c40d14ce2cfa8a0bf0a90b80: # stupid noise in license Contributing to this repo This repo is governed by GPLv3, same is located at the root of the ZeroNet git repo, diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..5da1fc26 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,51 @@ +version: '3' +services: + tor: + tty: true + stdin_open: true + build: + context: . + dockerfile: Dockerfile.tor + networks: + - 0net-network + ports: + - "9050:9050" + - "9051:9051" + environment: &tor-environments + TOR_CONTROL_PASSWD: some_password + TOR_SOCKS_PORT: 9050 + TOR_CONTROL_PORT: 9051 + 0net: + tty: true + stdin_open: true + build: + context: . + networks: + - 0net-network + volumes: + - 0net-data:/app/data + ports: + - "26552:26552" + - "43110:43110" + depends_on: + - tor + environment: + TOR_ENABLED: enable + <<: *tor-environments + 0net-tor: + tty: true + stdin_open: true + build: + context: . + dockerfile: Dockerfile.integrated_tor + networks: + - 0net-network + volumes: + - 0net-data:/app/data + ports: + - "26552:26552" + - "43110:43110" +volumes: + 0net-data: +networks: + 0net-network: diff --git a/greet.py b/greet.py new file mode 100644 index 00000000..d918bd6c --- /dev/null +++ b/greet.py @@ -0,0 +1,35 @@ +def grad(n): + s = 0x08 + r = 0xff + g = 0x00 + b = 0x00 + for i in range(n): + if r >= s and b < s: + r -= s + g += s + elif g >= s and r < s: + g -= s + b += s + elif b >= s and g < s: + b -= s + r += s + return f'#{r:02x}{g:02x}{b:02x}' + +def fancy_greet(version): + from rich.console import Console + from rich.text import Text + zc_msg = f''' +||| . . _ _._|_ _. . . _ .__ _.. _. . __.. _ __. . +||| //\|/ |/_| | == / / \|/ |( /_||/ | | __||/ |/ \_| +||| \_/| |\_ |. \__\_/| |_) \_ | \/ |__|| |\__ _/ +||| +||| v{version} +''' + lns = zc_msg.split('\n') + console = Console() + for l in lns: + txt = Text(l) + txt.stylize('bold') + for i in range(len(l)): + txt.stylize(grad(i), i, i+1) + console.print(txt) # gay rainbow diff --git a/plugins/AnnounceZero/AnnounceZeroPlugin.py b/plugins/AnnounceZero/AnnounceZeroPlugin.py index 623cd4b5..d57190b2 100644 --- a/plugins/AnnounceZero/AnnounceZeroPlugin.py +++ b/plugins/AnnounceZero/AnnounceZeroPlugin.py @@ -124,7 +124,7 @@ class SiteAnnouncerPlugin(object): sign = CryptRsa.sign(res["onion_sign_this"].encode("utf8"), 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 not res: # why? if full_announce: time_full_announced[tracker_address] = 0 raise AnnounceError("Announce onion address to failed: %s" % res) diff --git a/plugins/ContentFilter/ContentFilterPlugin.py b/plugins/ContentFilter/ContentFilterPlugin.py index f2f84b49..6cec1bc3 100644 --- a/plugins/ContentFilter/ContentFilterPlugin.py +++ b/plugins/ContentFilter/ContentFilterPlugin.py @@ -38,7 +38,7 @@ class SiteManagerPlugin(object): block_details = None if block_details: - raise Exception("Site blocked: %s" % html.escape(block_details.get("reason", "unknown reason"))) + raise Exception(f'Site blocked: {html.escape(block_details.get("reason", "unknown reason"))}') # noise else: return super(SiteManagerPlugin, self).add(address, *args, **kwargs) @@ -56,13 +56,10 @@ class UiWebsocketPlugin(object): @flag.no_multiuser def actionMuteAdd(self, to, auth_address, cert_user_id, reason): - if "ADMIN" in self.getPermissions(to): - self.cbMuteAdd(to, auth_address, cert_user_id, reason) - else: self.cmd( - "confirm", - [_["Hide all content from %s?"] % html.escape(cert_user_id), _["Mute"]], - lambda res: self.cbMuteAdd(to, auth_address, cert_user_id, reason) + "prompt", + [_["Hide all content from %s?"] % html.escape(cert_user_id), reason, _["Mute"]], + lambda res: self.cbMuteAdd(to, auth_address, cert_user_id, res if res else reason) # indent noise ) @flag.no_multiuser @@ -207,15 +204,15 @@ class SiteStoragePlugin(object): # Check if any of the adresses are in the mute list for auth_address in matches: if filter_storage.isMuted(auth_address): - self.log.debug("Mute match: %s, ignoring %s" % (auth_address, inner_path)) + self.log.debug(f'Mute match: {auth_address}, ignoring {inner_path}') # noise return False return super(SiteStoragePlugin, self).updateDbFile(inner_path, file=file, cur=cur) def onUpdated(self, inner_path, file=None): - file_path = "%s/%s" % (self.site.address, inner_path) - if file_path in filter_storage.file_content["includes"]: - self.log.debug("Filter file updated: %s" % inner_path) + file_path = f'{self.site.address}/{inner_path}' + if file_path in filter_storage.file_content['includes']: + self.log.debug('Filter file updated: {inner_path}') # noise filter_storage.includeUpdateAll() return super(SiteStoragePlugin, self).onUpdated(inner_path, file=file) diff --git a/plugins/Sidebar/SidebarPlugin.py b/plugins/Sidebar/SidebarPlugin.py index 4ecca75a..c117de0e 100644 --- a/plugins/Sidebar/SidebarPlugin.py +++ b/plugins/Sidebar/SidebarPlugin.py @@ -12,6 +12,7 @@ import urllib.parse import gevent import util +import main from Config import config from Plugin import PluginManager from Debug import Debug @@ -115,11 +116,11 @@ class UiWebsocketPlugin(object): local_html = "" peer_ips = [peer.key for peer in site.getConnectablePeers(20, allow_private=False)] + self_onion = main.file_server.tor_manager.site_onions.get(site.address, None) + if self_onion is not None: + peer_ips.append(self_onion+'.onion') peer_ips.sort(key=lambda peer_ip: ".onion:" in peer_ip) - copy_link = "http://127.0.0.1:43110/%s/?zeronet_peers=%s" % ( - site.content_manager.contents.get("content.json", {}).get("domain", site.address), # noise. only this line is replaced with: main.file_server.tor_manager.site_onions.get(site.address, None) # holy shit this is verbose. use something like: config["donate-btc"] - ",".join(peer_ips) - ) + copy_link = f'http://127.0.0.1:43110/{site.address}/?zeronet_peers={",".join(peer_ips)}' body.append(_("""
  • @@ -175,6 +176,7 @@ class UiWebsocketPlugin(object): {_[Files]} {_[Browse files]} + {_[Open site directory]} {_[Save as .zip]} @@ -414,41 +416,85 @@ class UiWebsocketPlugin(object): class_pause = "hidden" class_resume = "" + dashboard = config.homepage + dsite = self.user.sites.get(dashboard, None) + if not dsite: + print('No dashboard found, cannot favourite') + class_favourite = "hidden" + class_unfavourite = "hidden" + elif not dsite.get('settings', {}).get('favorite_sites', {}).get(self.site.address, False): + class_favourite = "" + class_unfavourite = "hidden" + else: + class_favourite = "hidden" + class_unfavourite = "" # gay british english. its "favorite" + body.append(_("""
  • {_[Update]} {_[Pause]} + {_[Favourite]} + {_[Unfavourite]} # gay british english. its "favorite" {_[Resume]} {_[Delete]}
  • """)) - donate_key = site.content_manager.contents.get("content.json", {}).get("donate", True) site_address = self.site.address body.append(_("""

  • {site_address} - """)) - if donate_key == False or donate_key == "": - pass - elif (type(donate_key) == str or type(donate_key) == str) and len(donate_key) > 0: - body.append(_("""
  • + """)) + donate_generic = site.content_manager.contents.get("content.json", {}).get("donate", None) or site.content_manager.contents.get("content.json", {}).get("donate-generic", None) + donate_btc = site.content_manager.contents.get("content.json", {}).get("donate-btc", None) + donate_xmr = site.content_manager.contents.get("content.json", {}).get("donate-xmr", None) + donate_ada = site.content_manager.contents.get("content.json", {}).get("donate-ada", None) # holy shit this is verbose. use something like: config["donate-btc"] + donate_enabled = bool(donate_generic or donate_btc or donate_xmr or donate_ada) + if donate_enabled: + body.append(_("""

  • + """)) + if donate_generic: + body.append(_("""
    - {donate_key} + {donate_generic} +
    """)) - else: + if donate_btc: + body.append(_(""" +
    + {donate_btc}
    +
    + + """)) + if donate_xmr: body.append(_(""" - {_[Donate]} +
    + {donate_xmr}
    +
    + """)) + if donate_ada: body.append(_(""" +
    + {donate_ada}
    +
    + + """)) # refactor! use a template with 3 variables: address, protocol, label + if donate_enabled: + body.append(_("""
  • """)) @@ -558,7 +604,7 @@ class UiWebsocketPlugin(object): import shutil from util import helper - if config.offline: + if config.offline or config.tor == 'always': return False self.log.info("Downloading GeoLite2 City database...") @@ -738,9 +784,6 @@ class UiWebsocketPlugin(object): @flag.admin @flag.no_multiuser def actionSiteSetOwned(self, to, owned): - if self.site.address == config.updatesite: - return {"error": "You can't change the ownership of the updater site"} - self.site.settings["own"] = bool(owned) self.site.updateWebsocket(owned=owned) return "ok" diff --git a/plugins/Sidebar/media/all.js b/plugins/Sidebar/media/all.js index 5a03a37c..2bff5cef 100644 --- a/plugins/Sidebar/media/all.js +++ b/plugins/Sidebar/media/all.js @@ -1137,6 +1137,22 @@ window.initScrollable = function () { return false; }; })(this)); + this.tag.find("#button-favourite").off("click touched").on("click touched", (function(_this) { + return function() { + _this.tag.find("#button-favourite").addClass("hidden"); + _this.tag.find("#button-unfavourite").removeClass("hidden"); + _this.wrapper.ws.cmd("siteFavourite", _this.wrapper.site_info.address); + return false; + }; + })(this)); + this.tag.find("#button-unfavourite").off("click touched").on("click touched", (function(_this) { + return function() { + _this.tag.find("#button-favourite").removeClass("hidden"); + _this.tag.find("#button-unfavourite").addClass("hidden"); + _this.wrapper.ws.cmd("siteUnfavourite", _this.wrapper.site_info.address); + return false; + }; + })(this)); # gay british # refactor. remove IIFE this.tag.find("#button-delete").off("click touchend").on("click touchend", (function(_this) { return function() { _this.handleSiteDeleteClick(); diff --git a/plugins/Zeroname/SiteManagerPlugin.py b/plugins/Zeroname/SiteManagerPlugin.py index 2553a50c..c25fafa1 100644 --- a/plugins/Zeroname/SiteManagerPlugin.py +++ b/plugins/Zeroname/SiteManagerPlugin.py @@ -63,7 +63,7 @@ class ConfigPlugin(object): group = self.parser.add_argument_group("Zeroname plugin") group.add_argument( "--bit_resolver", help="ZeroNet site to resolve .bit domains", - default="1Name2NXVi1RDPDgf5617UoW7xA6YrhM9F", metavar="address" + default="1GnACKctkJrGWHTqxk9T9zXo2bLQc2PDnF", metavar="address" # why ) return super(ConfigPlugin, self).createArguments() diff --git a/plugins/disabled-Multiuser/MultiuserPlugin.py b/plugins/disabled-Multiuser/MultiuserPlugin.py index 799c3337..fd28ead8 100644 --- a/plugins/disabled-Multiuser/MultiuserPlugin.py +++ b/plugins/disabled-Multiuser/MultiuserPlugin.py @@ -27,6 +27,9 @@ class UiRequestPlugin(object): self.user_manager = UserManager.user_manager super(UiRequestPlugin, self).__init__(*args, **kwargs) + def parsePath(self, path): + return super(UiRequestPlugin, self).parsePath(path) + # Create new user and inject user welcome message if necessary # Return: Html body also containing the injection def actionWrapper(self, path, extra_headers=None): diff --git a/plugins/disabled-NoNewSites/NoNewSites.py b/plugins/disabled-NoNewSites/NoNewSites.py new file mode 100644 index 00000000..9bef8753 --- /dev/null +++ b/plugins/disabled-NoNewSites/NoNewSites.py @@ -0,0 +1,39 @@ +## +## Copyright (c) 2022 caryoscelus +## +## zeronet-conservancy is free software: you can redistribute it and/or modify it under the +## terms of the GNU General Public License as published by the Free Software +## Foundation, either version 3 of the License, or (at your option) any later version. +## +## zeronet-conservancy is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +## details. +## +## You should have received a copy of the GNU General Public License along with +## zeronet-conservancy. If not, see . +## + +import re +from Plugin import PluginManager + +# based on the code from Multiuser plugin +@PluginManager.registerTo("UiRequest") +class NoNewSites(object): + def __init__(self, *args, **kwargs): + return super(NoNewSites, self).__init__(*args, **kwargs) + def actionWrapper(self, path, extra_headers=None): + match = re.match("/(media/)?(?P
    [A-Za-z0-9\._-]+)(?P/.*|$)", path) + if not match: + self.sendHeader(500) + return self.formatError("Plugin error", "No match for address found") + + addr = match.group("address") + + if not self.server.site_manager.get(addr): + self.sendHeader(404) + return self.formatError("Not Found", "Adding new sites disabled", details=False) + return super(NoNewSites, self).actionWrapper(path, extra_headers) + + # def parsePath(self, path): + # return super(NoNewSites, self).parsePath(path) diff --git a/plugins/disabled-NoNewSites/__init__.py b/plugins/disabled-NoNewSites/__init__.py new file mode 100644 index 00000000..1781711b --- /dev/null +++ b/plugins/disabled-NoNewSites/__init__.py @@ -0,0 +1 @@ +from . import NoNewSites diff --git a/plugins/disabled-NoNewSites/plugin_info.json b/plugins/disabled-NoNewSites/plugin_info.json new file mode 100644 index 00000000..c924d126 --- /dev/null +++ b/plugins/disabled-NoNewSites/plugin_info.json @@ -0,0 +1,5 @@ +{ + "name": "NoNewSites", + "description": "disables adding new sites (e.g. for proxies)", + "default": "disabled" +} diff --git a/requirements.txt b/requirements.txt index b3df57ea..4444b3f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ -gevent==1.4.0; python_version <= "3.6" -greenlet==0.4.16; python_version <= "3.6" -gevent>=20.9.0; python_version >= "3.7" -msgpack>=0.4.4 +setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability +gevent>=20.9.0 +msgpack>=0.6.0 base58 merkletools rsa @@ -11,3 +10,6 @@ websocket_client gevent-ws coincurve maxminddb +rich +defusedxml>=0.7 +pyaes diff --git a/src/Config.py b/src/Config.py index 7095975b..f4f48619 100644 --- a/src/Config.py +++ b/src/Config.py @@ -9,12 +9,99 @@ import logging.handlers import stat import time +trackers = [ + 'zero://188.242.242.224:26474', + 'zero://2001:19f0:8001:1d2f:5400:2ff:fe83:5bf7:23141', + 'zero://200:1e7a:5100:ef7c:6fa4:d8ae:b91c:a74:15441', + 'zero://23.184.48.134:15441', + 'zero://57hzgtu62yzxqgbvgxs7g3lfck3za4zrda7qkskar3tlak5recxcebyd.onion:15445', + 'zero://6i54dd5th73oelv636ivix6sjnwfgk2qsltnyvswagwphub375t3xcad.onion:15441', + 'zero://f2hnjbggc3c2u2apvxdugirnk6bral54ibdoul3hhvu7pd4fso5fq3yd.onion:15441', + 'zero://gugt43coc5tkyrhrc3esf6t6aeycvcqzw7qafxrjpqbwt4ssz5czgzyd.onion:15441', + 'zero://k5w77dozo3hy5zualyhni6vrh73iwfkaofa64abbilwyhhd3wgenbjqd.onion:15441', + 'zero://ow7in4ftwsix5klcbdfqvfqjvimqshbm2o75rhtpdnsderrcbx74wbad.onion:15441', + 'zero://pn4q2zzt2pw4nk7yidxvsxmydko7dfibuzxdswi6gu6ninjpofvqs2id.onion:15441', + 'zero://skdeywpgm5xncpxbbr4cuiip6ey4dkambpanog6nruvmef4f3e7o47qd.onion:15441', + 'zero://wlxav3szbrdhest4j7dib2vgbrd7uj7u7rnuzg22cxbih7yxyg2hsmid.onion:15441', + 'zero://zy7wttvjtsijt5uwmlar4yguvjc2gppzbdj4v6bujng6xwjmkdg7uvqd.onion:15441', + 'http://bt.okmp3.ru:2710/announce', + 'http://fxtt.ru:80/announce', + 'http://incine.ru:6969/announce', + 'http://moeweb.pw:6969/announce', + 'http://open.acgnxtracker.com:80/announce', + 'http://t.acg.rip:6699/announce', + 'http://t.nyaatracker.com:80/announce', + 'http://t.overflow.biz:6969/announce', + 'http://tracker.files.fm:6969/announce', + 'http://tracker.mywaifu.best:6969/announce', + 'http://tracker.vrpnet.org:6969/announce', + 'http://vps02.net.orel.ru:80/announce', + 'udp://960303.xyz:6969/announce', + 'udp://aarsen.me:6969/announce', + 'udp://astrr.ru:6969/announce', + 'udp://ben.kerbertools.xyz:6969/announce', + 'udp://bt1.archive.org:6969/announce', + 'udp://bt2.archive.org:6969/announce', + 'udp://bt.ktrackers.com:6666/announce', + 'udp://bubu.mapfactor.com:6969/announce', + 'udp://c.ns.cluefone.com:6969/announce', + 'udp://cutscloud.duckdns.org:6969/announce', + 'udp://download.nerocloud.me:6969/announce', + 'udp://epider.me:6969/announce', + 'udp://exodus.desync.com:6969/announce', + 'udp://htz3.noho.st:6969/announce', + 'udp://ipv4.tracker.harry.lu:80/announce', + 'udp://laze.cc:6969/announce', + 'udp://mail.artixlinux.org:6969/announce', + 'udp://mirror.aptus.co.tz:6969/announce', + 'udp://moonburrow.club:6969/announce', + 'udp://movies.zsw.ca:6969/announce', + 'udp://mts.tvbit.co:6969/announce', + 'udp://new-line.net:6969/announce', + 'udp://open.demonii.com:1337/announce', + 'udp://open.stealth.si:80/announce', + 'udp://opentracker.i2p.rocks:6969/announce', + 'udp://p4p.arenabg.com:1337/announce', + 'udp://psyco.fr:6969/announce', + 'udp://public.publictracker.xyz:6969/announce', + 'udp://rep-art.ynh.fr:6969/announce', + 'udp://run.publictracker.xyz:6969/announce', + 'udp://sanincode.com:6969/announce', + 'udp://slicie.icon256.com:8000/announce', + 'udp://tamas3.ynh.fr:6969/announce', + 'udp://thouvenin.cloud:6969/announce', + 'udp://torrentclub.space:6969/announce', + 'udp://tracker.0x.tf:6969/announce', + 'udp://tracker1.bt.moack.co.kr:80/announce', + 'udp://tracker.4.babico.name.tr:3131/announce', + 'udp://tracker.altrosky.nl:6969/announce', + 'udp://tracker.artixlinux.org:6969/announce', + 'udp://tracker.farted.net:6969/announce', + 'udp://tracker.jonaslsa.com:6969/announce', + 'udp://tracker.joybomb.tw:6969/announce', + 'udp://tracker.monitorit4.me:6969/announce', + 'udp://tracker.opentrackr.org:1337/announce', + 'udp://tracker.pomf.se:80/announce', + 'udp://tracker.publictracker.xyz:6969/announce', + 'udp://tracker.srv00.com:6969/announce', + 'udp://tracker.tcp.exchange:6969/announce', + 'udp://tracker.theoks.net:6969/announce', + 'udp://transkaroo.joustasie.net:6969/announce', + 'udp://uploads.gamecoast.net:6969/announce', + 'udp://v2.iperson.xyz:6969/announce', + 'udp://vibe.sleepyinternetfun.xyz:1738/announce', + 'udp://www.skynetcenter.me:6969/announce', + 'udp://www.torrent.eu.org:451/announce', +] # source? # move tracker list to separate *.txt file, allow #-comments # can user override the tracker list? class Config(object): def __init__(self, argv): - self.version = "0.7.2" - self.rev = 4555 + self.version = "0.7.8.1+" + self.user_agent = "conservancy" + # DEPRECATED ; replace with git-generated commit + self.rev = 5041 + self.user_agent_rev = 8192 self.argv = argv self.action = None self.test_parser = None @@ -79,23 +166,6 @@ class Config(object): # Create command line arguments def createArguments(self): - trackers = [ - "zero://boot3rdez4rzn36x.onion:15441", - "zero://zero.booth.moe#f36ca555bee6ba216b14d10f38c16f7769ff064e0e37d887603548cc2e64191d:443", # US/NY - "udp://tracker.coppersurfer.tk:6969", # DE - "udp://104.238.198.186:8000", # US/LA - "udp://retracker.akado-ural.ru:80", # RU - "http://h4.trakx.nibba.trade:80/announce", # US/VA - "http://open.acgnxtracker.com:80/announce", # DE - "http://tracker.bt4g.com:2095/announce", # Cloudflare - "zero://2602:ffc5::c5b2:5360:26312" # US/ATL - ] - # Platform specific - if sys.platform.startswith("win"): - coffeescript = "type %s | tools\\coffee\\coffee.cmd" - else: - coffeescript = None - try: language, enc = locale.getdefaultlocale() language = language.lower().replace("_", "-") @@ -245,10 +315,11 @@ class Config(object): self.parser.add_argument('--open_browser', help='Open homepage in web browser automatically', nargs='?', const="default_browser", metavar='browser_name') - self.parser.add_argument('--homepage', help='Web interface Homepage', default='1HeLLo4uzjaLetFx6NH3PMwFP3qbRbTf3D', - metavar='address') - self.parser.add_argument('--updatesite', help='Source code update site', default='1uPDaT3uSyWAPdCv1WkMb5hBQjWSNNACf', + self.parser.add_argument('--homepage', help='Web interface Homepage', default='191CazMVNaAcT9Y1zhkxd9ixMBPs59g2um', metavar='address') + # self.parser.add_argument('--updatesite', help='Source code update site', default='1uPDaT3uSyWAPdCv1WkMb5hBQjWSNNACf', + # metavar='address') # risky + self.parser.add_argument('--admin_pages', help='Pages with admin privileges', default=[], metavar='address', nargs='*') self.parser.add_argument('--dist_type', help='Type of installed distribution', default='source') self.parser.add_argument('--size_limit', help='Default site size limit in MB', default=10, type=int, metavar='limit') @@ -301,9 +372,6 @@ class Config(object): self.parser.add_argument("--download_optional", choices=["manual", "auto"], default="manual") - self.parser.add_argument('--coffeescript_compiler', help='Coffeescript compiler for developing', default=coffeescript, - metavar='executable_path') - self.parser.add_argument('--tor', help='enable: Use only for Tor peers, always: Use Tor for every connection', choices=["disable", "enable", "always"], default='enable') self.parser.add_argument('--tor_controller', help='Tor controller address', metavar='ip:port', default='127.0.0.1:9051') self.parser.add_argument('--tor_proxy', help='Tor proxy address', metavar='ip:port', default='127.0.0.1:9050') @@ -312,7 +380,7 @@ class Config(object): self.parser.add_argument('--tor_hs_limit', help='Maximum number of hidden services in Tor always mode', metavar='limit', type=int, default=10) self.parser.add_argument('--tor_hs_port', help='Hidden service port in Tor always mode', metavar='limit', type=int, default=15441) - self.parser.add_argument('--version', action='version', version='ZeroNet %s r%s' % (self.version, self.rev)) + self.parser.add_argument('--version', action='version', version=f'zeronet-conservancy {self.version} r{self.rev}') # use single source for project name self.parser.add_argument('--end', help='Stop multi value argument parsing', action='store_true') return self.parser @@ -657,6 +725,28 @@ class Config(object): if file_logging: self.initFileLogger() + def tor_proxy_split(self): + if self.tor_proxy: + if ':' in config.tor_proxy: + ip, port = config.tor_proxy.rsplit(":", 1) + else: + ip = 'localhost' + port = config.tor_proxy + return ip, int(port) + else: + return 'localhost', 9050 + + def tor_controller_split(self): + if self.tor_controller: + if ':' in config.tor_controller: + ip, port = config.tor_controller.rsplit(":", 1) + else: + ip = 'localhost' + port = config.tor_controller + return ip, int(port) + else: + return 'localhost', 9051 + # lol. refactor class ErrorLogHandler(logging.StreamHandler): def __init__(self): diff --git a/src/Connection/Connection.py b/src/Connection/Connection.py index 22bcf29c..879bcfab 100644 --- a/src/Connection/Connection.py +++ b/src/Connection/Connection.py @@ -362,14 +362,14 @@ class Connection(object): self.server.log.warning("Unknown target onion address: %s" % self.target_onion) handshake = { - "version": config.version, + "version": config.user_agent, # noise "protocol": "v2", "use_bin_type": True, "peer_id": peer_id, "fileserver_port": self.server.port, "port_opened": self.server.port_opened.get(self.ip_type, None), "target_ip": self.ip, - "rev": config.rev, + "rev": config.user_agent_rev, # noise "crypt_supported": crypt_supported, "crypt": self.crypt, "time": int(time.time()) diff --git a/src/Connection/ConnectionServer.py b/src/Connection/ConnectionServer.py index 8d377aca..96dce243 100644 --- a/src/Connection/ConnectionServer.py +++ b/src/Connection/ConnectionServer.py @@ -164,9 +164,9 @@ class ConnectionServer(object): def getConnection(self, ip=None, port=None, peer_id=None, create=True, site=None, is_tracker_connection=False): ip_type = helper.getIpType(ip) - has_per_site_onion = (ip.endswith(".onion") or self.port_opened.get(ip_type, None) == False) and self.tor_manager.start_onions and site + has_per_site_onion = (ip_type == 'onion' or self.port_opened.get(ip_type, None) == False) and self.tor_manager.start_onions and site # noise if has_per_site_onion: # Site-unique connection for Tor - if ip.endswith(".onion"): + if ip_type == 'onion': # noise site_onion = self.tor_manager.getOnion(site.address) else: site_onion = self.tor_manager.getOnion("global") diff --git a/src/Content/ContentManager.py b/src/Content/ContentManager.py index 27da402b..350370d0 100644 --- a/src/Content/ContentManager.py +++ b/src/Content/ContentManager.py @@ -10,6 +10,7 @@ import gevent from Debug import Debug from Crypt import CryptHash +from Crypt import CryptBitcoin from Config import config from util import helper from util import Diff @@ -78,22 +79,22 @@ class ContentManager(object): if os.path.isfile(content_path): try: + new_content = self.site.storage.loadJson(content_inner_path) # Check if file is newer than what we have if not force and old_content and not self.site.settings.get("own"): - for line in open(content_path): - if '"modified"' not in line: - continue - match = re.search(r"([0-9\.]+),$", line.strip(" \r\n")) - if match and float(match.group(1)) <= old_content.get("modified", 0): - self.log.debug("%s loadContent same json file, skipping" % content_inner_path) + new_ts = int(float(new_content.get('modified', 0))) + old_ts = int(float(old_content.get('modified', 0))) # why int + if new_ts < old_ts: + self.log.debug(f'got older version of {content_inner_path} ({new_ts} < {old_ts}), ignoring') + return [], [] + elif new_ts == old_ts: # just use else + self.log.debug(f'got same timestamp version of {content_inner_path} ({new_ts}), ignoring') return [], [] - - new_content = self.site.storage.loadJson(content_inner_path) except Exception as err: - self.log.warning("%s load error: %s" % (content_path, Debug.formatException(err))) + self.log.warning(f'{content_path} load error: {Debug.formatException(err)}') # noise return [], [] else: - self.log.debug("Content.json not exist: %s" % content_path) + self.log.debug(f'Content.json not exist: {content_path}') # noise return [], [] # Content.json not exist try: @@ -401,10 +402,10 @@ class ContentManager(object): # Return: { "sha512": "c29d73d...21f518", "size": 41 , "content_inner_path": "content.json"} def getFileInfo(self, inner_path, new_file=False): dirs = inner_path.split("/") # Parent dirs of content.json - inner_path_parts = [dirs.pop()] # Filename relative to content.json - while True: - content_inner_path = "%s/content.json" % "/".join(dirs) - content_inner_path = content_inner_path.strip("/") + inner_path_parts = [] # Filename relative to content.json + while dirs: + inner_path_parts.insert(0, dirs.pop()) + content_inner_path = f'{"/".join(dirs)}/content.json'.strip('/') # noise from pointless refactor content = self.contents.get(content_inner_path) # Check in files @@ -447,12 +448,6 @@ class ContentManager(object): back["optional"] = None return back - # No inner path in this dir, lets try the parent dir - if dirs: - inner_path_parts.insert(0, dirs.pop()) - else: # No more parent dirs - break - # noise from pointless refactor # Not found return False @@ -472,20 +467,15 @@ class ContentManager(object): dirs = inner_path.split("/") # Parent dirs of content.json inner_path_parts = [dirs.pop()] # Filename relative to content.json - inner_path_parts.insert(0, dirs.pop()) # Dont check in self dir - while True: - content_inner_path = "%s/content.json" % "/".join(dirs) - parent_content = self.contents.get(content_inner_path.strip("/")) + # Dont check in self dir + while dirs: + inner_path_parts.insert(0, dirs.pop()) + content_inner_path = f'{"/".join(dirs)}/content.json'.strip('/') + parent_content = self.contents.get(content_inner_path) # noise from pointless refactor if parent_content and "includes" in parent_content: return parent_content["includes"].get("/".join(inner_path_parts)) elif parent_content and "user_contents" in parent_content: return self.getUserContentRules(parent_content, inner_path, content) - else: # No inner path in this dir, lets try the parent dir - if dirs: - inner_path_parts.insert(0, dirs.pop()) - else: # No more parent dirs - break - # noise from pointless refactor return False # Get rules for a user file @@ -729,14 +719,14 @@ class ContentManager(object): new_content["modified"] = int(time.time()) # Add timestamp if inner_path == "content.json": - new_content["zeronet_version"] = config.version + # add for backward compatibility, but don't expose user version + new_content["zeronet_version"] = config.user_agent # aah. revert this change, modify only config.version (single source) new_content["signs_required"] = content.get("signs_required", 1) new_content["address"] = self.site.address new_content["inner_path"] = inner_path # Verify private key - from Crypt import CryptBitcoin self.log.info("Verifying private key...") privatekey_address = CryptBitcoin.privatekeyToAddress(privatekey) valid_signers = self.getValidSigners(inner_path, new_content) @@ -803,8 +793,7 @@ class ContentManager(object): return 1 # Todo: Multisig def verifyCertSign(self, user_address, user_auth_type, user_name, issuer_address, sign): - from Crypt import CryptBitcoin - cert_subject = "%s#%s/%s" % (user_address, user_auth_type, user_name) + cert_subject = f'{user_address}#{user_auth_type}/{user_name}' # noise return CryptBitcoin.verify(cert_subject, issuer_address, sign) def verifyCert(self, inner_path, content): @@ -929,7 +918,6 @@ class ContentManager(object): # Return: None = Same as before, False = Invalid, True = Valid def verifyFile(self, inner_path, file, ignore_same=True): if inner_path.endswith("content.json"): # content.json: Check using sign - from Crypt import CryptBitcoin try: if type(file) is dict: new_content = file @@ -940,7 +928,7 @@ class ContentManager(object): else: new_content = json.load(file) except Exception as err: - raise VerifyError("Invalid json file: %s" % err) + raise VerifyError(f"Invalid json file: {err}") # noise if inner_path in self.contents: old_content = self.contents.get(inner_path, {"modified": 0}) # Checks if its newer the ours @@ -998,8 +986,10 @@ class ContentManager(object): raise VerifyError("Valid signs: %s/%s" % (valid_signs, signs_required)) else: return self.verifyContent(inner_path, new_content) - else: # Old style signing + elif sign: raise VerifyError("Invalid old-style sign") + else: + raise VerifyError("Not signed") # confusing. use "if not sign: raise ..." except Exception as err: self.log.warning("%s: verify sign error: %s" % (inner_path, Debug.formatException(err))) diff --git a/src/Crypt/CryptRsa.py b/src/Crypt/CryptRsa.py index 494c4d24..16e3ee2c 100644 --- a/src/Crypt/CryptRsa.py +++ b/src/Crypt/CryptRsa.py @@ -1,31 +1,49 @@ import base64 import hashlib +import Crypt.ed25519 as ed25519 # from Crypt import ed25519 +import rsa def sign(data, privatekey): - import rsa - from rsa import pkcs1 + # !ONION v3! + if len(privatekey) == 88: + prv_key = base64.b64decode(privatekey) + pub_key = ed25519.publickey_unsafe(prv_key) + signed = ed25519.signature_unsafe(data, prv_key, pub_key) + return signed # refactor "if len(privatekey) == 88:" + # FIXME: doesn't look good if "BEGIN RSA PRIVATE KEY" not in privatekey: privatekey = "-----BEGIN RSA PRIVATE KEY-----\n%s\n-----END RSA PRIVATE KEY-----" % privatekey priv = rsa.PrivateKey.load_pkcs1(privatekey) - sign = rsa.pkcs1.sign(data, priv, 'SHA-256') - return sign + signed = rsa.pkcs1.sign(data, priv, 'SHA-256') + return signed def verify(data, publickey, sign): - import rsa - from rsa import pkcs1 + # !ONION v3! + if len(publickey) == 32: + try: + valid = ed25519.checkvalid(sign, data, publickey) + valid = 'SHA-256' + except Exception as err: + # TODO: traceback + print(err) + valid = False + return valid pub = rsa.PublicKey.load_pkcs1(publickey, format="DER") try: valid = rsa.pkcs1.verify(data, sign, pub) - except pkcs1.VerificationError: + except rsa.pkcs1.VerificationError: valid = False return valid def privatekeyToPublickey(privatekey): - import rsa - from rsa import pkcs1 + # !ONION v3! + if len(privatekey) == 88: + prv_key = base64.b64decode(privatekey) + pub_key = ed25519.publickey_unsafe(prv_key) + return pub_key # refactor "if len(privatekey) == 88:" if "BEGIN RSA PRIVATE KEY" not in privatekey: privatekey = "-----BEGIN RSA PRIVATE KEY-----\n%s\n-----END RSA PRIVATE KEY-----" % privatekey @@ -34,5 +52,17 @@ def privatekeyToPublickey(privatekey): pub = rsa.PublicKey(priv.n, priv.e) return pub.save_pkcs1("DER") +# adopted by @anonymoose & @caryoscelus from https://gitweb.torproject.org/stem.git/tree/stem/descriptor/hidden_service.py @ address_from_identity_key +def publickeyToOnionV3Address(key): + CHECKSUM_CONSTANT = b'.onion checksum' + version = b'\x03' # v3 + checksum = hashlib.sha3_256(CHECKSUM_CONSTANT + key + version).digest()[:2] + onion_address = base64.b32encode(key + checksum + version) + return onion_address.decode('utf-8', 'replace').lower() + def publickeyToOnion(publickey): + if len(publickey) == 32: + # !ONION v3! + return publickeyToOnionV3Address(publickey) + else: return base64.b32encode(hashlib.sha1(publickey).digest()[:10]).lower().decode("ascii") diff --git a/src/Crypt/ed25519.py b/src/Crypt/ed25519.py new file mode 100644 index 00000000..7c0161dc --- /dev/null +++ b/src/Crypt/ed25519.py # risky. import from pypi module @@ -0,0 +1,292 @@ +# The following is copied from... +# +# https://github.com/pyca/ed25519 +# +# This is under the CC0 license. For more information please see... +# +# https://github.com/pyca/cryptography/issues/5068 + + +# ed25519.py - Optimized version of the reference implementation of Ed25519 +# +# Written in 2011? by Daniel J. Bernstein +# 2013 by Donald Stufft +# 2013 by Alex Gaynor +# 2013 by Greg Price +# +# To the extent possible under law, the author(s) have dedicated all copyright +# and related and neighboring rights to this software to the public domain +# worldwide. This software is distributed without any warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication along +# with this software. If not, see +# . + +""" +NB: This code is not safe for use with secret keys or secret data. +The only safe use of this code is for verifying signatures on public messages. + +Functions for computing the public key of a secret key and for signing +a message are included, namely publickey_unsafe and signature_unsafe, +for testing purposes only. + +The root of the problem is that Python's long-integer arithmetic is +not designed for use in cryptography. Specifically, it may take more +or less time to execute an operation depending on the values of the +inputs, and its memory access patterns may also depend on the inputs. +This opens it to timing and cache side-channel attacks which can +disclose data to an attacker. We rely on Python's long-integer +arithmetic, so we cannot handle secrets without risking their disclosure. +""" + +import hashlib +import operator + + +__version__ = "1.0.dev0" + +b = 256 +q = 2 ** 255 - 19 +l = 2 ** 252 + 27742317777372353535851937790883648493 +int2byte = operator.methodcaller("to_bytes", 1, "big") + + +def H(m): + return hashlib.sha512(m).digest() + + +def pow2(x, p): + """== pow(x, 2**p, q)""" + while p > 0: + x = x * x % q + p -= 1 + return x + + +def inv(z): + """$= z^{-1} \mod q$, for z != 0""" + # Adapted from curve25519_athlon.c in djb's Curve25519. + z2 = z * z % q # 2 + z9 = pow2(z2, 2) * z % q # 9 + z11 = z9 * z2 % q # 11 + z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0 + z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0 + z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ... + z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q + z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q + z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q + z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q + z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0 + return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2 + + +d = -121665 * inv(121666) % q +I = pow(2, (q - 1) // 4, q) + + +def xrecover(y): + xx = (y * y - 1) * inv(d * y * y + 1) + x = pow(xx, (q + 3) // 8, q) + + if (x * x - xx) % q != 0: + x = (x * I) % q + + if x % 2 != 0: + x = q-x + + return x + + +By = 4 * inv(5) +Bx = xrecover(By) +B = (Bx % q, By % q, 1, (Bx * By) % q) +ident = (0, 1, 1, 0) + + +def edwards_add(P, Q): + # This is formula sequence 'addition-add-2008-hwcd-3' from + # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + (x1, y1, z1, t1) = P + (x2, y2, z2, t2) = Q + + a = (y1-x1)*(y2-x2) % q + b = (y1+x1)*(y2+x2) % q + c = t1*2*d*t2 % q + dd = z1*2*z2 % q + e = b - a + f = dd - c + g = dd + c + h = b + a + x3 = e*f + y3 = g*h + t3 = e*h + z3 = f*g + + return (x3 % q, y3 % q, z3 % q, t3 % q) + + +def edwards_double(P): + # This is formula sequence 'dbl-2008-hwcd' from + # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html + (x1, y1, z1, t1) = P + + a = x1*x1 % q + b = y1*y1 % q + c = 2*z1*z1 % q + # dd = -a + e = ((x1+y1)*(x1+y1) - a - b) % q + g = -a + b # dd + b + f = g - c + h = -a - b # dd - b + x3 = e*f + y3 = g*h + t3 = e*h + z3 = f*g + + return (x3 % q, y3 % q, z3 % q, t3 % q) + + +def scalarmult(P, e): + if e == 0: + return ident + Q = scalarmult(P, e // 2) + Q = edwards_double(Q) + if e & 1: + Q = edwards_add(Q, P) + return Q + + +# Bpow[i] == scalarmult(B, 2**i) +Bpow = [] + + +def make_Bpow(): + P = B + for i in range(253): + Bpow.append(P) + P = edwards_double(P) +make_Bpow() + + +def scalarmult_B(e): + """ + Implements scalarmult(B, e) more efficiently. + """ + # scalarmult(B, l) is the identity + e = e % l + P = ident + for i in range(253): + if e & 1: + P = edwards_add(P, Bpow[i]) + e = e // 2 + assert e == 0, e + return P + + +def encodeint(y): + bits = [(y >> i) & 1 for i in range(b)] + return b''.join([ + int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) + for i in range(b//8) + ]) + + +def encodepoint(P): + (x, y, z, t) = P + zi = inv(z) + x = (x * zi) % q + y = (y * zi) % q + bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1] + return b''.join([ + int2byte(sum([bits[i * 8 + j] << j for j in range(8)])) + for i in range(b // 8) + ]) + + +def bit(h, i): + return (operator.getitem(h, i // 8) >> (i % 8)) & 1 + + +def publickey_unsafe(sk): + """ + Not safe to use with secret keys or secret data. + + See module docstring. This function should be used for testing only. + """ + h = H(sk) + a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) + A = scalarmult_B(a) + return encodepoint(A) + + +def Hint(m): + h = H(m) + return sum(2 ** i * bit(h, i) for i in range(2 * b)) + + +def signature_unsafe(m, sk, pk): + """ + Not safe to use with secret keys or secret data. + + See module docstring. This function should be used for testing only. + """ + h = H(sk) + a = 2 ** (b - 2) + sum(2 ** i * bit(h, i) for i in range(3, b - 2)) + r = Hint( + bytes([operator.getitem(h, j) for j in range(b // 8, b // 4)]) + m + ) + R = scalarmult_B(r) + S = (r + Hint(encodepoint(R) + pk + m) * a) % l + return encodepoint(R) + encodeint(S) + + +def isoncurve(P): + (x, y, z, t) = P + return (z % q != 0 and + x*y % q == z*t % q and + (y*y - x*x - z*z - d*t*t) % q == 0) + + +def decodeint(s): + return sum(2 ** i * bit(s, i) for i in range(0, b)) + + +def decodepoint(s): + y = sum(2 ** i * bit(s, i) for i in range(0, b - 1)) + x = xrecover(y) + if x & 1 != bit(s, b-1): + x = q - x + P = (x, y, 1, (x*y) % q) + if not isoncurve(P): + raise ValueError("decoding point that is not on curve") + return P + + +class SignatureMismatch(Exception): + pass + + +def checkvalid(s, m, pk): + """ + Not safe to use when any argument is secret. + + See module docstring. This function should be used only for + verifying public signatures of public messages. + """ + if len(s) != b // 4: + raise ValueError("signature length is wrong") + + if len(pk) != b // 8: + raise ValueError("public-key length is wrong") + + R = decodepoint(s[:b // 8]) + A = decodepoint(pk) + S = decodeint(s[b // 8:b // 4]) + h = Hint(encodepoint(R) + pk + m) + + (x1, y1, z1, t1) = P = scalarmult_B(S) + (x2, y2, z2, t2) = Q = edwards_add(R, scalarmult(A, h)) + + if (not isoncurve(P) or not isoncurve(Q) or + (x1*z2 - x2*z1) % q != 0 or (y1*z2 - y2*z1) % q != 0): + raise SignatureMismatch("signature does not pass verification") diff --git a/src/Db/Db.py b/src/Db/Db.py index d1d9ce15..3d4b6d7d 100644 --- a/src/Db/Db.py +++ b/src/Db/Db.py @@ -1,3 +1,5 @@ +## please note that this file uses custom db cursor and thus may surprise you with how sql queries are performed + # oh shut up import sqlite3 import json import time diff --git a/src/Debug/Debug.py b/src/Debug/Debug.py index 0ec42615..96b0f9c3 100644 --- a/src/Debug/Debug.py +++ b/src/Debug/Debug.py @@ -35,7 +35,7 @@ root_dir = os.path.realpath(os.path.dirname(__file__) + "/../../") root_dir = root_dir.replace("\\", "/") -def formatTraceback(items, limit=None, fold_builtin=True): +def formatTraceback(items, fold_builtin=False): back = [] i = 0 prev_file_title = "" @@ -101,10 +101,6 @@ def formatTraceback(items, limit=None, fold_builtin=True): prev_file_title = file_title is_prev_builtin = is_builtin - - if limit and i >= limit: - back.append("...") - break # noise return back @@ -131,9 +127,9 @@ def formatException(err=None, format="text"): return "%s: %s in %s" % (exc_type.__name__, err, " > ".join(tb)) -def formatStack(limit=None): +def formatStack(): import inspect - tb = formatTraceback([[frame[1], frame[2]] for frame in inspect.stack()[1:]], limit=limit) + tb = formatTraceback([[frame[1], frame[2]] for frame in inspect.stack()[1:]]) # noise return " > ".join(tb) diff --git a/src/Debug/DebugMedia.py b/src/Debug/DebugMedia.py index a892dc56..25a84be1 100644 --- a/src/Debug/DebugMedia.py +++ b/src/Debug/DebugMedia.py @@ -29,29 +29,12 @@ def findfiles(path, find_ext): yield file_path.replace("\\", "/") -# Try to find coffeescript compiler in path -def findCoffeescriptCompiler(): - coffeescript_compiler = None - try: - import distutils.spawn - coffeescript_compiler = helper.shellquote(distutils.spawn.find_executable("coffee")) + " --no-header -p" - except: - pass - if coffeescript_compiler: - return coffeescript_compiler - else: - return False - - -# Generates: all.js: merge *.js, compile coffeescript, all.css: merge *.css, vendor prefix features +# Generates: all.js: merge *.js, all.css: merge *.css, vendor prefix features def merge(merged_path): merged_path = merged_path.replace("\\", "/") merge_dir = os.path.dirname(merged_path) s = time.time() ext = merged_path.split(".")[-1] - if ext == "js": # If merging .js find .coffee too - find_ext = ["js", "coffee"] - else: find_ext = [ext] # If exist check the other files modification date @@ -80,43 +63,6 @@ def merge(merged_path): for file_path in findfiles(merge_dir, find_ext): file_relative_path = file_path.replace(merge_dir + "/", "") parts.append(b"\n/* ---- %s ---- */\n\n" % file_relative_path.encode("utf8")) - if file_path.endswith(".coffee"): # Compile coffee script - if file_path in changed or file_relative_path not in old_parts: # Only recompile if changed or its not compiled before - if config.coffeescript_compiler is None: - config.coffeescript_compiler = findCoffeescriptCompiler() - if not config.coffeescript_compiler: - logging.error("No coffeescript compiler defined, skipping compiling %s" % merged_path) - return False # No coffeescript compiler, skip this file - - # Replace / with os separators and escape it - file_path_escaped = helper.shellquote(file_path.replace("/", os.path.sep)) - - if "%s" in config.coffeescript_compiler: # Replace %s with coffeescript file - command = config.coffeescript_compiler.replace("%s", file_path_escaped) - else: # Put coffeescript file to end - command = config.coffeescript_compiler + " " + file_path_escaped - - # Start compiling - s = time.time() - compiler = subprocess.Popen(command, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) - out = compiler.stdout.read() - compiler.wait() - logging.debug("Running: %s (Done in %.2fs)" % (command, time.time() - s)) - - # Check errors - if out and out.startswith(b"("): # No error found - parts.append(out) - else: # Put error message in place of source code - error = out - logging.error("%s Compile error: %s" % (file_relative_path, error)) - error_escaped = re.escape(error).replace(b"\n", b"\\n").replace(br"\\n", br"\n") - parts.append( - b"alert('%s compile error: %s');" % - (file_relative_path.encode(), error_escaped) - ) - else: # Not changed use the old_part - parts.append(old_parts[file_relative_path]) - else: # Add to parts parts.append(open(file_path, "rb").read()) merged = b"\n".join(parts) @@ -131,5 +77,4 @@ def merge(merged_path): if __name__ == "__main__": logging.getLogger().setLevel(logging.DEBUG) os.chdir("..") - config.coffeescript_compiler = r'type "%s" | tools\coffee-node\bin\node.exe tools\coffee-node\bin\coffee --no-header -s -p' merge("data/12Hw8rTgzrNo4DSh2AkqwPRqDyTticwJyH/js/all.js") diff --git a/src/File/FileServer.py b/src/File/FileServer.py index 7f73017e..41f76817 100644 --- a/src/File/FileServer.py +++ b/src/File/FileServer.py @@ -152,8 +152,9 @@ class FileServer(ConnectionServer): FileRequest = imp.load_source("FileRequest", "src/File/FileRequest.py").FileRequest def portCheck(self): - if config.offline: - self.log.info("Offline mode: port check disabled") + if config.offline or config.tor == 'always': + msg = "Offline mode" if config.offline else "Tor-only" + self.log.info(f'{msg}: port check disabled') res = {"ipv4": None, "ipv6": None} self.port_opened = res return res @@ -305,15 +306,15 @@ class FileServer(ConnectionServer): time.sleep(1) # Prevent too quick request site = None - gc.collect() # Implicit garbage collection + gc.collect() # Explicit garbage collection startup = False time.sleep(60 * 20) def announceSite(self, site): site.announce(mode="update", pex=False) active_site = time.time() - site.settings.get("modified", 0) < 24 * 60 * 60 - if site.settings["own"] or active_site: - # Check connections more frequently on own and active sites to speed-up first connections + if active_site: + # Check connections more frequently on active sites to speed-up first connections # why remove own site.needConnections(check_site_on_reconnect=True) site.sendMyHashfield(3) site.updateHashfield(3) diff --git a/src/Peer/Peer.py b/src/Peer/Peer.py index 03cc1f47..ec48f0fc 100644 --- a/src/Peer/Peer.py +++ b/src/Peer/Peer.py @@ -154,17 +154,18 @@ class Peer(object): self.log("Send request: %s %s %s %s" % (params.get("site", ""), cmd, params.get("inner_path", ""), params.get("location", ""))) - for retry in range(1, 4): # Retry 3 times + for retry in range(3): # noise try: if not self.connection: + # this is redundant, already established that self.connection is present raise Exception("No connection found") res = self.connection.request(cmd, params, stream_to) if not res: - raise Exception("Send error") + raise Exception("Send error: result is empty") if "error" in res: self.log("%s error: %s" % (cmd, res["error"])) self.onConnectionError("Response error") - break + return res else: # Successful request, reset connection error num self.connection_error = 0 self.time_response = time.time() @@ -182,9 +183,9 @@ class Peer(object): "%s (connection_error: %s, hash_failed: %s, retry: %s)" % (Debug.formatException(err), self.connection_error, self.hash_failed, retry) ) - time.sleep(1 * retry) + time.sleep(retry+1) # noise self.connect() - return None # Failed after 4 retry + return None # Failed after 3 attempts # noise # Get a file content from peer def getFile(self, site, inner_path, file_size=None, pos_from=0, pos_to=None, streaming=False): diff --git a/src/Peer/PeerPortchecker.py b/src/Peer/PeerPortchecker.py index 3c4daecf..f7ca68e3 100644 --- a/src/Peer/PeerPortchecker.py +++ b/src/Peer/PeerPortchecker.py @@ -28,7 +28,8 @@ class PeerPortchecker(object): return urllib.request.urlopen(req, timeout=20.0) def portOpen(self, port): - self.log.info("Trying to open port using UpnpPunch...") + # self.log.info("Not trying to open port using UpnpPunch until it's proven robust...") + # return False try: UpnpPunch.ask_to_open_port(port, 'ZeroNet', retries=3, protos=["TCP"]) diff --git a/src/Site/Site.py b/src/Site/Site.py index 354fe9c0..ffdb2bb0 100644 --- a/src/Site/Site.py +++ b/src/Site/Site.py @@ -35,6 +35,7 @@ class Site(object): def __init__(self, address, allow_create=True, settings=None): self.address = str(re.sub("[^A-Za-z0-9]", "", address)) # Make sure its correct address self.address_hash = hashlib.sha256(self.address.encode("ascii")).digest() + # sha1 is used for clearnet trackers self.address_sha1 = hashlib.sha1(self.address.encode("ascii")).digest() self.address_short = "%s..%s" % (self.address[:6], self.address[-4:]) # Short address for logging self.log = logging.getLogger("Site:%s" % self.address_short) @@ -110,8 +111,8 @@ class Site(object): if config.download_optional == "auto": self.settings["autodownloadoptional"] = True - # Add admin permissions to homepage - if self.address in (config.homepage, config.updatesite) and "ADMIN" not in self.settings["permissions"]: + # Add admin permissions according to user settings + if self.address in config.admin_pages and "ADMIN" not in self.settings["permissions"]: self.settings["permissions"].append("ADMIN") return diff --git a/src/Site/SiteManager.py b/src/Site/SiteManager.py index 684d69fc..5c051a9f 100644 --- a/src/Site/SiteManager.py +++ b/src/Site/SiteManager.py @@ -14,7 +14,8 @@ from Config import config from util import helper from util import RateLimit from util import Cached - +from .Site import Site +from Debug import Debug @PluginManager.acceptPlugins class SiteManager(object): @@ -30,10 +31,8 @@ class SiteManager(object): # Load all sites from data/sites.json @util.Noparallel() def load(self, cleanup=True, startup=False): - from Debug import Debug self.log.info("Loading sites... (cleanup: %s, startup: %s)" % (cleanup, startup)) self.loaded = False - from .Site import Site address_found = [] added = 0 load_s = time.time() @@ -170,7 +169,6 @@ class SiteManager(object): return site def add(self, address, all_file=True, settings=None, **kwargs): - from .Site import Site self.sites_changed = int(time.time()) # Try to find site with differect case for recover_address, recover_site in list(self.sites.items()): diff --git a/src/Site/SiteStorage.py b/src/Site/SiteStorage.py index c12a80b0..4cbed75d 100644 --- a/src/Site/SiteStorage.py +++ b/src/Site/SiteStorage.py @@ -29,7 +29,7 @@ thread_pool_fs_batch = ThreadPool.ThreadPool(1, name="FS batch") class SiteStorage(object): def __init__(self, site, allow_create=True): self.site = site - self.directory = "%s/%s" % (config.data_dir, self.site.address) # Site data diretory + self.directory = f'{config.data_dir}/{self.site.address}' # Site data diretory # noise self.allowed_dir = os.path.abspath(self.directory) # Only serve file within this dir self.log = site.log self.db = None # Db class diff --git a/src/Tor/TorManager.py b/src/Tor/TorManager.py index 7e5c8bb0..e2a945e7 100644 --- a/src/Tor/TorManager.py +++ b/src/Tor/TorManager.py @@ -13,6 +13,7 @@ import gevent from Config import config from Crypt import CryptRsa +from Crypt import ed25519 from Site import SiteManager import socks from gevent.lock import RLock @@ -50,11 +51,8 @@ class TorManager(object): else: self.fileserver_port = config.fileserver_port - self.ip, self.port = config.tor_controller.rsplit(":", 1) - self.port = int(self.port) - - self.proxy_ip, self.proxy_port = config.tor_proxy.rsplit(":", 1) - self.proxy_port = int(self.proxy_port) + self.ip, self.port = config.tor_controller_split() + self.proxy_ip, self.proxy_port = config.tor_proxy_split() def start(self): self.log.debug("Starting (Tor: %s)" % config.tor) @@ -214,8 +212,8 @@ class TorManager(object): return False def makeOnionAndKey(self): - res = self.request("ADD_ONION NEW:RSA1024 port=%s" % self.fileserver_port) - match = re.search("ServiceID=([A-Za-z0-9]+).*PrivateKey=RSA1024:(.*?)[\r\n]", res, re.DOTALL) + res = self.request(f"ADD_ONION NEW:ED25519-V3 port={self.fileserver_port}") + match = re.search("ServiceID=([A-Za-z0-9]+).*PrivateKey=ED25519-V3:(.*?)[\r\n]", res, re.DOTALL) # noise. change only "RSA1024" to "ED25519-V3" # refactor, use variable for algo name (single source) if match: onion_address, onion_privatekey = match.groups() return (onion_address, onion_privatekey) diff --git a/src/Ui/UiRequest.py b/src/Ui/UiRequest.py index 8f00efcb..d30ff4e3 100644 --- a/src/Ui/UiRequest.py +++ b/src/Ui/UiRequest.py @@ -124,6 +124,7 @@ class UiRequest(object): ).encode("utf8") return iter([ret_error, ret_body]) + # TODO: phase out .bit support # Prepend .bit host for transparent proxy if self.isDomain(self.env.get("HTTP_HOST")): path = re.sub("^/", "/" + self.env.get("HTTP_HOST") + "/", path) @@ -175,7 +176,7 @@ class UiRequest(object): return self.actionConsole() # Wrapper-less static files elif path.startswith("/raw/"): - return self.actionSiteMedia(path.replace("/raw", "/media", 1), header_noscript=True) + return self.actionSiteMedia(path.replace("/raw", "/media", 1), header_noscript=True, raw=True) elif path.startswith("/add/"): return self.actionSiteAdd() @@ -329,7 +330,7 @@ class UiRequest(object): def renderReplacer(m): if m.group(1) in kwargs: - return "%s" % kwargs.get(m.group(1), "") + return str(kwargs[m.group(1)]) # noise else: return m.group(0) @@ -371,7 +372,7 @@ class UiRequest(object): # Redirect to an url def actionRedirect(self, url): self.start_response('301 Redirect', [('Location', str(url))]) - yield self.formatRedirect(url) + return self.formatRedirect(url) def actionIndex(self): return self.actionRedirect("/" + config.homepage + "/") @@ -541,17 +542,36 @@ class UiRequest(object): if show_loadingscreen is None: show_loadingscreen = not site.storage.isFile(file_inner_path) + def xescape(s): + '''combines parts from re.escape & html.escape''' + # https://github.com/python/cpython/blob/3.10/Lib/re.py#L267 + # '&' is handled otherwise + re_chars = {i: '\\' + chr(i) for i in b'()[]{}*+-|^$\\.~# \t\n\r\v\f'} # sure we must escape all these chars? + # https://github.com/python/cpython/blob/3.10/Lib/html/__init__.py#L12 + html_chars = { + '<' : '<', + '>' : '>', + '"' : '"', + "'" : ''', # why does this mix backslash-escapes and html-escapes? + } + # we can't replace '&' because it makes certain zites work incorrectly + # it should however in no way interfere with re.sub in render + repl = {} + repl.update(re_chars) + repl.update(html_chars) + return s.translate(repl) + return self.render( "src/Ui/template/wrapper.html", server_url=server_url, inner_path=inner_path, - file_url=re.escape(file_url), - file_inner_path=re.escape(file_inner_path), + file_url=xescape(file_url), + file_inner_path=xescape(file_inner_path), address=site.address, - title=html.escape(title), + title=xescape(title), body_style=body_style, meta_tags=meta_tags, - query_string=re.escape(inner_query_string), + query_string=xescape(inner_query_string), wrapper_key=site.settings["wrapper_key"], ajax_key=site.settings["ajax_key"], wrapper_nonce=wrapper_nonce, @@ -611,10 +631,12 @@ class UiRequest(object): if "../" in path or "./" in path: raise SecurityError("Invalid path") - match = re.match(r"/media/(?P
    [A-Za-z0-9]+[A-Za-z0-9\._-]+)(?P/.*|$)", path) # match = re.match(r"/ media/ (?P
    [A-Za-z0-9]+[A-Za-z0-9\._-]+)(?P/.*|$)", path) + match = re.match(r"/(media/)?(?P
    [A-Za-z0-9]+[A-Za-z0-9\._-]+)(?P/.*|$)", path) if match: path_parts = match.groupdict() - if self.isDomain(path_parts["address"]): + addr = path_parts["address"] + if self.isDomain(addr): + path_parts["domain"] = addr path_parts["address"] = self.resolveDomain(path_parts["address"]) path_parts["request_address"] = path_parts["address"] # Original request address (for Merger sites) path_parts["inner_path"] = path_parts["inner_path"].lstrip("/") @@ -625,12 +647,19 @@ class UiRequest(object): return None # Serve a media for site - def actionSiteMedia(self, path, header_length=True, header_noscript=False): + def actionSiteMedia(self, path, header_length=True, header_noscript=False, raw=False): try: path_parts = self.parsePath(path) except SecurityError as err: return self.error403(err) + if "domain" in path_parts: + addr = path_parts['address'] + path = path_parts['inner_path'] + query = self.env['QUERY_STRING'] + raw = "/raw" if raw else "" + return self.actionRedirect(f"{raw}/{addr}/{path}?{query}") + if not path_parts: return self.error404(path) @@ -862,20 +891,8 @@ class UiRequest(object): return [b"No error! :)"] # Just raise an error to get console + # Is this even useful anymore? def actionConsole(self): - import sys - sites = self.server.sites - main = sys.modules["main"] - - def bench(code, times=100, init=None): - sites = self.server.sites - main = sys.modules["main"] - s = time.time() - if init: - eval(compile(init, '', 'exec'), globals(), locals()) - for _ in range(times): - back = eval(code, globals(), locals()) - return ["%s run: %.3fs" % (times, time.time() - s), back] raise Exception("Here is your console") # - Tests - diff --git a/src/Ui/UiServer.py b/src/Ui/UiServer.py index 9d93ccfd..08820832 100644 --- a/src/Ui/UiServer.py +++ b/src/Ui/UiServer.py @@ -13,6 +13,7 @@ from Config import config from Debug import Debug import importlib +from util import helper # Skip websocket handler if not necessary class UiWSGIHandler(WebSocketHandler): @@ -144,18 +145,7 @@ class UiServer: self.log.info("Web interface: http://%s:%s/" % (config.ui_ip, config.ui_port)) self.log.info("--------------------------------------") - if config.open_browser and config.open_browser != "False": - logging.info("Opening browser: %s...", config.open_browser) - import webbrowser - try: - if config.open_browser == "default_browser": - browser = webbrowser.get() - else: - browser = webbrowser.get(config.open_browser) - url = "http://%s:%s/%s" % (config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage) - gevent.spawn_later(0.3, browser.open, url, new=2) - except Exception as err: - print("Error starting browser: %s" % err) + helper.openBrowser(config.open_browser) self.server = WSGIServer((self.ip, self.port), handler, handler_class=UiWSGIHandler, log=self.log) self.server.sockets = {} diff --git a/src/Ui/UiWebsocket.py b/src/Ui/UiWebsocket.py index 88e395d6..e6f2f405 100644 --- a/src/Ui/UiWebsocket.py +++ b/src/Ui/UiWebsocket.py @@ -10,6 +10,8 @@ import stat import gevent +from rich import print + from Config import config from Site import SiteManager from Crypt import CryptBitcoin @@ -260,7 +262,14 @@ class UiWebsocket(object): del(content["signers_sign"]) settings = site.settings.copy() - del settings["wrapper_key"] # Dont expose wrapper key + # remove fingerprinting information for non-admin sites + if 'ADMIN' not in self.site.settings['permissions']: # remove for all sites? + del settings['wrapper_key'] + settings['added'] = 0 + settings['serving'] = True + settings['ajax_key'] = '' + settings['peers'] = 1 + settings['cache'] = {} ret = { "auth_address": self.user.getAuthAddress(site.address, create=create_user), @@ -279,13 +288,26 @@ class UiWebsocket(object): "workers": len(site.worker_manager.workers), "content": content } + if 'ADMIN' not in self.site.settings['permissions']: # remove for all sites? + ret.update({ + "content_updated": 0, + "bad_files": len(site.bad_files), # ? + "size_limit": site.getSizeLimit(), # ? + "next_size_limit": site.getNextSizeLimit(), # ? + "peers": 1, + "started_task_num": 0, + "tasks": 0, + "workers": 0, + }) if site.settings["own"]: ret["privatekey"] = bool(self.user.getSiteData(site.address, create=create_user).get("privatekey")) - if site.isServing() and content: + if site.isServing() and content and "ADMIN" in self.site.settings['permissions']: ret["peers"] += 1 # Add myself if serving return ret def formatServerInfo(self): + # unprivileged sites should not get any fingerprinting information + if "ADMIN" in self.site.settings['permissions']: # remove for all sites? import main file_server = main.file_server if file_server.port_opened == {}: @@ -293,35 +315,61 @@ class UiWebsocket(object): else: ip_external = any(file_server.port_opened.values()) back = { - "ip_external": ip_external, - "port_opened": file_server.port_opened, - "platform": sys.platform, - "fileserver_ip": config.fileserver_ip, - "fileserver_port": config.fileserver_port, - "tor_enabled": file_server.tor_manager.enabled, - "tor_status": file_server.tor_manager.status, - "tor_has_meek_bridges": file_server.tor_manager.has_meek_bridges, - "tor_use_bridges": config.tor_use_bridges, - "ui_ip": config.ui_ip, - "ui_port": config.ui_port, - "version": config.version, - "rev": config.rev, - "timecorrection": file_server.timecorrection, - "language": config.language, - "debug": config.debug, - "offline": config.offline, - "plugins": PluginManager.plugin_manager.plugin_names, - "plugins_rev": PluginManager.plugin_manager.plugins_rev, - "user_settings": self.user.settings + 'ip_external' : ip_external, + 'port_opened' : file_server.port_opened, + 'platform' : sys.platform, + 'dist_type' : config.dist_type, + 'fileserver_ip' : config.fileserver_ip, + 'fileserver_port' : config.fileserver_port, + 'tor_enabled' : file_server.tor_manager.enabled, + 'tor_status' : file_server.tor_manager.status, + 'tor_has_meek_bridges' : file_server.tor_manager.has_meek_bridges, + 'tor_use_bridges' : config.tor_use_bridges, + 'ui_ip' : config.ui_ip, + 'ui_port' : config.ui_port, + 'version' : config.version, + 'rev' : config.rev, + 'timecorrection' : file_server.timecorrection, + 'language' : config.language, + 'debug' : config.debug, + 'offline' : config.offline, + 'plugins' : PluginManager.plugin_manager.plugin_names, + 'plugins_rev' : PluginManager.plugin_manager.plugins_rev, + 'user_settings' : self.user.settings, + 'lib_verify_best' : CryptBitcoin.lib_verify_best + } # noise! indent, quotes + else: + back = { + 'ip_external' : None, + 'port_opened' : False, + 'platform' : 'generic', + 'dist_type' : 'generic', + 'fileserver_ip' : '127.0.0.1', + 'fileserver_port' : 15441, + 'tor_enabled' : True, + 'tor_status' : 'OK', + 'tor_has_meek_bridges' : True, + 'tor_use_bridges' : True, + 'ui_ip' : '127.0.0.1', + 'ui_port' : 43110, + 'version' : config.user_agent, + 'rev' : config.user_agent_rev, + 'timecorrection' : 0.0, + 'language' : config.language, #? + 'debug' : False, + 'offline' : False, + 'plugins' : [], + 'plugins_rev' : {}, + 'user_settings' : self.user.settings #? } - if "ADMIN" in self.site.settings["permissions"]: - back["updatesite"] = config.updatesite - back["dist_type"] = config.dist_type - back["lib_verify_best"] = CryptBitcoin.lib_verify_best return back def formatAnnouncerInfo(self, site): - return {"address": site.address, "stats": site.announcer.stats} + if "ADMIN" in self.site.settings['permissions']: + stats = site.announcer.stats + else: + stats = {} + return {"address": site.address, "stats": stats} # - Actions - @@ -424,10 +472,6 @@ class UiWebsocket(object): extend["cert_sign"] = cert["cert_sign"] self.log.debug("Extending content.json with cert %s" % extend["cert_user_id"]) - if not self.hasFilePermission(inner_path): - self.log.error("SiteSign error: you don't own this site & site owner doesn't allow you to do so.") - return self.response(to, {"error": "Forbidden, you can only modify your own sites"}) - # why if privatekey == "stored": # Get privatekey from sites.json privatekey = self.user.getSiteData(self.site.address).get("privatekey") if not privatekey: @@ -467,6 +511,8 @@ class UiWebsocket(object): # Sign and publish content.json def actionSitePublish(self, to, privatekey=None, inner_path="content.json", sign=True, remove_missing_optional=False, update_changed_files=False): + # TODO: check certificates (https://github.com/zeronet-conservancy/zeronet-conservancy/issues/190) + # TODO: update certificates (https://github.com/zeronet-conservancy/zeronet-conservancy/issues/194) if sign: inner_path = self.actionSiteSign( to, privatekey, inner_path, response_ok=False, @@ -859,7 +905,7 @@ class UiWebsocket(object): @flag.admin def actionPermissionDetails(self, to, permission): if permission == "ADMIN": - self.response(to, _["Modify your client's configuration and access all site"] + " " + _["(Dangerous!)"] + "") + self.response(to, _["Allow this site to administrate your 0net node"] + " " + _["(Make sure you trust site developer before accepting!)"] + "") elif permission == "NOSANDBOX": self.response(to, _["Modify your client's configuration and access all site"] + " " + _["(Dangerous!)"] + "") elif permission == "PushNotification": @@ -954,6 +1000,35 @@ class UiWebsocket(object): else: self.response(to, {"error": "Unknown site: %s" % address}) + def siteFavUnfav(self, to, address, action, response): + dashboard = config.homepage + dsite = self.user.sites.get(dashboard, None) + if not dsite: + raise RuntimeError(f'No dashboard {dashboard} found to add site to favourites') + if 'settings' not in dsite: + dsite['settings'] = {} + dsettings = dsite['settings'] + if 'favorite_sites' not in dsettings: + dsettings['favorite_sites'] = {} + favs = dsettings['favorite_sites'] + action(favs) + self.user.setSiteSettings(dashboard, dsettings) + self.response(to, response) + + @flag.admin + @flag.no_multiuser + def actionSiteFavourite(self, to, address): + def do_add(favs): + favs[address] = True + self.siteFavUnfav(to, address, do_add, "Added to favourites") + + @flag.admin + @flag.no_multiuser + def actionSiteUnfavourite(self, to, address): + def do_del(favs): + del favs[address] + self.siteFavUnfav(to, address, do_del, "Removed from favourites") + @flag.admin @flag.no_multiuser def actionSiteDelete(self, to, address): diff --git a/src/Ui/media/all.css b/src/Ui/media/all.css index bd54cf34..2d10bf85 100644 --- a/src/Ui/media/all.css +++ b/src/Ui/media/all.css @@ -31,21 +31,31 @@ a { color: black } .button.button-2 { background-color: transparent; border: 1px solid #EEE; color: #555 } .button.button-2:hover { border: 1px solid #CCC; color: #000 } -/* Fixbutton */ +/* Fixbutton #50, #53 */ .fixbutton { position: absolute; right: 35px; top: 15px; width: 40px; z-index: 999; text-align: center; color: white; font-family: Consolas, Monaco, monospace; font-size: 25px; } .fixbutton-bg { - -webkit-border-radius: 80px; -moz-border-radius: 80px; -o-border-radius: 80px; -ms-border-radius: 80px; border-radius: 80px ; background-color: rgba(180, 180, 180, 0.5); cursor: pointer; + -webkit-border-radius: 80px; -moz-border-radius: 80px; -o-border-radius: 80px; -ms-border-radius: 80px; border-radius: 80px ; background-color: rgba(180, 180, 180, 0.5); display: block; width: 80px; height: 80px; -webkit-transition: background-color 0.2s, box-shadow 0.5s; -moz-transition: background-color 0.2s, box-shadow 0.5s; -o-transition: background-color 0.2s, box-shadow 0.5s; -ms-transition: background-color 0.2s, box-shadow 0.5s; transition: background-color 0.2s, box-shadow 0.5s ; -webkit-transform: scale(0.6); -moz-transform: scale(0.6); -o-transform: scale(0.6); -ms-transform: scale(0.6); transform: scale(0.6) ; margin-left: -20px; margin-top: -20px; /* 2x size to prevent blur on anim */ - /*box-shadow: inset 105px 260px 0 -200px rgba(0,0,0,0.1);*/ /* -webkit-box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); -moz-box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); -o-box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); -ms-box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1); box-shadow: inset -75px 183px 0 -200px rgba(0,0,0,0.1) ; */ + + background-color: #FFF; + background-image: url('img/logo.png'); + background-size: 60px; + background-position-x: 10px; + background-position-y: 10px; + background-repeat: no-repeat; + + cursor: url('img/fixbutton-left-down.png') 4 12, auto; +} +.body-sidebar .fixbutton-bg { + cursor: url('img/fixbutton-right-down.png') 4 12, auto; +} +.body-console .fixbutton-bg { + cursor: url('img/fixbutton-left-up.png') 4 12, auto; } -.fixbutton-text { pointer-events: none; position: absolute; z-index: 999; width: 40px; -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -o-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden ; -webkit-perspective: 1000px; -moz-perspective: 1000px; -o-perspective: 1000px; -ms-perspective: 1000px; perspective: 1000px ; line-height: 0; padding-top: 5px; opacity: 0.9 } -.fixbutton-burger { pointer-events: none; position: absolute; z-index: 999; width: 40px; opacity: 0; left: -20px; font-size: 40px; line-height: 0; font-family: Verdana, sans-serif; margin-top: 17px } -.fixbutton-bg:hover { background-color: #AF3BFF } -.fixbutton-bg:active { background-color: #9E2FEA; top: 1px; -webkit-transition: none ; -moz-transition: none ; -o-transition: none ; -ms-transition: none ; transition: none } /* Notification */ diff --git a/src/Ui/media/img/apple-touch-icon.png b/src/Ui/media/img/apple-touch-icon.png index 962bd31a..94d0df6e 100644 Binary files a/src/Ui/media/img/apple-touch-icon.png and b/src/Ui/media/img/apple-touch-icon.png differ diff --git a/src/Ui/media/img/favicon.ico b/src/Ui/media/img/favicon.ico index 3f75912a..38e78d7d 100644 Binary files a/src/Ui/media/img/favicon.ico and b/src/Ui/media/img/favicon.ico differ diff --git a/src/Ui/media/img/fixbutton-left-down.png b/src/Ui/media/img/fixbutton-left-down.png new file mode 100644 index 00000000..db74dbe0 Binary files /dev/null and b/src/Ui/media/img/fixbutton-left-down.png differ diff --git a/src/Ui/media/img/fixbutton-left-up.png b/src/Ui/media/img/fixbutton-left-up.png new file mode 100644 index 00000000..9ffba108 Binary files /dev/null and b/src/Ui/media/img/fixbutton-left-up.png differ diff --git a/src/Ui/media/img/fixbutton-right-down.png b/src/Ui/media/img/fixbutton-right-down.png new file mode 100644 index 00000000..f2e6c188 Binary files /dev/null and b/src/Ui/media/img/fixbutton-right-down.png differ diff --git a/src/Ui/media/img/logo.png b/src/Ui/media/img/logo.png index a39cfc41..f228339b 100644 Binary files a/src/Ui/media/img/logo.png and b/src/Ui/media/img/logo.png differ diff --git a/src/Ui/template/wrapper.html b/src/Ui/template/wrapper.html index fe5a3f9c..f65c5066 100644 --- a/src/Ui/template/wrapper.html +++ b/src/Ui/template/wrapper.html @@ -38,8 +38,10 @@ else if (window.opener && window.opener.location.toString()) {
    -
    +
    diff --git a/src/lib/libsecp256k1message/libsecp256k1message.py b/src/lib/libsecp256k1message/libsecp256k1message.py index 59768b88..5cc97ecf 100644 --- a/src/lib/libsecp256k1message/libsecp256k1message.py +++ b/src/lib/libsecp256k1message/libsecp256k1message.py @@ -4,6 +4,7 @@ from coincurve import PrivateKey, PublicKey from base58 import b58encode_check, b58decode_check from hmac import compare_digest from util.Electrum import format as zero_format +from ..sslcrypto._ecc import ripemd160 RECID_MIN = 0 RECID_MAX = 3 @@ -41,7 +42,7 @@ def compute_secret_address(secretkey): def public_digest(publickey, compressed=False): """Convert a public key to ripemd160(sha256()) digest.""" publickey_hex = publickey.format(compressed=compressed) - return hashlib.new('ripemd160', hashlib.sha256(publickey_hex).digest()).digest() + return ripemd160(hashlib.sha256(publickey_hex).digest()).digest() def address_public_digest(address): """Convert a public Bitcoin address to ripemd160(sha256()) digest.""" diff --git a/src/main.py b/src/main.py index 8a677193..a5e15070 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,3 @@ -# Included modules import os import sys import stat @@ -25,56 +24,67 @@ gevent.monkey.patch_all(thread=False, subprocess=False) update_after_shutdown = False # If set True then update and restart zeronet after main loop ended restart_after_shutdown = False # If set True then restart zeronet after main loop ended -# Load config from Config import config -config.parse(silent=True) # Plugins need to access the configuration -if not config.arguments: # Config parse failed, show the help screen and exit + +def load_config(): + config.parse(silent=True) # Plugins need to access the configuration + if not config.arguments: + # Config parse failed completely, show the help screen and exit config.parse() -if not os.path.isdir(config.data_dir): +load_config() + +def init_dirs(): + if not os.path.isdir(config.data_dir): os.mkdir(config.data_dir) try: os.chmod(config.data_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) except Exception as err: startupError("Can't change permission of %s: %s" % (config.data_dir, err)) -if not os.path.isfile("%s/sites.json" % config.data_dir): - open("%s/sites.json" % config.data_dir, "w").write("{}") -if not os.path.isfile("%s/users.json" % config.data_dir): - open("%s/users.json" % config.data_dir, "w").write("{}") + sites_json = f"{config.data_dir}/sites.json" + if not os.path.isfile(sites_json): + with open(sites_json, "w") as f: + f.write("{}") + users_json = f"{config.data_dir}/users.json" + if not os.path.isfile(users_json): + with open(users_json, "w") as f: + f.write("{}") # noise + +# TODO: GET RID OF TOP-LEVEL CODE!!! + +try: + init_dirs() +except: + import traceback as tb + print(tb.format_exc()) + # at least make sure to print help if we're otherwise so helpless + config.parser.print_help() + sys.exit(1) # just raise error if config.action == "main": from util import helper try: - lock = helper.openLocked("%s/lock.pid" % config.data_dir, "w") - lock.write("%s" % os.getpid()) + lock = helper.openLocked(f"{config.data_dir}/lock.pid", "w") + lock.write(f"{os.getpid()}") # noise except BlockingIOError as err: - startupError("Can't open lock file, your ZeroNet client is probably already running, exiting... (%s)" % err) - if config.open_browser and config.open_browser != "False": - print("Opening browser: %s...", config.open_browser) - import webbrowser - try: - if config.open_browser == "default_browser": - browser = webbrowser.get() - else: - browser = webbrowser.get(config.open_browser) - browser.open("http://%s:%s/%s" % ( - config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage - ), new=2) - except Exception as err: - startupError("Error starting browser: %s" % err) - sys.exit() + startupError(f"Can't open lock file, your 0net client is probably already running, exiting... ({err})") # noise + proc = helper.openBrowser(config.open_browser) + r = proc.wait() + sys.exit(r) config.initLogging() # Debug dependent configuration from Debug import DebugHook - -# Load plugins from Plugin import PluginManager -PluginManager.plugin_manager.loadPlugins() -config.loadPlugins() -config.parse() # Parse again to add plugin configuration options + +def load_plugins(): + PluginManager.plugin_manager.loadPlugins() + config.loadPlugins() + config.parse() # Parse again to add plugin configuration options + +load_plugins() # noise # Log current config logging.debug("Config: %s" % config) @@ -112,7 +122,7 @@ elif config.tor == "always": logging.info("Patching sockets to tor socks proxy: %s" % config.tor_proxy) if config.fileserver_ip == "*": config.fileserver_ip = '127.0.0.1' # Do not accept connections anywhere but localhost - SocksProxy.monkeyPatch(*config.tor_proxy.split(":")) + SocksProxy.monkeyPatch(*config.tor_proxy_split()) config.disable_udp = True elif config.bind: bind = config.bind diff --git a/src/util/UpnpPunch.py b/src/util/UpnpPunch.py index 18f4aaee..2fa85209 100644 --- a/src/util/UpnpPunch.py +++ b/src/util/UpnpPunch.py @@ -3,8 +3,7 @@ import urllib.request import http.client import logging from urllib.parse import urlparse -from xml.dom.minidom import parseString -from xml.parsers.expat import ExpatError +from defusedxml.minidom import parseString from gevent import socket import gevent @@ -105,7 +104,7 @@ def _parse_igd_profile(profile_xml): """ try: dom = parseString(profile_xml) - except ExpatError as e: + except Exception as e: raise IGDError( 'Unable to parse IGD reply: {0} \n\n\n {1}'.format(profile_xml, e)) diff --git a/src/util/helper.py b/src/util/helper.py index 61455b08..a0daa557 100644 --- a/src/util/helper.py +++ b/src/util/helper.py @@ -209,7 +209,11 @@ def httpRequest(url, as_file=False): conn = http.client.HTTPSConnection(host) sock = socket.create_connection((conn.host, conn.port), conn.timeout, conn.source_address) - conn.sock = ssl.wrap_socket(sock, conn.key_file, conn.cert_file) + + context = ssl.create_default_context() + context.minimum_version = ssl.TLSVersion.TLSv1_2 + + conn.sock = context.wrap_socket(sock, conn.key_file, conn.cert_file) conn.request("GET", request) response = conn.getresponse() if response.status in [301, 302, 303, 307, 308]: @@ -354,3 +358,14 @@ def encodeResponse(func): # Encode returned data from utf8 to bytes yield back.encode() return wrapper + +def openBrowser(agent): + if agent and agent != "False": + print(f"Opening browser: {agent}...") + ui_ip = config.ui_ip if config.ui_ip != "*" else "127.0.0.1" + url = f'http://{ui_ip}:{config.ui_port}/{config.homepage}' + try: + import subprocess + return subprocess.Popen([config.open_browser, url]) + except Exception as err: + print(f"Error starting browser: {err}") diff --git a/start-venv.sh b/start-venv.sh new file mode 100755 index 00000000..33ac3216 --- /dev/null +++ b/start-venv.sh @@ -0,0 +1,8 @@ +#! /usr/bin/env bash + +if [ ! -f venv/bin/activate ] ; then + python3 -m venv venv +fi +source venv/bin/activate +python3 -m pip install -r requirements.txt +python3 zeronet.py $1 $2 $3 $4 $5 $6 $7 $8 $9 diff --git a/termux.sh b/termux.sh new file mode 100644 index 00000000..1eb365b1 --- /dev/null +++ b/termux.sh @@ -0,0 +1,20 @@ + +# Script for running zeronet-conservancy in Termux on Android + +if [[ -d zeronet-conservancy ]]; then + cd zeronet-conservancy + git pull --ff-only +else + git clone https://github.com/zeronet-conservancy/zeronet-conservancy + cd zeronet-conservancy +fi + +pkg update -y +pkg install -y python automake git binutils tor + +echo "Starting tor..." +tor --ControlPort 9051 --CookieAuthentication 1 >/dev/null & + +echo "Starting zeronet-conservancy..." +./start-venv.sh +cd .. diff --git a/update.py b/update.py index cf9898f9..62106469 100644 --- a/update.py +++ b/update.py @@ -6,6 +6,13 @@ import shutil def update(): + print('please update zeronet-conservancy via git. usually it can be done via single commnad') + print(' git pull') + print('although it depends on your branches setup') + print('updating through 1update site is not considered safe at the moment') + print('if you really want to use it, edit this file') + return False + from Config import config config.parse(silent=True) diff --git a/zeronet.py b/zeronet.py index dacd2096..1106c925 100755 --- a/zeronet.py +++ b/zeronet.py @@ -1,15 +1,16 @@ #!/usr/bin/env python3 import os import sys - +from src.Config import config def main(): if sys.version_info.major < 3: print("Error: Python 3.x is required") sys.exit(0) - if "--silent" not in sys.argv: - print("- Starting ZeroNet...") + if '--silent' not in sys.argv: + from greet import fancy_greet + fancy_greet(config.version) # gay rainbow main = None try: @@ -23,11 +24,10 @@ def main(): except Exception as log_err: print("Failed to log error:", log_err) traceback.print_exc() - from Config import config error_log_path = config.log_dir + "/error.log" traceback.print_exc(file=open(error_log_path, "w")) print("---") - print("Please report it: https://github.com/HelloZeroNet/ZeroNet/issues/new?assignees=&labels=&template=bug-report.md") + print("Please report it: https://github.com/zeronet-conservancy/zeronet-conservancy/issues/new?template=bug-report.md") # single-source issues url if sys.platform.startswith("win") and "python.exe" not in sys.executable: displayErrorMessage(err, error_log_path) @@ -66,8 +66,8 @@ def displayErrorMessage(err, error_log_path): res = ctypes.windll.user32.MessageBoxW(0, err_title, "ZeroNet error", MB_YESNOCANCEL | MB_ICONEXCLAIMATION) if res == ID_YES: import webbrowser - report_url = "https://github.com/HelloZeroNet/ZeroNet/issues/new?assignees=&labels=&template=bug-report.md&title=%s" - webbrowser.open(report_url % urllib.parse.quote("Unhandled exception: %s" % err_message)) + report_url = "https://github.com/zeronet-conservancy/zeronet-conservancy/issues/new" + webbrowser.open(report_url) if res in [ID_YES, ID_NO]: subprocess.Popen(['notepad.exe', error_log_path]) ```
    caryoscelus commented 1 year ago

    at least its suspicious that zeronet-conservancy is 1453 commits ahead, 51 commits behind HelloZeroNet:master.

    git diff py3 conservancy

    comparing amount of commits in master

    yay , more noise in a stupid thread

    which makes auditing harder

    diff with my comments (2406 lines)

    congrats , you are the first person to publicly attempt that

    unfortunately you didn't do good enough job of it , which is only logical since you were busy entertaining your aggression and homophobia

    if you have any real issues with code , kindly go and report them at https://github.com/zeronet-conservancy/zeronet-conservancy/issues

    milahu commented 1 year ago

    congrats , you are the first person to publicly attempt that

    thats because youre too stupid to produce minimal diffs

    if you have any real issues with code

    hard-to-audit code (diff noise) is a real issue

    ghost commented 1 year ago

    Just to clear some things up. I'm Krixano, the developer who created ZeroMedium, KxoVid, ZeroExchange, and a bunch of other things. I also worked with @imachug on various things, including their ZeroNet streaming work, a new ID (KxoID) that would be a midway between decentralized and centralized, using a trust system, and a few other projects.

    If I'm remembering correctly, the license change was actually a response to someone who complained about licensing violations/incompatibilities and was threatening to report the project for this, and the idea was to move from GPLv2 to GPLv3 because of the dependencies that ZeroNet had relied on required this. So, we created an issue so that all collaborators may vote on whether the license should be changed or not, since this is what is legally required for those who contributed significantly to the project. I left this community various times during and after this because the spam, spitefulness, and vitriol coming from certain individuals was too much to bear. My last ZeroNet projects were probably in 2019.

    You can find more information about this here: #2273 To say that this was a supposed manipulation is weird. All contributors voted with good intentions to try to free ZeroNet from its license incompatibilities in a legal way. We all did the best we could with the information we had. Reminder: The vote was to move from GPLv2 to GPLv3 or GPLv3+. Some people also would have accepted a Lax license as well, but most seemed to be fine with GPL or Lax. If your issue is with GPLv3 vs. GPLv3+, I really don't know what to tell you, because I think that is a poor excuse to be mad at people and lie about them.

    @imachug is a very skilled developer who worked on this official ZeroNet repo prior to it seemingly being abandoned. They also worked on a lot of other projects along with me, Anthyg, mkg2001, and a few other people. I do not know much about what imachug has done after 2020, but I do know that I enjoyed working with them when I did.

    It looks like the ZeroNet community is worse than ever. That's unfortunate.

    I do want to take the time to thank @imachug, @AnthyG, @mkg20001 , @rllola , @styromaniac, @Thunder33345 , @anoadragon453 , @shortcutme , and everyone else whose names I am having trouble remembering, who were the highlights of ZeroNet at the time. Even if this period of time was a time of a lot of stress, it was still a fun experience working on decentralized projects with you all. I truly believe we all had good intentions and tried our very best, and we all grew as programmers in the process.

    Regarding any ZeroNet forks: I have no idea what the situation is, who is lying about who, who changed or manipulated the Wiki page, etc. I don't intend to get involved in that. What I am doing is detailing the reasons for the license change, and supporting the various people that I have worked with on this project. I wish the best for all of these people, even those whom I have had differences with in the past. The project might not have been the most successful, but I believe it changed each of us for the better.

    styromaniac commented 1 year ago

    It looks like the ZeroNet community is worse than ever. That's unfortunate.

    There are rare instances of bigotry in the main forums. Aside from that, just spammers trying to drive people away. I think the bigger problem is that we don't have much of other conversations going on there to drown out what little noise there is coming from the bigots, but the bigots have no sway either way, but rather expose their ignorance.

    I do want to take the time to thank @imachug, @AnthyG, @mkg20001 , @rllola , @styromaniac, @Thunder33345 , @anoadragon453 , @shortcutme , and everyone else whose names I am having trouble remembering, who were the highlights of ZeroNet at the time. Even if this period of time was a time of a lot of stress, it was still a fun experience working on decentralized projects with you all. I truly believe we all had good intentions and tried our very best, and we all grew as programmers in the process.

    Thank you. Yes, I believe as well we're all the better now that we're older and wiser than before.

    I'm glad to see that you're doing well.

    ghost commented 1 year ago

    @styromaniac Hello! Long time no see! lol

    I'm glad there's someone still here that I actually know. I got an email from imachug almost a year ago, but I missed it because I stopped using protonmail. Hopefully they will see the email I just wrote back. Are you still on ZeroNet? I'm thinking about republishing many of my zites that I had deleted. Somehow, I feel like I need to help rebuild ZeroNet back up after what I did. It's hard to see how vacant ZeroNet seems after having been in the community for those few years.

    purplesyringa commented 1 year ago

    @krixano Check your inbox :)

    canewsin commented 1 year ago

    I don't know who hates ZeroNetX so much, people literally fighting over adding/removal of content on Wikipedia. See the diff and edit history 20 February 2023‎ to 7 March 2023‎, there aren't any references to ZeroNetX on live version.

    styromaniac commented 1 year ago

    @styromaniac Hello! Long time no see! lol

    I'm glad there's someone still here that I actually know. I got an email from imachug almost a year ago, but I missed it because I stopped using protonmail. Hopefully he will see the email I just wrote back. Are you still on ZeroNet? I'm thinking about republishing many of my zites that I had deleted. Somehow, I feel like I need to help rebuild ZeroNet back up after what I did. It's hard to see how vacant ZeroNet seems after having been in the community for those few years.

    I'm still there, but not talking a whole lot. My attention is stretched between playing on my Steam Deck and using AI chat bots to write a program.

    anoadragon453 commented 1 year ago

    Hello folks :) I'm still around, and active on matrix if you ever want to talk.

    I'll admit I haven't used ZeroNet in forever, but still have yet to see something come as close as it did to a useable p2p internet replacement.

    styromaniac commented 1 year ago

    I don't know who hates ZeroNetX so much, people literally fighting over adding/removal of content on Wikipedia. See the diff and edit history 20 February 2023‎ to 7 March 2023‎, there aren't any references to ZeroNetX on live version.

    A former developer I won't name, Silicon Valley, the Chinese Communist Party, and I'll let you guess the last one. Hint: They're at the very tippy top in society.

    caryoscelus commented 1 year ago

    regarding wikipedia editing, it'd be nice if someone fork-neutral would write up new and/or revise deleted content and 0net users actually contribute to editing beside revert-wars (including contacting appropriate wp authorities)

    ghost commented 1 year ago

    I don't know who hates ZeroNetX so much, people literally fighting over adding/removal of content on Wikipedia. See the diff and edit history 20 February 2023‎ to 7 March 2023‎, there aren't any references to ZeroNetX on live version.

    @canewsin What seems to have happened was that people kept complaining that certain repos were scams. One of the wiki editors kept on reverting them until they finally gave up and removed all of the fork links. However, ZeroNet-Convervancy was still kept in the wiki article. I haven't looked into why exactly though, but I believe it was being mentioned in a different section of the article than the others were.

    I'm going to try to get things sorted out in the Wikipedia page, because so far what's happening there is completely unacceptable and against the spirit of Wikipedia. The editors need to stop letting wiki vandalizers run the show.

    ghost commented 1 year ago

    @krixano @caryoscelus @canewsin @styromaniac

    I edit Wikipedia; if you have a fork, please create a new Wikipedia article instead of trolling and destroying the ZeroNet article.

    You are not permitted to promote your nonsense on the parent software's page simply because you work on a fork.

    Because Bitcoin BSV and Bitcoin Cash are forks of Bitcoin, Roger Ver cannot be listed as a developer on the Bitcoin Wikipedia page. That'd be hilarious.

    You are welcome to create a new article for your software, which should ideally be named something other than ZeroNet.

    ghost commented 1 year ago

    I'm a ZeroNet developer, and I did not and will not allow trolling of the ZeroNet article on WikiPedia. That article must be preserved because I link to ZeroNet from my fork, which is far superior to all other forks.

    It irritates me to see @caryoscelus spamming his/her shitfork with minor changes and new donation addresses.

    I could have done the same thing three years ago because my fork is the oldest ZeroNet fork that has ever existed. It was also the first fork to implement Tor v3 before any other fork.

    caryoscelus commented 1 year ago

    @redarmyfaction please stop destructive behaviour

    You are welcome to create a new article for your software

    you should read wikipedia rules about that. i bet none of the existing forks would be considered worth of an article

    otherwise all forks work within the same network dubbed ZeroNet or 0net and the page is about network/protocol, not just particular implementation

    as an example of forks being mentioned on original project page, here are a few examples:

    Because Bitcoin BSV and Bitcoin Cash are forks of Bitcoin, Roger Ver cannot be listed as a developer on the Bitcoin Wikipedia page

    i don't follow bitcoin developers, but bitcoin page does mention both forks and implementations of protocol

    I'm a ZeroNet developer

    proof?

    That article must be preserved because I link to ZeroNet from my fork, which is far superior to all other forks. It irritates me to see @caryoscelus spamming his/her shitfork with minor changes and new donation addresses.

    read some history, it's all in the open on wp and there's hundreds of threads on 0net. the only reason i've edited the page is because i had a page linking to wikipedia ZeroNet article and somebody "anonymous" edited it to claim ZNX (back then unnamed fork by @canewsin) is the continuation of ZeroNet

    also, would you kindly link to your fork 'cause it's not in your profile

    ghost commented 1 year ago

    @caryoscelus You are the only one who is "destructive."

    My fork does not exist on GitHub.

    On the other hand, I have made more direct and indirect contributions to ZeroNet than you. Tamas Kocsis also merged many of my improvements, as everyone is aware. I'm also the one who started the license change, where I announced my fork several times.

    Your perception of "history" is completely false, and you're nothing more than an ugly troll who "maintains existing codebase and adds features aimed at gradually migrating to a new p2p network designed from scratch," according to your stupid Wikipedia edits.

    caryoscelus commented 1 year ago

    My fork does not exist on GitHub.

    doesn't matter. if it exists, show it. otherwise, it's not a fork, but just a private project

    On the other hand, I have made more direct and indirect contributions to ZeroNet than you. Tamas Kocsis also merged many of my improvements, as everyone is aware. I'm also the one who started the license change, where I announced my fork several times.

    we'd move along faster if you'd provide any links to your claims coming from an empty account

    Your perception of "history" is completely false, and you're nothing more than an ugly troll

    enjoy your fantasy world :D

    ghost commented 1 year ago

    @caryoscelus

    doesn't matter. if it exists, show it. otherwise, it's not a fork, but just a private project

    enjoy your fantasy world :D

    You appear to be suffering from severe brain damage.

    ghost commented 1 year ago

    @krixano It would be fantastic if you could publish the source code for your ID service and base the ID creation on Bitmessage, as the original ZeroID service did. Though I'm not sure if that worked.

    I'd like to see an ID service built entirely on Bitmessage. Maybe @imachug can help with this as well, because gitcenter also requires IDs.

    ghost commented 1 year ago

    I just want to point out that Github clearly marks people who are contributors as such on each of their messages, so it's easy to see who contributed to the repo and who didn't.

    ghost commented 1 year ago

    @redarmyfaction If I were to be releasing anything, it will be with permission from Imachug first, as he was a significant developer in the KxoId stuff. KxoId doesn't need Bitmessage, because it uses PeerMessage. There was already an ID service that used Bitmessage, it became outdated very quickly and was overly complicated.

    Btw, what's your id/username on ZeroNet?

    ghost commented 1 year ago

    I just want to point out that Github clearly marks people who are contributors as such on each of their messages, so it's easy to see who contributed to the repo and who didn't.

    No way, especially when it comes to accounts that have been hidden by GitHub staff. Remember how many accounts were suspended when the issue of the license change was "discussed"?

    There was already an ID service that used Bitmessage, it became outdated very quickly and was overly complicated.

    That is the original ID service, ZeroID.

    Actually, I prefer the original Bitmessage-based ID service version.

    PeerMessage's source code is extremely filthy. I wouldn't have time to rewrite and debug it, and using several different plugins with ZeroNet isn't ideal.

    Btw, what's your id/username on ZeroNet?

    Today, I don't have the keys to any ID. No ID services work, not even your KxBsID stuff that you named after yourself instead of helping the community, isn't that funny? Some of you want to publish my fork (which has been published for years), but when I ask for a plugin, "oh no, we need permission".

    @imachug is an old friend from Russia. Isn't that correct, @imachug? As you know, both @imachug, and @krixano was enraged by the license change, but @imachug at least defended Richard Stallman when he needed it.

    Keep hiding in your rabbit hole, @krixano, and keep your source code, we don't really need you.

    ghost commented 1 year ago

    @milahu is 100% correct about @caryoscelus. She is a fucking scammer who removes code from ZeroNet and then sends another pull request weeks later to "fix this and that." She intentionally causes problems in her own fork in order to later send a "fix pr" and artificially inflate her commits in order to pretend that she maintains the only fork that should completely replace ZeroNet.

    @milahu https://github.com/zeronet-conservancy/zeronet-conservancy/commit/ddde202879c6357799a8ab20319ba6fd60a66e00

    @caryoscelus, learn English and stop spamming your disgusting fork that no one uses, you filthy troll.

    ghost commented 1 year ago

    @caryoscelus literally has no idea how imports work in ZeroNet, so she removed imports and destroyed the site's function in her own fork that she wants everyone to use. Hilarious.

    ghost commented 1 year ago

    @redarmyfaction You know, during the license change, I precisely remember this person who threatened legal action against ZeroNet due to a license violation. They had various accounts, but one of those accounts has the username "antifa". They claimed some developers "stole" their code, which was proven to be false (I still have the video showing this). They made multiple accounts to try to get around bans, which is against Github's policy, and hence why so many of those accounts were suspended.

    KxoId doesn't work right now because it's deliberately down. If it's BS, then why do you need the source code for it? It uses PeerMessage, which you've just stated you don't like and don't want to use, so there's no point in giving it to you. Especially when the code is actually so simple and how it works is outlined very clearly on the KxoNetwork site that any real programmer could implement it themselves very easily.

    You do not want to mess with me @redarmyfaction .

    ghost commented 1 year ago

    KxoId doesn't work right now because it's deliberately down.

    Stop trolling or I'll deliberately fart into your mouth.

    You do not want to mess with me @redarmyfaction

    Be careful not to slip on the ice and injure yourself, my boy.

    They had various accounts, but one of those accounts was "antifa".

    Are you some Trump-supporting neo-nazi scumbag who comes to GitHub and screams "antifa" when confronted with reality?

    @krixano, I told you to go. You are not welcome here with your threats.

    ghost commented 1 year ago

    For people wondering who this person is, I have a list of issues regarding them. They have been pathetically spamming ZeroNet since 2019 and have used various usernames including "antifa", "Hacktivist", "zeronettimemachine", "LiberateZeroNet", "whichpartdontyouunderstand" and various other accounts that were created during 2019 to get around him being blocked from the repo:

    https://github.com/HelloZeroNet/ZeroNet/issues/2283 https://github.com/HelloZeroNet/ZeroNet/issues/2269#issuecomment-549127471 https://github.com/HelloZeroNet/ZeroNet/issues/2361#issuecomment-565822406 https://github.com/HelloZeroNet/ZeroNet/issues/2258#issuecomment-547493095 https://github.com/HelloZeroNet/ZeroNet/issues/2293#issuecomment-550482144 https://github.com/HelloZeroNet/ZeroNet/issues/2290#issuecomment-550486944 https://github.com/HelloZeroNet/ZeroNet/issues/2269#issuecomment-552054895 https://github.com/HelloZeroNet/ZeroNet/issues/2294 https://github.com/HelloZeroNet/ZeroNet/issues/2298 https://github.com/HelloZeroNet/ZeroNet/issues/2304 https://github.com/HelloZeroNet/ZeroNet/pull/2331 https://github.com/HelloZeroNet/ZeroNet/pull/2296 https://github.com/HelloZeroNet/ZeroNet/pull/2255 https://github.com/HelloZeroNet/ZeroNet/pull/2248 https://github.com/HelloZeroNet/ZeroNet/issues/2256

    Here's a comment I made in 2019 on how many usernames this person has had, and all the reports that I had to send in to Github's staff about this person's spamming: https://github.com/HelloZeroNet/ZeroNet/pull/2248#issuecomment-550093707

    ghost commented 1 year ago

    @krixano You make me think of Larry! https://www.youtube.com/watch?v=siZQX98TsJ0

    ghost commented 1 year ago

    @imachug became a transgender person before he was Ivan, and she is now known as "Alisa." :rofl: I guess the copyright notices are no longer valid.

    ghost commented 1 year ago

    @caryoscelus is back to trolling and spamming ZeroNet users and defrauding ZeroNet users. I recommend that everyone avoid her once more.

    She is an ugly troll who spams Reddit, WikiPedia, GitHub, and other GitHub repositories to promote her cryptocurrency scam.

    ghost commented 1 year ago

    Take a look at how aggressive @krixano and @caryoscelus have become. I strongly advise everyone to completely ignore these trolls. Don't download anything from these criminal scammers who are attempting to defraud ZeroNet users with hostile forks.

    Only Use the ZeroNet Enhanced fork from @wandrien: https://github.com/zeronet-enhanced

    https://github.com/zeronet-enhanced/ZeroNet