-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Spammers attack ZeroNet with hostile forks in order to defraud ZeroNet users. #2823
Comments
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? |
@paboum don't pay attention to the troll ;) they've been spreading FUD around |
@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! @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. |
read changelog
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 |
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 |
This is something we discussed not long ago. @caryoscelus, pay close attention. 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. |
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. |
Official ZeroNet fork? It'd be amusing. @caryoscelus is on her way to prison. |
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. |
so many claims, so little evidence ... at least its suspicious that zeronet-conservancy is
question as always: stupid or evil? lets read some diff ... spoiler: just stupid, not evil. clone, diffcd $(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 --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 <https://www.gnu.org/licenses/>.
-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 <b>%s</b>?"] % html.escape(cert_user_id), _["Mute"]],
- lambda res: self.cbMuteAdd(to, auth_address, cert_user_id, reason)
+ "prompt",
+ [_["Hide all content from <b>%s</b>?"] % 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(_("""
<li>
@@ -175,6 +176,7 @@ class UiWebsocketPlugin(object):
{_[Files]}
<a href='/list/{site.address}' class='link-right link-outline' id="browse-files">{_[Browse files]}</a>
<small class="label-right">
+ <a href='#Site+directory' id='link-directory' class='link-right'>{_[Open site directory]}</a>
<a href='/ZeroNet-Internal/Zip?address={site.address}' id='link-zip' class='link-right' download='site.zip'>{_[Save as .zip]}</a>
</small>
</label>
@@ -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(_("""
<li>
<label>{_[Site control]}</label>
<a href='#Update' class='button noupdate' id='button-update'>{_[Update]}</a>
<a href='#Pause' class='button {class_pause}' id='button-pause'>{_[Pause]}</a>
+ <a href='#Favourite' class='button {class_favourite}' id='button-favourite'>{_[Favourite]}</a>
+ <a href='#Unfavourite' class='button {class_unfavourite}' id='button-unfavourite'>{_[Unfavourite]}</a>
# gay british english. its "favorite"
<a href='#Resume' class='button {class_resume}' id='button-resume'>{_[Resume]}</a>
<a href='#Delete' class='button noupdate' id='button-delete'>{_[Delete]}</a>
</li>
"""))
- donate_key = site.content_manager.contents.get("content.json", {}).get("donate", True)
site_address = self.site.address
body.append(_("""
<li>
<label>{_[Site address]}</label><br>
<div class='flex'>
<span class='input text disabled'>{site_address}</span>
- """))
- 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(_("""
</div>
</li>
+ """))
+ 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(_("""
<li>
<label>{_[Donate]}</label><br>
+ """))
+ if donate_generic:
+ body.append(_("""
<div class='flex'>
- {donate_key}
+ {donate_generic}
+ </div>
"""))
- else:
+ if donate_btc:
+ body.append(_("""
+ <div class='flex'>
+ <span style="font-size:90%">{donate_btc}</span><br/>
+ </div>
+ <div class='flex'>
+ <a href='bitcoin:{donate_btc}' class='button'>{_[Donate BTC]}</a>
+ </div>
+ """))
+ if donate_xmr:
body.append(_("""
- <a href='bitcoin:{site_address}' class='button' id='button-donate'>{_[Donate]}</a>
+ <div class='flex'>
+ <span style="font-size:90%">{donate_xmr}</span><br/>
+ </div>
+ <div class='flex'>
+ <a href='monero:{donate_xmr}' class='button'>{_[Donate Monero]}</a>
+ </div>
"""))
+ if donate_ada:
body.append(_("""
+ <div class='flex'>
+ <span style="font-size:90%">{donate_ada}</span><br/>
+ </div>
+ <div class='flex'>
+ <a href='web+cardano:{donate_ada}' class='button'>{_[Donate Ada/Cardano]}</a>
</div>
+ """))
# refactor! use a template with 3 variables: address, protocol, label
+ if donate_enabled:
+ body.append(_("""
</li>
"""))
@@ -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 <https://www.gnu.org/licenses/>.
+##
+
+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<address>[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", 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 <djb@cr.yp.to>
+# 2013 by Donald Stufft <donald@stufft.io>
+# 2013 by Alex Gaynor <alex.gaynor@gmail.com>
+# 2013 by Greg Price <price@mit.edu>
+#
+# 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
+# <http://creativecommons.org/publicdomain/zero/1.0/>.
+
+"""
+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<address>[A-Za-z0-9]+[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path)
# match = re.match(r"/ media/ (?P<address>[A-Za-z0-9]+[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", path)
+ match = re.match(r"/(media/)?(?P<address>[A-Za-z0-9]+[A-Za-z0-9\._-]+)(?P<inner_path>/.*|$)", 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, '<string>', '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"] + " <span style='color: red'>" + _["(Dangerous!)"] + "</span>")
+ self.response(to, _["Allow this site to administrate your 0net node"] + " <span style='color: red'>" + _["(Make sure you trust site developer before accepting!)"] + "</span>")
elif permission == "NOSANDBOX":
self.response(to, _["Modify your client's configuration and access all site"] + " <span style='color: red'>" + _["(Dangerous!)"] + "</span>")
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()) {
<!-- Fixed button -->
<div class='fixbutton'>
- <div class='fixbutton-text'><img width=30 src='/uimedia/img/logo-white.svg'/></div>
+ <!-- #50, #53 @TODO remove
+ <div class='fixbutton-text' style='font-size:150%'>←</div>
<div class='fixbutton-burger'>≡</div>
+ -->
<a class='fixbutton-bg' href="{homepage}/"></a>
</div>
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])
|
yay , more noise in a stupid thread
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 |
thats because youre too stupid to produce minimal diffs
hard-to-audit code (diff noise) is a real issue |
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 @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. |
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.
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. |
@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. |
@krixano Check your inbox :) |
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. |
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. |
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. |
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. |
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) |
@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. |
@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. |
I'm a ZeroNet developer, and I did not and will not allow trolling of the ZeroNet article on WikiPedia. 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. |
@redarmyfaction please stop destructive behaviour
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:
i don't follow bitcoin developers, but bitcoin page does mention both forks and implementations of protocol
proof?
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 |
@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. 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. |
doesn't matter. if it exists, show it. otherwise, it's not a fork, but just a private project
we'd move along faster if you'd provide any links to your claims coming from an empty account
enjoy your fantasy world :D |
This comment was marked as a violation of GitHub Acceptable Use Policies
This comment was marked as a violation of GitHub Acceptable Use Policies
@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. |
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. |
@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. Btw, what's your id/username on ZeroNet? |
No way, especially when it comes to accounts that have been hidden by GitHub staff.
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.
Today, I don't have the keys to any ID. @imachug is an old friend from Russia. Isn't that correct, @imachug? Keep hiding in your rabbit hole, @krixano, and keep your source code, we don't really need you. |
@milahu is 100% correct about @caryoscelus. @milahu @caryoscelus, learn English and stop spamming your disgusting fork that no one uses, you filthy troll. |
@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. |
@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 . |
Stop trolling or I'll deliberately fart into your mouth.
Be careful not to slip on the ice and injure yourself, my boy.
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. |
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: #2283 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: #2248 (comment) |
@krixano You make me think of Larry! |
@imachug became a transgender person before he was Ivan, and she is now known as "Alisa." 🤣 I guess the copyright notices are no longer valid. |
@caryoscelus is back to trolling and spamming ZeroNet users and defrauding ZeroNet users. She is an ugly troll who spams Reddit, WikiPedia, GitHub, and other GitHub repositories to promote her cryptocurrency scam. |
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-enhancedhttps://github.com/zeronet-enhanced/ZeroNet |
@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.
The text was updated successfully, but these errors were encountered: