123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604 |
- # Included modules
- import os
- import sys
- import stat
- import time
- import logging
- import loglevel_overrides
- startup_errors = []
- def startupError(msg):
- startup_errors.append(msg)
- print("Startup error: %s" % msg)
- # Third party modules
- import gevent
- if gevent.version_info.major <= 1: # Workaround for random crash when libuv used with threads
- try:
- if "libev" not in str(gevent.config.loop):
- gevent.config.loop = "libev-cext"
- except Exception as err:
- startupError("Unable to switch gevent loop to libev: %s" % err)
- import gevent.monkey
- 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
- config.parse()
- 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("{}")
- if config.action == "main":
- from util import helper
- try:
- lock = helper.openLocked("%s/lock.pid" % config.data_dir, "w")
- lock.write("%s" % os.getpid())
- 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()
- 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
- # Log current config
- logging.debug("Config: %s" % config)
- # Modify stack size on special hardwares
- if config.stack_size:
- import threading
- threading.stack_size(config.stack_size)
- # Use pure-python implementation of msgpack to save CPU
- if config.msgpack_purepython:
- os.environ["MSGPACK_PUREPYTHON"] = "True"
- # Fix console encoding on Windows
- if sys.platform.startswith("win"):
- import subprocess
- try:
- chcp_res = subprocess.check_output("chcp 65001", shell=True).decode(errors="ignore").strip()
- logging.debug("Changed console encoding to utf8: %s" % chcp_res)
- except Exception as err:
- logging.error("Error changing console encoding to utf8: %s" % err)
- # Socket monkey patch
- if config.proxy:
- from util import SocksProxy
- import urllib.request
- logging.info("Patching sockets to socks proxy: %s" % config.proxy)
- if config.fileserver_ip == "*":
- config.fileserver_ip = '127.0.0.1' # Do not accept connections anywhere but localhost
- config.disable_udp = True # UDP not supported currently with proxy
- SocksProxy.monkeyPatch(*config.proxy.split(":"))
- elif config.tor == "always":
- from util import SocksProxy
- import urllib.request
- 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(":"))
- config.disable_udp = True
- elif config.bind:
- bind = config.bind
- if ":" not in config.bind:
- bind += ":0"
- from util import helper
- helper.socketBindMonkeyPatch(*bind.split(":"))
- # -- Actions --
- @PluginManager.acceptPlugins
- class Actions(object):
- def call(self, function_name, kwargs):
- logging.info("Version: %s r%s, Python %s, Gevent: %s" % (config.version, config.rev, sys.version, gevent.__version__))
- func = getattr(self, function_name, None)
- back = func(**kwargs)
- if back:
- print(back)
- # Default action: Start serving UiServer and FileServer
- def main(self):
- global ui_server, file_server
- from File import FileServer
- from Ui import UiServer
- logging.info("Creating FileServer....")
- file_server = FileServer()
- logging.info("Creating UiServer....")
- ui_server = UiServer()
- file_server.ui_server = ui_server
- for startup_error in startup_errors:
- logging.error("Startup error: %s" % startup_error)
- logging.info("Removing old SSL certs...")
- from Crypt import CryptConnection
- CryptConnection.manager.removeCerts()
- logging.info("Starting servers....")
- gevent.joinall([gevent.spawn(ui_server.start), gevent.spawn(file_server.start)])
- logging.info("All servers stopped")
- # Site commands
- def siteCreate(self, use_master_seed=True):
- logging.info("Generating new privatekey (use_master_seed: %s)..." % config.use_master_seed)
- from Crypt import CryptBitcoin
- if use_master_seed:
- from User import UserManager
- user = UserManager.user_manager.get()
- if not user:
- user = UserManager.user_manager.create()
- address, address_index, site_data = user.getNewSiteData()
- privatekey = site_data["privatekey"]
- logging.info("Generated using master seed from users.json, site index: %s" % address_index)
- else:
- privatekey = CryptBitcoin.newPrivatekey()
- address = CryptBitcoin.privatekeyToAddress(privatekey)
- logging.info("----------------------------------------------------------------------")
- logging.info("Site private key: %s" % privatekey)
- logging.info(" !!! ^ Save it now, required to modify the site ^ !!!")
- logging.info("Site address: %s" % address)
- logging.info("----------------------------------------------------------------------")
- while True and not config.batch and not use_master_seed:
- if input("? Have you secured your private key? (yes, no) > ").lower() == "yes":
- break
- else:
- logging.info("Please, secure it now, you going to need it to modify your site!")
- logging.info("Creating directory structure...")
- from Site.Site import Site
- from Site import SiteManager
- SiteManager.site_manager.load()
- os.mkdir("%s/%s" % (config.data_dir, address))
- open("%s/%s/index.html" % (config.data_dir, address), "w").write("Hello %s!" % address)
- logging.info("Creating content.json...")
- site = Site(address)
- extend = {"postmessage_nonce_security": True}
- if use_master_seed:
- extend["address_index"] = address_index
- site.content_manager.sign(privatekey=privatekey, extend=extend)
- site.settings["own"] = True
- site.saveSettings()
- logging.info("Site created!")
- def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False, remove_missing_optional=False):
- from Site.Site import Site
- from Site import SiteManager
- from Debug import Debug
- SiteManager.site_manager.load()
- logging.info("Signing site: %s..." % address)
- site = Site(address, allow_create=False)
- if not privatekey: # If no privatekey defined
- from User import UserManager
- user = UserManager.user_manager.get()
- if user:
- site_data = user.getSiteData(address)
- privatekey = site_data.get("privatekey")
- else:
- privatekey = None
- if not privatekey:
- # Not found in users.json, ask from console
- import getpass
- privatekey = getpass.getpass("Private key (input hidden):")
- try:
- succ = site.content_manager.sign(
- inner_path=inner_path, privatekey=privatekey,
- update_changed_files=True, remove_missing_optional=remove_missing_optional
- )
- except Exception as err:
- logging.error("Sign error: %s" % Debug.formatException(err))
- succ = False
- if succ and publish:
- self.sitePublish(address, inner_path=inner_path)
- def siteVerify(self, address):
- import time
- from Site.Site import Site
- from Site import SiteManager
- SiteManager.site_manager.load()
- s = time.time()
- logging.info("Verifing site: %s..." % address)
- site = Site(address)
- bad_files = []
- for content_inner_path in site.content_manager.contents:
- s = time.time()
- logging.info("Verifing %s signature..." % content_inner_path)
- err = None
- try:
- file_correct = site.content_manager.verifyFile(
- content_inner_path, site.storage.open(content_inner_path, "rb"), ignore_same=False
- )
- except Exception as err:
- file_correct = False
- if file_correct is True:
- logging.info("[OK] %s (Done in %.3fs)" % (content_inner_path, time.time() - s))
- else:
- logging.error("[ERROR] %s: invalid file: %s!" % (content_inner_path, err))
- input("Continue?")
- bad_files += content_inner_path
- logging.info("Verifying site files...")
- bad_files += site.storage.verifyFiles()["bad_files"]
- if not bad_files:
- logging.info("[OK] All file sha512sum matches! (%.3fs)" % (time.time() - s))
- else:
- logging.error("[ERROR] Error during verifying site files!")
- def dbRebuild(self, address):
- from Site.Site import Site
- from Site import SiteManager
- SiteManager.site_manager.load()
- logging.info("Rebuilding site sql cache: %s..." % address)
- site = SiteManager.site_manager.get(address)
- s = time.time()
- try:
- site.storage.rebuildDb()
- logging.info("Done in %.3fs" % (time.time() - s))
- except Exception as err:
- logging.error(err)
- def dbQuery(self, address, query):
- from Site.Site import Site
- from Site import SiteManager
- SiteManager.site_manager.load()
- import json
- site = Site(address)
- result = []
- for row in site.storage.query(query):
- result.append(dict(row))
- print(json.dumps(result, indent=4))
- def siteAnnounce(self, address):
- from Site.Site import Site
- from Site import SiteManager
- SiteManager.site_manager.load()
- logging.info("Opening a simple connection server")
- global file_server
- from File import FileServer
- file_server = FileServer("127.0.0.1", 1234)
- file_server.start()
- logging.info("Announcing site %s to tracker..." % address)
- site = Site(address)
- s = time.time()
- site.announce()
- print("Response time: %.3fs" % (time.time() - s))
- print(site.peers)
- def siteDownload(self, address):
- from Site.Site import Site
- from Site import SiteManager
- SiteManager.site_manager.load()
- logging.info("Opening a simple connection server")
- global file_server
- from File import FileServer
- file_server = FileServer("127.0.0.1", 1234)
- file_server_thread = gevent.spawn(file_server.start, check_sites=False)
- site = Site(address)
- on_completed = gevent.event.AsyncResult()
- def onComplete(evt):
- evt.set(True)
- site.onComplete.once(lambda: onComplete(on_completed))
- print("Announcing...")
- site.announce()
- s = time.time()
- print("Downloading...")
- site.downloadContent("content.json", check_modifications=True)
- print("Downloaded in %.3fs" % (time.time()-s))
- def siteNeedFile(self, address, inner_path):
- from Site.Site import Site
- from Site import SiteManager
- SiteManager.site_manager.load()
- def checker():
- while 1:
- s = time.time()
- time.sleep(1)
- print("Switch time:", time.time() - s)
- gevent.spawn(checker)
- logging.info("Opening a simple connection server")
- global file_server
- from File import FileServer
- file_server = FileServer("127.0.0.1", 1234)
- file_server_thread = gevent.spawn(file_server.start, check_sites=False)
- site = Site(address)
- site.announce()
- print(site.needFile(inner_path, update=True))
- def siteCmd(self, address, cmd, parameters):
- import json
- from Site import SiteManager
- site = SiteManager.site_manager.get(address)
- if not site:
- logging.error("Site not found: %s" % address)
- return None
- ws = self.getWebsocket(site)
- ws.send(json.dumps({"cmd": cmd, "params": parameters, "id": 1}))
- res_raw = ws.recv()
- try:
- res = json.loads(res_raw)
- except Exception as err:
- return {"error": "Invalid result: %s" % err, "res_raw": res_raw}
- if "result" in res:
- return res["result"]
- else:
- return res
- def getWebsocket(self, site):
- import websocket
- ws_address = "ws://%s:%s/Websocket?wrapper_key=%s" % (config.ui_ip, config.ui_port, site.settings["wrapper_key"])
- logging.info("Connecting to %s" % ws_address)
- ws = websocket.create_connection(ws_address)
- return ws
- def sitePublish(self, address, peer_ip=None, peer_port=15441, inner_path="content.json"):
- global file_server
- from Site.Site import Site
- from Site import SiteManager
- from File import FileServer # We need fileserver to handle incoming file requests
- from Peer import Peer
- file_server = FileServer()
- site = SiteManager.site_manager.get(address)
- logging.info("Loading site...")
- site.settings["serving"] = True # Serving the site even if its disabled
- try:
- ws = self.getWebsocket(site)
- logging.info("Sending siteReload")
- self.siteCmd(address, "siteReload", inner_path)
- logging.info("Sending sitePublish")
- self.siteCmd(address, "sitePublish", {"inner_path": inner_path, "sign": False})
- logging.info("Done.")
- except Exception as err:
- logging.info("Can't connect to local websocket client: %s" % err)
- logging.info("Creating FileServer....")
- file_server_thread = gevent.spawn(file_server.start, check_sites=False) # Dont check every site integrity
- time.sleep(0.001)
- # Started fileserver
- file_server.portCheck()
- if peer_ip: # Announce ip specificed
- site.addPeer(peer_ip, peer_port)
- else: # Just ask the tracker
- logging.info("Gathering peers from tracker")
- site.announce() # Gather peers
- published = site.publish(5, inner_path) # Push to peers
- if published > 0:
- time.sleep(3)
- logging.info("Serving files (max 60s)...")
- gevent.joinall([file_server_thread], timeout=60)
- logging.info("Done.")
- else:
- logging.info("No peers found, sitePublish command only works if you already have visitors serving your site")
- # Crypto commands
- def cryptPrivatekeyToAddress(self, privatekey=None):
- from Crypt import CryptBitcoin
- if not privatekey: # If no privatekey in args then ask it now
- import getpass
- privatekey = getpass.getpass("Private key (input hidden):")
- print(CryptBitcoin.privatekeyToAddress(privatekey))
- def cryptSign(self, message, privatekey):
- from Crypt import CryptBitcoin
- print(CryptBitcoin.sign(message, privatekey))
- def cryptVerify(self, message, sign, address):
- from Crypt import CryptBitcoin
- print(CryptBitcoin.verify(message, address, sign))
- def cryptGetPrivatekey(self, master_seed, site_address_index=None):
- from Crypt import CryptBitcoin
- if len(master_seed) != 64:
- logging.error("Error: Invalid master seed length: %s (required: 64)" % len(master_seed))
- return False
- privatekey = CryptBitcoin.hdPrivatekey(master_seed, site_address_index)
- print("Requested private key: %s" % privatekey)
- # Peer
- def peerPing(self, peer_ip, peer_port=None):
- if not peer_port:
- peer_port = 15441
- logging.info("Opening a simple connection server")
- global file_server
- from Connection import ConnectionServer
- file_server = ConnectionServer("127.0.0.1", 1234)
- file_server.start(check_connections=False)
- from Crypt import CryptConnection
- CryptConnection.manager.loadCerts()
- from Peer import Peer
- logging.info("Pinging 5 times peer: %s:%s..." % (peer_ip, int(peer_port)))
- s = time.time()
- peer = Peer(peer_ip, peer_port)
- peer.connect()
- if not peer.connection:
- print("Error: Can't connect to peer (connection error: %s)" % peer.connection_error)
- return False
- if "shared_ciphers" in dir(peer.connection.sock):
- print("Shared ciphers:", peer.connection.sock.shared_ciphers())
- if "cipher" in dir(peer.connection.sock):
- print("Cipher:", peer.connection.sock.cipher()[0])
- if "version" in dir(peer.connection.sock):
- print("TLS version:", peer.connection.sock.version())
- print("Connection time: %.3fs (connection error: %s)" % (time.time() - s, peer.connection_error))
- for i in range(5):
- ping_delay = peer.ping()
- print("Response time: %.3fs" % ping_delay)
- time.sleep(1)
- peer.remove()
- print("Reconnect test...")
- peer = Peer(peer_ip, peer_port)
- for i in range(5):
- ping_delay = peer.ping()
- print("Response time: %.3fs" % ping_delay)
- time.sleep(1)
- def peerGetFile(self, peer_ip, peer_port, site, filename, benchmark=False):
- logging.info("Opening a simple connection server")
- global file_server
- from Connection import ConnectionServer
- file_server = ConnectionServer("127.0.0.1", 1234)
- file_server.start(check_connections=False)
- from Crypt import CryptConnection
- CryptConnection.manager.loadCerts()
- from Peer import Peer
- logging.info("Getting %s/%s from peer: %s:%s..." % (site, filename, peer_ip, peer_port))
- peer = Peer(peer_ip, peer_port)
- s = time.time()
- if benchmark:
- for i in range(10):
- peer.getFile(site, filename),
- print("Response time: %.3fs" % (time.time() - s))
- input("Check memory")
- else:
- print(peer.getFile(site, filename).read())
- def peerCmd(self, peer_ip, peer_port, cmd, parameters):
- logging.info("Opening a simple connection server")
- global file_server
- from Connection import ConnectionServer
- file_server = ConnectionServer()
- file_server.start(check_connections=False)
- from Crypt import CryptConnection
- CryptConnection.manager.loadCerts()
- from Peer import Peer
- peer = Peer(peer_ip, peer_port)
- import json
- if parameters:
- parameters = json.loads(parameters.replace("'", '"'))
- else:
- parameters = {}
- try:
- res = peer.request(cmd, parameters)
- print(json.dumps(res, indent=2, ensure_ascii=False))
- except Exception as err:
- print("Unknown response (%s): %s" % (err, res))
- def getConfig(self):
- import json
- print(json.dumps(config.getServerInfo(), indent=2, ensure_ascii=False))
- def test(self, test_name, *args, **kwargs):
- import types
- def funcToName(func_name):
- test_name = func_name.replace("test", "")
- return test_name[0].lower() + test_name[1:]
- test_names = [funcToName(name) for name in dir(self) if name.startswith("test") and name != "test"]
- if not test_name:
- # No test specificed, list tests
- print("\nNo test specified, possible tests:")
- for test_name in test_names:
- func_name = "test" + test_name[0].upper() + test_name[1:]
- func = getattr(self, func_name)
- if func.__doc__:
- print("- %s: %s" % (test_name, func.__doc__.strip()))
- else:
- print("- %s" % test_name)
- return None
- # Run tests
- func_name = "test" + test_name[0].upper() + test_name[1:]
- if hasattr(self, func_name):
- func = getattr(self, func_name)
- print("- Running %s" % test_name, end="")
- s = time.time()
- ret = func(*args, **kwargs)
- if type(ret) is types.GeneratorType:
- for progress in ret:
- print(progress, end="")
- sys.stdout.flush()
- print("\n* Test %s done in %.3fs" % (test_name, time.time() - s))
- else:
- print("Unknown test: %r (choose from: %s)" % (
- test_name, test_names
- ))
- actions = Actions()
- # Starts here when running zeronet.py
- def start():
- # Call function
- action_kwargs = config.getActionArguments()
- actions.call(config.action, action_kwargs)
|