main.py 18 KB

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