main.py 18 KB

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