|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
import os
|
|
|
|
import json
|
|
|
|
import socks
|
|
|
|
import time
|
|
|
|
import urllib
|
|
|
|
import hashlib
|
|
|
|
from xmlrpc.client import (
|
|
|
|
Fault,
|
|
|
|
Transport,
|
|
|
|
SafeTransport,
|
|
|
|
)
|
|
|
|
from sockshandler import SocksiPyConnection
|
|
|
|
from .util import jsonDecimal
|
|
|
|
|
|
|
|
|
|
|
|
class SocksTransport(Transport):
|
|
|
|
|
|
|
|
def set_proxy(self, proxy_host, proxy_port):
|
|
|
|
self.proxy_host = proxy_host
|
|
|
|
self.proxy_port = proxy_port
|
|
|
|
|
|
|
|
self.proxy_type = socks.PROXY_TYPE_SOCKS5
|
|
|
|
self.proxy_rdns = True
|
|
|
|
self.proxy_username = None
|
|
|
|
self.proxy_password = None
|
|
|
|
|
|
|
|
def make_connection(self, host):
|
|
|
|
# return an existing connection if possible. This allows
|
|
|
|
# HTTP/1.1 keep-alive.
|
|
|
|
if self._connection and host == self._connection[0]:
|
|
|
|
return self._connection[1]
|
|
|
|
# create a HTTP connection object from a host descriptor
|
|
|
|
chost, self._extra_headers, x509 = self.get_host_info(host)
|
|
|
|
self._connection = host, SocksiPyConnection(self.proxy_type, self.proxy_host, self.proxy_port, self.proxy_rdns, self.proxy_username, self.proxy_password, chost)
|
|
|
|
return self._connection[1]
|
|
|
|
|
|
|
|
|
|
|
|
class JsonrpcDigest():
|
|
|
|
# __getattr__ complicates extending ServerProxy
|
|
|
|
def __init__(self, uri, transport=None, encoding=None, verbose=False,
|
|
|
|
allow_none=False, use_datetime=False, use_builtin_types=False,
|
|
|
|
*, context=None):
|
|
|
|
|
|
|
|
parsed = urllib.parse.urlparse(uri)
|
|
|
|
if parsed.scheme not in ('http', 'https'):
|
|
|
|
raise OSError('unsupported XML-RPC protocol')
|
|
|
|
self.__host = parsed.netloc
|
|
|
|
self.__handler = parsed.path
|
|
|
|
|
|
|
|
if transport is None:
|
|
|
|
handler = SafeTransport if parsed.scheme == 'https' else Transport
|
|
|
|
extra_kwargs = {}
|
|
|
|
transport = handler(use_datetime=use_datetime,
|
|
|
|
use_builtin_types=use_builtin_types,
|
|
|
|
**extra_kwargs)
|
|
|
|
self.__transport = transport
|
|
|
|
|
|
|
|
self.__encoding = encoding or 'utf-8'
|
|
|
|
self.__verbose = verbose
|
|
|
|
self.__allow_none = allow_none
|
|
|
|
|
|
|
|
self.__request_id = 0
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
if self.__transport is not None:
|
|
|
|
self.__transport.close()
|
|
|
|
|
|
|
|
def request_id(self):
|
|
|
|
return self.__request_id
|
|
|
|
|
|
|
|
def post_request(self, method, params, timeout=None):
|
|
|
|
try:
|
|
|
|
connection = self.__transport.make_connection(self.__host)
|
|
|
|
if timeout:
|
|
|
|
connection.timeout = timeout
|
|
|
|
headers = self.__transport._extra_headers[:]
|
|
|
|
|
|
|
|
connection.putrequest('POST', self.__handler)
|
|
|
|
headers.append(('Content-Type', 'application/json'))
|
|
|
|
headers.append(('User-Agent', 'jsonrpc'))
|
|
|
|
self.__transport.send_headers(connection, headers)
|
|
|
|
self.__transport.send_content(connection, '' if params is None else json.dumps(params, default=jsonDecimal).encode('utf-8'))
|
|
|
|
self.__request_id += 1
|
|
|
|
|
|
|
|
resp = connection.getresponse()
|
|
|
|
return resp.read()
|
|
|
|
|
|
|
|
except Fault:
|
|
|
|
raise
|
|
|
|
except Exception:
|
|
|
|
self.__transport.close()
|
|
|
|
raise
|
|
|
|
|
|
|
|
def json_request(self, request_body, username='', password='', timeout=None):
|
|
|
|
try:
|
|
|
|
connection = self.__transport.make_connection(self.__host)
|
|
|
|
if timeout:
|
|
|
|
connection.timeout = timeout
|
|
|
|
|
|
|
|
headers = self.__transport._extra_headers[:]
|
|
|
|
|
|
|
|
connection.putrequest('POST', self.__handler)
|
|
|
|
headers.append(('Content-Type', 'application/json'))
|
|
|
|
headers.append(('Connection', 'keep-alive'))
|
|
|
|
self.__transport.send_headers(connection, headers)
|
|
|
|
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
|
|
|
|
resp = connection.getresponse()
|
|
|
|
|
|
|
|
if resp.status == 401:
|
|
|
|
resp_headers = resp.getheaders()
|
|
|
|
v = resp.read()
|
|
|
|
|
|
|
|
algorithm = ''
|
|
|
|
realm = ''
|
|
|
|
nonce = ''
|
|
|
|
for h in resp_headers:
|
|
|
|
if h[0] != 'WWW-authenticate':
|
|
|
|
continue
|
|
|
|
fields = h[1].split(',')
|
|
|
|
for f in fields:
|
|
|
|
key, value = f.split('=', 1)
|
|
|
|
if key == 'algorithm' and value != 'MD5':
|
|
|
|
break
|
|
|
|
if key == 'realm':
|
|
|
|
realm = value.strip('"')
|
|
|
|
if key == 'nonce':
|
|
|
|
nonce = value.strip('"')
|
|
|
|
if realm != '' and nonce != '':
|
|
|
|
break
|
|
|
|
|
|
|
|
if realm == '' or nonce == '':
|
|
|
|
raise ValueError('Authenticate header not found.')
|
|
|
|
|
|
|
|
path = self.__handler
|
|
|
|
HA1 = hashlib.md5(f'{username}:{realm}:{password}'.encode('utf-8')).hexdigest()
|
|
|
|
|
|
|
|
http_method = 'POST'
|
|
|
|
HA2 = hashlib.md5(f'{http_method}:{path}'.encode('utf-8')).hexdigest()
|
|
|
|
|
|
|
|
ncvalue = '{:08x}'.format(1)
|
|
|
|
s = ncvalue.encode('utf-8')
|
|
|
|
s += nonce.encode('utf-8')
|
|
|
|
s += time.ctime().encode('utf-8')
|
|
|
|
s += os.urandom(8)
|
|
|
|
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
|
|
|
|
|
|
|
# MD5-SESS
|
|
|
|
HA1 = hashlib.md5(f'{HA1}:{nonce}:{cnonce}'.encode('utf-8')).hexdigest()
|
|
|
|
|
|
|
|
respdig = hashlib.md5(f'{HA1}:{nonce}:{ncvalue}:{cnonce}:auth:{HA2}'.encode('utf-8')).hexdigest()
|
|
|
|
|
|
|
|
header_value = f'Digest username="{username}", realm="{realm}", nonce="{nonce}", uri="{path}", response="{respdig}", algorithm="MD5-sess", qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
|
|
|
|
headers = self.__transport._extra_headers[:]
|
|
|
|
headers.append(('Authorization', header_value))
|
|
|
|
|
|
|
|
connection.putrequest('POST', self.__handler)
|
|
|
|
headers.append(('Content-Type', 'application/json'))
|
|
|
|
headers.append(('Connection', 'keep-alive'))
|
|
|
|
self.__transport.send_headers(connection, headers)
|
|
|
|
self.__transport.send_content(connection, json.dumps(request_body, default=jsonDecimal).encode('utf-8') if request_body else '')
|
|
|
|
resp = connection.getresponse()
|
|
|
|
|
|
|
|
self.__request_id += 1
|
|
|
|
return resp.read()
|
|
|
|
|
|
|
|
except Fault:
|
|
|
|
raise
|
|
|
|
except Exception:
|
|
|
|
self.__transport.close()
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
def callrpc_xmr(rpc_port, method, params=[], rpc_host='127.0.0.1', path='json_rpc', auth=None, timeout=120, transport=None, tag=''):
|
|
|
|
# auth is a tuple: (username, password)
|
|
|
|
try:
|
|
|
|
if rpc_host.count('://') > 0:
|
|
|
|
url = '{}:{}/{}'.format(rpc_host, rpc_port, path)
|
|
|
|
else:
|
|
|
|
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, path)
|
|
|
|
|
|
|
|
x = JsonrpcDigest(url, transport=transport)
|
|
|
|
request_body = {
|
|
|
|
'method': method,
|
|
|
|
'params': params,
|
|
|
|
'jsonrpc': '2.0',
|
|
|
|
'id': x.request_id()
|
|
|
|
}
|
|
|
|
if auth:
|
|
|
|
v = x.json_request(request_body, username=auth[0], password=auth[1], timeout=timeout)
|
|
|
|
else:
|
|
|
|
v = x.json_request(request_body, timeout=timeout)
|
|
|
|
x.close()
|
|
|
|
r = json.loads(v.decode('utf-8'))
|
|
|
|
except Exception as ex:
|
|
|
|
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
|
|
|
|
|
|
|
|
if 'error' in r and r['error'] is not None:
|
|
|
|
raise ValueError(tag + 'RPC error ' + str(r['error']))
|
|
|
|
|
|
|
|
return r['result']
|
|
|
|
|
|
|
|
|
|
|
|
def callrpc_xmr2(rpc_port: int, method: str, params=None, auth=None, rpc_host='127.0.0.1', timeout=120, transport=None, tag=''):
|
|
|
|
try:
|
|
|
|
if rpc_host.count('://') > 0:
|
|
|
|
url = '{}:{}/{}'.format(rpc_host, rpc_port, method)
|
|
|
|
else:
|
|
|
|
url = 'http://{}:{}/{}'.format(rpc_host, rpc_port, method)
|
|
|
|
|
|
|
|
x = JsonrpcDigest(url, transport=transport)
|
|
|
|
if auth:
|
|
|
|
v = x.json_request(params, username=auth[0], password=auth[1], timeout=timeout)
|
|
|
|
else:
|
|
|
|
v = x.json_request(params, timeout=timeout)
|
|
|
|
x.close()
|
|
|
|
r = json.loads(v.decode('utf-8'))
|
|
|
|
except Exception as ex:
|
|
|
|
raise ValueError('{}RPC Server Error: {}'.format(tag, str(ex)))
|
|
|
|
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
def make_xmr_rpc2_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
|
|
|
|
port = port
|
|
|
|
auth = auth
|
|
|
|
host = host
|
|
|
|
transport = None
|
|
|
|
default_timeout = default_timeout
|
|
|
|
tag = tag
|
|
|
|
|
|
|
|
if proxy_host:
|
|
|
|
transport = SocksTransport()
|
|
|
|
transport.set_proxy(proxy_host, proxy_port)
|
|
|
|
|
|
|
|
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
|
|
|
|
nonlocal port, auth, host, transport, tag
|
|
|
|
return callrpc_xmr2(port, method, params, auth=auth, rpc_host=host, timeout=timeout, transport=transport, tag=tag)
|
|
|
|
return rpc_func
|
|
|
|
|
|
|
|
|
|
|
|
def make_xmr_rpc_func(port, auth, host='127.0.0.1', proxy_host=None, proxy_port=None, default_timeout=120, tag=''):
|
|
|
|
port = port
|
|
|
|
auth = auth
|
|
|
|
host = host
|
|
|
|
transport = None
|
|
|
|
default_timeout = default_timeout
|
|
|
|
tag = tag
|
|
|
|
|
|
|
|
if proxy_host:
|
|
|
|
transport = SocksTransport()
|
|
|
|
transport.set_proxy(proxy_host, proxy_port)
|
|
|
|
|
|
|
|
def rpc_func(method, params=None, wallet=None, timeout=default_timeout):
|
|
|
|
nonlocal port, auth, host, transport, tag
|
|
|
|
return callrpc_xmr(port, method, params, rpc_host=host, auth=auth, timeout=timeout, transport=transport, tag=tag)
|
|
|
|
return rpc_func
|