2016-05-01 08:34:04 +02:00
|
|
|
# Copyright (c) 2012-2016 Jonathan Warren
|
2018-04-08 17:28:08 +02:00
|
|
|
# Copyright (c) 2012-2018 The Bitmessage developers
|
2014-01-13 01:56:30 +01:00
|
|
|
|
2018-04-08 17:28:08 +02:00
|
|
|
"""
|
2014-01-13 01:56:30 +01:00
|
|
|
This is not what you run to run the Bitmessage API. Instead, enable the API
|
|
|
|
( https://bitmessage.org/wiki/API ) and optionally enable daemon mode
|
2015-03-21 12:53:09 +01:00
|
|
|
( https://bitmessage.org/wiki/Daemon ) then run bitmessagemain.py.
|
2014-01-13 01:56:30 +01:00
|
|
|
"""
|
|
|
|
|
2017-06-03 02:53:13 +02:00
|
|
|
import base64
|
2018-04-08 17:28:08 +02:00
|
|
|
import hashlib
|
2014-01-13 01:56:30 +01:00
|
|
|
import json
|
2018-04-08 17:28:08 +02:00
|
|
|
import time
|
2017-06-03 02:53:13 +02:00
|
|
|
from binascii import hexlify, unhexlify
|
2018-04-08 17:28:08 +02:00
|
|
|
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCServer
|
|
|
|
from struct import pack
|
2014-01-13 01:56:30 +01:00
|
|
|
|
|
|
|
import shared
|
2018-04-08 17:28:08 +02:00
|
|
|
from addresses import (
|
|
|
|
decodeAddress, addBMIfNotPresent, decodeVarint,
|
2018-04-10 09:14:32 +02:00
|
|
|
calculateInventoryHash, varintDecodeError)
|
2017-02-22 09:34:54 +01:00
|
|
|
from bmconfigparser import BMConfigParser
|
2017-02-08 20:37:42 +01:00
|
|
|
import defaults
|
2014-01-13 01:56:30 +01:00
|
|
|
import helper_inbox
|
|
|
|
import helper_sent
|
|
|
|
|
2017-01-11 17:00:00 +01:00
|
|
|
import state
|
2017-02-08 13:41:56 +01:00
|
|
|
import queues
|
2017-08-21 10:39:03 +02:00
|
|
|
import shutdown
|
2017-06-09 10:07:51 +02:00
|
|
|
import network.stats
|
2014-01-13 01:56:30 +01:00
|
|
|
|
|
|
|
# Classes
|
2018-04-10 09:14:32 +02:00
|
|
|
from helper_sql import sqlQuery, sqlExecute, SqlBulkExecute, sqlStoredProcedure
|
2017-09-30 11:19:44 +02:00
|
|
|
from helper_ackPayload import genAckPayload
|
2014-01-13 01:56:30 +01:00
|
|
|
from debug import logger
|
2017-01-10 21:17:25 +01:00
|
|
|
from inventory import Inventory
|
2017-01-11 14:27:19 +01:00
|
|
|
from version import softwareVersion
|
2014-01-13 01:56:30 +01:00
|
|
|
|
|
|
|
# Helper Functions
|
|
|
|
import proofofwork
|
|
|
|
|
|
|
|
str_chan = '[chan]'
|
|
|
|
|
|
|
|
|
|
|
|
class APIError(Exception):
|
|
|
|
def __init__(self, error_number, error_message):
|
|
|
|
super(APIError, self).__init__()
|
|
|
|
self.error_number = error_number
|
|
|
|
self.error_message = error_message
|
2018-04-08 17:28:08 +02:00
|
|
|
|
2014-01-13 01:56:30 +01:00
|
|
|
def __str__(self):
|
|
|
|
return "API Error %04i: %s" % (self.error_number, self.error_message)
|
|
|
|
|
2015-11-24 01:55:17 +01:00
|
|
|
|
|
|
|
class StoppableXMLRPCServer(SimpleXMLRPCServer):
|
2017-08-09 17:29:23 +02:00
|
|
|
allow_reuse_address = True
|
|
|
|
|
2015-11-24 01:55:17 +01:00
|
|
|
def serve_forever(self):
|
2017-01-14 23:20:15 +01:00
|
|
|
while state.shutdown == 0:
|
2015-11-24 01:55:17 +01:00
|
|
|
self.handle_request()
|
|
|
|
|
|
|
|
|
2014-01-13 01:56:30 +01:00
|
|
|
# This is one of several classes that constitute the API
|
2018-04-08 17:28:08 +02:00
|
|
|
# This class was written by Vaibhav Bhatia.
|
|
|
|
# Modified by Jonathan Warren (Atheros).
|
2014-01-13 01:56:30 +01:00
|
|
|
# http://code.activestate.com/recipes/501148-xmlrpc-serverclient-which-does-cookie-handling-and/
|
|
|
|
class MySimpleXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
|
|
|
|
|
|
|
|
def do_POST(self):
|
|
|
|
# Handles the HTTP POST request.
|
|
|
|
# Attempts to interpret all HTTP POST requests as XML-RPC calls,
|
|
|
|
# which are forwarded to the server's _dispatch method for handling.
|
|
|
|
|
|
|
|
# Note: this method is the same as in SimpleXMLRPCRequestHandler,
|
|
|
|
# just hacked to handle cookies
|
|
|
|
|
|
|
|
# Check that the path is legal
|
|
|
|
if not self.is_rpc_path_valid():
|
|
|
|
self.report_404()
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
# Get arguments by reading body of request.
|
|
|
|
# We read this in chunks to avoid straining
|
|
|
|
# socket.read(); around the 10 or 15Mb mark, some platforms
|
|
|
|
# begin to have problems (bug #792570).
|
|
|
|
max_chunk_size = 10 * 1024 * 1024
|
|
|
|
size_remaining = int(self.headers["content-length"])
|
|
|
|
L = []
|
|
|
|
while size_remaining:
|
|
|
|
chunk_size = min(size_remaining, max_chunk_size)
|
|
|
|
L.append(self.rfile.read(chunk_size))
|
|
|
|
size_remaining -= len(L[-1])
|
|
|
|
data = ''.join(L)
|
|
|
|
|
|
|
|
# In previous versions of SimpleXMLRPCServer, _dispatch
|
|
|
|
# could be overridden in this class, instead of in
|
|
|
|
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
|
|
|
|
# check to see if a subclass implements _dispatch and dispatch
|
|
|
|
# using that method if present.
|
|
|
|
response = self.server._marshaled_dispatch(
|
|
|
|
data, getattr(self, '_dispatch', None)
|
|
|
|
)
|
|
|
|
except: # This should only happen if the module is buggy
|
|
|
|
# internal error, report as HTTP server error
|
|
|
|
self.send_response(500)
|
|
|
|
self.end_headers()
|
|
|
|
else:
|
|
|
|
# got a valid XML RPC response
|
|
|
|
self.send_response(200)
|
|
|
|
self.send_header("Content-type", "text/xml")
|
|
|
|
self.send_header("Content-length", str(len(response)))
|
|
|
|
|
|
|
|
# HACK :start -> sends cookies here
|
|
|
|
if self.cookies:
|
|
|
|
for cookie in self.cookies:
|
|
|
|
self.send_header('Set-Cookie', cookie.output(header=''))
|
|
|
|
# HACK :end
|
|
|
|
|
|
|
|
self.end_headers()
|
|
|
|
self.wfile.write(response)
|
|
|
|
|
|
|
|
# shut down the connection
|
|
|
|
self.wfile.flush()
|
|
|
|
self.connection.shutdown(1)
|
|
|
|
|
|
|
|
def APIAuthenticateClient(self):
|
|
|
|
if 'Authorization' in self.headers:
|
|
|
|
# handle Basic authentication
|
2018-04-08 17:28:08 +02:00
|
|
|
enctype, encstr = self.headers.get('Authorization').split()
|
|
|
|
emailid, password = encstr.decode('base64').split(':')
|
|
|
|
return (
|
|
|
|
emailid ==
|
|
|
|
BMConfigParser().get('bitmessagesettings', 'apiusername')
|
|
|
|
and password ==
|
|
|
|
BMConfigParser().get('bitmessagesettings', 'apipassword')
|
|
|
|
)
|
2014-01-13 01:56:30 +01:00
|
|
|
else:
|
2018-04-08 17:28:08 +02:00
|
|
|
logger.warning(
|
|
|
|
'Authentication failed because header lacks'
|
|
|
|
' Authentication field')
|
2014-01-13 01:56:30 +01:00
|
|
|
time.sleep(2)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _decode(self, text, decode_type):
|
|
|
|
try:
|
2017-06-03 02:53:13 +02:00
|
|
|
if decode_type == 'hex':
|
|
|
|
return unhexlify(text)
|
|
|
|
elif decode_type == 'base64':
|
|
|
|
return base64.b64decode(text)
|
2014-01-13 01:56:30 +01:00
|
|
|
except Exception as e:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
22, "Decode error - %s. Had trouble while decoding string: %r"
|
|
|
|
% (e, text)
|
|
|
|
)
|
2014-01-13 01:56:30 +01:00
|
|
|
|
|
|
|
def _verifyAddress(self, address):
|
2018-04-08 17:28:08 +02:00
|
|
|
status, addressVersionNumber, streamNumber, ripe = \
|
|
|
|
decodeAddress(address)
|
2014-01-13 01:56:30 +01:00
|
|
|
if status != 'success':
|
2018-04-08 17:28:08 +02:00
|
|
|
logger.warning(
|
|
|
|
'API Error 0007: Could not decode address %s. Status: %s.',
|
|
|
|
address, status
|
|
|
|
)
|
2014-01-13 01:56:30 +01:00
|
|
|
|
|
|
|
if status == 'checksumfailed':
|
|
|
|
raise APIError(8, 'Checksum failed for address: ' + address)
|
|
|
|
if status == 'invalidcharacters':
|
|
|
|
raise APIError(9, 'Invalid characters in address: ' + address)
|
|
|
|
if status == 'versiontoohigh':
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
10,
|
|
|
|
'Address version number too high (or zero) in address: '
|
|
|
|
+ address
|
|
|
|
)
|
2014-08-27 09:14:32 +02:00
|
|
|
if status == 'varintmalformed':
|
|
|
|
raise APIError(26, 'Malformed varint in address: ' + address)
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
7, 'Could not decode address: %s : %s' % (address, status))
|
2014-01-13 01:56:30 +01:00
|
|
|
if addressVersionNumber < 2 or addressVersionNumber > 4:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
11, 'The address version number currently must be 2, 3 or 4.'
|
|
|
|
' Others aren\'t supported. Check the address.'
|
|
|
|
)
|
2014-01-13 01:56:30 +01:00
|
|
|
if streamNumber != 1:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
12, 'The stream number must be 1. Others aren\'t supported.'
|
|
|
|
' Check the address.'
|
|
|
|
)
|
2014-01-13 01:56:30 +01:00
|
|
|
|
|
|
|
return (status, addressVersionNumber, streamNumber, ripe)
|
|
|
|
|
2018-04-08 17:28:08 +02:00
|
|
|
# Request Handlers
|
2015-09-28 20:55:40 +02:00
|
|
|
|
|
|
|
def HandleListAddresses(self, method):
|
|
|
|
data = '{"addresses":['
|
2017-05-15 12:18:07 +02:00
|
|
|
for addressInKeysFile in BMConfigParser().addresses():
|
|
|
|
status, addressVersionNumber, streamNumber, hash01 = decodeAddress(
|
|
|
|
addressInKeysFile)
|
|
|
|
if len(data) > 20:
|
|
|
|
data += ','
|
|
|
|
if BMConfigParser().has_option(addressInKeysFile, 'chan'):
|
|
|
|
chan = BMConfigParser().getboolean(addressInKeysFile, 'chan')
|
|
|
|
else:
|
|
|
|
chan = False
|
|
|
|
label = BMConfigParser().get(addressInKeysFile, 'label')
|
|
|
|
if method == 'listAddresses2':
|
2017-06-03 02:53:13 +02:00
|
|
|
label = base64.b64encode(label)
|
2018-04-08 17:28:08 +02:00
|
|
|
data += json.dumps({
|
|
|
|
'label': label,
|
|
|
|
'address': addressInKeysFile,
|
|
|
|
'stream': streamNumber,
|
|
|
|
'enabled':
|
|
|
|
BMConfigParser().getboolean(addressInKeysFile, 'enabled'),
|
|
|
|
'chan': chan
|
|
|
|
}, indent=4, separators=(',', ': '))
|
2015-03-21 12:53:09 +01:00
|
|
|
data += ']}'
|
|
|
|
return data
|
|
|
|
|
|
|
|
def HandleListAddressBookEntries(self, params):
|
2015-09-28 20:55:40 +02:00
|
|
|
if len(params) == 1:
|
|
|
|
label, = params
|
|
|
|
label = self._decode(label, "base64")
|
2018-04-08 17:28:08 +02:00
|
|
|
queryreturn = sqlQuery(
|
|
|
|
"SELECT label, address from addressbook WHERE label = ?",
|
|
|
|
label)
|
2015-09-28 20:55:40 +02:00
|
|
|
elif len(params) > 1:
|
|
|
|
raise APIError(0, "Too many paremeters, max 1")
|
|
|
|
else:
|
2018-04-08 17:28:08 +02:00
|
|
|
queryreturn = sqlQuery("SELECT label, address from addressbook")
|
2015-03-21 12:53:09 +01:00
|
|
|
data = '{"addresses":['
|
|
|
|
for row in queryreturn:
|
|
|
|
label, address = row
|
|
|
|
label = shared.fixPotentiallyInvalidUTF8Data(label)
|
|
|
|
if len(data) > 20:
|
|
|
|
data += ','
|
2018-04-08 17:28:08 +02:00
|
|
|
data += json.dumps({
|
|
|
|
'label': base64.b64encode(label),
|
|
|
|
'address': address}, indent=4, separators=(',', ': '))
|
2015-03-21 12:53:09 +01:00
|
|
|
data += ']}'
|
|
|
|
return data
|
|
|
|
|
|
|
|
def HandleAddAddressBookEntry(self, params):
|
|
|
|
if len(params) != 2:
|
|
|
|
raise APIError(0, "I need label and address")
|
|
|
|
address, label = params
|
|
|
|
label = self._decode(label, "base64")
|
|
|
|
address = addBMIfNotPresent(address)
|
|
|
|
self._verifyAddress(address)
|
2018-04-08 17:28:08 +02:00
|
|
|
queryreturn = sqlQuery(
|
|
|
|
"SELECT address FROM addressbook WHERE address=?", address)
|
2015-03-21 12:53:09 +01:00
|
|
|
if queryreturn != []:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
16, 'You already have this address in your address book.')
|
2015-03-21 12:53:09 +01:00
|
|
|
|
|
|
|
sqlExecute("INSERT INTO addressbook VALUES(?,?)", label, address)
|
2018-04-08 17:28:08 +02:00
|
|
|
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
|
|
|
|
queues.UISignalQueue.put(('rerenderMessagelistToLabels', ''))
|
|
|
|
queues.UISignalQueue.put(('rerenderAddressBook', ''))
|
2015-03-21 12:53:09 +01:00
|
|
|
return "Added address %s to address book" % address
|
|
|
|
|
|
|
|
def HandleDeleteAddressBookEntry(self, params):
|
|
|
|
if len(params) != 1:
|
|
|
|
raise APIError(0, "I need an address")
|
|
|
|
address, = params
|
|
|
|
address = addBMIfNotPresent(address)
|
|
|
|
self._verifyAddress(address)
|
|
|
|
sqlExecute('DELETE FROM addressbook WHERE address=?', address)
|
2018-04-08 17:28:08 +02:00
|
|
|
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
|
|
|
|
queues.UISignalQueue.put(('rerenderMessagelistToLabels', ''))
|
|
|
|
queues.UISignalQueue.put(('rerenderAddressBook', ''))
|
2015-03-21 12:53:09 +01:00
|
|
|
return "Deleted address book entry for %s if it existed" % address
|
|
|
|
|
|
|
|
def HandleCreateRandomAddress(self, params):
|
|
|
|
if len(params) == 0:
|
|
|
|
raise APIError(0, 'I need parameters!')
|
|
|
|
elif len(params) == 1:
|
|
|
|
label, = params
|
|
|
|
eighteenByteRipe = False
|
2017-01-11 14:27:19 +01:00
|
|
|
nonceTrialsPerByte = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultnoncetrialsperbyte')
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 2:
|
|
|
|
label, eighteenByteRipe = params
|
2017-01-11 14:27:19 +01:00
|
|
|
nonceTrialsPerByte = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultnoncetrialsperbyte')
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 3:
|
|
|
|
label, eighteenByteRipe, totalDifficulty = params
|
|
|
|
nonceTrialsPerByte = int(
|
2018-04-08 17:28:08 +02:00
|
|
|
defaults.networkDefaultProofOfWorkNonceTrialsPerByte
|
|
|
|
* totalDifficulty)
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 4:
|
2018-04-08 17:28:08 +02:00
|
|
|
label, eighteenByteRipe, totalDifficulty, \
|
|
|
|
smallMessageDifficulty = params
|
2015-03-21 12:53:09 +01:00
|
|
|
nonceTrialsPerByte = int(
|
2018-04-08 17:28:08 +02:00
|
|
|
defaults.networkDefaultProofOfWorkNonceTrialsPerByte
|
|
|
|
* totalDifficulty)
|
2015-03-21 12:53:09 +01:00
|
|
|
payloadLengthExtraBytes = int(
|
2018-04-08 17:28:08 +02:00
|
|
|
defaults.networkDefaultPayloadLengthExtraBytes
|
|
|
|
* smallMessageDifficulty)
|
2015-03-21 12:53:09 +01:00
|
|
|
else:
|
|
|
|
raise APIError(0, 'Too many parameters!')
|
|
|
|
label = self._decode(label, "base64")
|
|
|
|
try:
|
|
|
|
unicode(label, 'utf-8')
|
|
|
|
except:
|
|
|
|
raise APIError(17, 'Label is not valid UTF-8 data.')
|
2017-02-08 13:41:56 +01:00
|
|
|
queues.apiAddressGeneratorReturnQueue.queue.clear()
|
2015-03-21 12:53:09 +01:00
|
|
|
streamNumberForAddress = 1
|
2017-02-08 13:41:56 +01:00
|
|
|
queues.addressGeneratorQueue.put((
|
2018-04-08 17:28:08 +02:00
|
|
|
'createRandomAddress', 4, streamNumberForAddress, label, 1, "",
|
|
|
|
eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes
|
|
|
|
))
|
2017-02-08 13:41:56 +01:00
|
|
|
return queues.apiAddressGeneratorReturnQueue.get()
|
2015-03-21 12:53:09 +01:00
|
|
|
|
|
|
|
def HandleCreateDeterministicAddresses(self, params):
|
|
|
|
if len(params) == 0:
|
|
|
|
raise APIError(0, 'I need parameters!')
|
|
|
|
elif len(params) == 1:
|
|
|
|
passphrase, = params
|
2014-01-13 01:56:30 +01:00
|
|
|
numberOfAddresses = 1
|
2015-03-21 12:53:09 +01:00
|
|
|
addressVersionNumber = 0
|
|
|
|
streamNumber = 0
|
2015-03-21 12:45:56 +01:00
|
|
|
eighteenByteRipe = False
|
2017-01-11 14:27:19 +01:00
|
|
|
nonceTrialsPerByte = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultnoncetrialsperbyte')
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 2:
|
|
|
|
passphrase, numberOfAddresses = params
|
|
|
|
addressVersionNumber = 0
|
|
|
|
streamNumber = 0
|
|
|
|
eighteenByteRipe = False
|
2017-01-11 14:27:19 +01:00
|
|
|
nonceTrialsPerByte = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultnoncetrialsperbyte')
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 3:
|
|
|
|
passphrase, numberOfAddresses, addressVersionNumber = params
|
|
|
|
streamNumber = 0
|
|
|
|
eighteenByteRipe = False
|
2017-01-11 14:27:19 +01:00
|
|
|
nonceTrialsPerByte = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultnoncetrialsperbyte')
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 4:
|
2018-04-08 17:28:08 +02:00
|
|
|
passphrase, numberOfAddresses, addressVersionNumber, \
|
|
|
|
streamNumber = params
|
2015-03-21 12:53:09 +01:00
|
|
|
eighteenByteRipe = False
|
2017-01-11 14:27:19 +01:00
|
|
|
nonceTrialsPerByte = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultnoncetrialsperbyte')
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 5:
|
2018-04-08 17:28:08 +02:00
|
|
|
passphrase, numberOfAddresses, addressVersionNumber, \
|
|
|
|
streamNumber, eighteenByteRipe = params
|
2017-01-11 14:27:19 +01:00
|
|
|
nonceTrialsPerByte = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultnoncetrialsperbyte')
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 6:
|
2018-04-08 17:28:08 +02:00
|
|
|
passphrase, numberOfAddresses, addressVersionNumber, \
|
|
|
|
streamNumber, eighteenByteRipe, totalDifficulty = params
|
2015-03-21 12:53:09 +01:00
|
|
|
nonceTrialsPerByte = int(
|
2018-04-08 17:28:08 +02:00
|
|
|
defaults.networkDefaultProofOfWorkNonceTrialsPerByte
|
|
|
|
* totalDifficulty)
|
2017-01-11 14:27:19 +01:00
|
|
|
payloadLengthExtraBytes = BMConfigParser().get(
|
2015-03-21 12:53:09 +01:00
|
|
|
'bitmessagesettings', 'defaultpayloadlengthextrabytes')
|
|
|
|
elif len(params) == 7:
|
2018-04-08 17:28:08 +02:00
|
|
|
passphrase, numberOfAddresses, addressVersionNumber, \
|
|
|
|
streamNumber, eighteenByteRipe, totalDifficulty, \
|
|
|
|
smallMessageDifficulty = params
|
2015-03-21 12:53:09 +01:00
|
|
|
nonceTrialsPerByte = int(
|
2018-04-08 17:28:08 +02:00
|
|
|
defaults.networkDefaultProofOfWorkNonceTrialsPerByte
|
|
|
|
* totalDifficulty)
|
2015-03-21 12:53:09 +01:00
|
|
|
payloadLengthExtraBytes = int(
|
2018-04-08 17:28:08 +02:00
|
|
|
defaults.networkDefaultPayloadLengthExtraBytes
|
|
|
|
* smallMessageDifficulty)
|
2015-03-21 12:53:09 +01:00
|
|
|
else:
|
|
|
|
raise APIError(0, 'Too many parameters!')
|
|
|
|
if len(passphrase) == 0:
|
|
|
|
raise APIError(1, 'The specified passphrase is blank.')
|
|
|
|
if not isinstance(eighteenByteRipe, bool):
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
23, 'Bool expected in eighteenByteRipe, saw %s instead' %
|
|
|
|
type(eighteenByteRipe))
|
2015-03-21 12:53:09 +01:00
|
|
|
passphrase = self._decode(passphrase, "base64")
|
2018-04-08 17:28:08 +02:00
|
|
|
# 0 means "just use the proper addressVersionNumber"
|
|
|
|
if addressVersionNumber == 0:
|
2014-01-13 01:56:30 +01:00
|
|
|
addressVersionNumber = 4
|
2015-03-21 12:53:09 +01:00
|
|
|
if addressVersionNumber != 3 and addressVersionNumber != 4:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
2, 'The address version number currently must be 3, 4, or 0'
|
|
|
|
' (which means auto-select). %i isn\'t supported.' %
|
|
|
|
addressVersionNumber)
|
2015-03-21 12:53:09 +01:00
|
|
|
if streamNumber == 0: # 0 means "just use the most available stream"
|
2014-01-13 01:56:30 +01:00
|
|
|
streamNumber = 1
|
2015-03-21 12:53:09 +01:00
|
|
|
if streamNumber != 1:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
3, 'The stream number must be 1 (or 0 which means'
|
|
|
|
' auto-select). Others aren\'t supported.')
|
2015-03-21 12:53:09 +01:00
|
|
|
if numberOfAddresses == 0:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
4, 'Why would you ask me to generate 0 addresses for you?')
|
2015-03-21 12:53:09 +01:00
|
|
|
if numberOfAddresses > 999:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
5, 'You have (accidentally?) specified too many addresses to'
|
|
|
|
' make. Maximum 999. This check only exists to prevent'
|
|
|
|
' mischief; if you really want to create more addresses than'
|
|
|
|
' this, contact the Bitmessage developers and we can modify'
|
|
|
|
' the check or you can do it yourself by searching the source'
|
|
|
|
' code for this message.')
|
2017-02-08 13:41:56 +01:00
|
|
|
queues.apiAddressGeneratorReturnQueue.queue.clear()
|
2018-04-08 17:28:08 +02:00
|
|
|
logger.debug(
|
|
|
|
'Requesting that the addressGenerator create %s addresses.',
|
|
|
|
numberOfAddresses)
|
|
|
|
queues.addressGeneratorQueue.put((
|
|
|
|
'createDeterministicAddresses', addressVersionNumber, streamNumber,
|
|
|
|
'unused API address', numberOfAddresses, passphrase,
|
|
|
|
eighteenByteRipe, nonceTrialsPerByte, payloadLengthExtraBytes
|
|
|
|
))
|
2015-03-21 12:53:09 +01:00
|
|
|
data = '{"addresses":['
|
2017-02-08 13:41:56 +01:00
|
|
|
queueReturn = queues.apiAddressGeneratorReturnQueue.get()
|
2015-03-21 12:53:09 +01:00
|
|
|
for item in queueReturn:
|
|
|
|
if len(data) > 20:
|
|
|
|
data += ','
|
|
|
|
data += "\"" + item + "\""
|
|
|
|
data += ']}'
|
|
|
|
return data
|
|
|
|
|
|
|
|
def HandleGetDeterministicAddress(self, params):
|
|
|
|
if len(params) != 3:
|
|
|
|
raise APIError(0, 'I need exactly 3 parameters.')
|
|
|
|
passphrase, addressVersionNumber, streamNumber = params
|
|
|
|
numberOfAddresses = 1
|
|
|
|
eighteenByteRipe = False
|
|
|
|
if len(passphrase) == 0:
|
|
|
|
raise APIError(1, 'The specified passphrase is blank.')
|
|
|
|
passphrase = self._decode(passphrase, "base64")
|
|
|
|
if addressVersionNumber != 3 and addressVersionNumber != 4:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
2, 'The address version number currently must be 3 or 4. %i'
|
|
|
|
' isn\'t supported.' % addressVersionNumber)
|
2015-03-21 12:53:09 +01:00
|
|
|
if streamNumber != 1:
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
3, ' The stream number must be 1. Others aren\'t supported.')
|
2017-02-08 13:41:56 +01:00
|
|
|
queues.apiAddressGeneratorReturnQueue.queue.clear()
|
2018-04-08 17:28:08 +02:00
|
|
|
logger.debug(
|
|
|
|
'Requesting that the addressGenerator create %s addresses.',
|
|
|
|
numberOfAddresses)
|
|
|
|
queues.addressGeneratorQueue.put((
|
|
|
|
'getDeterministicAddress', addressVersionNumber, streamNumber,
|
|
|
|
'unused API address', numberOfAddresses, passphrase,
|
|
|
|
eighteenByteRipe
|
|
|
|
))
|
2017-02-08 13:41:56 +01:00
|
|
|
return queues.apiAddressGeneratorReturnQueue.get()
|
2015-03-21 12:53:09 +01:00
|
|
|
|
2015-09-28 20:55:40 +02:00
|
|
|
def HandleCreateChan(self, params):
|
|
|
|
if len(params) == 0:
|
|
|
|
raise APIError(0, 'I need parameters.')
|
|
|
|
elif len(params) == 1:
|
|
|
|
passphrase, = params
|
|
|
|
passphrase = self._decode(passphrase, "base64")
|
|
|
|
if len(passphrase) == 0:
|
|
|
|
raise APIError(1, 'The specified passphrase is blank.')
|
|
|
|
# It would be nice to make the label the passphrase but it is
|
|
|
|
# possible that the passphrase contains non-utf-8 characters.
|
|
|
|
try:
|
|
|
|
unicode(passphrase, 'utf-8')
|
|
|
|
label = str_chan + ' ' + passphrase
|
|
|
|
except:
|
|
|
|
label = str_chan + ' ' + repr(passphrase)
|
|
|
|
|
|
|
|
addressVersionNumber = 4
|
|
|
|
streamNumber = 1
|
2017-02-08 13:41:56 +01:00
|
|
|
queues.apiAddressGeneratorReturnQueue.queue.clear()
|
2018-04-08 17:28:08 +02:00
|
|
|
logger.debug(
|
|
|
|
'Requesting that the addressGenerator create chan %s.', passphrase)
|
|
|
|
queues.addressGeneratorQueue.put((
|
|
|
|
'createChan', addressVersionNumber, streamNumber, label,
|
|
|
|
passphrase, True
|
|
|
|
))
|
2017-02-08 13:41:56 +01:00
|
|
|
queueReturn = queues.apiAddressGeneratorReturnQueue.get()
|
2015-09-28 20:55:40 +02:00
|
|
|
if len(queueReturn) == 0:
|
|
|
|
raise APIError(24, 'Chan address is already present.')
|
|
|
|
address = queueReturn[0]
|
|
|
|
return address
|
|
|
|
|
|
|
|
def HandleJoinChan(self, params):
|
|
|
|
if len(params) < 2:
|
|
|
|
raise APIError(0, 'I need two parameters.')
|
|
|
|
elif len(params) == 2:
|
2018-04-08 17:28:08 +02:00
|
|
|
passphrase, suppliedAddress = params
|
2015-09-28 20:55:40 +02:00
|
|
|
passphrase = self._decode(passphrase, "base64")
|
|
|
|
if len(passphrase) == 0:
|
|
|
|
raise APIError(1, 'The specified passphrase is blank.')
|
|
|
|
# It would be nice to make the label the passphrase but it is
|
|
|
|
# possible that the passphrase contains non-utf-8 characters.
|
|
|
|
try:
|
|
|
|
unicode(passphrase, 'utf-8')
|
|
|
|
label = str_chan + ' ' + passphrase
|
|
|
|
except:
|
|
|
|
label = str_chan + ' ' + repr(passphrase)
|
|
|
|
|
2018-04-08 17:28:08 +02:00
|
|
|
status, addressVersionNumber, streamNumber, toRipe = \
|
|
|
|
self._verifyAddress(suppliedAddress)
|
2015-09-28 20:55:40 +02:00
|
|
|
suppliedAddress = addBMIfNotPresent(suppliedAddress)
|
2017-02-08 13:41:56 +01:00
|
|
|
queues.apiAddressGeneratorReturnQueue.queue.clear()
|
2018-04-08 17:28:08 +02:00
|
|
|
queues.addressGeneratorQueue.put((
|
|
|
|
'joinChan', suppliedAddress, label, passphrase, True
|
|
|
|
))
|
|
|
|
addressGeneratorReturnValue = \
|
|
|
|
queues.apiAddressGeneratorReturnQueue.get()
|
2015-09-28 20:55:40 +02:00
|
|
|
|
2018-04-08 17:28:08 +02:00
|
|
|
if addressGeneratorReturnValue[0] == \
|
|
|
|
'chan name does not match address':
|
2015-09-28 20:55:40 +02:00
|
|
|
raise APIError(18, 'Chan name does not match address.')
|
|
|
|
if len(addressGeneratorReturnValue) == 0:
|
|
|
|
raise APIError(24, 'Chan address is already present.')
|
2018-04-08 17:28:08 +02:00
|
|
|
# TODO: this variable is not used to anything
|
|
|
|
# in case we ever want it for anything.
|
|
|
|
# createdAddress = addressGeneratorReturnValue[0]
|
2015-09-28 20:55:40 +02:00
|
|
|
return "success"
|
|
|
|
|
|
|
|
def HandleLeaveChan(self, params):
|
|
|
|
if len(params) == 0:
|
|
|
|
raise APIError(0, 'I need parameters.')
|
|
|
|
elif len(params) == 1:
|
|
|
|
address, = params
|
2018-04-08 17:28:08 +02:00
|
|
|
status, addressVersionNumber, streamNumber, toRipe = \
|
|
|
|
self._verifyAddress(address)
|
2015-09-28 20:55:40 +02:00
|
|
|
address = addBMIfNotPresent(address)
|
2017-01-11 14:27:19 +01:00
|
|
|
if not BMConfigParser().has_section(address):
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
13, 'Could not find this address in your keys.dat file.')
|
2017-01-11 14:27:19 +01:00
|
|
|
if not BMConfigParser().safeGetBoolean(address, 'chan'):
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
25, 'Specified address is not a chan address.'
|
|
|
|
' Use deleteAddress API call instead.')
|
2017-01-11 14:27:19 +01:00
|
|
|
BMConfigParser().remove_section(address)
|
2017-01-11 17:00:00 +01:00
|
|
|
with open(state.appdata + 'keys.dat', 'wb') as configfile:
|
2017-01-11 14:27:19 +01:00
|
|
|
BMConfigParser().write(configfile)
|
2015-09-28 20:55:40 +02:00
|
|
|
return 'success'
|
|
|
|
|
|
|
|
def HandleDeleteAddress(self, params):
|
|
|
|
if len(params) == 0:
|
|
|
|
raise APIError(0, 'I need parameters.')
|
|
|
|
elif len(params) == 1:
|
|
|
|
address, = params
|
2018-04-08 17:28:08 +02:00
|
|
|
status, addressVersionNumber, streamNumber, toRipe = \
|
|
|
|
self._verifyAddress(address)
|
2015-09-28 20:55:40 +02:00
|
|
|
address = addBMIfNotPresent(address)
|
2017-01-11 14:27:19 +01:00
|
|
|
if not BMConfigParser().has_section(address):
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
13, 'Could not find this address in your keys.dat file.')
|
2017-01-11 14:27:19 +01:00
|
|
|
BMConfigParser().remove_section(address)
|
2017-01-11 17:00:00 +01:00
|
|
|
with open(state.appdata + 'keys.dat', 'wb') as configfile:
|
2017-01-11 14:27:19 +01:00
|
|
|
BMConfigParser().write(configfile)
|
2018-04-08 17:28:08 +02:00
|
|
|
queues.UISignalQueue.put(('rerenderMessagelistFromLabels', ''))
|
|
|
|
queues.UISignalQueue.put(('rerenderMessagelistToLabels', ''))
|
2015-09-28 20:55:40 +02:00
|
|
|
shared.reloadMyAddressHashes()
|
|
|
|
return 'success'
|
|
|
|
|
|
|
|
def HandleGetAllInboxMessages(self, params):
|
|
|
|
queryreturn = sqlQuery(
|
2018-04-08 17:28:08 +02:00
|
|
|
"SELECT msgid, toaddress, fromaddress, subject, received, message,"
|
|
|
|
" encodingtype, read FROM inbox where folder='inbox'"
|
|
|
|
" ORDER BY received"
|
|
|
|
)
|
2015-09-28 20:55:40 +02:00
|
|
|
data = '{"inboxMessages":['
|
|
|
|
for row in queryreturn:
|
2018-04-08 17:28:08 +02:00
|
|
|
msgid, toAddress, fromAddress, subject, received, message, \
|
|
|
|
encodingtype, read = row
|
2015-09-28 20:55:40 +02:00
|
|
|
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
|
|
|
|
message = shared.fixPotentiallyInvalidUTF8Data(message)
|
|
|
|
if len(data) > 25:
|
|
|
|
data += ','
|
2018-04-08 17:28:08 +02:00
|
|
|
data += json.dumps({
|
|
|
|
'msgid': hexlify(msgid),
|
|
|
|
'toAddress': toAddress,
|
|
|
|
'fromAddress': fromAddress,
|
|
|
|
'subject': base64.b64encode(subject),
|
|
|
|
'message': base64.b64encode(message),
|
|
|
|
'encodingType': encodingtype,
|
|
|
|
'receivedTime': received,
|
|
|
|
'read': read}, indent=4, separators=(',', ': '))
|
2015-09-28 20:55:40 +02:00
|
|
|
data += ']}'
|
|
|
|
return data
|
|
|
|
|
|
|
|
def HandleGetAllInboxMessageIds(self, params):
|
|
|
|
queryreturn = sqlQuery(
|
2018-04-08 17:28:08 +02:00
|
|
|
"SELECT msgid FROM inbox where folder='inbox' ORDER BY received")
|
2015-09-28 20:55:40 +02:00
|
|
|
data = '{"inboxMessageIds":['
|
|
|
|
for row in queryreturn:
|
|
|
|
msgid = row[0]
|
|
|
|
if len(data) > 25:
|
|
|
|
data += ','
|
2018-04-08 17:28:08 +02:00
|
|
|
data += json.dumps(
|
|
|
|
{'msgid': hexlify(msgid)}, indent=4, separators=(',', ': '))
|
2015-09-28 20:55:40 +02:00
|
|
|
data += ']}'
|
|
|
|
return data
|
|
|
|
|
|
|
|
def HandleGetInboxMessageById(self, params):
|
|
|
|
if len(params) == 0:
|
|
|
|
raise APIError(0, 'I need parameters!')
|
|
|
|
elif len(params) == 1:
|
2015-03-21 12:45:56 +01:00
|
|
|
msgid = self._decode(params[0], "hex")
|
2015-03-21 12:53:09 +01:00
|
|
|
elif len(params) >= 2:
|
|
|
|
msgid = self._decode(params[0], "hex")
|
|
|
|
readStatus = params[1]
|
|
|
|
if not isinstance(readStatus, bool):
|
2018-04-08 17:28:08 +02:00
|
|
|
raise APIError(
|
|
|
|
23, 'Bool expected in readStatus, saw %s instead.' %
|
|
|
|
type(readStatus))
|
|
|
|
queryreturn = sqlQuery(
|
|
|
|
"SELECT read FROM inbox WHERE msgid=?", msgid)
|
2015-03-21 12:53:09 +01:00
|
|
|
# UPDATE is slow, only update if status is different
|
|
|
|
if queryreturn != [] and (queryreturn[0][0] == 1) != readStatus:
|
2018-04-08 17:28:08 +02:00
|
|
|
sqlExecute(
|
|
|
|
"UPDATE inbox set read = ? WHERE msgid=?",
|
|
|
|
readStatus, msgid)
|
2017-02-08 13:41:56 +01:00
|
|
|
queues.UISignalQueue.put(('changedInboxUnread', None))
|
2018-04-08 17:28:08 +02:00
|
|
|
queryreturn = sqlQuery(
|
|
|
|
"SELECT msgid, toaddress, fromaddress, subject, received, message,"
|
|
|
|
" encodingtype, read FROM inbox WHERE msgid=?", msgid
|
|
|
|
)
|
2015-03-21 12:53:09 +01:00
|
|
|
data = '{"inboxMessage":['
|
|
|
|
for row in queryreturn:
|
2018-04-08 17:28:08 +02:00
|
|
|
msgid, toAddress, fromAddress, subject, received, message, \
|
|
|
|
encodingtype, read = row
|
2015-03-21 12:53:09 +01:00
|
|
|
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
|
|
|
|
message = shared.fixPotentiallyInvalidUTF8Data(message)
|
2018-04-08 17:28:08 +02:00
|
|
|
data += json.dumps({
|
|
|
|
'msgid': hexlify(msgid),
|
|
|
|
'toAddress': toAddress,
|
|
|
|
'fromAddress': fromAddress,
|
|
|
|
'subject': base64.b64encode(subject),
|
|
|
|
'message': base64.b64encode(message),
|
|
|
|
'encodingType': encodingtype,
|
|
|
|
'receivedTime': received,
|
|
|
|
'read': read}, indent=4, separators=(',', ': '))
|
2014-01-13 01:56:30 +01:00
|
|
|
data += ']}'
|
|
|
|
return data
|
2015-03-21 12:53:09 +01:00
|
|
|
|
|
|
|
def HandleGetAllSentMessages(self, params):
|
2018-04-08 17:28:08 +02:00
|
|
|
queryreturn = sqlQuery(
|
|
|
|
"SELECT msgid, toaddress, fromaddress, subject, lastactiontime,"
|
|
|
|
" message, encodingtype, status, ackdata FROM sent"
|
|
|
|
" WHERE folder='sent' ORDER BY lastactiontime"
|
|
|
|
)
|
2015-03-21 12:53:09 +01:00
|
|
|
data = '{"sentMessages":['
|
|
|
|
for row in queryreturn:
|
2018-04-08 17:28:08 +02:00
|
|
|
msgid, toAddress, fromAddress, subject, lastactiontime, message, \
|
|
|
|
encodingtype, status, ackdata = row
|
2015-03-21 12:53:09 +01:00
|
|
|
subject = shared.fixPotentiallyInvalidUTF8Data(subject)
|
|
|
|
message = shared.fixPotentiallyInvalidUTF8Data(message)
|
|
|
|
if len(data) > 25:
|
|
|
|
data += ','
|
2018-04-08 17:28:08 +02:00
|
|
|
data += json.dumps({
|
|
|
|
'msgid': hexlify(msgid),
|
|
|
|
'toAddress': toAddress,
|
|
|
|
'fromAddress': fromAddress,
|
|
|
|
'subject': base64.b64encode(subject),
|
|
|
|
'message': base64.b64encode(message),
|
|
|
|
'encodingType': encodingtype,
|
|
|
|
'lastActionTime': lastactiontime,
|
|
|
|
'status': status,
|
|
|
|
'ackData': hexlify(ackdata)}, indent=4, separators=(',', ': '))
|
2015-03-21 12:53:09 +01:00
|
|
|
data += ']}'
|
|
|
|
return data
|
|
|
|
|
|
|
|
def HandleGetAllSentMessageIds(self, params):
|
2018-04-08 17:28:08 +02:00
|
|
|
queryreturn = sqlQuery(
|
|
|
|
"SELECT msgid FROM sent where folder='sent'"
|
|
|
|
" ORDER BY lastactiontime"
|
|
|
|
)
|
2015-03-21 12:53:09 +01:00
|
|
|
data = '{"sentMessageIds":['
|
|
|
|
for row in queryreturn:
|
|
|
|
msgid = row[0]
|
|
|
|
if len(data) > 25:
|
|
|
|
data += ','
|
2018-04-08 17:28:08 +02:00
|
|
|
data += json.dumps(
|
|
|
|
{'msgid': hexlify(msgid)}, indent=4, separators=(',', ': '))
|
2015-03-21 12:53:09 +01:00
|
|
|
data += ']}'
|
|
|
|
return data
|
|