Merge pull request #1985 from rllola/fix-zeroname-local

New ZeronameLocal plugin with connection to namecoin node
This commit is contained in:
ZeroNet 2019-04-26 12:58:50 +02:00 committed by GitHub
commit 538f69235f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 180 additions and 337 deletions

View file

@ -1,68 +0,0 @@
import logging, json, os, re, sys, time
import gevent
from Plugin import PluginManager
from Config import config
from Debug import Debug
from .domainLookup import lookupDomain
allow_reload = False # No reload supported
log = logging.getLogger("Zeroname-localPlugin")
@PluginManager.registerTo("SiteManager")
class SiteManagerPlugin(object):
def load(self):
super(SiteManagerPlugin, self).load()
# Checks if its a valid address
def isAddress(self, address):
if self.isDomain(address):
return True
else:
return super(SiteManagerPlugin, self).isAddress(address)
# Return: True if the address is domain
def isDomain(self, address):
return re.match(r"(.*?)([A-Za-z0-9_-]+\.[A-Za-z0-9]+)$", address)
# Resolve domain
# Return: The address or None
def resolveDomain(self, domain):
return lookupDomain(domain)
# Return or create site and start download site files
# Return: Site or None if dns resolve failed
def need(self, address, all_file=True):
if self.isDomain(address): # Its looks like a domain
address_resolved = self.resolveDomain(address)
if address_resolved:
address = address_resolved
else:
return None
return super(SiteManagerPlugin, self).need(address, all_file)
# Return: Site object or None if not found
def get(self, address):
if self.sites == None: # Not loaded yet
self.load()
if self.isDomain(address): # Its looks like a domain
address_resolved = self.resolveDomain(address)
if address_resolved: # Domain found
site = self.sites.get(address_resolved)
if site:
site_domain = site.settings.get("domain")
if site_domain != address:
site.settings["domain"] = address
else: # Domain not found
site = self.sites.get(address)
else: # Access by site address
site = self.sites.get(address)
return site

View file

