2015-06-17 23:27:56 +02:00
|
|
|
# Included modules
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import time
|
2015-06-18 02:53:01 +02:00
|
|
|
import logging
|
2015-06-17 23:27:56 +02:00
|
|
|
|
|
|
|
# Third party modules
|
|
|
|
import gevent
|
|
|
|
from gevent import monkey
|
|
|
|
|
|
|
|
|
|
|
|
update_after_shutdown = False # If set True then update and restart zeronet after main loop ended
|
2015-01-12 02:03:45 +01:00
|
|
|
|
|
|
|
# Load config
|
|
|
|
from Config import config
|
|
|
|
|
2015-05-31 15:52:21 +02:00
|
|
|
# Create necessary files and dirs
|
|
|
|
if not os.path.isdir(config.log_dir): os.mkdir(config.log_dir)
|
|
|
|
if not os.path.isdir(config.data_dir): os.mkdir(config.data_dir)
|
2015-06-17 23:27:56 +02:00
|
|
|
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("{}")
|
2015-05-31 15:52:21 +02:00
|
|
|
|
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
# Setup logging
|
2015-06-17 23:27:56 +02:00
|
|
|
|
2015-01-12 02:03:45 +01:00
|
|
|
if config.action == "main":
|
2015-06-17 23:27:56 +02:00
|
|
|
if os.path.isfile("%s/debug.log" % config.log_dir): # Simple logrotate
|
|
|
|
if os.path.isfile("%s/debug-last.log" % config.log_dir):
|
|
|
|
os.unlink("%s/debug-last.log" % config.log_dir)
|
|
|
|
os.rename("%s/debug.log" % config.log_dir, "%s/debug-last.log" % config.log_dir)
|
|
|
|
logging.basicConfig(format='[%(asctime)s] %(levelname)-8s %(name)s %(message)s',
|
|
|
|
level=logging.DEBUG, filename="%s/debug.log" % config.log_dir)
|
2015-01-12 02:03:45 +01:00
|
|
|
else:
|
2015-06-17 23:27:56 +02:00
|
|
|
logging.basicConfig(level=logging.DEBUG, stream=open(os.devnull, "w")) # No file logging if action is not main
|
2015-01-12 02:03:45 +01:00
|
|
|
|
2015-01-17 18:50:56 +01:00
|
|
|
# Console logger
|
2015-01-12 02:03:45 +01:00
|
|
|
console_log = logging.StreamHandler()
|
2015-06-17 23:27:56 +02:00
|
|
|
if config.action == "main": # Add time if main action
|
|
|
|
console_log.setFormatter(logging.Formatter('[%(asctime)s] %(name)s %(message)s', "%H:%M:%S"))
|
2015-01-17 18:50:56 +01:00
|
|
|
else:
|
2015-06-17 23:27:56 +02:00
|
|
|
console_log.setFormatter(logging.Formatter('%(name)s %(message)s', "%H:%M:%S"))
|
2015-01-17 18:50:56 +01:00
|
|
|
|
2015-06-17 23:27:56 +02:00
|
|
|
logging.getLogger('').addHandler(console_log) # Add console logger
|
|
|
|
logging.getLogger('').name = "-" # Remove root prefix
|
2015-01-12 02:03:45 +01:00
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
2015-01-12 02:03:45 +01:00
|
|
|
# Debug dependent configuration
|
version 0.2.0, new lib for bitcoin ecc, dont display or track notify errors, dont reload again within 1 sec, null peer ip fix, signingmoved to ContentManager, content.json include support, content.json multisig ready, content.json proper bitcoincore compatible signing, content.json include permissions, multithreaded publish, publish timeout 60s, no exception on invalid bitcoin address, testcase for new lib, bip32 based persite privatekey generation, multiuser ready, simple json database query command, websocket api fileGet, wrapper loading title stuck bugfix
2015-02-09 02:09:02 +01:00
|
|
|
from Debug import DebugHook
|
2015-01-12 02:03:45 +01:00
|
|
|
if config.debug:
|
2015-06-17 23:27:56 +02:00
|
|
|
console_log.setLevel(logging.DEBUG) # Display everything to console
|
2015-01-12 02:03:45 +01:00
|
|
|
else:
|
2015-06-17 23:27:56 +02:00
|
|
|
console_log.setLevel(logging.INFO) # Display only important info to console
|
|
|
|
|
2015-06-18 20:31:33 +02:00
|
|
|
monkey.patch_all(thread=False) # Make time, socket gevent compatible. Not thread: pyfilesystem and system tray icon not compatible
|
2015-06-17 23:27:56 +02:00
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
2015-01-12 02:03:45 +01:00
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
# Log current config
|
2015-03-19 21:19:14 +01:00
|
|
|
logging.debug("Config: %s" % config)
|
2015-01-12 02:03:45 +01:00
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
|
2015-04-12 23:59:22 +02:00
|
|
|
# Socks Proxy monkey patch
|
|
|
|
if config.proxy:
|
2015-06-17 23:27:56 +02:00
|
|
|
from util import SocksProxy
|
2015-06-18 02:53:01 +02:00
|
|
|
import urllib2
|
2015-06-17 23:27:56 +02:00
|
|
|
logging.info("Patching sockets to socks proxy: %s" % config.proxy)
|
|
|
|
config.fileserver_ip = '127.0.0.1' # Do not accept connections anywhere but localhost
|
|
|
|
SocksProxy.monkeyPath(*config.proxy.split(":"))
|
2015-04-12 23:59:22 +02:00
|
|
|
|
|
|
|
|
version 0.2.7, plugin system, multiuser plugin for zeroproxies, reworked imports, cookie parse, stats moved to plugin, usermanager class, dont generate site auth on listing, multiline notifications, allow server side prompt from user, update script keep plugins disabled status
2015-03-24 01:33:09 +01:00
|
|
|
# Load plugins
|
|
|
|
from Plugin import PluginManager
|
|
|
|
PluginManager.plugin_manager.loadPlugins()
|
|
|
|
|
|
|
|
|
|
|
|
# -- Actions --
|
|
|
|
|
2015-04-15 02:54:10 +02:00
|
|
|
@PluginManager.acceptPlugins
|
2015-06-17 23:27:56 +02:00
|
|
|
class Actions(object):
|
|
|
|
# Default action: Start serving UiServer and FileServer
|
|
|
|
def main(self):
|
|
|
|
logging.info("Version: %s r%s, Python %s, Gevent: %s" % (config.version, config.rev, sys.version, gevent.__version__))
|
|
|
|
global ui_server, file_server
|
|
|
|
from File import FileServer
|
|
|
|
from Ui import UiServer
|
|
|
|
logging.info("Creating UiServer....")
|
|
|
|
ui_server = UiServer()
|
|
|
|
|
|
|
|
logging.info("Removing old SSL certs...")
|
|
|
|
from Crypt import CryptConnection
|
|
|
|
CryptConnection.manager.removeCerts()
|
|
|
|
|
|
|
|
logging.info("Creating FileServer....")
|
|
|
|
file_server = FileServer()
|
|
|
|
|
|
|
|
logging.info("Starting servers....")
|
|
|
|
gevent.joinall([gevent.spawn(ui_server.start), gevent.spawn(file_server.start)])
|
|
|
|
|
|
|
|
# Site commands
|
|
|
|
|
|
|
|
def siteCreate(self):
|
|
|
|
logging.info("Generating new privatekey...")
|
|
|
|
from Crypt import CryptBitcoin
|
|
|
|
privatekey = CryptBitcoin.newPrivatekey()
|
|
|
|
logging.info("----------------------------------------------------------------------")
|
|
|
|
logging.info("Site private key: %s" % privatekey)
|
|
|
|
logging.info(" !!! ^ Save it now, required to modify the site ^ !!!")
|
|
|
|
address = CryptBitcoin.privatekeyToAddress(privatekey)
|
|
|
|
logging.info("Site address: %s" % address)
|
|
|
|
logging.info("----------------------------------------------------------------------")
|
|
|
|
|
|
|
|
while True:
|
|
|
|
if raw_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 import Site
|
|
|
|
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)
|
|
|
|
site.content_manager.sign(privatekey=privatekey)
|
|
|
|
site.settings["own"] = True
|
|
|
|
site.saveSettings()
|
|
|
|
|
|
|
|
logging.info("Site created!")
|
|
|
|
|
|
|
|
def siteSign(self, address, privatekey=None, inner_path="content.json", publish=False):
|
|
|
|
from Site import Site
|
|
|
|
logging.info("Signing site: %s..." % address)
|
|
|
|
site = Site(address, allow_create = False)
|
|
|
|
|
|
|
|
if not privatekey: # If no privatekey in args then ask it now
|
|
|
|
import getpass
|
|
|
|
privatekey = getpass.getpass("Private key (input hidden):")
|
|
|
|
succ = site.content_manager.sign(inner_path=inner_path, privatekey=privatekey, update_changed_files=True)
|
|
|
|
if succ and publish:
|
|
|
|
self.sitePublish(address, inner_path=inner_path)
|
|
|
|
|
|
|
|
def siteVerify(self, address):
|
|
|
|
import time
|
|
|
|
from Site import Site
|
|
|
|
s = time.time()
|
|
|
|
logging.info("Verifing site: %s..." % address)
|
|
|
|
site = Site(address)
|
|
|
|
bad_files = []
|
|
|
|
|
|
|
|
for content_inner_path in site.content_manager.contents:
|
|
|
|
logging.info("Verifing %s signature..." % content_inner_path)
|
|
|
|
if site.content_manager.verifyFile(content_inner_path, site.storage.open(content_inner_path, "rb"), ignore_same=False) == True:
|
|
|
|
logging.info("[OK] %s signed by address %s!" % (content_inner_path, address))
|
|
|
|
else:
|
|
|
|
logging.error("[ERROR] %s: invalid file!" % content_inner_path)
|
|
|
|
bad_files += content_inner_path
|
|
|
|
|
|
|
|
logging.info("Verifying site files...")
|
|
|
|
bad_files += site.storage.verifyFiles()
|
|
|
|
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 import Site
|
|
|
|
logging.info("Rebuilding site sql cache: %s..." % address)
|
|
|
|
site = Site(address)
|
|
|
|
s = time.time()
|
|
|
|
site.storage.rebuildDb()
|
|
|
|
logging.info("Done in %.3fs" % (time.time()-s))
|
|
|
|
|
|
|
|
def dbQuery(self, address, query):
|
|
|
|
from Site import Site
|
|
|
|
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
|
|
|
|
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 siteNeedFile(self, address, inner_path):
|
|
|
|
from Site import Site
|
|
|
|
site = Site(address)
|
|
|
|
site.announce()
|
|
|
|
print site.needFile(inner_path, update=True)
|
|
|
|
|
|
|
|
|
|
|
|
def sitePublish(self, address, peer_ip=None, peer_port=15441, inner_path="content.json"):
|
|
|
|
global file_server
|
|
|
|
from Site import Site
|
|
|
|
from File import FileServer # We need fileserver to handle incoming file requests
|
|
|
|
|
|
|
|
logging.info("Creating FileServer....")
|
|
|
|
file_server = FileServer()
|
|
|
|
file_server_thread = gevent.spawn(file_server.start, check_sites=False) # Dont check every site integrity
|
|
|
|
file_server.openport()
|
|
|
|
site = file_server.sites[address]
|
|
|
|
site.settings["serving"] = True # Serving the site even if its disabled
|
|
|
|
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(20, inner_path) # Push to 20 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 for this site, sitePublish command only works if you already have peers 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)
|
|
|
|
|
|
|
|
# Peer
|
|
|
|
def peerPing(self, peer_ip, peer_port=None):
|
|
|
|
if not peer_port:
|
|
|
|
peer_port = config.fileserver_port
|
|
|
|
logging.info("Opening a simple connection server")
|
|
|
|
global file_server
|
|
|
|
from Connection import ConnectionServer
|
|
|
|
file_server = ConnectionServer("127.0.0.1", 1234)
|
|
|
|
|
|
|
|
from Peer import Peer
|
|
|
|
logging.info("Pinging 5 times peer: %s:%s..." % (peer_ip, int(peer_port)))
|
|
|
|
peer = Peer(peer_ip, peer_port)
|
|
|
|
for i in range(5):
|
|
|
|
s = time.time()
|
|
|
|
print peer.ping(),
|
|
|
|
print "Response time: %.3fs (crypt: %s)" % (time.time()-s, peer.connection.crypt)
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
def peerGetFile(self, peer_ip, peer_port, site, filename):
|
|
|
|
logging.info("Opening a simple connection server")
|
|
|
|
global file_server
|
|
|
|
from Connection import ConnectionServer
|
|
|
|
file_server = ConnectionServer()
|
|
|
|
|
|
|
|
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()
|
|
|
|
print peer.getFile(site, filename).read()
|
|
|
|
print "Response time: %.3fs" % (time.time()-s)
|
|
|
|
|
|
|
|
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()
|
|
|
|
from Peer import Peer
|
|
|
|
peer = Peer(peer_ip, peer_port)
|
|
|
|
|
|
|
|
import json
|
|
|
|
if parameters:
|
|
|
|
parameters = json.loads(parameters.replace("'", '"'))
|
|
|
|
else:
|
|
|
|
parameters = {}
|
|
|
|
logging.info("Response: %s" % peer.request(cmd, parameters))
|
rev125, Class statistics, OpenSSL disabled on OSX by default because of possible segfault, --disable_openssl command line parameter, Save memory on Connection, Peer and FileRequest objects using slots, Dont store modification time from the far future, Able to query modified files from peer, Allow reannounce in 30secs, Use with command in SiteStorage, Always create dir before write file, PeerCmd shell command to query specific command from peer
2015-04-29 23:12:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
2015-04-15 02:54:10 +02:00
|
|
|
actions = Actions()
|
|
|
|
# Starts here when running zeronet.py
|
|
|
|
def start():
|
2015-06-17 23:27:56 +02:00
|
|
|
# Call function
|
|
|
|
func = getattr(actions, config.action, None)
|
|
|
|
action_kwargs = config.getActionArguments()
|
|
|
|
func(**action_kwargs)
|