main.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. # Included modules
  2. import os
  3. import sys
  4. import stat
  5. import time
  6. import logging
  7. import loglevel_overrides
  8. startup_errors = []
  9. def startupError(msg):
  10. startup_errors.append(msg)
  11. print("Startup error: %s" % msg)
  12. # Third party modules
  13. import gevent
  14. if gevent.version_info.major <= 1: # Workaround for random crash when libuv used with threads
  15. try:
  16. if "libev" not in str(gevent.config.loop):
  17. gevent.config.loop = "libev-cext"
  18. except Exception as err:
  19. startupError("Unable to switch gevent loop to libev: %s" % err)
  20. import gevent.monkey
  21. gevent.monkey.patch_all(thread=False, subprocess=False)
  22. update_after_shutdown = False # If set True then update and restart zeronet after main loop ended
  23. restart_after_shutdown = False # If set True then restart zeronet after main loop ended
  24. # Load config
  25. from Config import config
  26. config.parse(silent=True) # Plugins need to access the configuration
  27. if not config.arguments: # Config parse failed, show the help screen and exit
  28. config.parse()
  29. if not os.path.isdir(config.data_dir):
  30. os.mkdir(config.data_dir)
  31. try:
  32. os.chmod(config.data_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
  33. except Exception as err:
  34. startupError("Can't change permission of %s: %s" % (config.data_dir, err))
  35. if not os.path.isfile("%s/sites.json" % config.data_dir):
  36. open("%s/sites.json" % config.data_dir, "w").write("{}")
  37. if not os.path.isfile("%s/users.json" % config.data_dir):
  38. open("%s/users.json" % config.data_dir, "w").write("{}")
  39. if config.action == "main":
  40. from util import helper
  41. try:
  42. lock = helper.openLocked("%s/lock.pid" % config.data_dir, "w")
  43. lock.write("%s" % os.getpid())
  44. except BlockingIOError as err:
  45. startupError("Can't open lock file, your ZeroNet client is probably already running, exiting... (%s)" % err)
  46. if config.open_browser and config.open_browser != "False":
  47. print("Opening browser: %s...", config.open_browser)
  48. import webbrowser
  49. try:
  50. if config.open_browser == "default_browser":
  51. browser = webbrowser.get()
  52. else:
  53. browser = webbrowser.get(config.open_browser)
  54. browser.open("http://%s:%s/%s" % (
  55. config.ui_ip if config.ui_ip != "*" else "127.0.0.1", config.ui_port, config.homepage
  56. ), new=2)
  57. except Exception as err:
  58. startupError("Error starting browser: %s" % err)
  59. sys.exit()
  60. config.initLogging()
  61. # Debug dependent configuration
  62. from Debug import DebugHook
  63. # Load plugins
  64. from Plugin import PluginManager
  65. PluginManager.plugin_manager.loadPlugins()
  66. config.loadPlugins()
  67. config.parse() # Parse again to add plugin configuration options
  68. # Log current config
  69. logging.debug("Config: %s" % config)
  70. # Modify stack size on special hardwares
  71. if config.stack_size:
  72. import threading
  73. threading.stack_size(config.stack_size)
  74. # Use pure-python implementation of msgpack to save CPU
  75. if config.msgpack_purepython:
  76. os.environ["MSGPACK_PUREPYTHON"] = "True"
  77. # Fix console encoding on Windows
  78. if sys.platform.startswith("win"):
  79. import subprocess
  80. try:
  81. chcp_res = subprocess.check_output("chcp 65001", shell=True).decode(errors="ignore").strip()
  82. logging.debug("Changed console encoding to utf8: %s" % chcp_res)
  83. except Exception as err:
  84. logging.error("Error changing console encoding to utf8: %s" % err)
  85. # Socket monkey patch
  86. if config.proxy:
  87. from util import SocksProxy
  88. import urllib.request
  89. logging.info("Patching sockets to socks proxy: %s" % config.proxy)
  90. if config.fileserver_ip == "*":
  91. config.fileserver_ip = '127.0.0.1' # Do not accept connections anywhere but localhost
  92. config.disable_udp = True # UDP not supported currently with proxy
  93. SocksProxy.monkeyPatch(*config.proxy.split(":"))
  94. elif config.tor == "always":
  95. from util import SocksProxy
  96. import urllib.request
  97. logging.info("Patching sockets to tor socks proxy: %s" % config.tor_proxy)
  98. if config.fileserver_ip == "*":
  99. config.fileserver_ip = '127.0.0.1' # Do not accept connections anywhere but localhost
  100. SocksProxy.monkeyPatch(*config.tor_proxy.split(":"))
  101. config.disable_udp = True
  102. elif config.bind:
  103. bind = config.bind
  104. if ":" not in config.bind:
  105. bind += ":0"
  106. from util import helper
  107. helper.socketBindMonkeyPatch(*bind.split(":"))
  108. # -- Actions --
  109. @PluginManager.acceptPlugins
  110. class Actions(object):
  111. def call(self, function_name, kwargs):
  112. logging.info("Version: %s r%s, Python %s, Gevent: %s" % (config.version, config.rev, sys.version, gevent.__version__))
  113. func = getattr(self, function_name, None)
  114. back = func(**kwargs)
  115. if back:
  116. print(back)
  117. # Default action: Start serving UiServer and FileServer
  118. def main(self):
  119. global ui_server, file_server
  120. from File import FileServer
  121. from Ui import UiServer
  122. logging.info("Creating FileServer....")
  123. file_server = FileServer()
  124. logging.info("Creating UiServer....")
  125. ui_server = UiServer()
  126. file_server.ui_server = ui_server
  127. for startup_error in startup_errors:
  128. logging.error("Startup error: %s" % startup_error)
  129. logging.info("Removing old SSL certs...")
  130. from Crypt import CryptConnection
  131. CryptConnection.manager.removeCerts()
  132. logging.info("Starting servers....")
  133. gevent.joinall([gevent.spawn(ui_server.start), gevent.spawn(file_server.start)])
  134. logging.info("All servers stopped")
  135. # Site commands
  136. def siteCreate(self, use_master_seed=True):
  137. logging.info("Generating new privatekey (use_master_seed: %s)..." % config.use_master_seed)
  138. from Crypt import CryptBitcoin
  139. if use_master_seed:
  140. from User import UserManager
  141. user = UserManager.user_manager.get()
  142. if not user:
  143. user = UserManager.user_manager.create()
  144. address, address_index, site_data = user.getNewSiteData()
  145. privatekey = site_data["privatekey"]
  146. logging.info("Generated using master seed from users.json, site index: %s" % address_index)
  147. else:
  148. privatekey = CryptBitcoin.newPrivatekey()
  149. address = CryptBitcoin.privatekeyToAddress(privatekey)
  150. logging.info("----------------------------------------------------------------------")
  151. logging.info("Site private key: %s" % privatekey)
  152. logging.info(" !!! ^ Save it now, required to modify the site ^ !!!")
  153. logging.info("Site address: %s" % address)
  154. logging.info("----------------------------------------------------------------------")
  155. while True and not config.batch and not use_master_seed:
  156. if input("? Have you secured your private key? (yes, no) > ").lower() == "yes":
  157. break
  158. else:
  159. logging.info("Please, secure it now, you going to need it to modify your site!")
  160. logging.info("Creating directory structure...")
  161. from Site.Site import Site
  162. from Site import SiteManager
  163. SiteManager.site_manager.load()
  164. os.mkdir("%s/%s" % (config.data_dir, address))
  165. open("%s/%s/index.html" % (config.data_dir, address), "w").write("Hello %s!" % address)
  166. logging.info("Creating content.json...")
  167. site = Site(address)
  168. extend = {"postmessage_nonce_security": True}
  169. if use_master_seed:
  170. extend["address_index"] = address_index
  171. site.content_manager.sign(privatekey=privatekey, extend=extend)
  172. site.settings["own"] = True
  173. site.saveSettings()
  174. logging.info("Site created!")
  175. def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False, remove_missing_optional=False):
  176. from Site.Site import Site
  177. from Site import SiteManager
  178. from Debug import Debug
  179. SiteManager.site_manager.load()
  180. logging.info("Signing site: %s..." % address)
  181. site = Site(address, allow_create=False)
  182. if not privatekey: # If no privatekey defined
  183. from User import UserManager
  184. user = UserManager.user_manager.get()
  185. if user:
  186. site_data = user.getSiteData(address)
  187. privatekey = site_data.get("privatekey")
  188. else:
  189. privatekey = None
  190. if not privatekey:
  191. # Not found in users.json, ask from console
  192. import getpass
  193. privatekey = getpass.getpass("Private key (input hidden):")
  194. try:
  195. succ = site.content_manager.sign(
  196. inner_path=inner_path, privatekey=privatekey,
  197. update_changed_files=True, remove_missing_optional=remove_missing_optional
  198. )
  199. except Exception as err:
  200. logging.error("Sign error: %s" % Debug.formatException(err))
  201. succ = False
  202. if succ and publish:
  203. self.sitePublish(address, inner_path=inner_path)
  204. def siteVerify(self, address):
  205. import time
  206. from Site.Site import Site
  207. from Site import SiteManager
  208. SiteManager.site_manager.load()
  209. s = time.time()
  210. logging.info("Verifing site: %s..." % address)
  211. site = Site(address)
  212. bad_files = []
  213. for content_inner_path in site.content_manager.contents:
  214. s = time.time()
  215. logging.info("Verifing %s signature..." % content_inner_path)
  216. err = None
  217. try:
  218. file_correct = site.content_manager.verifyFile(
  219. content_inner_path, site.storage.open(content_inner_path, "rb"), ignore_same=False
  220. )
  221. except Exception as err:
  222. file_correct = False
  223. if file_correct is True:
  224. logging.info("[OK] %s (Done in %.3fs)" % (content_inner_path, time.time() - s))
  225. else:
  226. logging.error("[ERROR] %s: invalid file: %s!" % (content_inner_path, err))
  227. input("Continue?")
  228. bad_files += content_inner_path
  229. logging.info("Verifying site files...")
  230. bad_files += site.storage.verifyFiles()["bad_files"]
  231. if not bad_files:
  232. logging.info("[OK] All file sha512sum matches! (%.3fs)" % (time.time() - s))
  233. else:
  234. logging.error("[ERROR] Error during verifying site files!")
  235. def dbRebuild(self, address):
  236. from Site.Site import Site
  237. from Site import SiteManager
  238. SiteManager.site_manager.load()
  239. logging.info("Rebuilding site sql cache: %s..." % address)
  240. site = SiteManager.site_manager.get(address)
  241. s = time.time()
  242. try:
  243. site.storage.rebuildDb()
  244. logging.info("Done in %.3fs" % (time.time() - s))
  245. except Exception as err:
  246. logging.error(err)
  247. def dbQuery(self, address, query):
  248. from Site.Site import Site
  249. from Site import SiteManager
  250. SiteManager.site_manager.load()
  251. import json
  252. site = Site(address)
  253. result = []
  254. for row in site.storage.query(query):
  255. result.append(dict(row))
  256. print(json.dumps(result, indent=4))
  257. def siteAnnounce(self, address):
  258. from Site.Site import Site
  259. from Site import SiteManager
  260. SiteManager.site_manager.load()
  261. logging.info("Opening a simple connection server")
  262. global file_server
  263. from File import FileServer
  264. file_server = FileServer("127.0.0.1", 1234)
  265. file_server.start()
  266. logging.info("Announcing site %s to tracker..." % address)
  267. site = Site(address)
  268. s = time.time()
  269. site.announce()
  270. print("Response time: %.3fs" % (time.time() - s))
  271. print(site.peers)
  272. def siteDownload(self, address):
  273. from Site.Site import Site
  274. from Site import SiteManager
  275. SiteManager.site_manager.load()
  276. logging.info("Opening a simple connection server")
  277. global file_server
  278. from File import FileServer
  279. file_server = FileServer("127.0.0.1", 1234)
  280. file_server_thread = gevent.spawn(file_server.start, check_sites=False)
  281. site = Site(address)
  282. on_completed = gevent.event.AsyncResult()
  283. def onComplete(evt):
  284. evt.set(True)
  285. site.onComplete.once(lambda: onComplete(on_completed))
  286. print("Announcing...")
  287. site.announce()
  288. s = time.time()
  289. print("Downloading...")
  290. site.downloadContent("content.json", check_modifications=True)
  291. print("Downloaded in %.3fs" % (time.time()-s))
  292. def siteNeedFile(self, address, inner_path):
  293. from Site.Site import Site
  294. from Site import SiteManager
  295. SiteManager.site_manager.load()
  296. def checker():
  297. while 1:
  298. s = time.time()
  299. time.sleep(1)
  300. print("Switch time:", time.time() - s)
  301. gevent.spawn(checker)
  302. logging.info("Opening a simple connection server")
  303. global file_server
  304. from File import FileServer
  305. file_server = FileServer("127.0.0.1", 1234)
  306. file_server_thread = gevent.spawn(file_server.start, check_sites=False)
  307. site = Site(address)
  308. site.announce()
  309. print(site.needFile(inner_path, update=True))
  310. def siteCmd(self, address, cmd, parameters):
  311. import json
  312. from Site import SiteManager
  313. site = SiteManager.site_manager.get(address)
  314. if not site:
  315. logging.error("Site not found: %s" % address)
  316. return None
  317. ws = self.getWebsocket(site)
  318. ws.send(json.dumps({"cmd": cmd, "params": parameters, "id": 1}))
  319. res_raw = ws.recv()
  320. try:
  321. res = json.loads(res_raw)
  322. except Exception as err:
  323. return {"error": "Invalid result: %s" % err, "res_raw": res_raw}
  324. if "result" in res:
  325. return res["result"]
  326. else:
  327. return res
  328. def getWebsocket(self, site):
  329. import websocket
  330. ws_address = "ws://%s:%s/Websocket?wrapper_key=%s" % (config.ui_ip, config.ui_port, site.settings["wrapper_key"])
  331. logging.info("Connecting to %s" % ws_address)
  332. ws = websocket.create_connection(ws_address)
  333. return ws
  334. def sitePublish(self, address, peer_ip=None, peer_port=15441, inner_path="content.json"):
  335. global file_server
  336. from Site.Site import Site
  337. from Site import SiteManager
  338. from File import FileServer # We need fileserver to handle incoming file requests
  339. from Peer import Peer
  340. file_server = FileServer()
  341. site = SiteManager.site_manager.get(address)
  342. logging.info("Loading site...")
  343. site.settings["serving"] = True # Serving the site even if its disabled
  344. try:
  345. ws = self.getWebsocket(site)
  346. logging.info("Sending siteReload")
  347. self.siteCmd(address, "siteReload", inner_path)
  348. logging.info("Sending sitePublish")
  349. self.siteCmd(address, "sitePublish", {"inner_path": inner_path, "sign": False})
  350. logging.info("Done.")
  351. except Exception as err:
  352. logging.info("Can't connect to local websocket client: %s" % err)
  353. logging.info("Creating FileServer....")
  354. file_server_thread = gevent.spawn(file_server.start, check_sites=False) # Dont check every site integrity
  355. time.sleep(0.001)
  356. # Started fileserver
  357. file_server.portCheck()
  358. if peer_ip: # Announce ip specificed
  359. site.addPeer(peer_ip, peer_port)
  360. else: # Just ask the tracker
  361. logging.info("Gathering peers from tracker")
  362. site.announce() # Gather peers
  363. published = site.publish(5, inner_path) # Push to peers
  364. if published > 0:
  365. time.sleep(3)
  366. logging.info("Serving files (max 60s)...")
  367. gevent.joinall([file_server_thread], timeout=60)
  368. logging.info("Done.")
  369. else:
  370. logging.info("No peers found, sitePublish command only works if you already have visitors serving your site")
  371. # Crypto commands
  372. def cryptPrivatekeyToAddress(self, privatekey=None):
  373. from Crypt import CryptBitcoin
  374. if not privatekey: # If no privatekey in args then ask it now
  375. import getpass
  376. privatekey = getpass.getpass("Private key (input hidden):")
  377. print(CryptBitcoin.privatekeyToAddress(privatekey))
  378. def cryptSign(self, message, privatekey):
  379. from Crypt import CryptBitcoin
  380. print(CryptBitcoin.sign(message, privatekey))
  381. def cryptVerify(self, message, sign, address):
  382. from Crypt import CryptBitcoin
  383. print(CryptBitcoin.verify(message, address, sign))
  384. def cryptGetPrivatekey(self, master_seed, site_address_index=None):
  385. from Crypt import CryptBitcoin
  386. if len(master_seed) != 64:
  387. logging.error("Error: Invalid master seed length: %s (required: 64)" % len(master_seed))
  388. return False
  389. privatekey = CryptBitcoin.hdPrivatekey(master_seed, site_address_index)
  390. print("Requested private key: %s" % privatekey)
  391. # Peer
  392. def peerPing(self, peer_ip, peer_port=None):
  393. if not peer_port:
  394. peer_port = 15441
  395. logging.info("Opening a simple connection server")
  396. global file_server
  397. from Connection import ConnectionServer
  398. file_server = ConnectionServer("127.0.0.1", 1234)
  399. file_server.start(check_connections=False)
  400. from Crypt import CryptConnection
  401. CryptConnection.manager.loadCerts()
  402. from Peer import Peer
  403. logging.info("Pinging 5 times peer: %s:%s..." % (peer_ip, int(peer_port)))
  404. s = time.time()
  405. peer = Peer(peer_ip, peer_port)
  406. peer.connect()
  407. if not peer.connection:
  408. print("Error: Can't connect to peer (connection error: %s)" % peer.connection_error)
  409. return False
  410. if "shared_ciphers" in dir(peer.connection.sock):
  411. print("Shared ciphers:", peer.connection.sock.shared_ciphers())
  412. if "cipher" in dir(peer.connection.sock):
  413. print("Cipher:", peer.connection.sock.cipher()[0])
  414. if "version" in dir(peer.connection.sock):
  415. print("TLS version:", peer.connection.sock.version())
  416. print("Connection time: %.3fs (connection error: %s)" % (time.time() - s, peer.connection_error))
  417. for i in range(5):
  418. ping_delay = peer.ping()
  419. print("Response time: %.3fs" % ping_delay)
  420. time.sleep(1)
  421. peer.remove()
  422. print("Reconnect test...")
  423. peer = Peer(peer_ip, peer_port)
  424. for i in range(5):
  425. ping_delay = peer.ping()
  426. print("Response time: %.3fs" % ping_delay)
  427. time.sleep(1)
  428. def peerGetFile(self, peer_ip, peer_port, site, filename, benchmark=False):
  429. logging.info("Opening a simple connection server")
  430. global file_server
  431. from Connection import ConnectionServer
  432. file_server = ConnectionServer("127.0.0.1", 1234)
  433. file_server.start(check_connections=False)
  434. from Crypt import CryptConnection
  435. CryptConnection.manager.loadCerts()
  436. from Peer import Peer
  437. logging.info("Getting %s/%s from peer: %s:%s..." % (site, filename, peer_ip, peer_port))
  438. peer = Peer(peer_ip, peer_port)
  439. s = time.time()
  440. if benchmark:
  441. for i in range(10):
  442. peer.getFile(site, filename),
  443. print("Response time: %.3fs" % (time.time() - s))
  444. input("Check memory")
  445. else:
  446. print(peer.getFile(site, filename).read())
  447. def peerCmd(self, peer_ip, peer_port, cmd, parameters):
  448. logging.info("Opening a simple connection server")
  449. global file_server
  450. from Connection import ConnectionServer
  451. file_server = ConnectionServer()
  452. file_server.start(check_connections=False)
  453. from Crypt import CryptConnection
  454. CryptConnection.manager.loadCerts()
  455. from Peer import Peer
  456. peer = Peer(peer_ip, peer_port)
  457. import json
  458. if parameters:
  459. parameters = json.loads(parameters.replace("'", '"'))
  460. else:
  461. parameters = {}
  462. try:
  463. res = peer.request(cmd, parameters)
  464. print(json.dumps(res, indent=2, ensure_ascii=False))
  465. except Exception as err:
  466. print("Unknown response (%s): %s" % (err, res))
  467. def getConfig(self):
  468. import json
  469. print(json.dumps(config.getServerInfo(), indent=2, ensure_ascii=False))
  470. def test(self, test_name, *args, **kwargs):
  471. import types
  472. def funcToName(func_name):
  473. test_name = func_name.replace("test", "")
  474. return test_name[0].lower() + test_name[1:]
  475. test_names = [funcToName(name) for name in dir(self) if name.startswith("test") and name != "test"]
  476. if not test_name:
  477. # No test specificed, list tests
  478. print("\nNo test specified, possible tests:")
  479. for test_name in test_names:
  480. func_name = "test" + test_name[0].upper() + test_name[1:]
  481. func = getattr(self, func_name)
  482. if func.__doc__:
  483. print("- %s: %s" % (test_name, func.__doc__.strip()))
  484. else:
  485. print("- %s" % test_name)
  486. return None
  487. # Run tests
  488. func_name = "test" + test_name[0].upper() + test_name[1:]
  489. if hasattr(self, func_name):
  490. func = getattr(self, func_name)
  491. print("- Running %s" % test_name, end="")
  492. s = time.time()
  493. ret = func(*args, **kwargs)
  494. if type(ret) is types.GeneratorType:
  495. for progress in ret:
  496. print(progress, end="")
  497. sys.stdout.flush()
  498. print("\n* Test %s done in %.3fs" % (test_name, time.time() - s))
  499. else:
  500. print("Unknown test: %r (choose from: %s)" % (
  501. test_name, test_names
  502. ))
  503. actions = Actions()
  504. # Starts here when running zeronet.py
  505. def start():
  506. # Call function
  507. action_kwargs = config.getActionArguments()
  508. actions.call(config.action, action_kwargs)