@ -1,190 +0,0 @@
"""
Copyright 2011 Jeff Garzik
AuthServiceProxy has the following improvements over python-jsonrpc's
ServiceProxy class:
- HTTP connections persist for the life of the AuthServiceProxy object
(if server supports HTTP/1.1)
- sends protocol 'version', per JSON-RPC 1.1
- sends proper, incrementing 'id'
- sends Basic HTTP authentication headers
- parses all JSON numbers that look like floats as Decimal
- uses standard Python json lib
Previous copyright, from python-jsonrpc/jsonrpc/proxy.py:
Copyright (c) 2007 Jan-Klaas Kollhof
This file is part of jsonrpc.
jsonrpc is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this software; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
try:
import http.client as httplib
except ImportError:
import http.client
import base64
import decimal
import json
import logging
try:
import urllib.parse as urlparse
except ImportError:
import urllib.parse
USER_AGENT = "AuthServiceProxy/0.1"
HTTP_TIMEOUT = 30
log = logging.getLogger("BitcoinRPC")
class JSONRPCException(Exception):
def __init__(self, rpc_error):
parent_args = []
try:
parent_args.append(rpc_error['message'])
except:
pass
Exception.__init__(self, *parent_args)
self.error = rpc_error
self.code = rpc_error['code'] if 'code' in rpc_error else None
self.message = rpc_error['message'] if 'message' in rpc_error else None
def __str__(self):
return '%d: %s' % (self.code, self.message)
def __repr__(self):
return '<%s \'%s\'>' % (self.__class__.__name__, self)
def EncodeDecimal(o):
if isinstance(o, decimal.Decimal):
return float(round(o, 8))
raise TypeError(repr(o) + " is not JSON serializable")
class AuthServiceProxy(object):
__id_count = 0
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None):
self.__service_url = service_url
self.__service_name = service_name
self.__url = urllib.parse.urlparse(service_url)
if self.__url.port is None:
port = 80
else:
port = self.__url.port
(user, passwd) = (self.__url.username, self.__url.password)
try:
user = user.encode('utf8')
except AttributeError:
pass
try:
passwd = passwd.encode('utf8')
except AttributeError:
pass
authpair = user + b':' + passwd
self.__auth_header = b'Basic ' + base64.b64encode(authpair)
self.__timeout = timeout
if connection:
# Callables re-use the connection of the original proxy
self.__conn = connection
elif self.__url.scheme == 'https':
self.__conn = http.client.HTTPSConnection(self.__url.hostname, port,
timeout=timeout)
else:
self.__conn = http.client.HTTPConnection(self.__url.hostname, port,
timeout=timeout)
def __getattr__(self, name):
if name.startswith('__') and name.endswith('__'):
# Python internal stuff
raise AttributeError
if self.__service_name is not None:
name = "%s.%s" % (self.__service_name, name)
return AuthServiceProxy(self.__service_url, name, self.__timeout, self.__conn)
def __call__(self, *args):
AuthServiceProxy.__id_count += 1
log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self.__service_name,
json.dumps(args, default=EncodeDecimal)))
postdata = json.dumps({'version': '1.1',
'method': self.__service_name,
'params': args,
'id': AuthServiceProxy.__id_count}, default=EncodeDecimal)
self.__conn.request('POST', self.__url.path, postdata,
{'Host': self.__url.hostname,
'User-Agent': USER_AGENT,
'Authorization': self.__auth_header,
'Content-type': 'application/json'})
self.__conn.sock.settimeout(self.__timeout)
response = self._get_response()
if response.get('error') is not None:
raise JSONRPCException(response['error'])
elif 'result' not in response:
raise JSONRPCException({
'code': -343, 'message': 'missing JSON-RPC result'})
return response['result']
def batch_(self, rpc_calls):
"""Batch RPC call.
Pass array of arrays: [ [ "method", params... ], ... ]
Returns array of results.
"""
batch_data = []
for rpc_call in rpc_calls:
AuthServiceProxy.__id_count += 1
m = rpc_call.pop(0)
batch_data.append({"jsonrpc":"2.0", "method":m, "params":rpc_call, "id":AuthServiceProxy.__id_count})
postdata = json.dumps(batch_data, default=EncodeDecimal)
log.debug("--> "+postdata)
self.__conn.request('POST', self.__url.path, postdata,
{'Host': self.__url.hostname,
'User-Agent': USER_AGENT,
'Authorization': self.__auth_header,
'Content-type': 'application/json'})
results = []
responses = self._get_response()
for response in responses:
if response['error'] is not None:
raise JSONRPCException(response['error'])
elif 'result' not in response:
raise JSONRPCException({
'code': -343, 'message': 'missing JSON-RPC result'})
else:
results.append(response['result'])
return results
def _get_response(self):
http_response = self.__conn.getresponse()
if http_response is None:
raise JSONRPCException({
'code': -342, 'message': 'missing HTTP response from server'})
responsedata = http_response.read().decode('utf8')
response = json.loads(responsedata, parse_float=decimal.Decimal)
if "error" in response and response["error"] is None:
log.debug("<-%s- %s"%(response["id"], json.dumps(response["result"], default=EncodeDecimal)))
else:
log.debug("<-- "+responsedata)
return response

View file

@ -1,78 +0,0 @@
from .bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException
import time, json, os, sys, re, socket
# Connecting to RPC
def initRpc(config):
"""Initialize Namecoin RPC"""
rpc_data = {
'connect': '127.0.0.1',
'port': '8336',
'user': 'PLACEHOLDER',
'password': 'PLACEHOLDER',
'clienttimeout': '900'
}
try:
fptr = open(config, 'r')
lines = fptr.readlines()
fptr.close()
except:
return None # Or take some other appropriate action
for line in lines:
if not line.startswith('rpc'):
continue
key_val = line.split(None, 1)[0]
(key, val) = key_val.split('=', 1)
if not key or not val:
continue
rpc_data[key[3:]] = val
url = 'http://%(user)s:%(password)s@%(connect)s:%(port)s' % rpc_data
return url, int(rpc_data['clienttimeout'])
# Either returns domain's address or none if it doesn't exist
# Supports subdomains and .bit on the end
def lookupDomain(domain):
domain = domain.lower()
#remove .bit on end
if domain[-4:] == ".bit":
domain = domain[0:-4]
#check for subdomain
if domain.find(".") != -1:
subdomain = domain[0:domain.find(".")]
domain = domain[domain.find(".")+1:]
else:
subdomain = ""
try:
domain_object = rpc.name_show("d/"+domain)
except:
#domain doesn't exist
return None
domain_json = json.loads(domain_object["value"])
try:
domain_address = domain_json["zeronet"][subdomain]
except:
#domain exists but doesn't have any zeronet value
return None
return domain_address
# Loading config...
# Check whether platform is on windows or linux
# On linux namecoin is installed under ~/.namecoin, while on on windows it is in %appdata%/Namecoin
if sys.platform == "win32":
namecoin_location = os.getenv('APPDATA') + "/Namecoin/"
else:
namecoin_location = os.path.expanduser("~/.namecoin/")
# Initialize rpc connection
rpc_auth, rpc_timeout = initRpc(namecoin_location + "namecoin.conf")
rpc = AuthServiceProxy(rpc_auth, timeout=rpc_timeout)

View file

@ -0,0 +1,180 @@
import logging, json, os, re, sys, time, socket
from Plugin import PluginManager
from Config import config
from Debug import Debug
from http.client import HTTPSConnection, HTTPConnection, HTTPException
from base64 import b64encode
allow_reload = False # No reload supported
@PluginManager.registerTo("SiteManager")
class SiteManagerPlugin(object):
def load(self, *args, **kwargs):
super(SiteManagerPlugin, self).load(*args, **kwargs)
self.log = logging.getLogger("ZeronetLocal Plugin")
self.error_message = None
if not config.namecoin_host or not config.namecoin_rpcport or not config.namecoin_rpcuser or not config.namecoin_rpcpassword:
self.error_message = "Missing parameters"
self.log.error("Missing parameters to connect to namecoin node. Please check all the arguments needed with '--help'. Zeronet will continue working without it.")
return
url = "%(host)s:%(port)s" % {"host": config.namecoin_host, "port": config.namecoin_rpcport}
self.c = HTTPConnection(url, timeout=3)
user_pass = "%(user)s:%(password)s" % {"user": config.namecoin_rpcuser, "password": config.namecoin_rpcpassword}
userAndPass = b64encode(bytes(user_pass, "utf-8")).decode("ascii")
self.headers = {"Authorization" : "Basic %s" % userAndPass, "Content-Type": " application/json " }
payload = json.dumps({
"jsonrpc": "2.0",
"id": "zeronet",
"method": "ping",
"params": []
})
try:
self.c.request("POST", "/", payload, headers=self.headers)
response = self.c.getresponse()
data = response.read()
self.c.close()
if response.status == 200:
result = json.loads(data.decode())["result"]
else:
raise Exception(response.reason)
except Exception as err:
self.log.error("The Namecoin node is unreachable. Please check the configuration value are correct. Zeronet will continue working without it.")
self.error_message = err
self.cache = dict()
# Checks if it's a valid address
def isAddress(self, address):
return self.isBitDomain(address) or super(SiteManagerPlugin, self).isAddress(address)
# Return: True if the address is domain
def isDomain(self, address):
return self.isBitDomain(address) or super(SiteManagerPlugin, self).isDomain(address)
# Return: True if the address is .bit domain
def isBitDomain(self, address):
return re.match(r"(.*?)([A-Za-z0-9_-]+\.bit)$", address)
# Return: Site object or None if not found
def get(self, address):
if self.isBitDomain(address): # Its looks like a domain
address_resolved = self.resolveDomain(address)
if address_resolved: # Domain found
site = self.sites.get(address_resolved)
if site:
site_domain = site.settings.get("domain")
if site_domain != address:
site.settings["domain"] = address
else: # Domain not found
site = self.sites.get(address)
else: # Access by site address
site = super(SiteManagerPlugin, self).get(address)
return site
# Return or create site and start download site files
# Return: Site or None if dns resolve failed
def need(self, address, *args, **kwargs):
if self.isBitDomain(address): # Its looks like a domain
address_resolved = self.resolveDomain(address)
if address_resolved:
address = address_resolved
else:
return None
return super(SiteManagerPlugin, self).need(address, *args, **kwargs)
# Resolve domain
# Return: The address or None
def resolveDomain(self, domain):
domain = domain.lower()
#remove .bit on end
if domain[-4:] == ".bit":
domain = domain[0:-4]
domain_array = domain.split(".")
if self.error_message:
self.log.error("Not able to connect to Namecoin node : {!s}".format(self.error_message))
return None
if len(domain_array) > 2:
self.log.error("Too many subdomains! Can only handle one level (eg. staging.mixtape.bit)")
return None
subdomain = ""
if len(domain_array) == 1:
domain = domain_array[0]
else:
subdomain = domain_array[0]
domain = domain_array[1]
if domain in self.cache:
delta = time.time() - self.cache[domain]["time"]
if delta < 3600:
# Must have been less than 1hour
return self.cache[domain]["addresses_resolved"][subdomain]
payload = json.dumps({
"jsonrpc": "2.0",
"id": "zeronet",
"method": "name_show",
"params": ["d/"+domain]
})
try:
self.c.request("POST", "/", payload, headers=self.headers)
response = self.c.getresponse()
data = response.read()
self.c.close()
domain_object = json.loads(data.decode())["result"]
except Exception as err:
#domain doesn't exist
return None
if "zeronet" in domain_object["value"]:
zeronet_domains = json.loads(domain_object["value"])["zeronet"]
if isinstance(zeronet_domains, str):
# {
# "zeronet":"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9"
# } is valid
zeronet_domains = {"": zeronet_domains}
self.cache[domain] = {"addresses_resolved": zeronet_domains, "time": time.time()}
elif "map" in domain_object["value"]:
# Namecoin standard use {"map": { "blog": {"zeronet": "1D..."} }}
data_map = json.loads(domain_object["value"])["map"]
zeronet_domains = dict()
for subdomain in data_map:
if "zeronet" in data_map[subdomain]:
zeronet_domains[subdomain] = data_map[subdomain]["zeronet"]
if "zeronet" in data_map and isinstance(data_map["zeronet"], str):
# {"map":{
# "zeronet":"19rXKeKptSdQ9qt7omwN82smehzTuuq6S9",
# }}
zeronet_domains[""] = data_map["zeronet"]
self.cache[domain] = {"addresses_resolved": zeronet_domains, "time": time.time()}
else:
# No Zeronet address registered
return None
return self.cache[domain]["addresses_resolved"][subdomain]
@PluginManager.registerTo("ConfigPlugin")
class ConfigPlugin(object):
def createArguments(self):
group = self.parser.add_argument_group("Zeroname Local plugin")
group.add_argument('--namecoin_host', help="Host to namecoin node (eg. 127.0.0.1)")
group.add_argument('--namecoin_rpcport', help="Port to connect (eg. 8336)")
group.add_argument('--namecoin_rpcuser', help="RPC user to connect to the namecoin node (eg. nofish)")
group.add_argument('--namecoin_rpcpassword', help="RPC password to connect to namecoin node")
return super(ConfigPlugin, self).createArguments()

View file

@ -37,4 +37,3 @@ class UiRequestPlugin(object):
return True
else: # Invalid referer
return False